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