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 7" Only load this indent file when no other was loaded. 8if exists("b:did_indent") 9 finish 10endif 11let b:did_indent = 1 12 13setlocal cindent 14setlocal cinoptions=L0,(0,Ws,J1,j1 15setlocal cinkeys=0{,0},!^F,o,O,0[,0] 16" Don't think cinwords will actually do anything at all... never mind 17setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern 18 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 :-/ 23setlocal indentkeys=0{,0},!^F,o,O,0[,0] 24 25setlocal indentexpr=GetRustIndent(v:lnum) 26 27" Only define the function once. 28if exists("*GetRustIndent") 29 finish 30endif 31 32let s:save_cpo = &cpo 33set cpo&vim 34 35" Come here when loading the script the first time. 36 37function! 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 66endfunction 67 68function! 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 80endfunction 81 82function 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 135 if prevline[len(prevline) - 1] == "," 136 \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]' 137 \ && prevline !~ '^\s*fn\s' 138 \ && prevline !~ '([^()]\+,$' 139 \ && s:get_line_trimmed(a:lnum) !~ '^\s*\S\+\s*=>' 140 " Oh ho! The previous line ended in a comma! I bet cindent will try to 141 " take this too far... For now, let's normally use the previous line's 142 " indent. 143 144 " One case where this doesn't work out is where *this* line contains 145 " square or curly brackets; then we normally *do* want to be indenting 146 " further. 147 " 148 " Another case where we don't want to is one like a function 149 " definition with arguments spread over multiple lines: 150 " 151 " fn foo(baz: Baz, 152 " baz: Baz) // <-- cindent gets this right by itself 153 " 154 " Another case is similar to the previous, except calling a function 155 " instead of defining it, or any conditional expression that leaves 156 " an open paren: 157 " 158 " foo(baz, 159 " baz); 160 " 161 " if baz && (foo || 162 " bar) { 163 " 164 " Another case is when the current line is a new match arm. 165 " 166 " There are probably other cases where we don't want to do this as 167 " well. Add them as needed. 168 return indent(prevlinenum) 169 endif 170 171 if !has("patch-7.4.355") 172 " cindent before 7.4.355 doesn't do the module scope well at all; e.g.:: 173 " 174 " static FOO : &'static [bool] = [ 175 " true, 176 " false, 177 " false, 178 " true, 179 " ]; 180 " 181 " uh oh, next statement is indented further! 182 183 " Note that this does *not* apply the line continuation pattern properly; 184 " that's too hard to do correctly for my liking at present, so I'll just 185 " start with these two main cases (square brackets and not returning to 186 " column zero) 187 188 call cursor(a:lnum, 1) 189 if searchpair('{\|(', '', '}\|)', 'nbW', 190 \ 's:is_string_comment(line("."), col("."))') == 0 191 if searchpair('\[', '', '\]', 'nbW', 192 \ 's:is_string_comment(line("."), col("."))') == 0 193 " Global scope, should be zero 194 return 0 195 else 196 " At the module scope, inside square brackets only 197 "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum 198 if line =~ "^\\s*]" 199 " It's the closing line, dedent it 200 return 0 201 else 202 return shiftwidth() 203 endif 204 endif 205 endif 206 endif 207 208 " Fall back on cindent, which does it mostly right 209 return cindent(a:lnum) 210endfunction 211 212let &cpo = s:save_cpo 213unlet s:save_cpo 214