3 " Maintainer: David Fishburn <fishburn at ianywhere dot com>
4 " Last Change: Wed Sep 14 2005 10:21:15 PM
6 " Download: http://vim.sourceforge.net/script.php?script_id=495
9 " Indenting keywords are based on Oracle and Sybase Adaptive Server
10 " Anywhere (ASA). Test indenting was done with ASA stored procedures and
11 " fuctions and Oracle packages which contain stored procedures and
13 " This has not been tested against Microsoft SQL Server or
14 " Sybase Adaptive Server Enterprise (ASE) which use the Transact-SQL
15 " syntax. That syntax does not have end tags for IF's, which makes
16 " indenting more difficult.
19 " The Oracle MERGE statement does not have an end tag associated with
20 " it, this can leave the indent hanging to the right one too many.
22 " Only load this indent file when no other was loaded.
23 if exists("b:did_indent")
27 let b:current_indent = "sqlanywhere"
29 setlocal indentkeys-=0{
30 setlocal indentkeys-=0}
31 setlocal indentkeys-=:
32 setlocal indentkeys-=0#
33 setlocal indentkeys-=e
35 " This indicates formatting should take place when one of these
36 " expressions is used. These expressions would normally be something
37 " you would type at the BEGINNING of a line
38 " SQL is generally case insensitive, so this files assumes that
39 " These keywords are something that would trigger an indent LEFT, not
40 " an indent right, since the SQLBlockStart is used for those keywords
41 setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=)
43 " GetSQLIndent is executed whenever one of the expressions
44 " in the indentkeys is typed
45 setlocal indentexpr=GetSQLIndent()
47 " Only define the functions once.
48 if exists("*GetSQLIndent")
52 " List of all the statements that start a new block.
53 " These are typically words that start a line.
54 " IS is excluded, since it is difficult to determine when the
55 " ending block is (especially for procedures/functions).
56 let s:SQLBlockStart = '^\s*\%('.
57 \ 'if\|else\|elseif\|elsif\|'.
58 \ 'while\|loop\|do\|'.
60 \ 'case\|when\|merge\|exception'.
62 let s:SQLBlockEnd = '^\s*\(end\)\>'
64 " The indent level is also based on unmatched paranethesis
65 " If a line has an extra "(" increase the indent
66 " If a line has an extra ")" decrease the indent
67 function s:CountUnbalancedParan( line, paran_to_check )
69 let lp = substitute(l, '[^(]', '', 'g')
71 let rp = substitute(l, '[^)]', '', 'g')
73 if a:paran_to_check =~ ')'
74 " echom 'CountUnbalancedParan ) returning: ' .
75 " \ (strlen(rp) - strlen(lp))
76 return (strlen(rp) - strlen(lp))
77 elseif a:paran_to_check =~ '('
78 " echom 'CountUnbalancedParan ( returning: ' .
79 " \ (strlen(lp) - strlen(rp))
80 return (strlen(lp) - strlen(rp))
82 " echom 'CountUnbalancedParan unknown paran to check: ' .
88 " Unindent commands based on previous indent level
89 function s:CheckToIgnoreRightParan( prev_lnum, num_levels )
90 let lnum = a:prev_lnum
91 let line = getline(lnum)
93 let num_right_paran = a:num_levels
97 while num_right_paran > 0
98 silent! exec 'norm! '.lnum."G\<bar>".vircol."\<bar>"
99 let right_paran = search( ')', 'W' )
100 if right_paran != lnum
101 " This should not happen since there should be at least
102 " num_right_paran matches for this line
105 let vircol = virtcol(".")
107 " if getline(".") =~ '^)'
108 let matching_paran = searchpair('(', '', ')', 'bW',
109 \ 'IsColComment(line("."), col("."))')
111 if matching_paran < 1
113 " echom 'CTIRP - no match found, ignoring'
117 if matching_paran == lnum
118 " This was not an unmatched parantenses, start the search again
119 " again after this column
120 " echom 'CTIRP - same line match, ignoring'
124 " echom 'CTIRP - match: ' . line(".") . ' ' . getline(".")
126 if getline(matching_paran) =~? '\(if\|while\)\>'
127 " echom 'CTIRP - if/while ignored: ' . line(".") . ' ' . getline(".")
128 let ignore_paran = ignore_paran + 1
131 " One match found, decrease and check for further matches
132 let num_right_paran = num_right_paran - 1
136 " Fallback - just move back one
137 " return a:prev_indent - &sw
141 " Based on the keyword provided, loop through previous non empty
142 " non comment lines to find the statement that initated the keyword.
143 " Return its indent level
146 " Should return indent level of CASE
151 " Should return indent level of exception.
152 function s:GetStmtStarterIndent( keyword, curr_lnum )
153 let lnum = a:curr_lnum
155 " Default - reduce indent by 1
156 let ind = indent(a:curr_lnum) - &sw
158 if a:keyword =~? 'end'
160 let stmts = '^\s*\%('.
162 \ '\%(\%(\<end\s\+\)\@<!\<loop\>\)\|' .
163 \ '\%(\%(\<end\s\+\)\@<!\<case\>\)\|' .
164 \ '\%(\%(\<end\s\+\)\@<!\<for\>\)\|' .
165 \ '\%(\%(\<end\s\+\)\@<!\<if\>\)'.
167 let matching_lnum = searchpair(stmts, '', '\<end\>\zs', 'bW',
168 \ 'IsColComment(line("."), col(".")) == 1')
170 if matching_lnum > 0 && matching_lnum < a:curr_lnum
171 let ind = indent(matching_lnum)
173 elseif a:keyword =~? 'when'
175 let matching_lnum = searchpair(
176 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>',
178 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)',
180 \ 'IsColComment(line("."), col(".")) == 1')
182 if matching_lnum > 0 && matching_lnum < a:curr_lnum
183 let ind = indent(matching_lnum)
185 let ind = indent(a:curr_lnum)
193 " Check if the line is a comment
194 function IsLineComment(lnum)
197 \ match(getline(a:lnum), '\S')+1, 0)
205 " Check if the column is a comment
206 function IsColComment(lnum, cnum)
207 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name")
214 " Check if the column is a comment
215 function ModuloIndent(ind)
219 let modulo = ind % &shiftwidth
222 let ind = ind - modulo
230 " Find correct indent of a new line based upon the previous line
231 function GetSQLIndent()
233 let ind = indent(lnum)
235 " If the current line is a comment, leave the indent as is
236 " Comment out this additional check since it affects the
237 " indenting of =, and will not reindent comments as it should
238 " if IsLineComment(lnum) == 1
243 " Get previous non-blank line
244 let prevlnum = prevnonblank(lnum - 1)
249 if IsLineComment(prevlnum) == 1
250 if getline(v:lnum) =~ '^\s*\*'
251 let ind = ModuloIndent(indent(prevlnum))
254 " If the previous line is a comment, then return -1
255 " to tell Vim to use the formatoptions setting to determine
257 " But only if the next line is blank. This would be true if
258 " the user is typing, but it would not be true if the user
259 " is reindenting the file
260 if getline(v:lnum) =~ '^\s*$'
265 " let prevline = getline(prevlnum)
266 " if prevline !~ '^\s*$'
267 " " echom 'previous non blank - break: ' . prevline
272 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum)
274 " This is the line you just hit return on, it is not the current line
275 " which is new and empty
276 " Based on this line, we can determine how much to indent the new
279 " Get default indent (from prev. line)
280 let ind = indent(prevlnum)
281 let prevline = getline(prevlnum)
283 " Now check what's on the previous line to determine if the indent
284 " should be changed, for example IF, BEGIN, should increase the indent
285 " where END IF, END, should decrease the indent.
286 if prevline =~? s:SQLBlockStart
289 " echom 'prevl - SQLBlockStart - indent ' . ind . ' line: ' . prevline
290 elseif prevline =~ '[()]'
292 let num_unmatched_left = s:CountUnbalancedParan( prevline, '(' )
294 let num_unmatched_left = 0
297 let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
299 let num_unmatched_right = 0
300 " let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
302 if num_unmatched_left > 0
303 " There is a open left paranethesis
305 let ind = ind + ( &sw * num_unmatched_left )
306 elseif num_unmatched_right > 0
307 " if it is an unbalanced paranethesis only unindent if
308 " it was part of a command (ie create table(..) )
309 " instead of part of an if (ie if (....) then) which should
310 " maintain the indent level
311 let ignore = s:CheckToIgnoreRightParan( prevlnum, num_unmatched_right )
312 " echom 'prevl - ) unbalanced - CTIRP - ignore: ' . ignore
314 if prevline =~ '^\s*)'
315 let ignore = ignore + 1
316 " echom 'prevl - begins ) unbalanced ignore: ' . ignore
319 if (num_unmatched_right - ignore) > 0
320 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
327 " echom 'CURRENT INDENT: ' . ind . ' LINE: ' . getline(v:lnum)
329 " This is a new blank line since we just typed a carriage return
330 " Check current line; search for simplistic matching start-of-block
331 let line = getline(v:lnum)
333 if line =~? '^\s*els'
334 " Any line when you type else will automatically back up one
335 " ident level (ie else, elseif, elsif)
337 " echom 'curr - else - indent ' . ind
338 elseif line =~? '^\s*end\>'
339 let ind = s:GetStmtStarterIndent('end', v:lnum)
340 " General case for end
341 " let ind = ind - &sw
342 " echom 'curr - end - indent ' . ind
343 elseif line =~? '^\s*when\>'
344 let ind = s:GetStmtStarterIndent('when', v:lnum)
345 " If the WHEN clause is used with a MERGE or EXCEPTION
346 " clause, do not change the indent level, since these
347 " statements do not have a corresponding END statement.
348 " if stmt_starter =~? 'case'
349 " let ind = ind - &sw
351 " elseif line =~ '^\s*)\s*;\?\s*$'
352 " elseif line =~ '^\s*)'
353 elseif line =~ '^\s*)'
354 let num_unmatched_right = s:CountUnbalancedParan( line, ')' )
355 let ignore = s:CheckToIgnoreRightParan( v:lnum, num_unmatched_right )
356 " If the line ends in a ), then reduce the indent
357 " This catches items like:
362 " But we do not want to unindent a line like:
365 " let num_unmatched_right = s:CountUnbalancedParan( line, ')' )
366 " if num_unmatched_right > 0
367 " elseif strpart( line, strlen(line)-1, 1 ) =~ ')'
368 " let ind = ind - &sw
370 " let ignore = ignore + 1
371 " echom 'curr - begins ) unbalanced ignore: ' . ignore
374 if (num_unmatched_right - ignore) > 0
375 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
380 " echom 'final - indent ' . ind
381 return ModuloIndent(ind)