blob: edc9650ad7636c90fbed3107edbd123106d40e2e [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 Moolenaar34feacb2012-12-05 19:01:43 +01003" Maintainer: David Fishburn <dfishburn dot vim at gmail dot com>
4" Last Change: 2012 Dec 05
5" Version: 3.0
Bram Moolenaar0fd92892006-03-09 22:27:48 +00006" 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.
Bram Moolenaar34feacb2012-12-05 19:01:43 +010021"
22" History:
23" 3.0 (Dec 2012)
24" Added cpo check
25"
26" 2.0
27" Added the FOR keyword to SQLBlockStart to handle (Alec Tica):
28" for i in 1..100 loop
29" |<-- I expect to have indentation here
30" end loop;
31"
Bram Moolenaar0fd92892006-03-09 22:27:48 +000032
33" Only load this indent file when no other was loaded.
34if exists("b:did_indent")
35 finish
36endif
37let b:did_indent = 1
38let b:current_indent = "sqlanywhere"
Bram Moolenaar34feacb2012-12-05 19:01:43 +010039let s:keepcpo= &cpo
40set cpo&vim
Bram Moolenaar0fd92892006-03-09 22:27:48 +000041
42setlocal indentkeys-=0{
43setlocal indentkeys-=0}
44setlocal indentkeys-=:
45setlocal indentkeys-=0#
46setlocal indentkeys-=e
47
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000048" This indicates formatting should take place when one of these
Bram Moolenaar0fd92892006-03-09 22:27:48 +000049" expressions is used. These expressions would normally be something
50" you would type at the BEGINNING of a line
51" SQL is generally case insensitive, so this files assumes that
52" These keywords are something that would trigger an indent LEFT, not
53" an indent right, since the SQLBlockStart is used for those keywords
54setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=)
55
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000056" GetSQLIndent is executed whenever one of the expressions
Bram Moolenaar0fd92892006-03-09 22:27:48 +000057" in the indentkeys is typed
58setlocal indentexpr=GetSQLIndent()
59
Bram Moolenaar0fd92892006-03-09 22:27:48 +000060" List of all the statements that start a new block.
61" These are typically words that start a line.
Bram Moolenaarc9b4b052006-04-30 18:54:39 +000062" IS is excluded, since it is difficult to determine when the
Bram Moolenaar0fd92892006-03-09 22:27:48 +000063" ending block is (especially for procedures/functions).
64let s:SQLBlockStart = '^\s*\%('.
Bram Moolenaar34feacb2012-12-05 19:01:43 +010065 \ 'if\|else\|elseif\|elsif\|'.
66 \ 'while\|loop\|do\|for\|'.
Bram Moolenaar0fd92892006-03-09 22:27:48 +000067 \ 'begin\|'.
68 \ 'case\|when\|merge\|exception'.
69 \ '\)\>'
70let s:SQLBlockEnd = '^\s*\(end\)\>'
71
72" The indent level is also based on unmatched paranethesis
73" If a line has an extra "(" increase the indent
74" If a line has an extra ")" decrease the indent
Bram Moolenaar34feacb2012-12-05 19:01:43 +010075function! s:CountUnbalancedParan( line, paran_to_check )
Bram Moolenaar0fd92892006-03-09 22:27:48 +000076 let l = a:line
77 let lp = substitute(l, '[^(]', '', 'g')
78 let l = a:line
79 let rp = substitute(l, '[^)]', '', 'g')
80
81 if a:paran_to_check =~ ')'
82 " echom 'CountUnbalancedParan ) returning: ' .
83 " \ (strlen(rp) - strlen(lp))
84 return (strlen(rp) - strlen(lp))
85 elseif a:paran_to_check =~ '('
86 " echom 'CountUnbalancedParan ( returning: ' .
87 " \ (strlen(lp) - strlen(rp))
88 return (strlen(lp) - strlen(rp))
89 else
90 " echom 'CountUnbalancedParan unknown paran to check: ' .
91 " \ a:paran_to_check
92 return 0
93 endif
94endfunction
95
96" Unindent commands based on previous indent level
Bram Moolenaar34feacb2012-12-05 19:01:43 +010097function! s:CheckToIgnoreRightParan( prev_lnum, num_levels )
Bram Moolenaar0fd92892006-03-09 22:27:48 +000098 let lnum = a:prev_lnum
99 let line = getline(lnum)
100 let ends = 0
101 let num_right_paran = a:num_levels
102 let ignore_paran = 0
103 let vircol = 1
104
105 while num_right_paran > 0
106 silent! exec 'norm! '.lnum."G\<bar>".vircol."\<bar>"
107 let right_paran = search( ')', 'W' )
108 if right_paran != lnum
109 " This should not happen since there should be at least
110 " num_right_paran matches for this line
111 break
112 endif
113 let vircol = virtcol(".")
114
115 " if getline(".") =~ '^)'
116 let matching_paran = searchpair('(', '', ')', 'bW',
Bram Moolenaara7241f52008-06-24 20:39:31 +0000117 \ 's:IsColComment(line("."), col("."))')
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000118
119 if matching_paran < 1
120 " No match found
121 " echom 'CTIRP - no match found, ignoring'
122 break
123 endif
124
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000125 if matching_paran == lnum
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000126 " This was not an unmatched parantenses, start the search again
127 " again after this column
128 " echom 'CTIRP - same line match, ignoring'
129 continue
130 endif
131
132 " echom 'CTIRP - match: ' . line(".") . ' ' . getline(".")
133
134 if getline(matching_paran) =~? '\(if\|while\)\>'
135 " echom 'CTIRP - if/while ignored: ' . line(".") . ' ' . getline(".")
136 let ignore_paran = ignore_paran + 1
137 endif
138
139 " One match found, decrease and check for further matches
140 let num_right_paran = num_right_paran - 1
141
142 endwhile
143
144 " Fallback - just move back one
145 " return a:prev_indent - &sw
146 return ignore_paran
147endfunction
148
149" Based on the keyword provided, loop through previous non empty
150" non comment lines to find the statement that initated the keyword.
151" Return its indent level
152" CASE ..
153" WHEN ...
154" Should return indent level of CASE
155" EXCEPTION ..
156" WHEN ...
157" something;
158" WHEN ...
159" Should return indent level of exception.
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100160function! s:GetStmtStarterIndent( keyword, curr_lnum )
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000161 let lnum = a:curr_lnum
162
163 " Default - reduce indent by 1
164 let ind = indent(a:curr_lnum) - &sw
165
166 if a:keyword =~? 'end'
167 exec 'normal! ^'
168 let stmts = '^\s*\%('.
169 \ '\<begin\>\|' .
170 \ '\%(\%(\<end\s\+\)\@<!\<loop\>\)\|' .
171 \ '\%(\%(\<end\s\+\)\@<!\<case\>\)\|' .
172 \ '\%(\%(\<end\s\+\)\@<!\<for\>\)\|' .
173 \ '\%(\%(\<end\s\+\)\@<!\<if\>\)'.
174 \ '\)'
175 let matching_lnum = searchpair(stmts, '', '\<end\>\zs', 'bW',
Bram Moolenaara7241f52008-06-24 20:39:31 +0000176 \ 's:IsColComment(line("."), col(".")) == 1')
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000177 exec 'normal! $'
178 if matching_lnum > 0 && matching_lnum < a:curr_lnum
179 let ind = indent(matching_lnum)
180 endif
181 elseif a:keyword =~? 'when'
182 exec 'normal! ^'
183 let matching_lnum = searchpair(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000184 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>',
185 \ '',
186 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)',
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000187 \ 'bW',
Bram Moolenaara7241f52008-06-24 20:39:31 +0000188 \ 's:IsColComment(line("."), col(".")) == 1')
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000189 exec 'normal! $'
190 if matching_lnum > 0 && matching_lnum < a:curr_lnum
191 let ind = indent(matching_lnum)
192 else
193 let ind = indent(a:curr_lnum)
194 endif
195 endif
196
197 return ind
198endfunction
199
200
201" Check if the line is a comment
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100202function! s:IsLineComment(lnum)
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000203 let rc = synIDattr(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000204 \ synID(a:lnum,
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000205 \ match(getline(a:lnum), '\S')+1, 0)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000206 \ , "name")
207 \ =~? "comment"
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000208
209 return rc
210endfunction
211
212
213" Check if the column is a comment
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100214function! s:IsColComment(lnum, cnum)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000215 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name")
216 \ =~? "comment"
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000217
218 return rc
219endfunction
220
221
Bram Moolenaara7241f52008-06-24 20:39:31 +0000222" Instead of returning a column position, return
223" an appropriate value as a factor of shiftwidth.
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100224function! s:ModuloIndent(ind)
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000225 let ind = a:ind
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000226
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000227 if ind > 0
228 let modulo = ind % &shiftwidth
229
230 if modulo > 0
231 let ind = ind - modulo
232 endif
233 endif
234
235 return ind
236endfunction
237
238
239" Find correct indent of a new line based upon the previous line
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100240function! GetSQLIndent()
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000241 let lnum = v:lnum
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000242 let ind = indent(lnum)
243
244 " If the current line is a comment, leave the indent as is
245 " Comment out this additional check since it affects the
246 " indenting of =, and will not reindent comments as it should
Bram Moolenaara7241f52008-06-24 20:39:31 +0000247 " if s:IsLineComment(lnum) == 1
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000248 " return ind
249 " endif
250
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100251 " Get previous non-blank line
252 let prevlnum = prevnonblank(lnum - 1)
253 if prevlnum <= 0
254 return ind
255 endif
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000256
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100257 if s:IsLineComment(prevlnum) == 1
258 if getline(v:lnum) =~ '^\s*\*'
259 let ind = s:ModuloIndent(indent(prevlnum))
260 return ind + 1
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000261 endif
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100262 " If the previous line is a comment, then return -1
263 " to tell Vim to use the formatoptions setting to determine
264 " the indent to use
265 " But only if the next line is blank. This would be true if
266 " the user is typing, but it would not be true if the user
267 " is reindenting the file
268 if getline(v:lnum) =~ '^\s*$'
269 return -1
270 endif
271 endif
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000272
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000273 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum)
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000274
275 " This is the line you just hit return on, it is not the current line
276 " which is new and empty
277 " Based on this line, we can determine how much to indent the new
278 " line
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000279
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000280 " Get default indent (from prev. line)
281 let ind = indent(prevlnum)
282 let prevline = getline(prevlnum)
283
284 " Now check what's on the previous line to determine if the indent
285 " should be changed, for example IF, BEGIN, should increase the indent
286 " where END IF, END, should decrease the indent.
287 if prevline =~? s:SQLBlockStart
288 " Move indent in
289 let ind = ind + &sw
290 " echom 'prevl - SQLBlockStart - indent ' . ind . ' line: ' . prevline
291 elseif prevline =~ '[()]'
292 if prevline =~ '('
293 let num_unmatched_left = s:CountUnbalancedParan( prevline, '(' )
294 else
295 let num_unmatched_left = 0
296 endif
297 if prevline =~ ')'
298 let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
299 else
300 let num_unmatched_right = 0
301 " let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' )
302 endif
303 if num_unmatched_left > 0
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000304 " There is a open left paranethesis
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000305 " increase indent
306 let ind = ind + ( &sw * num_unmatched_left )
307 elseif num_unmatched_right > 0
308 " if it is an unbalanced paranethesis only unindent if
309 " it was part of a command (ie create table(..) )
310 " instead of part of an if (ie if (....) then) which should
311 " maintain the indent level
312 let ignore = s:CheckToIgnoreRightParan( prevlnum, num_unmatched_right )
313 " echom 'prevl - ) unbalanced - CTIRP - ignore: ' . ignore
314
315 if prevline =~ '^\s*)'
316 let ignore = ignore + 1
317 " echom 'prevl - begins ) unbalanced ignore: ' . ignore
318 endif
319
320 if (num_unmatched_right - ignore) > 0
321 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
322 endif
323
324 endif
325 endif
326
327
328 " echom 'CURRENT INDENT: ' . ind . ' LINE: ' . getline(v:lnum)
329
330 " This is a new blank line since we just typed a carriage return
331 " Check current line; search for simplistic matching start-of-block
332 let line = getline(v:lnum)
333
334 if line =~? '^\s*els'
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000335 " Any line when you type else will automatically back up one
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000336 " ident level (ie else, elseif, elsif)
337 let ind = ind - &sw
338 " echom 'curr - else - indent ' . ind
339 elseif line =~? '^\s*end\>'
340 let ind = s:GetStmtStarterIndent('end', v:lnum)
341 " General case for end
342 " let ind = ind - &sw
343 " echom 'curr - end - indent ' . ind
344 elseif line =~? '^\s*when\>'
345 let ind = s:GetStmtStarterIndent('when', v:lnum)
346 " If the WHEN clause is used with a MERGE or EXCEPTION
347 " clause, do not change the indent level, since these
348 " statements do not have a corresponding END statement.
349 " if stmt_starter =~? 'case'
350 " let ind = ind - &sw
351 " endif
352 " elseif line =~ '^\s*)\s*;\?\s*$'
353 " elseif line =~ '^\s*)'
354 elseif line =~ '^\s*)'
355 let num_unmatched_right = s:CountUnbalancedParan( line, ')' )
356 let ignore = s:CheckToIgnoreRightParan( v:lnum, num_unmatched_right )
357 " If the line ends in a ), then reduce the indent
358 " This catches items like:
359 " CREATE TABLE T1(
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000360 " c1 int,
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000361 " c2 int
362 " );
363 " But we do not want to unindent a line like:
Bram Moolenaarc9b4b052006-04-30 18:54:39 +0000364 " IF ( c1 = 1
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000365 " AND c2 = 3 ) THEN
366 " let num_unmatched_right = s:CountUnbalancedParan( line, ')' )
367 " if num_unmatched_right > 0
368 " elseif strpart( line, strlen(line)-1, 1 ) =~ ')'
369 " let ind = ind - &sw
370 if line =~ '^\s*)'
371 " let ignore = ignore + 1
372 " echom 'curr - begins ) unbalanced ignore: ' . ignore
373 endif
374
375 if (num_unmatched_right - ignore) > 0
376 let ind = ind - ( &sw * (num_unmatched_right - ignore) )
377 endif
378 " endif
379 endif
380
381 " echom 'final - indent ' . ind
Bram Moolenaara7241f52008-06-24 20:39:31 +0000382 return s:ModuloIndent(ind)
Bram Moolenaar0fd92892006-03-09 22:27:48 +0000383endfunction
384
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100385" Restore:
386let &cpo= s:keepcpo
Bram Moolenaar8e52a592012-05-18 21:49:28 +0200387unlet s:keepcpo
Bram Moolenaar34feacb2012-12-05 19:01:43 +0100388" vim: ts=4 fdm=marker sw=4