blob: 7c055ec73942ea5251cd9b2b24103cf3bba58126 [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
Bram Moolenaar3c2881d2017-03-21 19:18:29 +01005" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
6
7" Only load this indent file when no other was loaded.
8if exists("b:did_indent")
Gregory Andersfc935942023-09-12 13:23:38 -05009 finish
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010010endif
11let b:did_indent = 1
12
13setlocal cindent
Gregory Andersfc935942023-09-12 13:23:38 -050014setlocal cinoptions=L0,(s,Ws,J1,j1,m1
15setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010016" Don't think cinwords will actually do anything at all... never mind
Gregory Andersfc935942023-09-12 13:23:38 -050017setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010018
19" Some preliminary settings
20setlocal nolisp " Make sure lisp indenting doesn't supersede us
21setlocal autoindent " indentexpr isn't much help otherwise
22" Also do indentkeys, otherwise # gets shoved to column 0 :-/
Gregory Andersfc935942023-09-12 13:23:38 -050023setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010024
25setlocal indentexpr=GetRustIndent(v:lnum)
26
dkearns0382f052023-08-29 05:32:59 +100027let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< autoindent< indentkeys< indentexpr<"
28
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010029" Only define the function once.
30if exists("*GetRustIndent")
Gregory Andersfc935942023-09-12 13:23:38 -050031 finish
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010032endif
33
Gregory Andersfc935942023-09-12 13:23:38 -050034" vint: -ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010035let s:save_cpo = &cpo
36set cpo&vim
Gregory Andersfc935942023-09-12 13:23:38 -050037" vint: +ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010038
39" Come here when loading the script the first time.
40
41function! s:get_line_trimmed(lnum)
Gregory Andersfc935942023-09-12 13:23:38 -050042 " Get the line and remove a trailing comment.
43 " Use syntax highlighting attributes when possible.
44 " NOTE: this is not accurate; /* */ or a line continuation could trick it
45 let line = getline(a:lnum)
46 let line_len = strlen(line)
47 if has('syntax_items')
48 " If the last character in the line is a comment, do a binary search for
49 " the start of the comment. synID() is slow, a linear search would take
50 " too long on a long line.
51 if synIDattr(synID(a:lnum, line_len, 1), "name") =~? 'Comment\|Todo'
52 let min = 1
53 let max = line_len
54 while min < max
55 let col = (min + max) / 2
56 if synIDattr(synID(a:lnum, col, 1), "name") =~? 'Comment\|Todo'
57 let max = col
58 else
59 let min = col + 1
60 endif
61 endwhile
62 let line = strpart(line, 0, min - 1)
63 endif
64 return substitute(line, "\s*$", "", "")
65 else
66 " Sorry, this is not complete, nor fully correct (e.g. string "//").
67 " Such is life.
68 return substitute(line, "\s*//.*$", "", "")
69 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010070endfunction
71
72function! s:is_string_comment(lnum, col)
Gregory Andersfc935942023-09-12 13:23:38 -050073 if has('syntax_items')
74 for id in synstack(a:lnum, a:col)
75 let synname = synIDattr(id, "name")
76 if synname ==# "rustString" || synname =~# "^rustComment"
77 return 1
78 endif
79 endfor
80 else
81 " without syntax, let's not even try
82 return 0
83 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010084endfunction
85
Gregory Andersfc935942023-09-12 13:23:38 -050086if exists('*shiftwidth')
87 function! s:shiftwidth()
88 return shiftwidth()
89 endfunc
90else
91 function! s:shiftwidth()
92 return &shiftwidth
93 endfunc
94endif
95
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010096function GetRustIndent(lnum)
Gregory Andersfc935942023-09-12 13:23:38 -050097 " Starting assumption: cindent (called at the end) will do it right
98 " normally. We just want to fix up a few cases.
Bram Moolenaar3c2881d2017-03-21 19:18:29 +010099
Gregory Andersfc935942023-09-12 13:23:38 -0500100 let line = getline(a:lnum)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100101
Gregory Andersfc935942023-09-12 13:23:38 -0500102 if has('syntax_items')
103 let synname = synIDattr(synID(a:lnum, 1, 1), "name")
104 if synname ==# "rustString"
105 " If the start of the line is in a string, don't change the indent
106 return -1
107 elseif synname =~? '\(Comment\|Todo\)'
108 \ && line !~# '^\s*/\*' " not /* opening line
109 if synname =~? "CommentML" " multi-line
110 if line !~# '^\s*\*' && getline(a:lnum - 1) =~# '^\s*/\*'
111 " This is (hopefully) the line after a /*, and it has no
112 " leader, so the correct indentation is that of the
113 " previous line.
114 return GetRustIndent(a:lnum - 1)
115 endif
116 endif
117 " If it's in a comment, let cindent take care of it now. This is
118 " for cases like "/*" where the next line should start " * ", not
119 " "* " as the code below would otherwise cause for module scope
120 " Fun fact: " /*\n*\n*/" takes two calls to get right!
121 return cindent(a:lnum)
122 endif
123 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100124
Gregory Andersfc935942023-09-12 13:23:38 -0500125 " cindent gets second and subsequent match patterns/struct members wrong,
126 " as it treats the comma as indicating an unfinished statement::
127 "
128 " match a {
129 " b => c,
130 " d => e,
131 " f => g,
132 " };
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100133
Gregory Andersfc935942023-09-12 13:23:38 -0500134 " Search backwards for the previous non-empty line.
135 let prevlinenum = prevnonblank(a:lnum - 1)
136 let prevline = s:get_line_trimmed(prevlinenum)
137 while prevlinenum > 1 && prevline !~# '[^[:blank:]]'
138 let prevlinenum = prevnonblank(prevlinenum - 1)
139 let prevline = s:get_line_trimmed(prevlinenum)
140 endwhile
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100141
Gregory Andersfc935942023-09-12 13:23:38 -0500142 " A standalone '{', '}', or 'where'
143 let l:standalone_open = line =~# '\V\^\s\*{\s\*\$'
144 let l:standalone_close = line =~# '\V\^\s\*}\s\*\$'
145 let l:standalone_where = line =~# '\V\^\s\*where\s\*\$'
146 if l:standalone_open || l:standalone_close || l:standalone_where
147 " ToDo: we can search for more items than 'fn' and 'if'.
148 let [l:found_line, l:col, l:submatch] =
149 \ searchpos('\<\(fn\)\|\(if\)\>', 'bnWp')
150 if l:found_line !=# 0
151 " Now we count the number of '{' and '}' in between the match
152 " locations and the current line (there is probably a better
153 " way to compute this).
154 let l:i = l:found_line
155 let l:search_line = strpart(getline(l:i), l:col - 1)
156 let l:opens = 0
157 let l:closes = 0
158 while l:i < a:lnum
159 let l:search_line2 = substitute(l:search_line, '\V{', '', 'g')
160 let l:opens += strlen(l:search_line) - strlen(l:search_line2)
161 let l:search_line3 = substitute(l:search_line2, '\V}', '', 'g')
162 let l:closes += strlen(l:search_line2) - strlen(l:search_line3)
163 let l:i += 1
164 let l:search_line = getline(l:i)
165 endwhile
166 if l:standalone_open || l:standalone_where
167 if l:opens ==# l:closes
168 return indent(l:found_line)
169 endif
170 else
171 " Expect to find just one more close than an open
172 if l:opens ==# l:closes + 1
173 return indent(l:found_line)
174 endif
175 endif
176 endif
177 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100178
Gregory Andersfc935942023-09-12 13:23:38 -0500179 " A standalone 'where' adds a shift.
180 let l:standalone_prevline_where = prevline =~# '\V\^\s\*where\s\*\$'
181 if l:standalone_prevline_where
182 return indent(prevlinenum) + 4
183 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100184
Gregory Andersfc935942023-09-12 13:23:38 -0500185 " Handle where clauses nicely: subsequent values should line up nicely.
186 if prevline[len(prevline) - 1] ==# ","
187 \ && prevline =~# '^\s*where\s'
188 return indent(prevlinenum) + 6
189 endif
Raphael47866802023-08-21 02:42:39 +0800190
Gregory Andersfc935942023-09-12 13:23:38 -0500191 let l:last_prevline_character = prevline[len(prevline) - 1]
Raphael47866802023-08-21 02:42:39 +0800192
Gregory Andersfc935942023-09-12 13:23:38 -0500193 " A line that ends with '.<expr>;' is probably an end of a long list
194 " of method operations.
195 if prevline =~# '\V\^\s\*.' && l:last_prevline_character ==# ';'
196 call cursor(a:lnum - 1, 1)
197 let l:scope_start = searchpair('{\|(', '', '}\|)', 'nbW',
198 \ 's:is_string_comment(line("."), col("."))')
199 if l:scope_start != 0 && l:scope_start < a:lnum
200 return indent(l:scope_start) + 4
201 endif
202 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100203
Gregory Andersfc935942023-09-12 13:23:38 -0500204 if l:last_prevline_character ==# ","
205 \ && s:get_line_trimmed(a:lnum) !~# '^\s*[\[\]{})]'
206 \ && prevline !~# '^\s*fn\s'
207 \ && prevline !~# '([^()]\+,$'
208 \ && s:get_line_trimmed(a:lnum) !~# '^\s*\S\+\s*=>'
209 " Oh ho! The previous line ended in a comma! I bet cindent will try to
210 " take this too far... For now, let's normally use the previous line's
211 " indent.
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100212
Gregory Andersfc935942023-09-12 13:23:38 -0500213 " One case where this doesn't work out is where *this* line contains
214 " square or curly brackets; then we normally *do* want to be indenting
215 " further.
216 "
217 " Another case where we don't want to is one like a function
218 " definition with arguments spread over multiple lines:
219 "
220 " fn foo(baz: Baz,
221 " baz: Baz) // <-- cindent gets this right by itself
222 "
223 " Another case is similar to the previous, except calling a function
224 " instead of defining it, or any conditional expression that leaves
225 " an open paren:
226 "
227 " foo(baz,
228 " baz);
229 "
230 " if baz && (foo ||
231 " bar) {
232 "
233 " Another case is when the current line is a new match arm.
234 "
235 " There are probably other cases where we don't want to do this as
236 " well. Add them as needed.
237 return indent(prevlinenum)
238 endif
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100239
Gregory Andersfc935942023-09-12 13:23:38 -0500240 if !has("patch-7.4.355")
241 " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
242 "
243 " static FOO : &'static [bool] = [
244 " true,
245 " false,
246 " false,
247 " true,
248 " ];
249 "
250 " uh oh, next statement is indented further!
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100251
Gregory Andersfc935942023-09-12 13:23:38 -0500252 " Note that this does *not* apply the line continuation pattern properly;
253 " that's too hard to do correctly for my liking at present, so I'll just
254 " start with these two main cases (square brackets and not returning to
255 " column zero)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100256
Gregory Andersfc935942023-09-12 13:23:38 -0500257 call cursor(a:lnum, 1)
258 if searchpair('{\|(', '', '}\|)', 'nbW',
259 \ 's:is_string_comment(line("."), col("."))') == 0
260 if searchpair('\[', '', '\]', 'nbW',
261 \ 's:is_string_comment(line("."), col("."))') == 0
262 " Global scope, should be zero
263 return 0
264 else
265 " At the module scope, inside square brackets only
266 "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
267 if line =~# "^\\s*]"
268 " It's the closing line, dedent it
269 return 0
270 else
271 return &shiftwidth
272 endif
273 endif
274 endif
275 endif
276
277 " Fall back on cindent, which does it mostly right
278 return cindent(a:lnum)
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100279endfunction
280
Gregory Andersfc935942023-09-12 13:23:38 -0500281" vint: -ProhibitAbbreviationOption
Bram Moolenaar3c2881d2017-03-21 19:18:29 +0100282let &cpo = s:save_cpo
283unlet s:save_cpo
Gregory Andersfc935942023-09-12 13:23:38 -0500284" vint: +ProhibitAbbreviationOption
285
286" vim: set et sw=4 sts=4 ts=8: