blob: 726a5ddbe56581255320710583b2994a496f0732 [file] [log] [blame]
Bram Moolenaar071d4272004-06-13 20:20:40 +00001" 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