.vim: Add rust stuff
[cmccabe-etc] / .vim / indent / rust.vim
1 " Vim indent file
2 " Language:         Rust
3 " Author:           Chris Morgan <me@chrismorgan.info>
4 " Last Change:      2014 Sep 13
5 " from https://github.com/wting/rust.vim/blob/master/indent/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 " Come here when loading the script the first time.
33
34 function! s:get_line_trimmed(lnum)
35     " Get the line and remove a trailing comment.
36     " Use syntax highlighting attributes when possible.
37     " NOTE: this is not accurate; /* */ or a line continuation could trick it
38     let line = getline(a:lnum)
39     let line_len = strlen(line)
40     if has('syntax_items')
41         " If the last character in the line is a comment, do a binary search for
42         " the start of the comment.  synID() is slow, a linear search would take
43         " too long on a long line.
44         if synIDattr(synID(a:lnum, line_len, 1), "name") =~ 'Comment\|Todo'
45             let min = 1
46             let max = line_len
47             while min < max
48                 let col = (min + max) / 2
49                 if synIDattr(synID(a:lnum, col, 1), "name") =~ 'Comment\|Todo'
50                     let max = col
51                 else
52                     let min = col + 1
53                 endif
54             endwhile
55             let line = strpart(line, 0, min - 1)
56         endif
57         return substitute(line, "\s*$", "", "")
58     else
59         " Sorry, this is not complete, nor fully correct (e.g. string "//").
60         " Such is life.
61         return substitute(line, "\s*//.*$", "", "")
62     endif
63 endfunction
64
65 function! s:is_string_comment(lnum, col)
66     if has('syntax_items')
67         for id in synstack(a:lnum, a:col)
68             let synname = synIDattr(id, "name")
69             if synname == "rustString" || synname =~ "^rustComment"
70                 return 1
71             endif
72         endfor
73     else
74         " without syntax, let's not even try
75         return 0
76     endif
77 endfunction
78
79 function GetRustIndent(lnum)
80
81     " Starting assumption: cindent (called at the end) will do it right
82     " normally. We just want to fix up a few cases.
83
84     let line = getline(a:lnum)
85
86     if has('syntax_items')
87         let synname = synIDattr(synID(a:lnum, 1, 1), "name")
88         if synname == "rustString"
89             " If the start of the line is in a string, don't change the indent
90             return -1
91         elseif synname =~ '\(Comment\|Todo\)'
92                     \ && line !~ '^\s*/\*'  " not /* opening line
93             if synname =~ "CommentML" " multi-line
94                 if line !~ '^\s*\*' && getline(a:lnum - 1) =~ '^\s*/\*'
95                     " This is (hopefully) the line after a /*, and it has no
96                     " leader, so the correct indentation is that of the
97                     " previous line.
98                     return GetRustIndent(a:lnum - 1)
99                 endif
100             endif
101             " If it's in a comment, let cindent take care of it now. This is
102             " for cases like "/*" where the next line should start " * ", not
103             " "* " as the code below would otherwise cause for module scope
104             " Fun fact: "  /*\n*\n*/" takes two calls to get right!
105             return cindent(a:lnum)
106         endif
107     endif
108
109     " cindent gets second and subsequent match patterns/struct members wrong,
110     " as it treats the comma as indicating an unfinished statement::
111     "
112     " match a {
113     "     b => c,
114     "         d => e,
115     "         f => g,
116     " };
117
118     " Search backwards for the previous non-empty line.
119     let prevlinenum = prevnonblank(a:lnum - 1)
120     let prevline = s:get_line_trimmed(prevlinenum)
121     while prevlinenum > 1 && prevline !~ '[^[:blank:]]'
122         let prevlinenum = prevnonblank(prevlinenum - 1)
123         let prevline = s:get_line_trimmed(prevlinenum)
124     endwhile
125     if prevline[len(prevline) - 1] == ","
126                 \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]'
127                 \ && prevline !~ '^\s*fn\s'
128                 \ && prevline !~ '([^()]\+,$'
129         " Oh ho! The previous line ended in a comma! I bet cindent will try to
130         " take this too far... For now, let's normally use the previous line's
131         " indent.
132
133         " One case where this doesn't work out is where *this* line contains
134         " square or curly brackets; then we normally *do* want to be indenting
135         " further.
136         "
137         " Another case where we don't want to is one like a function
138         " definition with arguments spread over multiple lines:
139         "
140         " fn foo(baz: Baz,
141         "        baz: Baz) // <-- cindent gets this right by itself
142         "
143         " Another case is similar to the previous, except calling a function
144         " instead of defining it, or any conditional expression that leaves
145         " an open paren:
146         "
147         " foo(baz,
148         "     baz);
149         "
150         " if baz && (foo ||
151         "            bar) {
152         "
153         " There are probably other cases where we don't want to do this as
154         " well. Add them as needed.
155         return indent(prevlinenum)
156     endif
157
158     if !has("patch-7.4.355")
159         " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
160         "
161         " static FOO : &'static [bool] = [
162         " true,
163         "    false,
164         "    false,
165         "    true,
166         "    ];
167         "
168         "    uh oh, next statement is indented further!
169
170         " Note that this does *not* apply the line continuation pattern properly;
171         " that's too hard to do correctly for my liking at present, so I'll just
172         " start with these two main cases (square brackets and not returning to
173         " column zero)
174
175         call cursor(a:lnum, 1)
176         if searchpair('{\|(', '', '}\|)', 'nbW',
177                     \ 's:is_string_comment(line("."), col("."))') == 0
178             if searchpair('\[', '', '\]', 'nbW',
179                         \ 's:is_string_comment(line("."), col("."))') == 0
180                 " Global scope, should be zero
181                 return 0
182             else
183                 " At the module scope, inside square brackets only
184                 "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
185                 if line =~ "^\\s*]"
186                     " It's the closing line, dedent it
187                     return 0
188                 else
189                     return &shiftwidth
190                 endif
191             endif
192         endif
193     endif
194
195     " Fall back on cindent, which does it mostly right
196     return cindent(a:lnum)
197 endfunction