blob: c2f527ebc6f0efed8060bb68abd364aafc9d41f3 [file] [log] [blame]
Bram Moolenaar0fd92892006-03-09 22:27:48 +00001" Vim indent file
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00002" Language: SQL
Bram Moolenaar0fd92892006-03-09 22:27:48 +00003" Maintainer: David Fishburn <fishburn at ianywhere dot com>
4" Last Change: Wed Sep 14 2005 10:21:15 PM
5" Version: 1.4
6" Download: http://vim.sourceforge.net/script.php?script_id=495
7
8" Notes:
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
12" functions.
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000013" This has not been tested against Microsoft SQL Server or
Bram Moolenaar0fd92892006-03-09 22:27:48 +000014" 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.
17"
18" Known Issues:
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.
21
22" Only load this indent file when no other was loaded.
23if exists("b:did_indent")
24 finish
25endif
26let b:did_indent = 1
27let b:current_indent = "sqlanywhere"
28
29setlocal indentkeys-=0{
30setlocal indentkeys-=0}
31setlocal indentkeys-=:
32setlocal indentkeys-=0#
33setlocal indentkeys-=e
34
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000035" This indicates formatting should take place when one of these
Bram Moolenaar0fd92892006-03-09 22:27:48 +000036" 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
41setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=)
42
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000043" GetSQLIndent is executed whenever one of the expressions
Bram Moolenaar0fd92892006-03-09 22:27:48 +000044" in the indentkeys is typed
45setlocal indentexpr=GetSQLIndent()
46
47" Only define the functions once.
48if exists("*GetSQLIndent")
49 finish
50endif
51
52" List of all the statements that start a new block.
53" These are typically words that start a line.
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000054" IS is excluded, since it is difficult to determine when the
Bram Moolenaar0fd92892006-03-09 22:27:48 +000055" ending block is (especially for procedures/functions).
56let s:SQLBlockStart = '^\s*\%('.
57 \ 'if\|else\|elseif\|elsif\|'.
58 \ 'while\|loop\|do\|'.
59 \ 'begin\|'.
60 \ 'case\|when\|merge\|exception'.
61 \ '\)\>'
62let s:SQLBlockEnd = '^\s*\(end\)\>'
63
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
67function s:CountUnbalancedParan( line, paran_to_check )
68 let l = a:line
69 let lp = substitute(l, '[^(]', '', 'g')
70 let l = a:line
71 let rp = substitute(l, '[^)]', '', 'g')
72
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))
81 else
82 " echom 'CountUnbalancedParan unknown paran to check: ' .
83 " \ a:paran_to_check
84 return 0
85 endif
86endfunction
87
88" Unindent commands based on previous indent level
89function s:CheckToIgnoreRightParan( prev_lnum, num_levels )
90 let lnum = a:prev_lnum
91 let line = getline(lnum)
92 let ends = 0
93 let num_right_paran = a:num_levels
94 let ignore_paran = 0
95 let vircol = 1
96
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
103 break
104 endif
105 let vircol = virtcol(".")
106
107 " if getline(".") =~ '^)'
108 let matching_paran = searchpair('(', '', ')', 'bW',
109 \ 'IsColComment(line("."), col("."))')
110
111 if matching_paran < 1
112 " No match found
113 " echom 'CTIRP - no match found, ignoring'
114 break
115 endif
116
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000117 if matching_paran == lnum
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000118 " This was not an unmatched parantenses, start the search again
119 " again after this column
120 " echom 'CTIRP - same line match, ignoring'
121 continue
122 endif
123
124 " echom 'CTIRP - match: ' . line(".") . ' ' . getline(".")
125
126 if getline(matching_paran) =~? '\(if\|while\)\>'
127 " echom 'CTIRP - if/while ignored: ' . line(".") . ' ' . getline(".")
128 let ignore_paran = ignore_paran + 1
129 endif
130
131 " One match found, decrease and check for further matches
132 let num_right_paran = num_right_paran - 1
133
134 endwhile
135
136 " Fallback - just move back one
137 " return a:prev_indent - &sw
138 return ignore_paran
139endfunction
140
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
144" CASE ..
145" WHEN ...
146" Should return indent level of CASE
147" EXCEPTION ..
148" WHEN ...
149" something;
150" WHEN ...
151" Should return indent level of exception.
152function s:GetStmtStarterIndent( keyword, curr_lnum )
153 let lnum = a:curr_lnum
154
155 " Default - reduce indent by 1
156 let ind = indent(a:curr_lnum) - &sw
157
158 if a:keyword =~? 'end'
159 exec 'normal! ^'
160 let stmts = '^\s*\%('.
161 \ '\<begin\>\|' .
162 \ '\%(\%(\<end\s\+\)\@<!\<loop\>\)\|' .
163 \ '\%(\%(\<end\s\+\)\@<!\<case\>\)\|' .
164 \ '\%(\%(\<end\s\+\)\@<!\<for\>\)\|' .
165 \ '\%(\%(\<end\s\+\)\@<!\<if\>\)'.
166 \ '\)'
167 let matching_lnum = searchpair(stmts, '', '\<end\>\zs', 'bW',
168 \ 'IsColComment(line("."), col(".")) == 1')
169 exec 'normal! $'
170 if matching_lnum > 0 && matching_lnum < a:curr_lnum
171 let ind = indent(matching_lnum)
172 endif
173 elseif a:keyword =~? 'when'
174 exec 'normal! ^'
175 let matching_lnum = searchpair(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000176 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>',
177 \ '',
178 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)',
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000179 \ 'bW',
180 \ 'IsColComment(line("."), col(".")) == 1')
181 exec 'normal! $'
182 if matching_lnum > 0 && matching_lnum < a:curr_lnum
183 let ind = indent(matching_lnum)
184 else
185 let ind = indent(a:curr_lnum)
186 endif
187 endif
188
189 return ind
190endfunction
191
192
193" Check if the line is a comment
194function IsLineComment(lnum)
195 let rc = synIDattr(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000196 \ synID(a:lnum,
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000197 \ match(getline(a:lnum), '\S')+1, 0)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000198 \ , "name")
199 \ =~? "comment"
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000200
201 return rc
202endfunction
203
204
205" Check if the column is a comment
206function IsColComment(lnum, cnum)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000207 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name")
208 \ =~? "comment"
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000209
210 return rc
211endfunction
212
213
214" Check if the column is a comment
215function ModuloIndent(ind)
216 let ind = a:ind
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000217
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000218 if ind > 0
219 let modulo = ind % &shiftwidth
220
221 if modulo > 0
222 let ind = ind - modulo
223 endif
224 endif
225
226 return ind
227endfunction
228
229
230" Find correct indent of a new line based upon the previous line
231function GetSQLIndent()
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000232 let lnum = v:lnum
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000233 let ind = indent(lnum)
234
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
239 " return ind
240 " endif
241
242 " while 1
243 " Get previous non-blank line
244 let prevlnum = prevnonblank(lnum - 1)
245 if prevlnum <= 0
246 return ind
247 endif
248
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000249 if IsLineComment(prevlnum) == 1
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000250 if getline(v:lnum) =~ '^\s*\*'
251 let ind = ModuloIndent(indent(prevlnum))
252 return ind + 1
253 endif
254 " If the previous line is a comment, then return -1
255 " to tell Vim to use the formatoptions setting to determine
256 " the indent to use
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*$'
261 return -1
262 endif
263 endif
264
265 " let prevline = getline(prevlnum)
266 " if prevline !~ '^\s*$'
267 " " echom 'previous non blank - break: ' . prevline
268 " break
269 " endif
270 " endwhile
271
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000272 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum)
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000273
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
277 " line
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000278
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000279 " Get default indent (from prev. line)
280 let ind = indent(prevlnum)
281 let prevline = getline(prevlnum)
282
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
287 " Move indent in
288 let ind = ind + &sw
289 " echom 'prevl - SQLBlockStart - indent ' . ind . ' line: ' . prevline
290 elseif prevline =~ '[()]'
291 if prevline =~ '('
292 let num_unmatched_left = s:CountUnbalancedParan( prevline, '(' )
293 else
294 let num_unmatched_left = 0
295 endif
296 if prevline =~ ')'
297 let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
298 else
299 let num_unmatched_right = 0
300 " let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
301 endif
302 if num_unmatched_left > 0
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000303 " There is a open left paranethesis
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000304 " increase indent
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
313
314 if prevline =~ '^\s*)'
315 let ignore = ignore + 1
316 " echom 'prevl - begins ) unbalanced ignore: ' . ignore
317 endif
318
319 if (num_unmatched_right - ignore) > 0
320 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
321 endif
322
323 endif
324 endif
325
326
327 " echom 'CURRENT INDENT: ' . ind . ' LINE: ' . getline(v:lnum)
328
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)
332
333 if line =~? '^\s*els'
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000334 " Any line when you type else will automatically back up one
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000335 " ident level (ie else, elseif, elsif)
336 let ind = ind - &sw
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
350 " endif
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:
358 " CREATE TABLE T1(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000359 " c1 int,
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000360 " c2 int
361 " );
362 " But we do not want to unindent a line like:
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000363 " IF ( c1 = 1
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000364 " AND c2 = 3 ) THEN
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
369 if line =~ '^\s*)'
370 " let ignore = ignore + 1
371 " echom 'curr - begins ) unbalanced ignore: ' . ignore
372 endif
373
374 if (num_unmatched_right - ignore) > 0
375 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
376 endif
377 " endif
378 endif
379
380 " echom 'final - indent ' . ind
381 return ModuloIndent(ind)
382endfunction
383
384" vim:sw=4:ff=unix: