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