1" Vim indent file 2" Language: SQL 3" Maintainer: David Fishburn <fishburn at ianywhere dot com> 4" Last Change: Mon Apr 02 2007 9:13:47 AM 5" Version: 1.5 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" 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 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 41setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=) 42 43" GetSQLIndent is executed whenever one of the expressions 44" in the indentkeys is typed 45setlocal indentexpr=GetSQLIndent() 46 47" Only define the functions once. 48if exists("*GetSQLIndent") 49 finish 50endif 51let s:keepcpo= &cpo 52set cpo&vim 53 54" List of all the statements that start a new block. 55" These are typically words that start a line. 56" IS is excluded, since it is difficult to determine when the 57" ending block is (especially for procedures/functions). 58let s:SQLBlockStart = '^\s*\%('. 59 \ 'if\|else\|elseif\|elsif\|'. 60 \ 'while\|loop\|do\|'. 61 \ 'begin\|'. 62 \ 'case\|when\|merge\|exception'. 63 \ '\)\>' 64let s:SQLBlockEnd = '^\s*\(end\)\>' 65 66" The indent level is also based on unmatched paranethesis 67" If a line has an extra "(" increase the indent 68" If a line has an extra ")" decrease the indent 69function s:CountUnbalancedParan( line, paran_to_check ) 70 let l = a:line 71 let lp = substitute(l, '[^(]', '', 'g') 72 let l = a:line 73 let rp = substitute(l, '[^)]', '', 'g') 74 75 if a:paran_to_check =~ ')' 76 " echom 'CountUnbalancedParan ) returning: ' . 77 " \ (strlen(rp) - strlen(lp)) 78 return (strlen(rp) - strlen(lp)) 79 elseif a:paran_to_check =~ '(' 80 " echom 'CountUnbalancedParan ( returning: ' . 81 " \ (strlen(lp) - strlen(rp)) 82 return (strlen(lp) - strlen(rp)) 83 else 84 " echom 'CountUnbalancedParan unknown paran to check: ' . 85 " \ a:paran_to_check 86 return 0 87 endif 88endfunction 89 90" Unindent commands based on previous indent level 91function s:CheckToIgnoreRightParan( prev_lnum, num_levels ) 92 let lnum = a:prev_lnum 93 let line = getline(lnum) 94 let ends = 0 95 let num_right_paran = a:num_levels 96 let ignore_paran = 0 97 let vircol = 1 98 99 while num_right_paran > 0 100 silent! exec 'norm! '.lnum."G\<bar>".vircol."\<bar>" 101 let right_paran = search( ')', 'W' ) 102 if right_paran != lnum 103 " This should not happen since there should be at least 104 " num_right_paran matches for this line 105 break 106 endif 107 let vircol = virtcol(".") 108 109 " if getline(".") =~ '^)' 110 let matching_paran = searchpair('(', '', ')', 'bW', 111 \ 's:IsColComment(line("."), col("."))') 112 113 if matching_paran < 1 114 " No match found 115 " echom 'CTIRP - no match found, ignoring' 116 break 117 endif 118 119 if matching_paran == lnum 120 " This was not an unmatched parantenses, start the search again 121 " again after this column 122 " echom 'CTIRP - same line match, ignoring' 123 continue 124 endif 125 126 " echom 'CTIRP - match: ' . line(".") . ' ' . getline(".") 127 128 if getline(matching_paran) =~? '\(if\|while\)\>' 129 " echom 'CTIRP - if/while ignored: ' . line(".") . ' ' . getline(".") 130 let ignore_paran = ignore_paran + 1 131 endif 132 133 " One match found, decrease and check for further matches 134 let num_right_paran = num_right_paran - 1 135 136 endwhile 137 138 " Fallback - just move back one 139 " return a:prev_indent - &sw 140 return ignore_paran 141endfunction 142 143" Based on the keyword provided, loop through previous non empty 144" non comment lines to find the statement that initated the keyword. 145" Return its indent level 146" CASE .. 147" WHEN ... 148" Should return indent level of CASE 149" EXCEPTION .. 150" WHEN ... 151" something; 152" WHEN ... 153" Should return indent level of exception. 154function s:GetStmtStarterIndent( keyword, curr_lnum ) 155 let lnum = a:curr_lnum 156 157 " Default - reduce indent by 1 158 let ind = indent(a:curr_lnum) - &sw 159 160 if a:keyword =~? 'end' 161 exec 'normal! ^' 162 let stmts = '^\s*\%('. 163 \ '\<begin\>\|' . 164 \ '\%(\%(\<end\s\+\)\@<!\<loop\>\)\|' . 165 \ '\%(\%(\<end\s\+\)\@<!\<case\>\)\|' . 166 \ '\%(\%(\<end\s\+\)\@<!\<for\>\)\|' . 167 \ '\%(\%(\<end\s\+\)\@<!\<if\>\)'. 168 \ '\)' 169 let matching_lnum = searchpair(stmts, '', '\<end\>\zs', 'bW', 170 \ 's:IsColComment(line("."), col(".")) == 1') 171 exec 'normal! $' 172 if matching_lnum > 0 && matching_lnum < a:curr_lnum 173 let ind = indent(matching_lnum) 174 endif 175 elseif a:keyword =~? 'when' 176 exec 'normal! ^' 177 let matching_lnum = searchpair( 178 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>', 179 \ '', 180 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)', 181 \ 'bW', 182 \ 's:IsColComment(line("."), col(".")) == 1') 183 exec 'normal! $' 184 if matching_lnum > 0 && matching_lnum < a:curr_lnum 185 let ind = indent(matching_lnum) 186 else 187 let ind = indent(a:curr_lnum) 188 endif 189 endif 190 191 return ind 192endfunction 193 194 195" Check if the line is a comment 196function s:IsLineComment(lnum) 197 let rc = synIDattr( 198 \ synID(a:lnum, 199 \ match(getline(a:lnum), '\S')+1, 0) 200 \ , "name") 201 \ =~? "comment" 202 203 return rc 204endfunction 205 206 207" Check if the column is a comment 208function s:IsColComment(lnum, cnum) 209 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name") 210 \ =~? "comment" 211 212 return rc 213endfunction 214 215 216" Instead of returning a column position, return 217" an appropriate value as a factor of shiftwidth. 218function s:ModuloIndent(ind) 219 let ind = a:ind 220 221 if ind > 0 222 let modulo = ind % &shiftwidth 223 224 if modulo > 0 225 let ind = ind - modulo 226 endif 227 endif 228 229 return ind 230endfunction 231 232 233" Find correct indent of a new line based upon the previous line 234function GetSQLIndent() 235 let lnum = v:lnum 236 let ind = indent(lnum) 237 238 " If the current line is a comment, leave the indent as is 239 " Comment out this additional check since it affects the 240 " indenting of =, and will not reindent comments as it should 241 " if s:IsLineComment(lnum) == 1 242 " return ind 243 " endif 244 245 " while 1 246 " Get previous non-blank line 247 let prevlnum = prevnonblank(lnum - 1) 248 if prevlnum <= 0 249 return ind 250 endif 251 252 if s:IsLineComment(prevlnum) == 1 253 if getline(v:lnum) =~ '^\s*\*' 254 let ind = s:ModuloIndent(indent(prevlnum)) 255 return ind + 1 256 endif 257 " If the previous line is a comment, then return -1 258 " to tell Vim to use the formatoptions setting to determine 259 " the indent to use 260 " But only if the next line is blank. This would be true if 261 " the user is typing, but it would not be true if the user 262 " is reindenting the file 263 if getline(v:lnum) =~ '^\s*$' 264 return -1 265 endif 266 endif 267 268 " let prevline = getline(prevlnum) 269 " if prevline !~ '^\s*$' 270 " " echom 'previous non blank - break: ' . prevline 271 " break 272 " endif 273 " endwhile 274 275 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum) 276 277 " This is the line you just hit return on, it is not the current line 278 " which is new and empty 279 " Based on this line, we can determine how much to indent the new 280 " line 281 282 " Get default indent (from prev. line) 283 let ind = indent(prevlnum) 284 let prevline = getline(prevlnum) 285 286 " Now check what's on the previous line to determine if the indent 287 " should be changed, for example IF, BEGIN, should increase the indent 288 " where END IF, END, should decrease the indent. 289 if prevline =~? s:SQLBlockStart 290 " Move indent in 291 let ind = ind + &sw 292 " echom 'prevl - SQLBlockStart - indent ' . ind . ' line: ' . prevline 293 elseif prevline =~ '[()]' 294 if prevline =~ '(' 295 let num_unmatched_left = s:CountUnbalancedParan( prevline, '(' ) 296 else 297 let num_unmatched_left = 0 298 endif 299 if prevline =~ ')' 300 let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' ) 301 else 302 let num_unmatched_right = 0 303 " let num_unmatched_right = s:CountUnbalancedParan( prevline, ')' ) 304 endif 305 if num_unmatched_left > 0 306 " There is a open left paranethesis 307 " increase indent 308 let ind = ind + ( &sw * num_unmatched_left ) 309 elseif num_unmatched_right > 0 310 " if it is an unbalanced paranethesis only unindent if 311 " it was part of a command (ie create table(..) ) 312 " instead of part of an if (ie if (....) then) which should 313 " maintain the indent level 314 let ignore = s:CheckToIgnoreRightParan( prevlnum, num_unmatched_right ) 315 " echom 'prevl - ) unbalanced - CTIRP - ignore: ' . ignore 316 317 if prevline =~ '^\s*)' 318 let ignore = ignore + 1 319 " echom 'prevl - begins ) unbalanced ignore: ' . ignore 320 endif 321 322 if (num_unmatched_right - ignore) > 0 323 let ind = ind - ( &sw * (num_unmatched_right - ignore) ) 324 endif 325 326 endif 327 endif 328 329 330 " echom 'CURRENT INDENT: ' . ind . ' LINE: ' . getline(v:lnum) 331 332 " This is a new blank line since we just typed a carriage return 333 " Check current line; search for simplistic matching start-of-block 334 let line = getline(v:lnum) 335 336 if line =~? '^\s*els' 337 " Any line when you type else will automatically back up one 338 " ident level (ie else, elseif, elsif) 339 let ind = ind - &sw 340 " echom 'curr - else - indent ' . ind 341 elseif line =~? '^\s*end\>' 342 let ind = s:GetStmtStarterIndent('end', v:lnum) 343 " General case for end 344 " let ind = ind - &sw 345 " echom 'curr - end - indent ' . ind 346 elseif line =~? '^\s*when\>' 347 let ind = s:GetStmtStarterIndent('when', v:lnum) 348 " If the WHEN clause is used with a MERGE or EXCEPTION 349 " clause, do not change the indent level, since these 350 " statements do not have a corresponding END statement. 351 " if stmt_starter =~? 'case' 352 " let ind = ind - &sw 353 " endif 354 " elseif line =~ '^\s*)\s*;\?\s*$' 355 " elseif line =~ '^\s*)' 356 elseif line =~ '^\s*)' 357 let num_unmatched_right = s:CountUnbalancedParan( line, ')' ) 358 let ignore = s:CheckToIgnoreRightParan( v:lnum, num_unmatched_right ) 359 " If the line ends in a ), then reduce the indent 360 " This catches items like: 361 " CREATE TABLE T1( 362 " c1 int, 363 " c2 int 364 " ); 365 " But we do not want to unindent a line like: 366 " IF ( c1 = 1 367 " AND c2 = 3 ) THEN 368 " let num_unmatched_right = s:CountUnbalancedParan( line, ')' ) 369 " if num_unmatched_right > 0 370 " elseif strpart( line, strlen(line)-1, 1 ) =~ ')' 371 " let ind = ind - &sw 372 if line =~ '^\s*)' 373 " let ignore = ignore + 1 374 " echom 'curr - begins ) unbalanced ignore: ' . ignore 375 endif 376 377 if (num_unmatched_right - ignore) > 0 378 let ind = ind - ( &sw * (num_unmatched_right - ignore) ) 379 endif 380 " endif 381 endif 382 383 " echom 'final - indent ' . ind 384 return s:ModuloIndent(ind) 385endfunction 386 387let &cpo = s:keepcpo 388unlet s:keepcpo 389 390" vim:sw=4: 391