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 51 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). 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 \ 's: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 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' 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 \ 's: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( 176 \ '\%(\<end\s\+\)\@<!\<case\>\|\<exception\>\|\<merge\>', 177 \ '', 178 \ '\%(\%(\<when\s\+others\>\)\|\%(\<end\s\+case\>\)\)', 179 \ 'bW', 180 \ 's: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 s:IsLineComment(lnum) 195 let rc = synIDattr( 196 \ synID(a:lnum, 197 \ match(getline(a:lnum), '\S')+1, 0) 198 \ , "name") 199 \ =~? "comment" 200 201 return rc 202endfunction 203 204 205" Check if the column is a comment 206function s:IsColComment(lnum, cnum) 207 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name") 208 \ =~? "comment" 209 210 return rc 211endfunction 212 213 214" Instead of returning a column position, return 215" an appropriate value as a factor of shiftwidth. 216function s:ModuloIndent(ind) 217 let ind = a:ind 218 219 if ind > 0 220 let modulo = ind % &shiftwidth 221 222 if modulo > 0 223 let ind = ind - modulo 224 endif 225 endif 226 227 return ind 228endfunction 229 230 231" Find correct indent of a new line based upon the previous line 232function GetSQLIndent() 233 let lnum = v:lnum 234 let ind = indent(lnum) 235 236 " If the current line is a comment, leave the indent as is 237 " Comment out this additional check since it affects the 238 " indenting of =, and will not reindent comments as it should 239 " if s:IsLineComment(lnum) == 1 240 " return ind 241 " endif 242 243 " while 1 244 " Get previous non-blank line 245 let prevlnum = prevnonblank(lnum - 1) 246 if prevlnum <= 0 247 return ind 248 endif 249 250 if s:IsLineComment(prevlnum) == 1 251 if getline(v:lnum) =~ '^\s*\*' 252 let ind = s:ModuloIndent(indent(prevlnum)) 253 return ind + 1 254 endif 255 " If the previous line is a comment, then return -1 256 " to tell Vim to use the formatoptions setting to determine 257 " the indent to use 258 " But only if the next line is blank. This would be true if 259 " the user is typing, but it would not be true if the user 260 " is reindenting the file 261 if getline(v:lnum) =~ '^\s*$' 262 return -1 263 endif 264 endif 265 266 " let prevline = getline(prevlnum) 267 " if prevline !~ '^\s*$' 268 " " echom 'previous non blank - break: ' . prevline 269 " break 270 " endif 271 " endwhile 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" vim:sw=4: 386