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