neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

sqlanywhere.vim (13269B)


      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.
     37 if exists("b:did_indent")
     38    finish
     39 endif
     40 let b:did_indent     = 1
     41 let b:current_indent = "sqlanywhere"
     42 
     43 setlocal indentkeys-=0{
     44 setlocal indentkeys-=0}
     45 setlocal indentkeys-=:
     46 setlocal indentkeys-=0#
     47 setlocal 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
     55 setlocal indentkeys+==~end,=~else,=~elseif,=~elsif,0=~when,0=)
     56 
     57 " GetSQLIndent is executed whenever one of the expressions
     58 " in the indentkeys is typed
     59 setlocal indentexpr=GetSQLIndent()
     60 
     61 let b:undo_indent = "setl indentexpr< indentkeys<"
     62 
     63 " Only define the functions once.
     64 if exists("*GetSQLIndent")
     65    finish
     66 endif
     67 
     68 let s:keepcpo= &cpo
     69 set 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).
     75 let s:SQLBlockStart = '^\s*\%('.
     76                \ 'if\|else\|elseif\|elsif\|'.
     77                \ 'while\|loop\|do\|for\|'.
     78                \ 'begin\|'.
     79                \ 'case\|when\|merge\|exception'.
     80                \ '\)\>'
     81 let 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
     86 function! 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
    105 endfunction
    106 
    107 " Unindent commands based on previous indent level
    108 function! 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
    158 endfunction
    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.
    171 function! 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
    209 endfunction
    210 
    211 
    212 " Check if the line is a comment
    213 function! 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
    221 endfunction
    222 
    223 
    224 " Check if the column is a comment
    225 function! s:IsColComment(lnum, cnum)
    226    let rc = synIDattr(synID(a:lnum, a:cnum, 0), "name")
    227                \           =~? "comment"
    228 
    229    return rc
    230 endfunction
    231 
    232 
    233 " Instead of returning a column position, return
    234 " an appropriate value as a factor of shiftwidth.
    235 function! 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
    247 endfunction
    248 
    249 
    250 " Find correct indent of a new line based upon the previous line
    251 function! 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)
    394 endfunction
    395 
    396 "  Restore:
    397 let &cpo= s:keepcpo
    398 unlet s:keepcpo
    399 " vim: ts=4 fdm=marker sw=4