diff --git a/README.md b/README.md index 296a3fa4..258e3774 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Description -This is a vim plugin that provides [Rust][r] file detection and syntax highlighting. +This is a vim plugin that provides [Rust][r] file detection, syntax highlighting, and (optional) autoformatting. ## Installation @@ -31,3 +31,12 @@ git clone --depth=1 https://github.com/rust-lang/rust.vim.git ~/.vim/bundle/rust 1. Add `NeoBundle 'rust-lang/rust.vim'` to `~/.vimrc` 2. Re-open vim or execute `:source ~/.vimrc` + +## Enabling autoformat + +This plugin can optionally format your code using [rustfmt][rfmt] every time a +buffer is written. Simple put `let g:rustfmt_autosave = 1` in your `.vimrc`. + +## Help + +Further help can be found in the documentation with `:help rust`. diff --git a/autoload/rustfmt.vim b/autoload/rustfmt.vim new file mode 100644 index 00000000..fbbac65d --- /dev/null +++ b/autoload/rustfmt.vim @@ -0,0 +1,79 @@ +" Author: Stephen Sugden +" +" Adapted from https://github.com/fatih/vim-go + +if !exists("g:rustfmt_autosave") + let g:rustfmt_autosave = 0 +endif + +if !exists("g:rustfmt_command") + let g:rustfmt_command = "rustfmt" +endif + +if !exists("g:rustfmt_options") + let g:rustfmt_options = "" +endif + +if !exists("g:rustfmt_fail_silently") + let g:rustfmt_fail_silently = 0 +endif + +let s:got_fmt_error = 0 + +function! rustfmt#Format() + let l:curw = winsaveview() + let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt" + call writefile(getline(1, '$'), l:tmpname) + + let command = g:rustfmt_command . " --write-mode=overwrite " + + let out = systemlist(command . g:rustfmt_options . " " . shellescape(l:tmpname)) + + if v:shell_error == 0 + " remove undo point caused via BufWritePre + try | silent undojoin | catch | endtry + + " Replace current file with temp file, then reload buffer + call rename(l: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(l:tmpname) + endif + + call winrestview(l:curw) +endfunction diff --git a/doc/rust.txt b/doc/rust.txt index 99d64792..7a4091da 100644 --- a/doc/rust.txt +++ b/doc/rust.txt @@ -88,11 +88,37 @@ g:ftplugin_rust_source_path~ let g:ftplugin_rust_source_path = $HOME.'/dev/rust' < - *g:cargo_manifest_name* + *g:cargo_manifest_name* g:cargo_manifest_name~ Set this option to the name of the manifest file for your projects. If not specified it defaults to 'Cargo.toml' : > - let g:cargo_manifest_name = 'Cargo.toml' + let g:cargo_manifest_name = 'Cargo.toml' +< + + *g:rustfmt_command* +g:rustfmt_command~ + Set this option to the name of the 'rustfmt' executable in your $PATH. If + not specified it defaults to 'rustfmt' : > + let g:rustfmt_command = 'rustfmt' +< + *g:rustfmt_autosave* +g:rustfmt_autosave~ + Set this option to 1 to run |:RustFmt| automatically when saving a + buffer. If not specified it defaults to 0 : > + let g:rustfmt_autosave = 0 +< + *g:rustfmt_fail_silently* +g:rustfmt_fail_silently~ + Set this option to 1 to prevent 'rustfmt' from populating the + |location-list| with errors. If not specified it defaults to 0: > + let g:rustfmt_fail_silently = 0 +< + *g:rustfmt_options* +g:rustfmt_options~ + Set this option to a string of options to pass to 'rustfmt'. The + write-mode is already set to 'overwrite'. If not specified it + defaults to '' : > + let g:rustmft_options = '' < *g:rust_playpen_url* @@ -183,6 +209,15 @@ COMMANDS *rust-commands* |g:rust_shortener_url| is the base url for the shorterner, by default "https://is.gd/" +:RustFmt *:RustFmt* + Runs |g:rustfmt_command| on the current buffer. If + |g:rustfmt_options| is set then those will be passed to the + executable. + + If |g:rustfmt_fail_silently| is 0 (the default) then it + will populate the |location-list| with the errors from + |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1 + then it will not populate the |location-list|. ============================================================================== MAPPINGS *rust-mappings* diff --git a/ftplugin/rust.vim b/ftplugin/rust.vim index 0e7c3d97..db9fb3b9 100644 --- a/ftplugin/rust.vim +++ b/ftplugin/rust.vim @@ -109,6 +109,9 @@ command! -nargs=* -buffer RustEmitAsm call rust#Emit("asm", ) " See |:RustPlay| for docs command! -range=% RustPlay :call rust#Play(, , , ) +" See |:RustFmt| for docs +command! -buffer RustFmt call rustfmt#Format() + " Mappings {{{1 " Bind ⌘R in MacVim to :RustRun diff --git a/plugin/rustfmt.vim b/plugin/rustfmt.vim new file mode 100644 index 00000000..9db1f6e7 --- /dev/null +++ b/plugin/rustfmt.vim @@ -0,0 +1,8 @@ +augroup rustfmt + autocmd! + + " code formatting on save + if get(g:, "rustfmt_autosave", 0) + autocmd BufWritePre *.rs call rustfmt#Format() + endif +augroup END