diff options
| author | Christian Clason <c.clason@uni-graz.at> | 2023-09-12 22:30:02 +0200 |
|---|---|---|
| committer | Christian Clason <c.clason@uni-graz.at> | 2023-09-13 17:23:45 +0200 |
| commit | 2dd5e472df8ed448375910d61c050390f7f17f5f (patch) | |
| tree | c8fcaebe744a1ab94f161a9744dc3ef6b9bc9e04 /runtime/autoload | |
| parent | b1d24ca760ba16e73a504bebd704cec4c77291c5 (diff) | |
| download | rneovim-2dd5e472df8ed448375910d61c050390f7f17f5f.tar.gz rneovim-2dd5e472df8ed448375910d61c050390f7f17f5f.tar.bz2 rneovim-2dd5e472df8ed448375910d61c050390f7f17f5f.zip | |
vim-patch:fc93594d562d
runtime(rust): sync rust runtime files with upstream (vim/vim#13075)
https://github.com/vim/vim/commit/fc93594d562dbbd9da03c89754538f91efd0c7ca
Co-authored-by: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Diffstat (limited to 'runtime/autoload')
| -rw-r--r-- | runtime/autoload/cargo.vim | 149 | ||||
| -rw-r--r-- | runtime/autoload/cargo/quickfix.vim | 29 | ||||
| -rw-r--r-- | runtime/autoload/rust.vim | 773 | ||||
| -rw-r--r-- | runtime/autoload/rust/debugging.vim | 105 | ||||
| -rw-r--r-- | runtime/autoload/rustfmt.vim | 300 |
5 files changed, 974 insertions, 382 deletions
diff --git a/runtime/autoload/cargo.vim b/runtime/autoload/cargo.vim new file mode 100644 index 0000000000..6696b3105f --- /dev/null +++ b/runtime/autoload/cargo.vim @@ -0,0 +1,149 @@ +" Last Modified: 2023-09-11 + +function! cargo#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! cargo#cmd(args) abort + " Trim trailing spaces. This is necessary since :terminal command parses + " trailing spaces as an empty argument. + let args = substitute(a:args, '\s\+$', '', '') + if exists('g:cargo_shell_command_runner') + let cmd = g:cargo_shell_command_runner + elseif has('terminal') + let cmd = 'terminal' + elseif has('nvim') + let cmd = 'noautocmd new | terminal' + else + let cmd = '!' + endif + execute cmd 'cargo' args +endfunction + +function! s:nearest_cargo(...) abort + " If the second argument is not specified, the first argument determines + " whether we will start from the current directory or the directory of the + " current buffer, otherwise, we start with the provided path on the + " second argument. + + let l:is_getcwd = get(a:, 1, 0) + if l:is_getcwd + let l:starting_path = get(a:, 2, getcwd()) + else + let l:starting_path = get(a:, 2, expand('%:p:h')) + endif + + return findfile('Cargo.toml', l:starting_path . ';') +endfunction + +function! cargo#nearestCargo(is_getcwd) abort + return s:nearest_cargo(a:is_getcwd) +endfunction + +function! cargo#nearestWorkspaceCargo(is_getcwd) abort + let l:nearest = s:nearest_cargo(a:is_getcwd) + while l:nearest !=# '' + for l:line in readfile(l:nearest, '', 0x100) + if l:line =~# '\V[workspace]' + return l:nearest + endif + endfor + let l:next = fnamemodify(l:nearest, ':p:h:h') + let l:nearest = s:nearest_cargo(0, l:next) + endwhile + return '' +endfunction + +function! cargo#nearestRootCargo(is_getcwd) abort + " Try to find a workspace Cargo.toml, and if not found, take the nearest + " regular Cargo.toml + let l:workspace_cargo = cargo#nearestWorkspaceCargo(a:is_getcwd) + if l:workspace_cargo !=# '' + return l:workspace_cargo + endif + return s:nearest_cargo(a:is_getcwd) +endfunction + + +function! cargo#build(args) + call cargo#cmd("build " . a:args) +endfunction + +function! cargo#check(args) + call cargo#cmd("check " . a:args) +endfunction + +function! cargo#clean(args) + call cargo#cmd("clean " . a:args) +endfunction + +function! cargo#doc(args) + call cargo#cmd("doc " . a:args) +endfunction + +function! cargo#new(args) + call cargo#cmd("new " . a:args) + cd `=a:args` +endfunction + +function! cargo#init(args) + call cargo#cmd("init " . a:args) +endfunction + +function! cargo#run(args) + call cargo#cmd("run " . a:args) +endfunction + +function! cargo#test(args) + call cargo#cmd("test " . a:args) +endfunction + +function! cargo#bench(args) + call cargo#cmd("bench " . a:args) +endfunction + +function! cargo#update(args) + call cargo#cmd("update " . a:args) +endfunction + +function! cargo#search(args) + call cargo#cmd("search " . a:args) +endfunction + +function! cargo#publish(args) + call cargo#cmd("publish " . a:args) +endfunction + +function! cargo#install(args) + call cargo#cmd("install " . a:args) +endfunction + +function! cargo#runtarget(args) + let l:filename = expand('%:p') + let l:read_manifest = system('cargo read-manifest') + let l:metadata = json_decode(l:read_manifest) + let l:targets = get(l:metadata, 'targets', []) + let l:did_run = 0 + for l:target in l:targets + let l:src_path = get(l:target, 'src_path', '') + let l:kinds = get(l:target, 'kind', []) + let l:name = get(l:target, 'name', '') + if l:src_path == l:filename + if index(l:kinds, 'example') != -1 + let l:did_run = 1 + call cargo#run("--example " . shellescape(l:name) . " " . a:args) + return + elseif index(l:kinds, 'bin') != -1 + let l:did_run = 1 + call cargo#run("--bin " . shellescape(l:name) . " " . a:args) + return + endif + endif + endfor + if l:did_run != 1 + call cargo#run(a:args) + return + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/cargo/quickfix.vim b/runtime/autoload/cargo/quickfix.vim new file mode 100644 index 0000000000..f2a006f6c5 --- /dev/null +++ b/runtime/autoload/cargo/quickfix.vim @@ -0,0 +1,29 @@ +" Last Modified: 2023-09-11 + +function! cargo#quickfix#CmdPre() abort + if &filetype ==# 'rust' && get(b:, 'current_compiler', '') ==# 'cargo' && + \ &makeprg =~ '\V\^cargo\ \.\*' + " Preserve the current directory, and 'lcd' to the nearest Cargo file. + let b:rust_compiler_cargo_qf_has_lcd = haslocaldir() + let b:rust_compiler_cargo_qf_prev_cd = getcwd() + let b:rust_compiler_cargo_qf_prev_cd_saved = 1 + let l:nearest = fnamemodify(cargo#nearestRootCargo(0), ':h') + execute 'lchdir! '.l:nearest + else + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +function! cargo#quickfix#CmdPost() abort + if exists("b:rust_compiler_cargo_qf_prev_cd_saved") && b:rust_compiler_cargo_qf_prev_cd_saved + " Restore the current directory. + if b:rust_compiler_cargo_qf_has_lcd + execute 'lchdir! '.b:rust_compiler_cargo_qf_prev_cd + else + execute 'chdir! '.b:rust_compiler_cargo_qf_prev_cd + endif + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rust.vim b/runtime/autoload/rust.vim index 4230332fa7..5ccbf4b382 100644 --- a/runtime/autoload/rust.vim +++ b/runtime/autoload/rust.vim @@ -1,207 +1,258 @@ -" Author: Lily Ballard " Description: Helper functions for Rust commands/mappings -" Last Modified: May 27, 2014 +" Last Modified: 2023-09-11 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim +function! rust#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! rust#GetConfigVar(name, default) + " Local buffer variable with same name takes predeence over global + if has_key(b:, a:name) + return get(b:, a:name) + endif + if has_key(g:, a:name) + return get(g:, a:name) + endif + return a:default +endfunction + +" Include expression {{{1 + +function! rust#IncludeExpr(fname) abort + " Remove leading 'crate::' to deal with 2018 edition style 'use' + " statements + let l:fname = substitute(a:fname, '^crate::', '', '') + + " Remove trailing colons arising from lines like + " + " use foo::{Bar, Baz}; + let l:fname = substitute(l:fname, ':\+$', '', '') + + " Replace '::' with '/' + let l:fname = substitute(l:fname, '::', '/', 'g') + + " When we have + " + " use foo::bar::baz; + " + " we can't tell whether baz is a module or a function; and we can't tell + " which modules correspond to files. + " + " So we work our way up, trying + " + " foo/bar/baz.rs + " foo/bar.rs + " foo.rs + while l:fname !=# '.' + let l:path = findfile(l:fname) + if !empty(l:path) + return l:fname + endif + let l:fname = fnamemodify(l:fname, ':h') + endwhile + return l:fname +endfunction + " Jump {{{1 function! rust#Jump(mode, function) range - let cnt = v:count1 - normal! m' - if a:mode ==# 'v' - norm! gv - endif - let foldenable = &foldenable - set nofoldenable - while cnt > 0 - execute "call <SID>Jump_" . a:function . "()" - let cnt = cnt - 1 - endwhile - let &foldenable = foldenable + let cnt = v:count1 + normal! m' + if a:mode ==# 'v' + norm! gv + endif + let foldenable = &foldenable + set nofoldenable + while cnt > 0 + execute "call <SID>Jump_" . a:function . "()" + let cnt = cnt - 1 + endwhile + let &foldenable = foldenable endfunction function! s:Jump_Back() - call search('{', 'b') - keepjumps normal! w99[{ + call search('{', 'b') + keepjumps normal! w99[{ endfunction function! s:Jump_Forward() - normal! j0 - call search('{', 'b') - keepjumps normal! w99[{% - call search('{') + normal! j0 + call search('{', 'b') + keepjumps normal! w99[{% + call search('{') endfunction " Run {{{1 function! rust#Run(bang, args) - let args = s:ShellTokenize(a:args) - if a:bang - let idx = index(l:args, '--') - if idx != -1 - let rustc_args = idx == 0 ? [] : l:args[:idx-1] - let args = l:args[idx+1:] - else - let rustc_args = l:args - let args = [] - endif - else - let rustc_args = [] - endif - - let b:rust_last_rustc_args = l:rustc_args - let b:rust_last_args = l:args - - call s:WithPath(function("s:Run"), rustc_args, args) + let args = s:ShellTokenize(a:args) + if a:bang + let idx = index(l:args, '--') + if idx != -1 + let rustc_args = idx == 0 ? [] : l:args[:idx-1] + let args = l:args[idx+1:] + else + let rustc_args = l:args + let args = [] + endif + else + let rustc_args = [] + endif + + let b:rust_last_rustc_args = l:rustc_args + let b:rust_last_args = l:args + + call s:WithPath(function("s:Run"), rustc_args, args) endfunction function! s:Run(dict, rustc_args, args) - let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r') - if has('win32') - let exepath .= '.exe' - endif - - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let rustc_args = [relpath, '-o', exepath] + a:rustc_args - - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)'))) - if output != '' - echohl WarningMsg - echo output - echohl None - endif - if !v:shell_error - exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) - endif + let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r') + if has('win32') + let exepath .= '.exe' + endif + + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let rustc_args = [relpath, '-o', exepath] + a:rustc_args + + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)'))) + if output !=# '' + echohl WarningMsg + echo output + echohl None + endif + if !v:shell_error + exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) + endif endfunction " Expand {{{1 function! rust#Expand(bang, args) - let args = s:ShellTokenize(a:args) - if a:bang && !empty(l:args) - let pretty = remove(l:args, 0) - else - let pretty = "expanded" - endif - call s:WithPath(function("s:Expand"), pretty, args) + let args = s:ShellTokenize(a:args) + if a:bang && !empty(l:args) + let pretty = remove(l:args, 0) + else + let pretty = "expanded" + endif + call s:WithPath(function("s:Expand"), pretty, args) endfunction function! s:Expand(dict, pretty, args) - try - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)' - let flag = '--xpretty' - else - let flag = '--pretty' - endif - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) - if v:shell_error - echohl WarningMsg - echo output - echohl None - else - new - silent put =output - 1 - d - setl filetype=rust - setl buftype=nofile - setl bufhidden=hide - setl noswapfile - " give the buffer a nice name - let suffix = 1 - let basename = fnamemodify(a:dict.path, ':t:r') - while 1 - let bufname = basename - if suffix > 1 | let bufname .= ' ('.suffix.')' | endif - let bufname .= '.pretty.rs' - if bufexists(bufname) - let suffix += 1 - continue - endif - exe 'silent noautocmd keepalt file' fnameescape(bufname) - break - endwhile - endif - endtry + try + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)' + let flag = '--xpretty' + else + let flag = '--pretty' + endif + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) + if v:shell_error + echohl WarningMsg + echo output + echohl None + else + new + silent put =output + 1 + d + setl filetype=rust + setl buftype=nofile + setl bufhidden=hide + setl noswapfile + " give the buffer a nice name + let suffix = 1 + let basename = fnamemodify(a:dict.path, ':t:r') + while 1 + let bufname = basename + if suffix > 1 | let bufname .= ' ('.suffix.')' | endif + let bufname .= '.pretty.rs' + if bufexists(bufname) + let suffix += 1 + continue + endif + exe 'silent noautocmd keepalt file' fnameescape(bufname) + break + endwhile + endif + endtry endfunction function! rust#CompleteExpand(lead, line, pos) - if a:line[: a:pos-1] =~ '^RustExpand!\s*\S*$' - " first argument and it has a ! - let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"] - if !empty(a:lead) - call filter(list, "v:val[:len(a:lead)-1] == a:lead") - endif - return list - endif - - return glob(escape(a:lead, "*?[") . '*', 0, 1) + if a:line[: a:pos-1] =~# '^RustExpand!\s*\S*$' + " first argument and it has a ! + let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"] + if !empty(a:lead) + call filter(list, "v:val[:len(a:lead)-1] == a:lead") + endif + return list + endif + + return glob(escape(a:lead, "*?[") . '*', 0, 1) endfunction " Emit {{{1 function! rust#Emit(type, args) - let args = s:ShellTokenize(a:args) - call s:WithPath(function("s:Emit"), a:type, args) + let args = s:ShellTokenize(a:args) + call s:WithPath(function("s:Emit"), a:type, args) endfunction function! s:Emit(dict, type, args) - try - let output_path = a:dict.tmpdir.'/output' - - let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" - - let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) - let args = [relpath, '--emit', a:type, '-o', output_path] + a:args - let pwd = a:dict.istemp ? a:dict.tmpdir : '' - let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) - if output != '' - echohl WarningMsg - echo output - echohl None - endif - if !v:shell_error - new - exe 'silent keepalt read' fnameescape(output_path) - 1 - d - if a:type == "llvm-ir" - setl filetype=llvm - let extension = 'll' - elseif a:type == "asm" - setl filetype=asm - let extension = 's' - endif - setl buftype=nofile - setl bufhidden=hide - setl noswapfile - if exists('l:extension') - " give the buffer a nice name - let suffix = 1 - let basename = fnamemodify(a:dict.path, ':t:r') - while 1 - let bufname = basename - if suffix > 1 | let bufname .= ' ('.suffix.')' | endif - let bufname .= '.'.extension - if bufexists(bufname) - let suffix += 1 - continue - endif - exe 'silent noautocmd keepalt file' fnameescape(bufname) - break - endwhile - endif - endif - endtry + try + let output_path = a:dict.tmpdir.'/output' + + let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + + let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) + let args = [relpath, '--emit', a:type, '-o', output_path] + a:args + let pwd = a:dict.istemp ? a:dict.tmpdir : '' + let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) + if output !=# '' + echohl WarningMsg + echo output + echohl None + endif + if !v:shell_error + new + exe 'silent keepalt read' fnameescape(output_path) + 1 + d + if a:type ==# "llvm-ir" + setl filetype=llvm + let extension = 'll' + elseif a:type ==# "asm" + setl filetype=asm + let extension = 's' + endif + setl buftype=nofile + setl bufhidden=hide + setl noswapfile + if exists('l:extension') + " give the buffer a nice name + let suffix = 1 + let basename = fnamemodify(a:dict.path, ':t:r') + while 1 + let bufname = basename + if suffix > 1 | let bufname .= ' ('.suffix.')' | endif + let bufname .= '.'.extension + if bufexists(bufname) + let suffix += 1 + continue + endif + exe 'silent noautocmd keepalt file' fnameescape(bufname) + break + endwhile + endif + endif + endtry endfunction " Utility functions {{{1 @@ -219,145 +270,154 @@ endfunction " existing path of the current buffer. If the path is inside of {dict.tmpdir} " then it is guaranteed to have a '.rs' extension. function! s:WithPath(func, ...) - let buf = bufnr('') - let saved = {} - let dict = {} - try - let saved.write = &write - set write - let dict.path = expand('%') - let pathisempty = empty(dict.path) - - " Always create a tmpdir in case the wrapped command wants it - let dict.tmpdir = tempname() - call mkdir(dict.tmpdir) - - if pathisempty || !saved.write - let dict.istemp = 1 - " if we're doing this because of nowrite, preserve the filename - if !pathisempty - let filename = expand('%:t:r').".rs" - else - let filename = 'unnamed.rs' - endif - let dict.tmpdir_relpath = filename - let dict.path = dict.tmpdir.'/'.filename - - let saved.mod = &mod - set nomod - - silent exe 'keepalt write! ' . fnameescape(dict.path) - if pathisempty - silent keepalt 0file - endif - else - let dict.istemp = 0 - update - endif - - call call(a:func, [dict] + a:000) - finally - if bufexists(buf) - for [opt, value] in items(saved) - silent call setbufvar(buf, '&'.opt, value) - unlet value " avoid variable type mismatches - endfor - endif - if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif - endtry + let buf = bufnr('') + let saved = {} + let dict = {} + try + let saved.write = &write + set write + let dict.path = expand('%') + let pathisempty = empty(dict.path) + + " Always create a tmpdir in case the wrapped command wants it + let dict.tmpdir = tempname() + call mkdir(dict.tmpdir) + + if pathisempty || !saved.write + let dict.istemp = 1 + " if we're doing this because of nowrite, preserve the filename + if !pathisempty + let filename = expand('%:t:r').".rs" + else + let filename = 'unnamed.rs' + endif + let dict.tmpdir_relpath = filename + let dict.path = dict.tmpdir.'/'.filename + + let saved.mod = &modified + set nomodified + + silent exe 'keepalt write! ' . fnameescape(dict.path) + if pathisempty + silent keepalt 0file + endif + else + let dict.istemp = 0 + update + endif + + call call(a:func, [dict] + a:000) + finally + if bufexists(buf) + for [opt, value] in items(saved) + silent call setbufvar(buf, '&'.opt, value) + unlet value " avoid variable type mismatches + endfor + endif + if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif + endtry endfunction function! rust#AppendCmdLine(text) - call setcmdpos(getcmdpos()) - let cmd = getcmdline() . a:text - return cmd + call setcmdpos(getcmdpos()) + let cmd = getcmdline() . a:text + return cmd endfunction " Tokenize the string according to sh parsing rules function! s:ShellTokenize(text) - " states: - " 0: start of word - " 1: unquoted - " 2: unquoted backslash - " 3: double-quote - " 4: double-quoted backslash - " 5: single-quote - let l:state = 0 - let l:current = '' - let l:args = [] - for c in split(a:text, '\zs') - if l:state == 0 || l:state == 1 " unquoted - if l:c ==# ' ' - if l:state == 0 | continue | endif - call add(l:args, l:current) - let l:current = '' - let l:state = 0 - elseif l:c ==# '\' - let l:state = 2 - elseif l:c ==# '"' - let l:state = 3 - elseif l:c ==# "'" - let l:state = 5 - else - let l:current .= l:c - let l:state = 1 - endif - elseif l:state == 2 " unquoted backslash - if l:c !=# "\n" " can it even be \n? - let l:current .= l:c - endif - let l:state = 1 - elseif l:state == 3 " double-quote - if l:c ==# '\' - let l:state = 4 - elseif l:c ==# '"' - let l:state = 1 - else - let l:current .= l:c - endif - elseif l:state == 4 " double-quoted backslash - if stridx('$`"\', l:c) >= 0 - let l:current .= l:c - elseif l:c ==# "\n" " is this even possible? - " skip it - else - let l:current .= '\'.l:c - endif - let l:state = 3 - elseif l:state == 5 " single-quoted - if l:c == "'" - let l:state = 1 - else - let l:current .= l:c - endif - endif - endfor - if l:state != 0 - call add(l:args, l:current) - endif - return l:args + " states: + " 0: start of word + " 1: unquoted + " 2: unquoted backslash + " 3: double-quote + " 4: double-quoted backslash + " 5: single-quote + let l:state = 0 + let l:current = '' + let l:args = [] + for c in split(a:text, '\zs') + if l:state == 0 || l:state == 1 " unquoted + if l:c ==# ' ' + if l:state == 0 | continue | endif + call add(l:args, l:current) + let l:current = '' + let l:state = 0 + elseif l:c ==# '\' + let l:state = 2 + elseif l:c ==# '"' + let l:state = 3 + elseif l:c ==# "'" + let l:state = 5 + else + let l:current .= l:c + let l:state = 1 + endif + elseif l:state == 2 " unquoted backslash + if l:c !=# "\n" " can it even be \n? + let l:current .= l:c + endif + let l:state = 1 + elseif l:state == 3 " double-quote + if l:c ==# '\' + let l:state = 4 + elseif l:c ==# '"' + let l:state = 1 + else + let l:current .= l:c + endif + elseif l:state == 4 " double-quoted backslash + if stridx('$`"\', l:c) >= 0 + let l:current .= l:c + elseif l:c ==# "\n" " is this even possible? + " skip it + else + let l:current .= '\'.l:c + endif + let l:state = 3 + elseif l:state == 5 " single-quoted + if l:c ==# "'" + let l:state = 1 + else + let l:current .= l:c + endif + endif + endfor + if l:state != 0 + call add(l:args, l:current) + endif + return l:args endfunction function! s:RmDir(path) - " sanity check; make sure it's not empty, /, or $HOME - if empty(a:path) - echoerr 'Attempted to delete empty path' - return 0 - elseif a:path == '/' || a:path == $HOME - echoerr 'Attempted to delete protected path: ' . a:path - return 0 - endif - return system("rm -rf " . shellescape(a:path)) + " sanity check; make sure it's not empty, /, or $HOME + if empty(a:path) + echoerr 'Attempted to delete empty path' + return 0 + elseif a:path ==# '/' || a:path ==# $HOME + let l:path = expand(a:path) + if l:path ==# '/' || l:path ==# $HOME + echoerr 'Attempted to delete protected path: ' . a:path + return 0 + endif + endif + + if !isdirectory(a:path) + return 0 + endif + + " delete() returns 0 when removing file successfully + return delete(a:path, 'rf') == 0 endfunction " Executes {cmd} with the cwd set to {pwd}, without changing Vim's cwd. " If {pwd} is the empty string then it doesn't change the cwd. function! s:system(pwd, cmd) - let cmd = a:cmd - if !empty(a:pwd) - let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd - endif - return system(cmd) + let cmd = a:cmd + if !empty(a:pwd) + let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd + endif + return system(cmd) endfunction " Playpen Support {{{1 @@ -366,10 +426,10 @@ endfunction " http://github.com/mattn/gist-vim function! s:has_webapi() if !exists("*webapi#http#post") - try - call webapi#http#post() - catch - endtry + try + call webapi#http#post() + catch + endtry endif return exists("*webapi#http#post") endfunction @@ -381,35 +441,130 @@ function! rust#Play(count, line1, line2, ...) abort let l:rust_shortener_url = get(g:, 'rust_shortener_url', 'https://is.gd/') if !s:has_webapi() - echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None - return + echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None + return endif let bufname = bufname('%') if a:count < 1 - let content = join(getline(a:line1, a:line2), "\n") + let content = join(getline(a:line1, a:line2), "\n") else - let save_regcont = @" - let save_regtype = getregtype('"') - silent! normal! gvy - let content = @" - call setreg('"', save_regcont, save_regtype) + let save_regcont = @" + let save_regtype = getregtype('"') + silent! normal! gvy + let content = @" + call setreg('"', save_regcont, save_regtype) endif - let body = l:rust_playpen_url."?code=".webapi#http#encodeURI(content) + let url = l:rust_playpen_url."?code=".webapi#http#encodeURI(content) - if strlen(body) > 5000 - echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(body).')' | echohl None - return + if strlen(url) > 5000 + echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(url).')' | echohl None + return endif - let payload = "format=simple&url=".webapi#http#encodeURI(body) + let payload = "format=simple&url=".webapi#http#encodeURI(url) let res = webapi#http#post(l:rust_shortener_url.'create.php', payload, {}) - let url = res.content + if res.status[0] ==# '2' + let url = res.content + endif + + let footer = '' + if exists('g:rust_clip_command') + call system(g:rust_clip_command, url) + if !v:shell_error + let footer = ' (copied to clipboard)' + endif + endif + redraw | echomsg 'Done: '.url.footer +endfunction + +" Run a test under the cursor or all tests {{{1 + +" Finds a test function name under the cursor. Returns empty string when a +" test function is not found. +function! s:SearchTestFunctionNameUnderCursor() abort + let cursor_line = line('.') - redraw | echomsg 'Done: '.url + " Find #[test] attribute + if search('\m\C#\[test\]', 'bcW') is 0 + return '' + endif + + " Move to an opening brace of the test function + let test_func_line = search('\m\C^\s*fn\s\+\h\w*\s*(.\+{$', 'eW') + if test_func_line is 0 + return '' + endif + + " Search the end of test function (closing brace) to ensure that the + " cursor position is within function definition + if maparg('<Plug>(MatchitNormalForward)') ==# '' + keepjumps normal! % + else + " Prefer matchit.vim official plugin to native % since the plugin + " provides better behavior than original % (#391) + " To load the plugin, run: + " :packadd matchit + execute 'keepjumps' 'normal' "\<Plug>(MatchitNormalForward)" + endif + if line('.') < cursor_line + return '' + endif + + return matchstr(getline(test_func_line), '\m\C^\s*fn\s\+\zs\h\w*') +endfunction + +function! rust#Test(mods, winsize, all, options) abort + let manifest = findfile('Cargo.toml', expand('%:p:h') . ';') + if manifest ==# '' + return rust#Run(1, '--test ' . a:options) + endif + + " <count> defaults to 0, but we prefer an empty string + let winsize = a:winsize ? a:winsize : '' + + if has('terminal') + if has('patch-8.0.910') + let cmd = printf('%s noautocmd %snew | terminal ++curwin ', a:mods, winsize) + else + let cmd = printf('%s terminal ', a:mods) + endif + elseif has('nvim') + let cmd = printf('%s noautocmd %snew | terminal ', a:mods, winsize) + else + let cmd = '!' + let manifest = shellescape(manifest) + endif + + if a:all + if a:options ==# '' + execute cmd . 'cargo test --manifest-path' manifest + else + execute cmd . 'cargo test --manifest-path' manifest a:options + endif + return + endif + + let saved = getpos('.') + try + let func_name = s:SearchTestFunctionNameUnderCursor() + finally + call setpos('.', saved) + endtry + if func_name ==# '' + echohl ErrorMsg + echomsg 'No test function was found under the cursor. Please add ! to command if you want to run all tests' + echohl None + return + endif + if a:options ==# '' + execute cmd . 'cargo test --manifest-path' manifest func_name + else + execute cmd . 'cargo test --manifest-path' manifest func_name a:options + endif endfunction " }}}1 -" vim: set noet sw=8 ts=8: +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rust/debugging.vim b/runtime/autoload/rust/debugging.vim new file mode 100644 index 0000000000..0e84183172 --- /dev/null +++ b/runtime/autoload/rust/debugging.vim @@ -0,0 +1,105 @@ +" Last Modified: 2023-09-11 + +" For debugging, inspired by https://github.com/w0rp/rust/blob/master/autoload/rust/debugging.vim + +let s:global_variable_list = [ + \ '_rustfmt_autosave_because_of_config', + \ 'ftplugin_rust_source_path', + \ 'loaded_syntastic_rust_cargo_checker', + \ 'loaded_syntastic_rust_filetype', + \ 'loaded_syntastic_rust_rustc_checker', + \ 'rust_bang_comment_leader', + \ 'rust_cargo_avoid_whole_workspace', + \ 'rust_clip_command', + \ 'rust_conceal', + \ 'rust_conceal_mod_path', + \ 'rust_conceal_pub', + \ 'rust_fold', + \ 'rust_last_args', + \ 'rust_last_rustc_args', + \ 'rust_original_delimitMate_excluded_regions', + \ 'rust_playpen_url', + \ 'rust_prev_delimitMate_quotes', + \ 'rust_recent_nearest_cargo_tol', + \ 'rust_recent_root_cargo_toml', + \ 'rust_recommended_style', + \ 'rust_set_conceallevel', + \ 'rust_set_conceallevel=1', + \ 'rust_set_foldmethod', + \ 'rust_set_foldmethod=1', + \ 'rust_shortener_url', + \ 'rustc_makeprg_no_percent', + \ 'rustc_path', + \ 'rustfmt_autosave', + \ 'rustfmt_autosave_if_config_present', + \ 'rustfmt_command', + \ 'rustfmt_emit_files', + \ 'rustfmt_fail_silently', + \ 'rustfmt_options', + \ 'syntastic_extra_filetypes', + \ 'syntastic_rust_cargo_fname', + \] + +function! s:Echo(message) abort + execute 'echo a:message' +endfunction + +function! s:EchoGlobalVariables() abort + for l:key in s:global_variable_list + if l:key !~# '^_' + call s:Echo('let g:' . l:key . ' = ' . string(get(g:, l:key, v:null))) + endif + + if has_key(b:, l:key) + call s:Echo('let b:' . l:key . ' = ' . string(b:[l:key])) + endif + endfor +endfunction + +function! rust#debugging#Info() abort + call cargo#Load() + call rust#Load() + call rustfmt#Load() + call s:Echo('rust.vim Global Variables:') + call s:Echo('') + call s:EchoGlobalVariables() + + silent let l:output = system(g:rustfmt_command . ' --version') + echo l:output + + let l:rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" + silent let l:output = system(l:rustc . ' --version') + echo l:output + + silent let l:output = system('cargo --version') + echo l:output + + version + + if exists(":SyntasticInfo") + echo "----" + echo "Info from Syntastic:" + execute "SyntasticInfo" + endif +endfunction + +function! rust#debugging#InfoToClipboard() abort + redir @" + silent call rust#debugging#Info() + redir END + + call s:Echo('RustInfo copied to your clipboard') +endfunction + +function! rust#debugging#InfoToFile(filename) abort + let l:expanded_filename = expand(a:filename) + + redir => l:output + silent call rust#debugging#Info() + redir END + + call writefile(split(l:output, "\n"), l:expanded_filename) + call s:Echo('RustInfo written to ' . l:expanded_filename) +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/rustfmt.vim b/runtime/autoload/rustfmt.vim index a689b5e00d..652e6af33a 100644 --- a/runtime/autoload/rustfmt.vim +++ b/runtime/autoload/rustfmt.vim @@ -1,107 +1,261 @@ " Author: Stephen Sugden <stephen@stephensugden.com> +" Last Modified: 2023-09-11 " " Adapted from https://github.com/fatih/vim-go " For bugs, patches and license go to https://github.com/rust-lang/rust.vim if !exists("g:rustfmt_autosave") - let g:rustfmt_autosave = 0 + let g:rustfmt_autosave = 0 endif if !exists("g:rustfmt_command") - let g:rustfmt_command = "rustfmt" + let g:rustfmt_command = "rustfmt" endif if !exists("g:rustfmt_options") - let g:rustfmt_options = "" + let g:rustfmt_options = "" endif if !exists("g:rustfmt_fail_silently") - let g:rustfmt_fail_silently = 0 + let g:rustfmt_fail_silently = 0 +endif + +function! rustfmt#DetectVersion() + " Save rustfmt '--help' for feature inspection + silent let s:rustfmt_help = system(g:rustfmt_command . " --help") + let s:rustfmt_unstable_features = s:rustfmt_help =~# "--unstable-features" + + " Build a comparable rustfmt version varible out of its `--version` output: + silent let l:rustfmt_version_full = system(g:rustfmt_command . " --version") + let l:rustfmt_version_list = matchlist(l:rustfmt_version_full, + \ '\vrustfmt ([0-9]+[.][0-9]+[.][0-9]+)') + if len(l:rustfmt_version_list) < 3 + let s:rustfmt_version = "0" + else + let s:rustfmt_version = l:rustfmt_version_list[1] + endif + return s:rustfmt_version +endfunction + +call rustfmt#DetectVersion() + +if !exists("g:rustfmt_emit_files") + let g:rustfmt_emit_files = s:rustfmt_version >= "0.8.2" +endif + +if !exists("g:rustfmt_file_lines") + let g:rustfmt_file_lines = s:rustfmt_help =~# "--file-lines JSON" endif let s:got_fmt_error = 0 +function! rustfmt#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! s:RustfmtWriteMode() + if g:rustfmt_emit_files + return "--emit=files" + else + return "--write-mode=overwrite" + endif +endfunction + +function! s:RustfmtConfigOptions() + let l:rustfmt_toml = findfile('rustfmt.toml', expand('%:p:h') . ';') + if l:rustfmt_toml !=# '' + return '--config-path '.shellescape(fnamemodify(l:rustfmt_toml, ":p")) + endif + + let l:_rustfmt_toml = findfile('.rustfmt.toml', expand('%:p:h') . ';') + if l:_rustfmt_toml !=# '' + return '--config-path '.shellescape(fnamemodify(l:_rustfmt_toml, ":p")) + endif + + " Default to edition 2018 in case no rustfmt.toml was found. + return '--edition 2018' +endfunction + function! s:RustfmtCommandRange(filename, line1, line2) - let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]} - return printf("%s %s --write-mode=overwrite --file-lines '[%s]'", g:rustfmt_command, g:rustfmt_options, json_encode(l:arg)) + if g:rustfmt_file_lines == 0 + echo "--file-lines is not supported in the installed `rustfmt` executable" + return + endif + + let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]} + let l:write_mode = s:RustfmtWriteMode() + let l:rustfmt_config = s:RustfmtConfigOptions() + + " FIXME: When --file-lines gets to be stable, add version range checking + " accordingly. + let l:unstable_features = s:rustfmt_unstable_features ? '--unstable-features' : '' + + let l:cmd = printf("%s %s %s %s %s --file-lines '[%s]' %s", g:rustfmt_command, + \ l:write_mode, g:rustfmt_options, + \ l:unstable_features, l:rustfmt_config, + \ json_encode(l:arg), shellescape(a:filename)) + return l:cmd endfunction -function! s:RustfmtCommand(filename) - return g:rustfmt_command . " --write-mode=overwrite " . g:rustfmt_options . " " . shellescape(a:filename) +function! s:RustfmtCommand() + let write_mode = g:rustfmt_emit_files ? '--emit=stdout' : '--write-mode=display' + let config = s:RustfmtConfigOptions() + return join([g:rustfmt_command, write_mode, config, g:rustfmt_options]) endfunction -function! s:RunRustfmt(command, curw, tmpname) - if exists("*systemlist") - let out = systemlist(a:command) - else - let out = split(system(a:command), '\r\?\n') - endif - - if v:shell_error == 0 || v:shell_error == 3 - " remove undo point caused via BufWritePre - try | silent undojoin | catch | endtry - - " Replace current file with temp file, then reload buffer - call rename(a:tmpname, expand('%')) - silent edit! - let &syntax = &syntax - - " only clear location list if it was previously filled to prevent - " clobbering other additions - if s:got_fmt_error - let s:got_fmt_error = 0 - call setloclist(0, []) - lwindow - endif - elseif g:rustfmt_fail_silently == 0 - " otherwise get the errors and put them in the location list - let errors = [] - - for line in out - " src/lib.rs:13:5: 13:10 error: expected `,`, or `}`, found `value` - let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\):\s*\(\d\+:\d\+\s*\)\?\s*error: \(.*\)') - if !empty(tokens) - call add(errors, {"filename": @%, - \"lnum": tokens[2], - \"col": tokens[3], - \"text": tokens[5]}) - endif - endfor - - if empty(errors) - % | " Couldn't detect rustfmt error format, output errors - endif - - if !empty(errors) - call setloclist(0, errors, 'r') - echohl Error | echomsg "rustfmt returned error" | echohl None - endif - - let s:got_fmt_error = 1 - lwindow - " We didn't use the temp file, so clean up - call delete(a:tmpname) - endif - - call winrestview(a:curw) +function! s:DeleteLines(start, end) abort + silent! execute a:start . ',' . a:end . 'delete _' endfunction -function! rustfmt#FormatRange(line1, line2) - let l:curw = winsaveview() - let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt" - call writefile(getline(1, '$'), l:tmpname) +function! s:RunRustfmt(command, tmpname, from_writepre) + let l:view = winsaveview() + + let l:stderr_tmpname = tempname() + call writefile([], l:stderr_tmpname) + + let l:command = a:command . ' 2> ' . l:stderr_tmpname - let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2) + if a:tmpname ==# '' + " Rustfmt in stdin/stdout mode - call s:RunRustfmt(command, l:curw, l:tmpname) + " chdir to the directory of the file + let l:has_lcd = haslocaldir() + let l:prev_cd = getcwd() + execute 'lchdir! '.expand('%:h') + + let l:buffer = getline(1, '$') + if exists("*systemlist") + silent let out = systemlist(l:command, l:buffer) + else + silent let out = split(system(l:command, + \ join(l:buffer, "\n")), '\r\?\n') + endif + else + if exists("*systemlist") + silent let out = systemlist(l:command) + else + silent let out = split(system(l:command), '\r\?\n') + endif + endif + + let l:stderr = readfile(l:stderr_tmpname) + + call delete(l:stderr_tmpname) + + let l:open_lwindow = 0 + if v:shell_error == 0 + if a:from_writepre + " remove undo point caused via BufWritePre + try | silent undojoin | catch | endtry + endif + + if a:tmpname ==# '' + let l:content = l:out + else + " take the tmpfile's content, this is better than rename + " because it preserves file modes. + let l:content = readfile(a:tmpname) + endif + + call s:DeleteLines(len(l:content), line('$')) + call setline(1, l:content) + + " only clear location list if it was previously filled to prevent + " clobbering other additions + if s:got_fmt_error + let s:got_fmt_error = 0 + call setloclist(0, []) + let l:open_lwindow = 1 + endif + elseif g:rustfmt_fail_silently == 0 && !a:from_writepre + " otherwise get the errors and put them in the location list + let l:errors = [] + + let l:prev_line = "" + for l:line in l:stderr + " error: expected one of `;` or `as`, found `extern` + " --> src/main.rs:2:1 + let tokens = matchlist(l:line, '^\s\+-->\s\(.\{-}\):\(\d\+\):\(\d\+\)$') + if !empty(tokens) + call add(l:errors, {"filename": @%, + \"lnum": tokens[2], + \"col": tokens[3], + \"text": l:prev_line}) + endif + let l:prev_line = l:line + endfor + + if !empty(l:errors) + call setloclist(0, l:errors, 'r') + echohl Error | echomsg "rustfmt returned error" | echohl None + else + echo "rust.vim: was not able to parse rustfmt messages. Here is the raw output:" + echo "\n" + for l:line in l:stderr + echo l:line + endfor + endif + + let s:got_fmt_error = 1 + let l:open_lwindow = 1 + endif + + " Restore the current directory if needed + if a:tmpname ==# '' + if l:has_lcd + execute 'lchdir! '.l:prev_cd + else + execute 'chdir! '.l:prev_cd + endif + endif + + " Open lwindow after we have changed back to the previous directory + if l:open_lwindow == 1 + lwindow + endif + + call winrestview(l:view) +endfunction + +function! rustfmt#FormatRange(line1, line2) + let l:tmpname = tempname() + call writefile(getline(1, '$'), l:tmpname) + let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2) + call s:RunRustfmt(command, l:tmpname, v:false) + call delete(l:tmpname) endfunction function! rustfmt#Format() - let l:curw = winsaveview() - let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt" - call writefile(getline(1, '$'), l:tmpname) + call s:RunRustfmt(s:RustfmtCommand(), '', v:false) +endfunction - let command = s:RustfmtCommand(l:tmpname) +function! rustfmt#Cmd() + " Mainly for debugging + return s:RustfmtCommand() +endfunction + +function! rustfmt#PreWrite() + if !filereadable(expand("%@")) + return + endif + if rust#GetConfigVar('rustfmt_autosave_if_config_present', 0) + if findfile('rustfmt.toml', '.;') !=# '' || findfile('.rustfmt.toml', '.;') !=# '' + let b:rustfmt_autosave = 1 + let b:_rustfmt_autosave_because_of_config = 1 + endif + else + if has_key(b:, '_rustfmt_autosave_because_of_config') + unlet b:_rustfmt_autosave_because_of_config + unlet b:rustfmt_autosave + endif + endif + + if !rust#GetConfigVar("rustfmt_autosave", 0) + return + endif - call s:RunRustfmt(command, l:curw, l:tmpname) + call s:RunRustfmt(s:RustfmtCommand(), '', v:true) endfunction + + +" vim: set et sw=4 sts=4 ts=8: |