blob: a96650d41955311c0c95aee00ff24b578d032830 [file] [log] [blame]
Bram Moolenaar3c2881d2017-03-21 19:18:29 +01001" Vim indent file
2" Language: Rust
3" Author: Chris Morgan <me@chrismorgan.info>
Gregory Andersfc935942023-09-12 13:23:38 -05004" Last Change: 2023-09-11
novenary11d59922024-07-04 19:11:16 +02005" 2024 Jul 04 by Vim Project: use shiftwidth() instead of hard-coding shifted values (#15138)
6
Bram Moolenaar3c2881d2017-03-21 19:18:29 +01007" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
novenary11d59922024-07-04 19:11:16 +02008" Note: upstream seems umaintained: https://github.com/rust-lang/rust.vim/issues/502
Bram Moolenaar3c2881d2017-03-21 19:18:29 +01009
10" Only load this indent file when no other was loaded.
11if exists("b:did_indent")
Gregory Andersfc935942023-09-12 13:23:38 -050012 finish
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010013endif
14let b:did_indent = 1
15
16setlocal cindent
Gregory Andersfc935942023-09-12 13:23:38 -050017setlocal cinoptions=L0,(s,Ws,J1,j1,m1
18setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010019" Don't think cinwords will actually do anything at all... never mind
Gregory Andersfc935942023-09-12 13:23:38 -050020setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010021
22" Some preliminary settings
23setlocal nolisp " Make sure lisp indenting doesn't supersede us
24setlocal autoindent " indentexpr isn't much help otherwise
25" Also do indentkeys, otherwise # gets shoved to column 0 :-/
Gregory Andersfc935942023-09-12 13:23:38 -050026setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010027
28setlocal indentexpr=GetRustIndent(v:lnum)
29
dkearns0382f052023-08-29 05:32:59 +100030let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< autoindent< indentkeys< indentexpr<"
31
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010032" Only define the function once.
33if exists("*GetRustIndent")
Gregory Andersfc935942023-09-12 13:23:38 -050034 finish
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010035endif
36
Gregory Andersfc935942023-09-12 13:23:38 -050037" vint: -ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010038let s:save_cpo = &cpo
39set cpo&vim
Gregory Andersfc935942023-09-12 13:23:38 -050040" vint: +ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010041
42" Come here when loading the script the first time.
43
44function! s:get_line_trimmed(lnum)
Gregory Andersfc935942023-09-12 13:23:38 -050045 " Get the line and remove a trailing comment.
46 " Use syntax highlighting attributes when possible.
47 " NOTE: this is not accurate; /* */ or a line continuation could trick it
48 let line = getline(a:lnum)
49 let line_len = strlen(line)
50 if has('syntax_items')
51 " If the last character in the line is a comment, do a binary search for
52 " the start of the comment. synID() is slow, a linear search would take
53 " too long on a long line.
54 if synIDattr(synID(a:lnum, line_len, 1), "name") =~? 'Comment\|Todo'
55 let min = 1
56 let max = line_len
57 while min < max
58 let col = (min + max) / 2
59 if synIDattr(synID(a:lnum, col, 1), "name") =~? 'Comment\|Todo'
60 let max = col
61 else
62 let min = col + 1
63 endif
64 endwhile
65 let line = strpart(line, 0, min - 1)
66 endif
67 return substitute(line, "\s*$", "", "")
68 else
69 " Sorry, this is not complete, nor fully correct (e.g. string "//").
70 " Such is life.
71 return substitute(line, "\s*//.*$", "", "")
72 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010073endfunction
74
75function! s:is_string_comment(lnum, col)
Gregory Andersfc935942023-09-12 13:23:38 -050076 if has('syntax_items')
77 for id in synstack(a:lnum, a:col)
78 let synname = synIDattr(id, "name")
79 if synname ==# "rustString" || synname =~# "^rustComment"
80 return 1
81 endif
82 endfor
83 else
84 " without syntax, let's not even try
85 return 0
86 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010087endfunction
88
Gregory Andersfc935942023-09-12 13:23:38 -050089if exists('*shiftwidth')
90 function! s:shiftwidth()
91 return shiftwidth()
92 endfunc
93else
94 function! s:shiftwidth()
95 return &shiftwidth
96 endfunc
97endif
98
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010099function GetRustIndent(lnum)
Gregory Andersfc935942023-09-12 13:23:38 -0500100 " Starting assumption: cindent (called at the end) will do it right
101 " normally. We just want to fix up a few cases.
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100102
Gregory Andersfc935942023-09-12 13:23:38 -0500103 let line = getline(a:lnum)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100104
Gregory Andersfc935942023-09-12 13:23:38 -0500105 if has('syntax_items')
106 let synname = synIDattr(synID(a:lnum, 1, 1), "name")
107 if synname ==# "rustString"
108 " If the start of the line is in a string, don't change the indent
109 return -1
110 elseif synname =~? '\(Comment\|Todo\)'
111 \ && line !~# '^\s*/\*' " not /* opening line
112 if synname =~? "CommentML" " multi-line
113 if line !~# '^\s*\*' && getline(a:lnum - 1) =~# '^\s*/\*'
114 " This is (hopefully) the line after a /*, and it has no
115 " leader, so the correct indentation is that of the
116 " previous line.
117 return GetRustIndent(a:lnum - 1)
118 endif
119 endif
120 " If it's in a comment, let cindent take care of it now. This is
121 " for cases like "/*" where the next line should start " * ", not
122 " "* " as the code below would otherwise cause for module scope
123 " Fun fact: " /*\n*\n*/" takes two calls to get right!
124 return cindent(a:lnum)
125 endif
126 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100127
Gregory Andersfc935942023-09-12 13:23:38 -0500128 " cindent gets second and subsequent match patterns/struct members wrong,
129 " as it treats the comma as indicating an unfinished statement::
130 "
131 " match a {
132 " b => c,
133 " d => e,
134 " f => g,
135 " };
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100136
Gregory Andersfc935942023-09-12 13:23:38 -0500137 " Search backwards for the previous non-empty line.
138 let prevlinenum = prevnonblank(a:lnum - 1)
139 let prevline = s:get_line_trimmed(prevlinenum)
140 while prevlinenum > 1 && prevline !~# '[^[:blank:]]'
141 let prevlinenum = prevnonblank(prevlinenum - 1)
142 let prevline = s:get_line_trimmed(prevlinenum)
143 endwhile
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100144
Gregory Andersfc935942023-09-12 13:23:38 -0500145 " A standalone '{', '}', or 'where'
146 let l:standalone_open = line =~# '\V\^\s\*{\s\*\$'
147 let l:standalone_close = line =~# '\V\^\s\*}\s\*\$'
148 let l:standalone_where = line =~# '\V\^\s\*where\s\*\$'
149 if l:standalone_open || l:standalone_close || l:standalone_where
150 " ToDo: we can search for more items than 'fn' and 'if'.
151 let [l:found_line, l:col, l:submatch] =
152 \ searchpos('\<\(fn\)\|\(if\)\>', 'bnWp')
153 if l:found_line !=# 0
154 " Now we count the number of '{' and '}' in between the match
155 " locations and the current line (there is probably a better
156 " way to compute this).
157 let l:i = l:found_line
158 let l:search_line = strpart(getline(l:i), l:col - 1)
159 let l:opens = 0
160 let l:closes = 0
161 while l:i < a:lnum
162 let l:search_line2 = substitute(l:search_line, '\V{', '', 'g')
163 let l:opens += strlen(l:search_line) - strlen(l:search_line2)
164 let l:search_line3 = substitute(l:search_line2, '\V}', '', 'g')
165 let l:closes += strlen(l:search_line2) - strlen(l:search_line3)
166 let l:i += 1
167 let l:search_line = getline(l:i)
168 endwhile
169 if l:standalone_open || l:standalone_where
170 if l:opens ==# l:closes
171 return indent(l:found_line)
172 endif
173 else
174 " Expect to find just one more close than an open
175 if l:opens ==# l:closes + 1
176 return indent(l:found_line)
177 endif
178 endif
179 endif
180 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100181
Gregory Andersfc935942023-09-12 13:23:38 -0500182 " A standalone 'where' adds a shift.
183 let l:standalone_prevline_where = prevline =~# '\V\^\s\*where\s\*\$'
184 if l:standalone_prevline_where
novenary11d59922024-07-04 19:11:16 +0200185 return indent(prevlinenum) + shiftwidth()
Gregory Andersfc935942023-09-12 13:23:38 -0500186 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100187
Gregory Andersfc935942023-09-12 13:23:38 -0500188 " Handle where clauses nicely: subsequent values should line up nicely.
189 if prevline[len(prevline) - 1] ==# ","
190 \ && prevline =~# '^\s*where\s'
191 return indent(prevlinenum) + 6
192 endif
Raphael47866802023-08-21 02:42:39 +0800193
Gregory Andersfc935942023-09-12 13:23:38 -0500194 let l:last_prevline_character = prevline[len(prevline) - 1]
Raphael47866802023-08-21 02:42:39 +0800195
Gregory Andersfc935942023-09-12 13:23:38 -0500196 " A line that ends with '.<expr>;' is probably an end of a long list
197 " of method operations.
198 if prevline =~# '\V\^\s\*.' && l:last_prevline_character ==# ';'
199 call cursor(a:lnum - 1, 1)
200 let l:scope_start = searchpair('{\|(', '', '}\|)', 'nbW',
201 \ 's:is_string_comment(line("."), col("."))')
202 if l:scope_start != 0 && l:scope_start < a:lnum
novenary11d59922024-07-04 19:11:16 +0200203 return indent(l:scope_start) + shiftwidth()
Gregory Andersfc935942023-09-12 13:23:38 -0500204 endif
205 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100206
Gregory Andersfc935942023-09-12 13:23:38 -0500207 if l:last_prevline_character ==# ","
208 \ && s:get_line_trimmed(a:lnum) !~# '^\s*[\[\]{})]'
209 \ && prevline !~# '^\s*fn\s'
210 \ && prevline !~# '([^()]\+,$'
211 \ && s:get_line_trimmed(a:lnum) !~# '^\s*\S\+\s*=>'
212 " Oh ho! The previous line ended in a comma! I bet cindent will try to
213 " take this too far... For now, let's normally use the previous line's
214 " indent.
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100215
Gregory Andersfc935942023-09-12 13:23:38 -0500216 " One case where this doesn't work out is where *this* line contains
217 " square or curly brackets; then we normally *do* want to be indenting
218 " further.
219 "
220 " Another case where we don't want to is one like a function
221 " definition with arguments spread over multiple lines:
222 "
223 " fn foo(baz: Baz,
224 " baz: Baz) // <-- cindent gets this right by itself
225 "
226 " Another case is similar to the previous, except calling a function
227 " instead of defining it, or any conditional expression that leaves
228 " an open paren:
229 "
230 " foo(baz,
231 " baz);
232 "
233 " if baz && (foo ||
234 " bar) {
235 "
236 " Another case is when the current line is a new match arm.
237 "
238 " There are probably other cases where we don't want to do this as
239 " well. Add them as needed.
240 return indent(prevlinenum)
241 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100242
Gregory Andersfc935942023-09-12 13:23:38 -0500243 if !has("patch-7.4.355")
244 " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
245 "
246 " static FOO : &'static [bool] = [
247 " true,
248 " false,
249 " false,
250 " true,
251 " ];
252 "
253 " uh oh, next statement is indented further!
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100254
Gregory Andersfc935942023-09-12 13:23:38 -0500255 " Note that this does *not* apply the line continuation pattern properly;
256 " that's too hard to do correctly for my liking at present, so I'll just
257 " start with these two main cases (square brackets and not returning to
258 " column zero)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100259
Gregory Andersfc935942023-09-12 13:23:38 -0500260 call cursor(a:lnum, 1)
261 if searchpair('{\|(', '', '}\|)', 'nbW',
262 \ 's:is_string_comment(line("."), col("."))') == 0
263 if searchpair('\[', '', '\]', 'nbW',
264 \ 's:is_string_comment(line("."), col("."))') == 0
265 " Global scope, should be zero
266 return 0
267 else
268 " At the module scope, inside square brackets only
269 "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
270 if line =~# "^\\s*]"
271 " It's the closing line, dedent it
272 return 0
273 else
novenary11d59922024-07-04 19:11:16 +0200274 return shiftwidth()
Gregory Andersfc935942023-09-12 13:23:38 -0500275 endif
276 endif
277 endif
278 endif
279
280 " Fall back on cindent, which does it mostly right
281 return cindent(a:lnum)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100282endfunction
283
Gregory Andersfc935942023-09-12 13:23:38 -0500284" vint: -ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100285let &cpo = s:save_cpo
286unlet s:save_cpo
Gregory Andersfc935942023-09-12 13:23:38 -0500287" vint: +ProhibitAbbreviationOption
288
289" vim: set et sw=4 sts=4 ts=8: