Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 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 |
Bram Moolenaar | 5302d9e | 2011-09-14 17:55:08 +0200 | [diff] [blame] | 26 | " Fixed hang: 2011 Aug 31 |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 27 | |
| 28 | " Only load this indent file when no other was loaded. |
| 29 | if exists("b:did_indent") |
| 30 | finish |
| 31 | endif |
| 32 | |
| 33 | let b:did_indent = 1 |
| 34 | |
| 35 | setlocal indentexpr=GetAwkIndent() |
| 36 | " Mmm, copied from the tcl indent program. Is this okay? |
| 37 | setlocal indentkeys-=:,0# |
| 38 | |
| 39 | " Only define the function once. |
| 40 | if exists("*GetAwkIndent") |
| 41 | finish |
| 42 | endif |
| 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 | |
| 48 | function! GetAwkIndent() |
| 49 | |
| 50 | " Find previous line and get it's 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 |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 63 | return ind + shiftwidth() |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 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 '{' |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 102 | return ind + shiftwidth() |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 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 |
Bram Moolenaar | 5302d9e | 2011-09-14 17:55:08 +0200 | [diff] [blame] | 122 | while brace_balance != 0 && prev_lineno > 0 |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 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*}' |
Bram Moolenaar | 3ec574f | 2017-06-13 18:12:01 +0200 | [diff] [blame] | 143 | let ind = ind - shiftwidth() |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 144 | endif |
| 145 | |
| 146 | return ind |
| 147 | endfunction |
| 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 | |
| 153 | function! 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 |
| 159 | endfunction |
| 160 | |
| 161 | " Find out whether the line starts with a word (i.e. keyword or function |
| 162 | " call). Might need enhancements here. |
| 163 | |
| 164 | function! s:Starts_with_word( line ) |
| 165 | if a:line =~ '^\s*[a-zA-Z_0-9]\+\s*(' |
| 166 | return 1 |
| 167 | endif |
| 168 | return 0 |
| 169 | endfunction |
| 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 | |
| 176 | function! 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 |
| 184 | endfunction |
| 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 | |
| 190 | function! s:Seems_continuing( line ) |
| 191 | " Unfinished lines |
Bram Moolenaar | d960d76 | 2011-09-21 19:22:10 +0200 | [diff] [blame] | 192 | if a:line =~ '\(--\|++\)\s*$' |
| 193 | return 0 |
| 194 | endif |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 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 |
| 203 | endfunction |
| 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 | |
| 208 | function! 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 |
| 216 | endfunction |
| 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 | |
| 223 | function! 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 |
| 232 | endfunction |