awk.vim 7.6 KB

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