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