| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | " Vim indent file | 
					
						
							|  |  |  | " Language:         Rust | 
					
						
							|  |  |  | " Author:           Chris Morgan <me@chrismorgan.info> | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | " Last Change:      2023-09-11 | 
					
						
							| 
									
										
										
										
											2024-07-04 19:11:16 +02:00
										 |  |  | " 2024 Jul 04 by Vim Project: use shiftwidth() instead of hard-coding shifted values (#15138) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | " For bugs, patches and license go to https://github.com/rust-lang/rust.vim | 
					
						
							| 
									
										
										
										
											2024-07-04 19:11:16 +02:00
										 |  |  | " Note: upstream seems umaintained: https://github.com/rust-lang/rust.vim/issues/502 | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | " Only load this indent file when no other was loaded. | 
					
						
							|  |  |  | if exists("b:did_indent") | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     finish | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | endif | 
					
						
							|  |  |  | let b:did_indent = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | setlocal cindent | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | setlocal cinoptions=L0,(s,Ws,J1,j1,m1 | 
					
						
							|  |  |  | setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0) | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | " Don't think cinwords will actually do anything at all... never mind | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | " Some preliminary settings | 
					
						
							|  |  |  | setlocal nolisp		" Make sure lisp indenting doesn't supersede us | 
					
						
							|  |  |  | setlocal autoindent	" indentexpr isn't much help otherwise | 
					
						
							|  |  |  | " Also do indentkeys, otherwise # gets shoved to column 0 :-/ | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0) | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | setlocal indentexpr=GetRustIndent(v:lnum) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-29 05:32:59 +10:00
										 |  |  | let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< autoindent< indentkeys< indentexpr<" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | " Only define the function once. | 
					
						
							|  |  |  | if exists("*GetRustIndent") | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     finish | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | endif | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | " vint: -ProhibitAbbreviationOption | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | let s:save_cpo = &cpo | 
					
						
							|  |  |  | set cpo&vim | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | " vint: +ProhibitAbbreviationOption | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | " Come here when loading the script the first time. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function! s:get_line_trimmed(lnum) | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     " Get the line and remove a trailing comment. | 
					
						
							|  |  |  |     " Use syntax highlighting attributes when possible. | 
					
						
							|  |  |  |     " NOTE: this is not accurate; /* */ or a line continuation could trick it | 
					
						
							|  |  |  |     let line = getline(a:lnum) | 
					
						
							|  |  |  |     let line_len = strlen(line) | 
					
						
							|  |  |  |     if has('syntax_items') | 
					
						
							|  |  |  |         " If the last character in the line is a comment, do a binary search for | 
					
						
							|  |  |  |         " the start of the comment.  synID() is slow, a linear search would take | 
					
						
							|  |  |  |         " too long on a long line. | 
					
						
							|  |  |  |         if synIDattr(synID(a:lnum, line_len, 1), "name") =~? 'Comment\|Todo' | 
					
						
							|  |  |  |             let min = 1 | 
					
						
							|  |  |  |             let max = line_len | 
					
						
							|  |  |  |             while min < max | 
					
						
							|  |  |  |                 let col = (min + max) / 2 | 
					
						
							|  |  |  |                 if synIDattr(synID(a:lnum, col, 1), "name") =~? 'Comment\|Todo' | 
					
						
							|  |  |  |                     let max = col | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                     let min = col + 1 | 
					
						
							|  |  |  |                 endif | 
					
						
							|  |  |  |             endwhile | 
					
						
							|  |  |  |             let line = strpart(line, 0, min - 1) | 
					
						
							|  |  |  |         endif | 
					
						
							|  |  |  |         return substitute(line, "\s*$", "", "") | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         " Sorry, this is not complete, nor fully correct (e.g. string "//"). | 
					
						
							|  |  |  |         " Such is life. | 
					
						
							|  |  |  |         return substitute(line, "\s*//.*$", "", "") | 
					
						
							|  |  |  |     endif | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | endfunction | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function! s:is_string_comment(lnum, col) | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     if has('syntax_items') | 
					
						
							|  |  |  |         for id in synstack(a:lnum, a:col) | 
					
						
							|  |  |  |             let synname = synIDattr(id, "name") | 
					
						
							|  |  |  |             if synname ==# "rustString" || synname =~# "^rustComment" | 
					
						
							|  |  |  |                 return 1 | 
					
						
							|  |  |  |             endif | 
					
						
							|  |  |  |         endfor | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         " without syntax, let's not even try | 
					
						
							|  |  |  |         return 0 | 
					
						
							|  |  |  |     endif | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | endfunction | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | if exists('*shiftwidth') | 
					
						
							|  |  |  |     function! s:shiftwidth() | 
					
						
							|  |  |  |         return shiftwidth() | 
					
						
							|  |  |  |     endfunc | 
					
						
							|  |  |  | else | 
					
						
							|  |  |  |     function! s:shiftwidth() | 
					
						
							|  |  |  |         return &shiftwidth | 
					
						
							|  |  |  |     endfunc | 
					
						
							|  |  |  | endif | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | function GetRustIndent(lnum) | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     " Starting assumption: cindent (called at the end) will do it right | 
					
						
							|  |  |  |     " normally. We just want to fix up a few cases. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let line = getline(a:lnum) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if has('syntax_items') | 
					
						
							|  |  |  |         let synname = synIDattr(synID(a:lnum, 1, 1), "name") | 
					
						
							|  |  |  |         if synname ==# "rustString" | 
					
						
							|  |  |  |             " If the start of the line is in a string, don't change the indent | 
					
						
							|  |  |  |             return -1 | 
					
						
							|  |  |  |         elseif synname =~? '\(Comment\|Todo\)' | 
					
						
							|  |  |  |                     \ && line !~# '^\s*/\*'  " not /* opening line | 
					
						
							|  |  |  |             if synname =~? "CommentML" " multi-line | 
					
						
							|  |  |  |                 if line !~# '^\s*\*' && getline(a:lnum - 1) =~# '^\s*/\*' | 
					
						
							|  |  |  |                     " This is (hopefully) the line after a /*, and it has no | 
					
						
							|  |  |  |                     " leader, so the correct indentation is that of the | 
					
						
							|  |  |  |                     " previous line. | 
					
						
							|  |  |  |                     return GetRustIndent(a:lnum - 1) | 
					
						
							|  |  |  |                 endif | 
					
						
							|  |  |  |             endif | 
					
						
							|  |  |  |             " If it's in a comment, let cindent take care of it now. This is | 
					
						
							|  |  |  |             " for cases like "/*" where the next line should start " * ", not | 
					
						
							|  |  |  |             " "* " as the code below would otherwise cause for module scope | 
					
						
							|  |  |  |             " Fun fact: "  /*\n*\n*/" takes two calls to get right! | 
					
						
							|  |  |  |             return cindent(a:lnum) | 
					
						
							|  |  |  |         endif | 
					
						
							|  |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " cindent gets second and subsequent match patterns/struct members wrong, | 
					
						
							|  |  |  |     " as it treats the comma as indicating an unfinished statement:: | 
					
						
							|  |  |  |     " | 
					
						
							|  |  |  |     " match a { | 
					
						
							|  |  |  |     "     b => c, | 
					
						
							|  |  |  |     "         d => e, | 
					
						
							|  |  |  |     "         f => g, | 
					
						
							|  |  |  |     " }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " Search backwards for the previous non-empty line. | 
					
						
							|  |  |  |     let prevlinenum = prevnonblank(a:lnum - 1) | 
					
						
							|  |  |  |     let prevline = s:get_line_trimmed(prevlinenum) | 
					
						
							|  |  |  |     while prevlinenum > 1 && prevline !~# '[^[:blank:]]' | 
					
						
							|  |  |  |         let prevlinenum = prevnonblank(prevlinenum - 1) | 
					
						
							|  |  |  |         let prevline = s:get_line_trimmed(prevlinenum) | 
					
						
							|  |  |  |     endwhile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " A standalone '{', '}', or 'where' | 
					
						
							|  |  |  |     let l:standalone_open = line =~# '\V\^\s\*{\s\*\$' | 
					
						
							|  |  |  |     let l:standalone_close = line =~# '\V\^\s\*}\s\*\$' | 
					
						
							|  |  |  |     let l:standalone_where = line =~# '\V\^\s\*where\s\*\$' | 
					
						
							|  |  |  |     if l:standalone_open || l:standalone_close || l:standalone_where | 
					
						
							|  |  |  |         " ToDo: we can search for more items than 'fn' and 'if'. | 
					
						
							|  |  |  |         let [l:found_line, l:col, l:submatch] = | 
					
						
							|  |  |  |                     \ searchpos('\<\(fn\)\|\(if\)\>', 'bnWp') | 
					
						
							|  |  |  |         if l:found_line !=# 0 | 
					
						
							|  |  |  |             " Now we count the number of '{' and '}' in between the match | 
					
						
							|  |  |  |             " locations and the current line (there is probably a better | 
					
						
							|  |  |  |             " way to compute this). | 
					
						
							|  |  |  |             let l:i = l:found_line | 
					
						
							|  |  |  |             let l:search_line = strpart(getline(l:i), l:col - 1) | 
					
						
							|  |  |  |             let l:opens = 0 | 
					
						
							|  |  |  |             let l:closes = 0 | 
					
						
							|  |  |  |             while l:i < a:lnum | 
					
						
							|  |  |  |                 let l:search_line2 = substitute(l:search_line, '\V{', '', 'g') | 
					
						
							|  |  |  |                 let l:opens += strlen(l:search_line) - strlen(l:search_line2) | 
					
						
							|  |  |  |                 let l:search_line3 = substitute(l:search_line2, '\V}', '', 'g') | 
					
						
							|  |  |  |                 let l:closes += strlen(l:search_line2) - strlen(l:search_line3) | 
					
						
							|  |  |  |                 let l:i += 1 | 
					
						
							|  |  |  |                 let l:search_line = getline(l:i) | 
					
						
							|  |  |  |             endwhile | 
					
						
							|  |  |  |             if l:standalone_open || l:standalone_where | 
					
						
							|  |  |  |                 if l:opens ==# l:closes | 
					
						
							|  |  |  |                     return indent(l:found_line) | 
					
						
							|  |  |  |                 endif | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 " Expect to find just one more close than an open | 
					
						
							|  |  |  |                 if l:opens ==# l:closes + 1 | 
					
						
							|  |  |  |                     return indent(l:found_line) | 
					
						
							|  |  |  |                 endif | 
					
						
							|  |  |  |             endif | 
					
						
							|  |  |  |         endif | 
					
						
							|  |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " A standalone 'where' adds a shift. | 
					
						
							|  |  |  |     let l:standalone_prevline_where = prevline =~# '\V\^\s\*where\s\*\$' | 
					
						
							|  |  |  |     if l:standalone_prevline_where | 
					
						
							| 
									
										
										
										
											2024-07-04 19:11:16 +02:00
										 |  |  |         return indent(prevlinenum) + shiftwidth() | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " Handle where clauses nicely: subsequent values should line up nicely. | 
					
						
							|  |  |  |     if prevline[len(prevline) - 1] ==# "," | 
					
						
							|  |  |  |                 \ && prevline =~# '^\s*where\s' | 
					
						
							|  |  |  |         return indent(prevlinenum) + 6 | 
					
						
							|  |  |  |     endif | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |     let l:last_prevline_character = prevline[len(prevline) - 1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " A line that ends with '.<expr>;' is probably an end of a long list | 
					
						
							|  |  |  |     " of method operations. | 
					
						
							|  |  |  |     if prevline =~# '\V\^\s\*.' && l:last_prevline_character ==# ';' | 
					
						
							|  |  |  |         call cursor(a:lnum - 1, 1) | 
					
						
							|  |  |  |         let l:scope_start = searchpair('{\|(', '', '}\|)', 'nbW', | 
					
						
							|  |  |  |                     \ 's:is_string_comment(line("."), col("."))') | 
					
						
							|  |  |  |         if l:scope_start != 0 && l:scope_start < a:lnum | 
					
						
							| 
									
										
										
										
											2024-07-04 19:11:16 +02:00
										 |  |  |             return indent(l:scope_start) + shiftwidth() | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |         endif | 
					
						
							|  |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if l:last_prevline_character ==# "," | 
					
						
							|  |  |  |                 \ && s:get_line_trimmed(a:lnum) !~# '^\s*[\[\]{})]' | 
					
						
							|  |  |  |                 \ && prevline !~# '^\s*fn\s' | 
					
						
							|  |  |  |                 \ && prevline !~# '([^()]\+,$' | 
					
						
							|  |  |  |                 \ && s:get_line_trimmed(a:lnum) !~# '^\s*\S\+\s*=>' | 
					
						
							|  |  |  |         " Oh ho! The previous line ended in a comma! I bet cindent will try to | 
					
						
							|  |  |  |         " take this too far... For now, let's normally use the previous line's | 
					
						
							|  |  |  |         " indent. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         " One case where this doesn't work out is where *this* line contains | 
					
						
							|  |  |  |         " square or curly brackets; then we normally *do* want to be indenting | 
					
						
							|  |  |  |         " further. | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " Another case where we don't want to is one like a function | 
					
						
							|  |  |  |         " definition with arguments spread over multiple lines: | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " fn foo(baz: Baz, | 
					
						
							|  |  |  |         "        baz: Baz) // <-- cindent gets this right by itself | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " Another case is similar to the previous, except calling a function | 
					
						
							|  |  |  |         " instead of defining it, or any conditional expression that leaves | 
					
						
							|  |  |  |         " an open paren: | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " foo(baz, | 
					
						
							|  |  |  |         "     baz); | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " if baz && (foo || | 
					
						
							|  |  |  |         "            bar) { | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " Another case is when the current line is a new match arm. | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " There are probably other cases where we don't want to do this as | 
					
						
							|  |  |  |         " well. Add them as needed. | 
					
						
							|  |  |  |         return indent(prevlinenum) | 
					
						
							|  |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if !has("patch-7.4.355") | 
					
						
							|  |  |  |         " cindent before 7.4.355 doesn't do the module scope well at all; e.g.:: | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         " static FOO : &'static [bool] = [ | 
					
						
							|  |  |  |         " true, | 
					
						
							|  |  |  |         "	 false, | 
					
						
							|  |  |  |         "	 false, | 
					
						
							|  |  |  |         "	 true, | 
					
						
							|  |  |  |         "	 ]; | 
					
						
							|  |  |  |         " | 
					
						
							|  |  |  |         "	 uh oh, next statement is indented further! | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         " Note that this does *not* apply the line continuation pattern properly; | 
					
						
							|  |  |  |         " that's too hard to do correctly for my liking at present, so I'll just | 
					
						
							|  |  |  |         " start with these two main cases (square brackets and not returning to | 
					
						
							|  |  |  |         " column zero) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         call cursor(a:lnum, 1) | 
					
						
							|  |  |  |         if searchpair('{\|(', '', '}\|)', 'nbW', | 
					
						
							|  |  |  |                     \ 's:is_string_comment(line("."), col("."))') == 0 | 
					
						
							|  |  |  |             if searchpair('\[', '', '\]', 'nbW', | 
					
						
							|  |  |  |                         \ 's:is_string_comment(line("."), col("."))') == 0 | 
					
						
							|  |  |  |                 " Global scope, should be zero | 
					
						
							|  |  |  |                 return 0 | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 " At the module scope, inside square brackets only | 
					
						
							|  |  |  |                 "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum | 
					
						
							|  |  |  |                 if line =~# "^\\s*]" | 
					
						
							|  |  |  |                     " It's the closing line, dedent it | 
					
						
							|  |  |  |                     return 0 | 
					
						
							|  |  |  |                 else | 
					
						
							| 
									
										
										
										
											2024-07-04 19:11:16 +02:00
										 |  |  |                     return shiftwidth() | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  |                 endif | 
					
						
							|  |  |  |             endif | 
					
						
							|  |  |  |         endif | 
					
						
							|  |  |  |     endif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     " Fall back on cindent, which does it mostly right | 
					
						
							|  |  |  |     return cindent(a:lnum) | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | endfunction | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | " vint: -ProhibitAbbreviationOption | 
					
						
							| 
									
										
										
										
											2017-03-21 19:18:29 +01:00
										 |  |  | let &cpo = s:save_cpo | 
					
						
							|  |  |  | unlet s:save_cpo | 
					
						
							| 
									
										
										
										
											2023-09-12 13:23:38 -05:00
										 |  |  | " vint: +ProhibitAbbreviationOption | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | " vim: set et sw=4 sts=4 ts=8: |