blob: cf8132241cb6336b5f0168c6109f77288909121b [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
Bram Moolenaar5302d9e2011-09-14 17:55:08 +020026" Fixed hang: 2011 Aug 31
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +010027" 2022 April: b:undo_indent added by Doug Kearns
Bram Moolenaar071d4272004-06-13 20:20:40 +000028
29" Only load this indent file when no other was loaded.
30if exists("b:did_indent")
31 finish
32endif
33
34let b:did_indent = 1
35
36setlocal indentexpr=GetAwkIndent()
37" Mmm, copied from the tcl indent program. Is this okay?
38setlocal indentkeys-=:,0#
39
Bram Moolenaarcbaff5e2022-04-08 17:45:08 +010040let b:undo_indent = "setl inde< indk<"
41
Bram Moolenaar071d4272004-06-13 20:20:40 +000042" Only define the function once.
43if exists("*GetAwkIndent")
44 finish
45endif
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
51function! GetAwkIndent()
52
Bram Moolenaara6c27c42019-05-09 19:16:22 +020053 " Find previous line and get its indentation
Bram Moolenaar071d4272004-06-13 20:20:40 +000054 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
Bram Moolenaar3ec574f2017-06-13 18:12:01 +020066 return ind + shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +000067 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 '{'
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200105 return ind + shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000106 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
Bram Moolenaar5302d9e2011-09-14 17:55:08 +0200125 while brace_balance != 0 && prev_lineno > 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000126 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*}'
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200146 let ind = ind - shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000147 endif
148
149 return ind
150endfunction
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
156function! 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
162endfunction
163
164" Find out whether the line starts with a word (i.e. keyword or function
165" call). Might need enhancements here.
166
167function! s:Starts_with_word( line )
168 if a:line =~ '^\s*[a-zA-Z_0-9]\+\s*('
169 return 1
170 endif
171 return 0
172endfunction
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
179function! 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
187endfunction
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
193function! s:Seems_continuing( line )
194 " Unfinished lines
Bram Moolenaard960d762011-09-21 19:22:10 +0200195 if a:line =~ '\(--\|++\)\s*$'
196 return 0
197 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000198 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
206endfunction
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
211function! 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
219endfunction
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
226function! 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
235endfunction