rust.vim 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. " Vim indent file
  2. " Language: Rust
  3. " Author: Chris Morgan <me@chrismorgan.info>
  4. " Last Change: 2017 Jun 13
  5. " For bugs, patches and license go to https://github.com/rust-lang/rust.vim
  6. " Only load this indent file when no other was loaded.
  7. if exists("b:did_indent")
  8. finish
  9. endif
  10. let b:did_indent = 1
  11. setlocal cindent
  12. setlocal cinoptions=L0,(0,Ws,J1,j1
  13. setlocal cinkeys=0{,0},!^F,o,O,0[,0]
  14. " Don't think cinwords will actually do anything at all... never mind
  15. setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern
  16. " Some preliminary settings
  17. setlocal nolisp " Make sure lisp indenting doesn't supersede us
  18. setlocal autoindent " indentexpr isn't much help otherwise
  19. " Also do indentkeys, otherwise # gets shoved to column 0 :-/
  20. setlocal indentkeys=0{,0},!^F,o,O,0[,0]
  21. setlocal indentexpr=GetRustIndent(v:lnum)
  22. " Only define the function once.
  23. if exists("*GetRustIndent")
  24. finish
  25. endif
  26. let s:save_cpo = &cpo
  27. set cpo&vim
  28. " Come here when loading the script the first time.
  29. function! s:get_line_trimmed(lnum)
  30. " Get the line and remove a trailing comment.
  31. " Use syntax highlighting attributes when possible.
  32. " NOTE: this is not accurate; /* */ or a line continuation could trick it
  33. let line = getline(a:lnum)
  34. let line_len = strlen(line)
  35. if has('syntax_items')
  36. " If the last character in the line is a comment, do a binary search for
  37. " the start of the comment. synID() is slow, a linear search would take
  38. " too long on a long line.
  39. if synIDattr(synID(a:lnum, line_len, 1), "name") =~ 'Comment\|Todo'
  40. let min = 1
  41. let max = line_len
  42. while min < max
  43. let col = (min + max) / 2
  44. if synIDattr(synID(a:lnum, col, 1), "name") =~ 'Comment\|Todo'
  45. let max = col
  46. else
  47. let min = col + 1
  48. endif
  49. endwhile
  50. let line = strpart(line, 0, min - 1)
  51. endif
  52. return substitute(line, "\s*$", "", "")
  53. else
  54. " Sorry, this is not complete, nor fully correct (e.g. string "//").
  55. " Such is life.
  56. return substitute(line, "\s*//.*$", "", "")
  57. endif
  58. endfunction
  59. function! s:is_string_comment(lnum, col)
  60. if has('syntax_items')
  61. for id in synstack(a:lnum, a:col)
  62. let synname = synIDattr(id, "name")
  63. if synname == "rustString" || synname =~ "^rustComment"
  64. return 1
  65. endif
  66. endfor
  67. else
  68. " without syntax, let's not even try
  69. return 0
  70. endif
  71. endfunction
  72. function GetRustIndent(lnum)
  73. " Starting assumption: cindent (called at the end) will do it right
  74. " normally. We just want to fix up a few cases.
  75. let line = getline(a:lnum)
  76. if has('syntax_items')
  77. let synname = synIDattr(synID(a:lnum, 1, 1), "name")
  78. if synname == "rustString"
  79. " If the start of the line is in a string, don't change the indent
  80. return -1
  81. elseif synname =~ '\(Comment\|Todo\)'
  82. \ && line !~ '^\s*/\*' " not /* opening line
  83. if synname =~ "CommentML" " multi-line
  84. if line !~ '^\s*\*' && getline(a:lnum - 1) =~ '^\s*/\*'
  85. " This is (hopefully) the line after a /*, and it has no
  86. " leader, so the correct indentation is that of the
  87. " previous line.
  88. return GetRustIndent(a:lnum - 1)
  89. endif
  90. endif
  91. " If it's in a comment, let cindent take care of it now. This is
  92. " for cases like "/*" where the next line should start " * ", not
  93. " "* " as the code below would otherwise cause for module scope
  94. " Fun fact: " /*\n*\n*/" takes two calls to get right!
  95. return cindent(a:lnum)
  96. endif
  97. endif
  98. " cindent gets second and subsequent match patterns/struct members wrong,
  99. " as it treats the comma as indicating an unfinished statement::
  100. "
  101. " match a {
  102. " b => c,
  103. " d => e,
  104. " f => g,
  105. " };
  106. " Search backwards for the previous non-empty line.
  107. let prevlinenum = prevnonblank(a:lnum - 1)
  108. let prevline = s:get_line_trimmed(prevlinenum)
  109. while prevlinenum > 1 && prevline !~ '[^[:blank:]]'
  110. let prevlinenum = prevnonblank(prevlinenum - 1)
  111. let prevline = s:get_line_trimmed(prevlinenum)
  112. endwhile
  113. " Handle where clauses nicely: subsequent values should line up nicely.
  114. if prevline[len(prevline) - 1] == ","
  115. \ && prevline =~# '^\s*where\s'
  116. return indent(prevlinenum) + 6
  117. endif
  118. if prevline[len(prevline) - 1] == ","
  119. \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]'
  120. \ && prevline !~ '^\s*fn\s'
  121. \ && prevline !~ '([^()]\+,$'
  122. \ && s:get_line_trimmed(a:lnum) !~ '^\s*\S\+\s*=>'
  123. " Oh ho! The previous line ended in a comma! I bet cindent will try to
  124. " take this too far... For now, let's normally use the previous line's
  125. " indent.
  126. " One case where this doesn't work out is where *this* line contains
  127. " square or curly brackets; then we normally *do* want to be indenting
  128. " further.
  129. "
  130. " Another case where we don't want to is one like a function
  131. " definition with arguments spread over multiple lines:
  132. "
  133. " fn foo(baz: Baz,
  134. " baz: Baz) // <-- cindent gets this right by itself
  135. "
  136. " Another case is similar to the previous, except calling a function
  137. " instead of defining it, or any conditional expression that leaves
  138. " an open paren:
  139. "
  140. " foo(baz,
  141. " baz);
  142. "
  143. " if baz && (foo ||
  144. " bar) {
  145. "
  146. " Another case is when the current line is a new match arm.
  147. "
  148. " There are probably other cases where we don't want to do this as
  149. " well. Add them as needed.
  150. return indent(prevlinenum)
  151. endif
  152. if !has("patch-7.4.355")
  153. " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
  154. "
  155. " static FOO : &'static [bool] = [
  156. " true,
  157. " false,
  158. " false,
  159. " true,
  160. " ];
  161. "
  162. " uh oh, next statement is indented further!
  163. " Note that this does *not* apply the line continuation pattern properly;
  164. " that's too hard to do correctly for my liking at present, so I'll just
  165. " start with these two main cases (square brackets and not returning to
  166. " column zero)
  167. call cursor(a:lnum, 1)
  168. if searchpair('{\|(', '', '}\|)', 'nbW',
  169. \ 's:is_string_comment(line("."), col("."))') == 0
  170. if searchpair('\[', '', '\]', 'nbW',
  171. \ 's:is_string_comment(line("."), col("."))') == 0
  172. " Global scope, should be zero
  173. return 0
  174. else
  175. " At the module scope, inside square brackets only
  176. "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
  177. if line =~ "^\\s*]"
  178. " It's the closing line, dedent it
  179. return 0
  180. else
  181. return shiftwidth()
  182. endif
  183. endif
  184. endif
  185. endif
  186. " Fall back on cindent, which does it mostly right
  187. return cindent(a:lnum)
  188. endfunction
  189. let &cpo = s:save_cpo
  190. unlet s:save_cpo