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