Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 1 | " Vim indent file |
| 2 | " Language: Rust |
| 3 | " Author: Chris Morgan <me@chrismorgan.info> |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 4 | " Last Change: 2023-09-11 |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 5 | " 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. |
| 8 | if exists("b:did_indent") |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 9 | finish |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 10 | endif |
| 11 | let b:did_indent = 1 |
| 12 | |
| 13 | setlocal cindent |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 14 | setlocal cinoptions=L0,(s,Ws,J1,j1,m1 |
| 15 | setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0) |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 16 | " Don't think cinwords will actually do anything at all... never mind |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 17 | setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 18 | |
| 19 | " Some preliminary settings |
| 20 | setlocal nolisp " Make sure lisp indenting doesn't supersede us |
| 21 | setlocal autoindent " indentexpr isn't much help otherwise |
| 22 | " Also do indentkeys, otherwise # gets shoved to column 0 :-/ |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 23 | setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0) |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 24 | |
| 25 | setlocal indentexpr=GetRustIndent(v:lnum) |
| 26 | |
dkearns | 0382f05 | 2023-08-29 05:32:59 +1000 | [diff] [blame] | 27 | let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< autoindent< indentkeys< indentexpr<" |
| 28 | |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 29 | " Only define the function once. |
| 30 | if exists("*GetRustIndent") |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 31 | finish |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 32 | endif |
| 33 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 34 | " vint: -ProhibitAbbreviationOption |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 35 | let s:save_cpo = &cpo |
| 36 | set cpo&vim |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 37 | " vint: +ProhibitAbbreviationOption |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 38 | |
| 39 | " Come here when loading the script the first time. |
| 40 | |
| 41 | function! s:get_line_trimmed(lnum) |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 42 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 70 | endfunction |
| 71 | |
| 72 | function! s:is_string_comment(lnum, col) |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 73 | 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 84 | endfunction |
| 85 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 86 | if exists('*shiftwidth') |
| 87 | function! s:shiftwidth() |
| 88 | return shiftwidth() |
| 89 | endfunc |
| 90 | else |
| 91 | function! s:shiftwidth() |
| 92 | return &shiftwidth |
| 93 | endfunc |
| 94 | endif |
| 95 | |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 96 | function GetRustIndent(lnum) |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 97 | " Starting assumption: cindent (called at the end) will do it right |
| 98 | " normally. We just want to fix up a few cases. |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 99 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 100 | let line = getline(a:lnum) |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 101 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 102 | 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 124 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 125 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 133 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 134 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 141 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 142 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 178 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 179 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 184 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 185 | " 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 |
Raphael | 4786680 | 2023-08-21 02:42:39 +0800 | [diff] [blame] | 190 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 191 | let l:last_prevline_character = prevline[len(prevline) - 1] |
Raphael | 4786680 | 2023-08-21 02:42:39 +0800 | [diff] [blame] | 192 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 193 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 203 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 204 | 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 212 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 213 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 239 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 240 | 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 251 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 252 | " 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 256 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 257 | 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 Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 279 | endfunction |
| 280 | |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 281 | " vint: -ProhibitAbbreviationOption |
Bram Moolenaar | 3c2881d | 2017-03-21 19:18:29 +0100 | [diff] [blame] | 282 | let &cpo = s:save_cpo |
| 283 | unlet s:save_cpo |
Gregory Anders | fc93594 | 2023-09-12 13:23:38 -0500 | [diff] [blame] | 284 | " vint: +ProhibitAbbreviationOption |
| 285 | |
| 286 | " vim: set et sw=4 sts=4 ts=8: |