awk.vim 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. " vim: set sw=3 sts=3:
  2. " Awk indent script. It can handle multi-line statements and expressions.
  3. " It works up to the point where the distinction between correct/incorrect
  4. " and personal taste gets fuzzy. Drop me an e-mail for bug reports and
  5. " reasonable style suggestions.
  6. "
  7. " Bugs:
  8. " =====
  9. " - Some syntax errors may cause erratic indentation.
  10. " - Same for very unusual but syntacticly correct use of { }
  11. " - In some cases it's confused by the use of ( and { in strings constants
  12. " - This version likes the closing brace of a multiline pattern-action be on
  13. " character position 1 before the following pattern-action combination is
  14. " formatted
  15. " Author:
  16. " =======
  17. " Erik Janssen, ejanssen@itmatters.nl
  18. "
  19. " History:
  20. " ========
  21. " 26-04-2002 Got initial version working reasonably well
  22. " 29-04-2002 Fixed problems in function headers and max line width
  23. " Added support for two-line if's without curly braces
  24. " Fixed hang: 2011 Aug 31
  25. " 2022 April: b:undo_indent added by Doug Kearns
  26. " Only load this indent file when no other was loaded.
  27. if exists("b:did_indent")
  28. finish
  29. endif
  30. let b:did_indent = 1
  31. setlocal indentexpr=GetAwkIndent()
  32. " Mmm, copied from the tcl indent program. Is this okay?
  33. setlocal indentkeys-=:,0#
  34. let b:undo_indent = "setl inde< indk<"
  35. " Only define the function once.
  36. if exists("*GetAwkIndent")
  37. finish
  38. endif
  39. " This function contains a lot of exit points. It checks for simple cases
  40. " first to get out of the function as soon as possible, thereby reducing the
  41. " number of possibilities later on in the difficult parts
  42. function! GetAwkIndent()
  43. " Find previous line and get its indentation
  44. let prev_lineno = s:Get_prev_line( v:lnum )
  45. if prev_lineno == 0
  46. return 0
  47. endif
  48. let prev_data = getline( prev_lineno )
  49. let ind = indent( prev_lineno )
  50. " Increase indent if the previous line contains an opening brace. Search
  51. " for this brace the hard way to prevent errors if the previous line is a
  52. " 'pattern { action }' (simple check match on /{/ increases the indent then)
  53. if s:Get_brace_balance( prev_data, '{', '}' ) > 0
  54. return ind + shiftwidth()
  55. endif
  56. let brace_balance = s:Get_brace_balance( prev_data, '(', ')' )
  57. " If prev line has positive brace_balance and starts with a word (keyword
  58. " or function name), align the current line on the first '(' of the prev
  59. " line
  60. if brace_balance > 0 && s:Starts_with_word( prev_data )
  61. return s:Safe_indent( ind, s:First_word_len(prev_data), getline(v:lnum))
  62. endif
  63. " If this line starts with an open brace bail out now before the line
  64. " continuation checks.
  65. if getline( v:lnum ) =~ '^\s*{'
  66. return ind
  67. endif
  68. " If prev line seems to be part of multiline statement:
  69. " 1. Prev line is first line of a multiline statement
  70. " -> attempt to indent on first ' ' or '(' of prev line, just like we
  71. " indented the positive brace balance case above
  72. " 2. Prev line is not first line of a multiline statement
  73. " -> copy indent of prev line
  74. let continue_mode = s:Seems_continuing( prev_data )
  75. if continue_mode > 0
  76. if s:Seems_continuing( getline(s:Get_prev_line( prev_lineno )) )
  77. " Case 2
  78. return ind
  79. else
  80. " Case 1
  81. if continue_mode == 1
  82. " Need continuation due to comma, backslash, etc
  83. return s:Safe_indent( ind, s:First_word_len(prev_data), getline(v:lnum))
  84. else
  85. " if/for/while without '{'
  86. return ind + shiftwidth()
  87. endif
  88. endif
  89. endif
  90. " If the previous line doesn't need continuation on the current line we are
  91. " on the start of a new statement. We have to make sure we align with the
  92. " previous statement instead of just the previous line. This is a bit
  93. " complicated because the previous statement might be multi-line.
  94. "
  95. " The start of a multiline statement can be found by:
  96. "
  97. " 1 If the previous line contains closing braces and has negative brace
  98. " balance, search backwards until cumulative brace balance becomes zero,
  99. " take indent of that line
  100. " 2 If the line before the previous needs continuation search backward
  101. " until that's not the case anymore. Take indent of one line down.
  102. " Case 1
  103. if prev_data =~ ')' && brace_balance < 0
  104. while brace_balance != 0 && prev_lineno > 0
  105. let prev_lineno = s:Get_prev_line( prev_lineno )
  106. let prev_data = getline( prev_lineno )
  107. let brace_balance=brace_balance+s:Get_brace_balance(prev_data,'(',')' )
  108. endwhile
  109. let ind = indent( prev_lineno )
  110. else
  111. " Case 2
  112. if s:Seems_continuing( getline( prev_lineno - 1 ) )
  113. let prev_lineno = prev_lineno - 2
  114. let prev_data = getline( prev_lineno )
  115. while prev_lineno > 0 && (s:Seems_continuing( prev_data ) > 0)
  116. let prev_lineno = s:Get_prev_line( prev_lineno )
  117. let prev_data = getline( prev_lineno )
  118. endwhile
  119. let ind = indent( prev_lineno + 1 )
  120. endif
  121. endif
  122. " Decrease indent if this line contains a '}'.
  123. if getline(v:lnum) =~ '^\s*}'
  124. let ind = ind - shiftwidth()
  125. endif
  126. return ind
  127. endfunction
  128. " Find the open and close braces in this line and return how many more open-
  129. " than close braces there are. It's also used to determine cumulative balance
  130. " across multiple lines.
  131. function! s:Get_brace_balance( line, b_open, b_close )
  132. let line2 = substitute( a:line, a:b_open, "", "g" )
  133. let openb = strlen( a:line ) - strlen( line2 )
  134. let line3 = substitute( line2, a:b_close, "", "g" )
  135. let closeb = strlen( line2 ) - strlen( line3 )
  136. return openb - closeb
  137. endfunction
  138. " Find out whether the line starts with a word (i.e. keyword or function
  139. " call). Might need enhancements here.
  140. function! s:Starts_with_word( line )
  141. if a:line =~ '^\s*[a-zA-Z_0-9]\+\s*('
  142. return 1
  143. endif
  144. return 0
  145. endfunction
  146. " Find the length of the first word in a line. This is used to be able to
  147. " align a line relative to the 'print ' or 'if (' on the previous line in case
  148. " such a statement spans multiple lines.
  149. " Precondition: only to be used on lines where 'Starts_with_word' returns 1.
  150. function! s:First_word_len( line )
  151. let white_end = matchend( a:line, '^\s*' )
  152. if match( a:line, '^\s*func' ) != -1
  153. let word_end = matchend( a:line, '[a-z]\+\s\+[a-zA-Z_0-9]\+[ (]*' )
  154. else
  155. let word_end = matchend( a:line, '[a-zA-Z_0-9]\+[ (]*' )
  156. endif
  157. return word_end - white_end
  158. endfunction
  159. " Determine if 'line' completes a statement or is continued on the next line.
  160. " This one is far from complete and accepts illegal code. Not important for
  161. " indenting, however.
  162. function! s:Seems_continuing( line )
  163. " Unfinished lines
  164. if a:line =~ '\(--\|++\)\s*$'
  165. return 0
  166. endif
  167. if a:line =~ '[\\,\|\&\+\-\*\%\^]\s*$'
  168. return 1
  169. endif
  170. " if/for/while (cond) eol
  171. if a:line =~ '^\s*\(if\|while\|for\)\s*(.*)\s*$' || a:line =~ '^\s*else\s*'
  172. return 2
  173. endif
  174. return 0
  175. endfunction
  176. " Get previous relevant line. Search back until a line is that is no
  177. " comment or blank and return the line number
  178. function! s:Get_prev_line( lineno )
  179. let lnum = a:lineno - 1
  180. let data = getline( lnum )
  181. while lnum > 0 && (data =~ '^\s*#' || data =~ '^\s*$')
  182. let lnum = lnum - 1
  183. let data = getline( lnum )
  184. endwhile
  185. return lnum
  186. endfunction
  187. " This function checks whether an indented line exceeds a maximum linewidth
  188. " (hardcoded 80). If so and it is possible to stay within 80 positions (or
  189. " limit num of characters beyond linewidth) by decreasing the indent (keeping
  190. " it > base_indent), do so.
  191. function! s:Safe_indent( base, wordlen, this_line )
  192. let line_base = matchend( a:this_line, '^\s*' )
  193. let line_len = strlen( a:this_line ) - line_base
  194. let indent = a:base
  195. if (indent + a:wordlen + line_len) > 80
  196. " Simple implementation good enough for the time being
  197. let indent = indent + 3
  198. endif
  199. return indent + a:wordlen
  200. endfunction