sqlanywhere.vim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. " Vim indent file
  2. " Language: SQL
  3. " Maintainer: David Fishburn <dfishburn dot vim at gmail dot com>
  4. " Last Change: 2021 Oct 11
  5. " Version: 4.0
  6. " Download: http://vim.sourceforge.net/script.php?script_id=495
  7. " Notes:
  8. " Indenting keywords are based on Oracle and Sybase Adaptive Server
  9. " Anywhere (ASA). Test indenting was done with ASA stored procedures and
  10. " functions and Oracle packages which contain stored procedures and
  11. " functions.
  12. " This has not been tested against Microsoft SQL Server or
  13. " Sybase Adaptive Server Enterprise (ASE) which use the Transact-SQL
  14. " syntax. That syntax does not have end tags for IF's, which makes
  15. " indenting more difficult.
  16. "
  17. " Known Issues:
  18. " The Oracle MERGE statement does not have an end tag associated with
  19. " it, this can leave the indent hanging to the right one too many.
  20. "
  21. " History:
  22. " 4.0 (Oct 2021)
  23. " Added b:undo_indent
  24. "
  25. " 3.0 (Dec 2012)
  26. " Added cpo check
  27. "
  28. " 2.0
  29. " Added the FOR keyword to SQLBlockStart to handle (Alec Tica):
  30. " for i in 1..100 loop
  31. " |<-- I expect to have indentation here
  32. " end loop;
  33. "
  34. " Only load this indent file when no other was loaded.
  35. if exists("b:did_indent")
  36. finish
  37. endif
  38. let b:did_indent = 1
  39. let b:current_indent = "sqlanywhere"
  40. setlocal indentkeys-=0{
  41. setlocal indentkeys-=0}
  42. setlocal indentkeys-=:
  43. setlocal indentkeys-=0#
  44. setlocal indentkeys-=e
  45. " This indicates formatting should take place when one of these
  46. " expressions is used. These expressions would normally be something
  47. " you would type at the BEGINNING of a line
  48. " SQL is generally case insensitive, so this files assumes that
  49. " These keywords are something that would trigger an indent LEFT, not
  50. " an indent right, since the SQLBlockStart is used for those keywords
  51. setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=)
  52. " GetSQLIndent is executed whenever one of the expressions
  53. " in the indentkeys is typed
  54. setlocal indentexpr=GetSQLIndent()
  55. let b:undo_indent = "setl indentexpr< indentkeys<"
  56. " Only define the functions once.
  57. if exists("*GetSQLIndent")
  58. finish
  59. endif
  60. let s:keepcpo= &cpo
  61. set cpo&vim
  62. " List of all the statements that start a new block.
  63. " These are typically words that start a line.
  64. " IS is excluded, since it is difficult to determine when the
  65. " ending block is (especially for procedures/functions).
  66. let s:SQLBlockStart = '^\s*\%('.
  67. \ 'if\|else\|elseif\|elsif\|'.
  68. \ 'while\|loop\|do\|for\|'.
  69. \ 'begin\|'.
  70. \ 'case\|when\|merge\|exception'.
  71. \ '\)\>'
  72. let s:SQLBlockEnd = '^\s*\(end\)\>'
  73. " The indent level is also based on unmatched parentheses
  74. " If a line has an extra "(" increase the indent
  75. " If a line has an extra ")" decrease the indent
  76. function! s:CountUnbalancedParen( line, paren_to_check )
  77. let l = a:line
  78. let lp = substitute(l, '[^(]', '', 'g')
  79. let l = a:line
  80. let rp = substitute(l, '[^)]', '', 'g')
  81. if a:paren_to_check =~ ')'
  82. " echom 'CountUnbalancedParen ) returning: ' .
  83. " \ (strlen(rp) - strlen(lp))
  84. return (strlen(rp) - strlen(lp))
  85. elseif a:paren_to_check =~ '('
  86. " echom 'CountUnbalancedParen ( returning: ' .
  87. " \ (strlen(lp) - strlen(rp))
  88. return (strlen(lp) - strlen(rp))
  89. else
  90. " echom 'CountUnbalancedParen unknown paren to check: ' .
  91. " \ a:paren_to_check
  92. return 0
  93. endif
  94. endfunction
  95. " Unindent commands based on previous indent level
  96. function! s:CheckToIgnoreRightParen( prev_lnum, num_levels )
  97. let lnum = a:prev_lnum
  98. let line = getline(lnum)
  99. let ends = 0
  100. let num_right_paren = a:num_levels
  101. let ignore_paren = 0
  102. let vircol = 1
  103. while num_right_paren > 0
  104. silent! exec 'norm! '.lnum."G\<bar>".vircol."\<bar>"
  105. let right_paren = search( ')', 'W' )
  106. if right_paren != lnum
  107. " This should not happen since there should be at least
  108. " num_right_paren matches for this line
  109. break
  110. endif
  111. let vircol = virtcol(".")
  112. " if getline(".") =~ '^)'
  113. let matching_paren = searchpair('(', '', ')', 'bW',
  114. \ 's:IsColComment(line("."), col("."))')
  115. if matching_paren < 1
  116. " No match found
  117. " echom 'CTIRP - no match found, ignoring'
  118. break
  119. endif
  120. if matching_paren == lnum
  121. " This was not an unmatched parentheses, start the search again
  122. " again after this column
  123. " echom 'CTIRP - same line match, ignoring'
  124. continue
  125. endif
  126. " echom 'CTIRP - match: ' . line(".") . ' ' . getline(".")
  127. if getline(matching_paren) =~? '\(if\|while\)\>'
  128. " echom 'CTIRP - if/while ignored: ' . line(".") . ' ' . getline(".")
  129. let ignore_paren = ignore_paren + 1
  130. endif
  131. " One match found, decrease and check for further matches
  132. let num_right_paren = num_right_paren - 1
  133. endwhile
  134. " Fallback - just move back one
  135. " return a:prev_indent - shiftwidth()
  136. return ignore_paren
  137. endfunction
  138. " Based on the keyword provided, loop through previous non empty
  139. " non comment lines to find the statement that initiated the keyword.
  140. " Return its indent level
  141. " CASE ..
  142. " WHEN ...
  143. " Should return indent level of CASE
  144. " EXCEPTION ..
  145. " WHEN ...
  146. " something;
  147. " WHEN ...
  148. " Should return indent level of exception.
  149. function! s:GetStmtStarterIndent( keyword, curr_lnum )
  150. let lnum = a:curr_lnum
  151. " Default - reduce indent by 1
  152. let ind = indent(a:curr_lnum) - shiftwidth()
  153. if a:keyword =~? 'end'
  154. exec 'normal! ^'
  155. let stmts = '^\s*\%('.
  156. \ '\<begin\>\|' .
  157. \ '\%(\%(\<end\s\+\)\@<!\<loop\>\)\|' .
  158. \ '\%(\%(\<end\s\+\)\@<!\<case\>\)\|' .
  159. \ '\%(\%(\<end\s\+\)\@<!\<for\>\)\|' .
  160. \ '\%(\%(\<end\s\+\)\@<!\<if\>\)'.
  161. \ '\)'
  162. let matching_lnum = searchpair(stmts, '', '\<end\>\zs', 'bW',
  163. \ 's:IsColComment(line("."), col(".")) == 1')
  164. exec 'normal! $'
  165. if matching_lnum > 0 && matching_lnum < a:curr_lnum
  166. let ind = indent(matching_lnum)
  167. endif
  168. elseif a:keyword =~? 'when'
  169. exec 'normal! ^'
  170. let matching_lnum = searchpair(
  171. \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>',
  172. \ '',
  173. \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)',
  174. \ 'bW',
  175. \ 's:IsColComment(line("."), col(".")) == 1')
  176. exec 'normal! $'
  177. if matching_lnum > 0 && matching_lnum < a:curr_lnum
  178. let ind = indent(matching_lnum)
  179. else
  180. let ind = indent(a:curr_lnum)
  181. endif
  182. endif
  183. return ind
  184. endfunction
  185. " Check if the line is a comment
  186. function! s:IsLineComment(lnum)
  187. let rc = synIDattr(
  188. \ synID(a:lnum,
  189. \ match(getline(a:lnum), '\S')+1, 0)
  190. \ , "name")
  191. \ =~? "comment"
  192. return rc
  193. endfunction
  194. " Check if the column is a comment
  195. function! s:IsColComment(lnum, cnum)
  196. let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name")
  197. \ =~? "comment"
  198. return rc
  199. endfunction
  200. " Instead of returning a column position, return
  201. " an appropriate value as a factor of shiftwidth.
  202. function! s:ModuloIndent(ind)
  203. let ind = a:ind
  204. if ind > 0
  205. let modulo = ind % shiftwidth()
  206. if modulo > 0
  207. let ind = ind - modulo
  208. endif
  209. endif
  210. return ind
  211. endfunction
  212. " Find correct indent of a new line based upon the previous line
  213. function! GetSQLIndent()
  214. let lnum = v:lnum
  215. let ind = indent(lnum)
  216. " If the current line is a comment, leave the indent as is
  217. " Comment out this additional check since it affects the
  218. " indenting of =, and will not reindent comments as it should
  219. " if s:IsLineComment(lnum) == 1
  220. " return ind
  221. " endif
  222. " Get previous non-blank line
  223. let prevlnum = prevnonblank(lnum - 1)
  224. if prevlnum <= 0
  225. return ind
  226. endif
  227. if s:IsLineComment(prevlnum) == 1
  228. if getline(v:lnum) =~ '^\s*\*'
  229. let ind = s:ModuloIndent(indent(prevlnum))
  230. return ind + 1
  231. endif
  232. " If the previous line is a comment, then return -1
  233. " to tell Vim to use the formatoptions setting to determine
  234. " the indent to use
  235. " But only if the next line is blank. This would be true if
  236. " the user is typing, but it would not be true if the user
  237. " is reindenting the file
  238. if getline(v:lnum) =~ '^\s*$'
  239. return -1
  240. endif
  241. endif
  242. " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum)
  243. " This is the line you just hit return on, it is not the current line
  244. " which is new and empty
  245. " Based on this line, we can determine how much to indent the new
  246. " line
  247. " Get default indent (from prev. line)
  248. let ind = indent(prevlnum)
  249. let prevline = getline(prevlnum)
  250. " Now check what's on the previous line to determine if the indent
  251. " should be changed, for example IF, BEGIN, should increase the indent
  252. " where END IF, END, should decrease the indent.
  253. if prevline =~? s:SQLBlockStart
  254. " Move indent in
  255. let ind = ind + shiftwidth()
  256. " echom 'prevl - SQLBlockStart - indent ' . ind . ' line: ' . prevline
  257. elseif prevline =~ '[()]'
  258. if prevline =~ '('
  259. let num_unmatched_left = s:CountUnbalancedParen( prevline, '(' )
  260. else
  261. let num_unmatched_left = 0
  262. endif
  263. if prevline =~ ')'
  264. let num_unmatched_right = s:CountUnbalancedParen( prevline, ')' )
  265. else
  266. let num_unmatched_right = 0
  267. " let num_unmatched_right = s:CountUnbalancedParen( prevline, ')' )
  268. endif
  269. if num_unmatched_left > 0
  270. " There is a open left parenthesis
  271. " increase indent
  272. let ind = ind + ( shiftwidth() * num_unmatched_left )
  273. elseif num_unmatched_right > 0
  274. " if it is an unbalanced parenthesis only unindent if
  275. " it was part of a command (ie create table(..) )
  276. " instead of part of an if (ie if (....) then) which should
  277. " maintain the indent level
  278. let ignore = s:CheckToIgnoreRightParen( prevlnum, num_unmatched_right )
  279. " echom 'prevl - ) unbalanced - CTIRP - ignore: ' . ignore
  280. if prevline =~ '^\s*)'
  281. let ignore = ignore + 1
  282. " echom 'prevl - begins ) unbalanced ignore: ' . ignore
  283. endif
  284. if (num_unmatched_right - ignore) > 0
  285. let ind = ind - ( shiftwidth() * (num_unmatched_right - ignore) )
  286. endif
  287. endif
  288. endif
  289. " echom 'CURRENT INDENT: ' . ind . ' LINE: ' . getline(v:lnum)
  290. " This is a new blank line since we just typed a carriage return
  291. " Check current line; search for simplistic matching start-of-block
  292. let line = getline(v:lnum)
  293. if line =~? '^\s*els'
  294. " Any line when you type else will automatically back up one
  295. " ident level (ie else, elseif, elsif)
  296. let ind = ind - shiftwidth()
  297. " echom 'curr - else - indent ' . ind
  298. elseif line =~? '^\s*end\>'
  299. let ind = s:GetStmtStarterIndent('end', v:lnum)
  300. " General case for end
  301. " let ind = ind - shiftwidth()
  302. " echom 'curr - end - indent ' . ind
  303. elseif line =~? '^\s*when\>'
  304. let ind = s:GetStmtStarterIndent('when', v:lnum)
  305. " If the WHEN clause is used with a MERGE or EXCEPTION
  306. " clause, do not change the indent level, since these
  307. " statements do not have a corresponding END statement.
  308. " if stmt_starter =~? 'case'
  309. " let ind = ind - shiftwidth()
  310. " endif
  311. " elseif line =~ '^\s*)\s*;\?\s*$'
  312. " elseif line =~ '^\s*)'
  313. elseif line =~ '^\s*)'
  314. let num_unmatched_right = s:CountUnbalancedParen( line, ')' )
  315. let ignore = s:CheckToIgnoreRightParen( v:lnum, num_unmatched_right )
  316. " If the line ends in a ), then reduce the indent
  317. " This catches items like:
  318. " CREATE TABLE T1(
  319. " c1 int,
  320. " c2 int
  321. " );
  322. " But we do not want to unindent a line like:
  323. " IF ( c1 = 1
  324. " AND c2 = 3 ) THEN
  325. " let num_unmatched_right = s:CountUnbalancedParen( line, ')' )
  326. " if num_unmatched_right > 0
  327. " elseif strpart( line, strlen(line)-1, 1 ) =~ ')'
  328. " let ind = ind - shiftwidth()
  329. if line =~ '^\s*)'
  330. " let ignore = ignore + 1
  331. " echom 'curr - begins ) unbalanced ignore: ' . ignore
  332. endif
  333. if (num_unmatched_right - ignore) > 0
  334. let ind = ind - ( shiftwidth() * (num_unmatched_right - ignore) )
  335. endif
  336. " endif
  337. endif
  338. " echom 'final - indent ' . ind
  339. return s:ModuloIndent(ind)
  340. endfunction
  341. " Restore:
  342. let &cpo= s:keepcpo
  343. unlet s:keepcpo
  344. " vim: ts=4 fdm=marker sw=4