neovim

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

awk.vim (7863B)


      1 "  vim: set sw=3 sts=3:
      2 
      3 " Awk indent script. It can handle multi-line statements and expressions.
      4 " It works up to the point where the distinction between correct/incorrect
      5 " and personal taste gets fuzzy. Drop me an e-mail for bug reports and
      6 " reasonable style suggestions.
      7 "
      8 " Bugs:
      9 " =====
     10 " - Some syntax errors may cause erratic indentation.
     11 " - Same for very unusual but syntacticly correct use of { }
     12 " - In some cases it's confused by the use of ( and { in strings constants
     13 " - This version likes the closing brace of a multiline pattern-action be on
     14 "   character position 1 before the following pattern-action combination is
     15 "   formatted
     16 
     17 " Author:
     18 " =======
     19 " Erik Janssen, ejanssen@itmatters.nl
     20 "
     21 " History:
     22 " ========
     23 " 26-04-2002 Got initial version working reasonably well
     24 " 29-04-2002 Fixed problems in function headers and max line width
     25 "	     Added support for two-line if's without curly braces
     26 " Fixed hang: 2011 Aug 31
     27 " 2022 April: b:undo_indent added by Doug Kearns
     28 
     29 " Only load this indent file when no other was loaded.
     30 if exists("b:did_indent")
     31    finish
     32 endif
     33 
     34 let b:did_indent = 1
     35 
     36 setlocal indentexpr=GetAwkIndent()
     37 " Mmm, copied from the tcl indent program. Is this okay?
     38 setlocal indentkeys-=:,0#
     39 
     40 let b:undo_indent = "setl inde< indk<"
     41 
     42 " Only define the function once.
     43 if exists("*GetAwkIndent")
     44    finish
     45 endif
     46 
     47 " This function contains a lot of exit points. It checks for simple cases
     48 " first to get out of the function as soon as possible, thereby reducing the
     49 " number of possibilities later on in the difficult parts
     50 
     51 function! GetAwkIndent()
     52 
     53   " Find previous line and get its indentation
     54   let prev_lineno = s:Get_prev_line( v:lnum )
     55   if prev_lineno == 0
     56      return 0
     57   endif
     58   let prev_data = getline( prev_lineno )
     59   let ind = indent( prev_lineno )
     60 
     61   " Increase indent if the previous line contains an opening brace. Search
     62   " for this brace the hard way to prevent errors if the previous line is a
     63   " 'pattern { action }' (simple check match on /{/ increases the indent then)
     64 
     65   if s:Get_brace_balance( prev_data, '{', '}' ) > 0
     66      return ind + shiftwidth()
     67   endif
     68 
     69   let brace_balance = s:Get_brace_balance( prev_data, '(', ')' )
     70 
     71   " If prev line has positive brace_balance and starts with a word (keyword
     72   " or function name), align the current line on the first '(' of the prev
     73   " line
     74 
     75   if brace_balance > 0 && s:Starts_with_word( prev_data )
     76      return s:Safe_indent( ind, s:First_word_len(prev_data), getline(v:lnum))
     77   endif
     78 
     79   " If this line starts with an open brace bail out now before the line
     80   " continuation checks.
     81 
     82   if getline( v:lnum ) =~ '^\s*{'
     83      return ind
     84   endif
     85 
     86   " If prev line seems to be part of multiline statement:
     87   " 1. Prev line is first line of a multiline statement
     88   "    -> attempt to indent on first ' ' or '(' of prev line, just like we
     89   "       indented the positive brace balance case above
     90   " 2. Prev line is not first line of a multiline statement
     91   "    -> copy indent of prev line
     92 
     93   let continue_mode = s:Seems_continuing( prev_data )
     94   if continue_mode > 0
     95     if s:Seems_continuing( getline(s:Get_prev_line( prev_lineno )) )
     96       " Case 2
     97       return ind
     98     else
     99       " Case 1
    100       if continue_mode == 1
    101   " Need continuation due to comma, backslash, etc
    102   return s:Safe_indent( ind, s:First_word_len(prev_data), getline(v:lnum))
    103       else
    104  " if/for/while without '{'
    105  return ind + shiftwidth()
    106       endif
    107     endif
    108   endif
    109 
    110   " If the previous line doesn't need continuation on the current line we are
    111   " on the start of a new statement.  We have to make sure we align with the
    112   " previous statement instead of just the previous line. This is a bit
    113   " complicated because the previous statement might be multi-line.
    114   "
    115   " The start of a multiline statement can be found by:
    116   "
    117   " 1 If the previous line contains closing braces and has negative brace
    118   "   balance, search backwards until cumulative brace balance becomes zero,
    119   "   take indent of that line
    120   " 2 If the line before the previous needs continuation search backward
    121   "   until that's not the case anymore. Take indent of one line down.
    122 
    123   " Case 1
    124   if prev_data =~ ')' && brace_balance < 0
    125      while brace_balance != 0 && prev_lineno > 0
    126  let prev_lineno = s:Get_prev_line( prev_lineno )
    127  let prev_data = getline( prev_lineno )
    128  let brace_balance=brace_balance+s:Get_brace_balance(prev_data,'(',')' )
    129      endwhile
    130      let ind = indent( prev_lineno )
    131   else
    132      " Case 2
    133      if s:Seems_continuing( getline( prev_lineno - 1 ) )
    134  let prev_lineno = prev_lineno - 2
    135  let prev_data = getline( prev_lineno )
    136  while prev_lineno > 0 && (s:Seems_continuing( prev_data ) > 0)
    137     let prev_lineno = s:Get_prev_line( prev_lineno )
    138     let prev_data = getline( prev_lineno )
    139  endwhile
    140  let ind = indent( prev_lineno + 1 )
    141      endif
    142   endif
    143 
    144   " Decrease indent if this line contains a '}'.
    145   if getline(v:lnum) =~ '^\s*}'
    146      let ind = ind - shiftwidth()
    147   endif
    148 
    149   return ind
    150 endfunction
    151 
    152 " Find the open and close braces in this line and return how many more open-
    153 " than close braces there are. It's also used to determine cumulative balance
    154 " across multiple lines.
    155 
    156 function! s:Get_brace_balance( line, b_open, b_close )
    157   let line2 = substitute( a:line, a:b_open, "", "g" )
    158   let openb = strlen( a:line ) - strlen( line2 )
    159   let line3 = substitute( line2, a:b_close, "", "g" )
    160   let closeb = strlen( line2 ) - strlen( line3 )
    161   return openb - closeb
    162 endfunction
    163 
    164 " Find out whether the line starts with a word (i.e. keyword or function
    165 " call). Might need enhancements here.
    166 
    167 function! s:Starts_with_word( line )
    168  if a:line =~ '^\s*[a-zA-Z_0-9]\+\s*('
    169     return 1
    170  endif
    171  return 0
    172 endfunction
    173 
    174 " Find the length of the first word in a line. This is used to be able to
    175 " align a line relative to the 'print ' or 'if (' on the previous line in case
    176 " such a statement spans multiple lines.
    177 " Precondition: only to be used on lines where 'Starts_with_word' returns 1.
    178 
    179 function! s:First_word_len( line )
    180   let white_end = matchend( a:line, '^\s*' )
    181   if match( a:line, '^\s*func' ) != -1
    182     let word_end = matchend( a:line, '[a-z]\+\s\+[a-zA-Z_0-9]\+[ (]*' )
    183   else
    184     let word_end = matchend( a:line, '[a-zA-Z_0-9]\+[ (]*' )
    185   endif
    186   return word_end - white_end
    187 endfunction
    188 
    189 " Determine if 'line' completes a statement or is continued on the next line.
    190 " This one is far from complete and accepts illegal code. Not important for
    191 " indenting, however.
    192 
    193 function! s:Seems_continuing( line )
    194  " Unfinished lines
    195  if a:line =~ '\(--\|++\)\s*$'
    196    return 0
    197  endif
    198  if a:line =~ '[\\,\|\&\+\-\*\%\^]\s*$'
    199    return 1
    200  endif
    201  " if/for/while (cond) eol
    202  if a:line =~ '^\s*\(if\|while\|for\)\s*(.*)\s*$' || a:line =~ '^\s*else\s*'
    203      return 2
    204   endif
    205  return 0
    206 endfunction
    207 
    208 " Get previous relevant line. Search back until a line is that is no
    209 " comment or blank and return the line number
    210 
    211 function! s:Get_prev_line( lineno )
    212   let lnum = a:lineno - 1
    213   let data = getline( lnum )
    214   while lnum > 0 && (data =~ '^\s*#' || data =~ '^\s*$')
    215      let lnum = lnum - 1
    216      let data = getline( lnum )
    217   endwhile
    218   return lnum
    219 endfunction
    220 
    221 " This function checks whether an indented line exceeds a maximum linewidth
    222 " (hardcoded 80). If so and it is possible to stay within 80 positions (or
    223 " limit num of characters beyond linewidth) by decreasing the indent (keeping
    224 " it > base_indent), do so.
    225 
    226 function! s:Safe_indent( base, wordlen, this_line )
    227   let line_base = matchend( a:this_line, '^\s*' )
    228   let line_len = strlen( a:this_line ) - line_base
    229   let indent = a:base
    230   if (indent + a:wordlen + line_len) > 80
    231     " Simple implementation good enough for the time being
    232     let indent = indent + 3
    233   endif
    234   return indent + a:wordlen
    235 endfunction