1" Vim indent file 2" Language: SQL 3" Maintainer: David Fishburn <dfishburn dot vim at gmail dot com> 4" Last Change: 2012 Dec 05 5" Version: 3.0 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. 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. 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" 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" 32 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" 39let s:keepcpo= &cpo 40set cpo&vim 41 42setlocal indentkeys-=0{ 43setlocal indentkeys-=0} 44setlocal indentkeys-=: 45setlocal indentkeys-=0# 46setlocal indentkeys-=e 47 48" This indicates formatting should take place when one of these 49" 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 56" GetSQLIndent is executed whenever one of the expressions 57" in the indentkeys is typed 58setlocal indentexpr=GetSQLIndent() 59 60" List of all the statements that start a new block. 61" These are typically words that start a line. 62" IS is excluded, since it is difficult to determine when the 63" ending block is (especially for procedures/functions). 64let s:SQLBlockStart = '^\s*\%('. 65 \ 'if\|else\|elseif\|elsif\|'. 66 \ 'while\|loop\|do\|for\|'. 67 \ '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 75function! s:CountUnbalancedParan( line, paran_to_check ) 76 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 97function! s:CheckToIgnoreRightParan( prev_lnum, num_levels ) 98 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', 117 \ 's:IsColComment(line("."), col("."))') 118 119 if matching_paran < 1 120 " No match found 121 " echom 'CTIRP - no match found, ignoring' 122 break 123 endif 124 125 if matching_paran == lnum 126 " 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. 160function! s:GetStmtStarterIndent( keyword, curr_lnum ) 161 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', 176 \ 's:IsColComment(line("."), col(".")) == 1') 177 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( 184 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>', 185 \ '', 186 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)', 187 \ 'bW', 188 \ 's:IsColComment(line("."), col(".")) == 1') 189 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 202function! s:IsLineComment(lnum) 203 let rc = synIDattr( 204 \ synID(a:lnum, 205 \ match(getline(a:lnum), '\S')+1, 0) 206 \ , "name") 207 \ =~? "comment" 208 209 return rc 210endfunction 211 212 213" Check if the column is a comment 214function! s:IsColComment(lnum, cnum) 215 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name") 216 \ =~? "comment" 217 218 return rc 219endfunction 220 221 222" Instead of returning a column position, return 223" an appropriate value as a factor of shiftwidth. 224function! s:ModuloIndent(ind) 225 let ind = a:ind 226 227 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 240function! GetSQLIndent() 241 let lnum = v:lnum 242 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 247 " if s:IsLineComment(lnum) == 1 248 " return ind 249 " endif 250 251 " Get previous non-blank line 252 let prevlnum = prevnonblank(lnum - 1) 253 if prevlnum <= 0 254 return ind 255 endif 256 257 if s:IsLineComment(prevlnum) == 1 258 if getline(v:lnum) =~ '^\s*\*' 259 let ind = s:ModuloIndent(indent(prevlnum)) 260 return ind + 1 261 endif 262 " 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 272 273 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum) 274 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 279 280 " 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 304 " There is a open left paranethesis 305 " 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' 335 " Any line when you type else will automatically back up one 336 " 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( 360 " c1 int, 361 " c2 int 362 " ); 363 " But we do not want to unindent a line like: 364 " IF ( c1 = 1 365 " 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 382 return s:ModuloIndent(ind) 383endfunction 384 385" Restore: 386let &cpo= s:keepcpo 387unlet s:keepcpo 388" vim: ts=4 fdm=marker sw=4 389