|
1 | 1 | " Vim indent file
|
| 2 | +" Language: Rust |
| 3 | +" Author: Chris Morgan <[email protected]> |
| 4 | +" Last Change: 2013 Jul 10 |
2 | 5 |
|
| 6 | +" Only load this indent file when no other was loaded. |
3 | 7 | if exists("b:did_indent")
|
4 |
| - finish |
| 8 | + finish |
5 | 9 | endif
|
6 |
| - |
7 | 10 | let b:did_indent = 1
|
8 | 11 |
|
9 | 12 | setlocal cindent
|
10 | 13 | setlocal cinoptions=L0,(0,Ws,JN
|
11 |
| -setlocal cinkeys=0{,0},!^F,o,O |
| 14 | +setlocal cinkeys=0{,0},!^F,o,O,0[,0] |
| 15 | +" Don't think cinwords will actually do anything at all... never mind |
| 16 | +setlocal cinwords=do,for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern |
| 17 | + |
| 18 | +" Some preliminary settings |
| 19 | +setlocal nolisp " Make sure lisp indenting doesn't supersede us |
| 20 | +setlocal autoindent " indentexpr isn't much help otherwise |
| 21 | +" Also do indentkeys, otherwise # gets shoved to column 0 :-/ |
| 22 | +setlocal indentkeys=0{,0},!^F,o,O,0[,0] |
| 23 | + |
| 24 | +setlocal indentexpr=GetRustIndent(v:lnum) |
| 25 | + |
| 26 | +" Only define the function once. |
| 27 | +if exists("*GetRustIndent") |
| 28 | + finish |
| 29 | +endif |
| 30 | + |
| 31 | +" Come here when loading the script the first time. |
| 32 | + |
| 33 | +function s:get_line_trimmed(lnum) |
| 34 | + " Get the line and remove a trailing comment. |
| 35 | + " Use syntax highlighting attributes when possible. |
| 36 | + " NOTE: this is not accurate; /* */ or a line continuation could trick it |
| 37 | + let line = getline(a:lnum) |
| 38 | + let line_len = strlen(line) |
| 39 | + if has('syntax_items') |
| 40 | + " If the last character in the line is a comment, do a binary search for |
| 41 | + " the start of the comment. synID() is slow, a linear search would take |
| 42 | + " too long on a long line. |
| 43 | + if synIDattr(synID(a:lnum, line_len, 1), "name") =~ "Comment\|Todo" |
| 44 | + let min = 1 |
| 45 | + let max = line_len |
| 46 | + while min < max |
| 47 | + let col = (min + max) / 2 |
| 48 | + if synIDattr(synID(a:lnum, col, 1), "name") =~ "Comment\|Todo" |
| 49 | + let max = col |
| 50 | + else |
| 51 | + let min = col + 1 |
| 52 | + endif |
| 53 | + endwhile |
| 54 | + let line = strpart(line, 0, min - 1) |
| 55 | + endif |
| 56 | + return substitute(line, "\s*$", "", "") |
| 57 | + else |
| 58 | + " Sorry, this is not complete, nor fully correct (e.g. string "//"). |
| 59 | + " Such is life. |
| 60 | + return substitute(line, "\s*//.*$", "", "") |
| 61 | + endif |
| 62 | +endfunction |
| 63 | + |
| 64 | +function GetRustIndent(lnum) |
| 65 | + |
| 66 | + " Starting assumption: cindent (called at the end) will do it right |
| 67 | + " normally. We just want to fix up a few cases. |
| 68 | + |
| 69 | + if has('syntax_items') |
| 70 | + if synIDattr(synID(a:lnum, 1, 1), "name") == "rustString" |
| 71 | + " If the start of the line is in a string, don't change the indent |
| 72 | + return -1 |
| 73 | + elseif synIDattr(synID(a:lnum, 1, 1), "name") =~ "\\(Comment\\|Todo\\)" |
| 74 | + \ && getline(a:lnum) !~ "^\\s*/\\*" |
| 75 | + " If it's in a comment, let cindent take care of it now. This is |
| 76 | + " for cases like "/*" where the next line should start " * ", not |
| 77 | + " "* " as the code below would otherwise cause for module scope |
| 78 | + " Fun fact: " /*\n*\n*/" takes two calls to get right! |
| 79 | + return cindent(a:lnum) |
| 80 | + endif |
| 81 | + endif |
| 82 | + |
| 83 | + " cindent gets second and subsequent match patterns/struct members wrong, |
| 84 | + " as it treats the comma as indicating an unfinished statement:: |
| 85 | + " |
| 86 | + " match a { |
| 87 | + " b => c, |
| 88 | + " d => e, |
| 89 | + " f => g, |
| 90 | + " }; |
| 91 | + |
| 92 | + " Search backwards for the previous non-empty line. |
| 93 | + let prevline = s:get_line_trimmed(prevnonblank(a:lnum - 1)) |
| 94 | + if prevline[len(prevline) - 1] == "," |
| 95 | + \ && s:get_line_trimmed(a:lnum) !~ "^\\s*[\\[\\]{}]" |
| 96 | + " Oh ho! The previous line ended in a comma! I bet cindent will try to |
| 97 | + " take this too far... For now, let's use the previous line's indent |
| 98 | + return GetRustIndent(a:lnum - 1) |
| 99 | + endif |
| 100 | + |
| 101 | + " cindent doesn't do the module scope well at all; e.g.:: |
| 102 | + " |
| 103 | + " static FOO : &'static [bool] = [ |
| 104 | + " true, |
| 105 | + " false, |
| 106 | + " false, |
| 107 | + " true, |
| 108 | + " ]; |
| 109 | + " |
| 110 | + " uh oh, next statement is indented further! |
| 111 | + |
| 112 | + " Note that this does *not* apply the line continuation pattern properly; |
| 113 | + " that's too hard to do correctly for my liking at present, so I'll just |
| 114 | + " start with these two main cases (square brackets and not returning to |
| 115 | + " column zero) |
| 116 | + |
| 117 | + let line = getline(a:lnum) |
| 118 | + call cursor(a:lnum, 1) |
| 119 | + if searchpair('{\|(', '', '}\|)', 'nbW') == 0 |
| 120 | + if searchpair('\[', '', '\]', 'nbW') == 0 |
| 121 | + " Global scope, should be zero |
| 122 | + return 0 |
| 123 | + else |
| 124 | + " At the module scope, inside square brackets only |
| 125 | + "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum |
| 126 | + if line =~ "^\\s*]" |
| 127 | + " It's the closing line, dedent it |
| 128 | + return 0 |
| 129 | + else |
| 130 | + return &shiftwidth |
| 131 | + endif |
| 132 | + endif |
| 133 | + endif |
| 134 | + |
| 135 | + " Fall back on cindent, which does it mostly right |
| 136 | + return cindent(a:lnum) |
| 137 | +endfunction |
0 commit comments