1" Vim indent file 2" Language: SQL 3" 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. 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 \ '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 \ '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 \ '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( 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 IsColComment(lnum, cnum) 207 let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name") 208 \ =~? "comment" 209 210 return rc 211endfunction 212 213 214" Check if the column is a comment 215function ModuloIndent(ind) 216 let ind = a:ind 217 218 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() 232 let lnum = v:lnum 233 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 249 if IsLineComment(prevlnum) == 1 250 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 272 " echom 'PREVIOUS INDENT: ' . indent(prevlnum) . ' LINE: ' . getline(prevlnum) 273 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 278 279 " 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 303 " There is a open left paranethesis 304 " 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' 334 " Any line when you type else will automatically back up one 335 " 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( 359 " c1 int, 360 " c2 int 361 " ); 362 " But we do not want to unindent a line like: 363 " IF ( c1 = 1 364 " 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: 385