blob: e65331977c41c228d31cba2a54432a7b5b0b514a [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 Moolenaar071d4272004-06-13 20:20:40 +000027
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
Bram Moolenaara6c27c42019-05-09 19:16:22 +020050 " Find previous line and get its indentation
Bram Moolenaar071d4272004-06-13 20:20:40 +000051 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
Bram Moolenaar3ec574f2017-06-13 18:12:01 +020063 return ind + shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +000064 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 '{'
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200102 return ind + shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000103 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
Bram Moolenaar5302d9e2011-09-14 17:55:08 +0200122 while brace_balance != 0 && prev_lineno > 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000123 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*}'
Bram Moolenaar3ec574f2017-06-13 18:12:01 +0200143 let ind = ind - shiftwidth()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000144 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
Bram Moolenaard960d762011-09-21 19:22:10 +0200192 if a:line =~ '\(--\|++\)\s*$'
193 return 0
194 endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000195 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