From 3d725eb2a3172af941e582f8b1d30ba832df0e47 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Thu, 30 Aug 2018 16:21:32 +0300 Subject: [PATCH 1/2] go.mod file support This adds initial support for the `go.mod` file. It adds the followings: * Syntax highligthing. We highlight keywords, strings, operator and semver version. It works pretty well for now. * Auto fmt on save. Command `:GoModFmt` or `Plug(go-mod-fmt)` for custom mappings Before we fully support the semantics of go.mod, I think this initially will be helpful because I discovered that `go.mod` is read and edited a lot. So going forward, this will make it easier experimenting with Go modules. related: #1906 --- autoload/go/config.vim | 8 +++ autoload/go/mod.vim | 128 ++++++++++++++++++++++++++++++++++++ ftdetect/gofiletype.vim | 4 ++ ftplugin/go/commands.vim | 3 + ftplugin/gomod.vim | 15 +++++ ftplugin/gomod/commands.vim | 1 + ftplugin/gomod/mappings.vim | 1 + plugin/go.vim | 8 +++ syntax/gomod.vim | 45 +++++++++++++ 9 files changed, 213 insertions(+) create mode 100644 autoload/go/mod.vim create mode 100644 ftplugin/gomod.vim create mode 100644 ftplugin/gomod/commands.vim create mode 100644 ftplugin/gomod/mappings.vim create mode 100644 syntax/gomod.vim diff --git a/autoload/go/config.vim b/autoload/go/config.vim index 8e72b1a83a..9a073eb6c8 100644 --- a/autoload/go/config.vim +++ b/autoload/go/config.vim @@ -278,6 +278,14 @@ function! go#config#SetAsmfmtAutosave(value) abort let g:go_asmfmt_autosave = a:value endfunction +function! go#config#ModfmtAutosave() abort + return get(g:, "go_modfmt_autosave", 1) +endfunction + +function! go#config#SetModfmtAutosave(value) abort + let g:go_modfmt_autosave = a:value +endfunction + function! go#config#DocMaxHeight() abort return get(g:, "go_doc_max_height", 20) endfunction diff --git a/autoload/go/mod.vim b/autoload/go/mod.vim new file mode 100644 index 0000000000..1c5d940685 --- /dev/null +++ b/autoload/go/mod.vim @@ -0,0 +1,128 @@ +function! go#mod#Format() abort + let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') + + " Save cursor position and many other things. + let l:curw = winsaveview() + + " Write current unsaved buffer to a temp file + let l:tmpname = tempname() . '.go' + call writefile(go#util#GetLines(), l:tmpname) + if go#util#IsWin() + let l:tmpname = tr(l:tmpname, '\', '/') + endif + + let current_col = col('.') + let l:args = ['go', 'mod', 'edit', '--fmt', l:tmpname] + let [l:out, l:err] = go#util#Exec(l:args) + let diff_offset = len(readfile(l:tmpname)) - line('$') + + if l:err == 0 + call go#mod#update_file(l:tmpname, fname) + else + let errors = s:parse_errors(fname, l:out) + call s:show_errors(errors) + endif + + " We didn't use the temp file, so clean up + call delete(l:tmpname) + + " Restore our cursor/windows positions. + call winrestview(l:curw) + + " be smart and jump to the line the new statement was added/removed + call cursor(line('.') + diff_offset, current_col) + + " Syntax highlighting breaks less often. + syntax sync fromstart +endfunction + +" update_file updates the target file with the given formatted source +function! go#mod#update_file(source, target) + " remove undo point caused via BufWritePre + try | silent undojoin | catch | endtry + + let old_fileformat = &fileformat + if exists("*getfperm") + " save file permissions + let original_fperm = getfperm(a:target) + endif + + call rename(a:source, a:target) + + " restore file permissions + if exists("*setfperm") && original_fperm != '' + call setfperm(a:target , original_fperm) + endif + + " reload buffer to reflect latest changes + silent edit! + + let &fileformat = old_fileformat + let &syntax = &syntax + + let l:listtype = go#list#Type("GoModFmt") + + " the title information was introduced with 7.4-2200 + " https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640 + if has('patch-7.4.2200') + " clean up previous list + if l:listtype == "quickfix" + let l:list_title = getqflist({'title': 1}) + else + let l:list_title = getloclist(0, {'title': 1}) + endif + else + " can't check the title, so assume that the list was for go fmt. + let l:list_title = {'title': 'Format'} + endif + + if has_key(l:list_title, "title") && l:list_title['title'] == "Format" + call go#list#Clean(l:listtype) + endif +endfunction + +" parse_errors parses the given errors and returns a list of parsed errors +function! s:parse_errors(filename, content) abort + let splitted = split(a:content, '\n') + + " list of errors to be put into location list + let errors = [] + for line in splitted + let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)') + if !empty(tokens) + call add(errors,{ + \"filename": a:filename, + \"lnum": tokens[2], + \"col": tokens[3], + \"text": tokens[4], + \ }) + endif + endfor + + return errors +endfunction + +" show_errors opens a location list and shows the given errors. If the given +" errors is empty, it closes the the location list +function! s:show_errors(errors) abort + let l:listtype = go#list#Type("GoModFmt") + if !empty(a:errors) + call go#list#Populate(l:listtype, a:errors, 'Format') + echohl Error | echomsg "GoModFmt returned error" | echohl None + endif + + " this closes the window if there are no errors or it opens + " it if there is any + call go#list#Window(l:listtype, len(a:errors)) +endfunction + +function! go#mod#ToggleModfmtAutoSave() abort + if go#config#ModfmtAutosave() + call go#config#SetModfmtAutosave(0) + call go#util#EchoProgress("auto mod fmt disabled") + return + end + + call go#config#SetModfmtAutosave(1) + call go#util#EchoProgress("auto fmt enabled") +endfunction diff --git a/ftdetect/gofiletype.vim b/ftdetect/gofiletype.vim index d3662f4be1..8cd1148d84 100644 --- a/ftdetect/gofiletype.vim +++ b/ftdetect/gofiletype.vim @@ -31,4 +31,8 @@ au BufReadPost *.s call s:gofiletype_post() au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl +au BufNewFile *.mod setfiletype gomod | setlocal fileencoding=utf-8 fileformat=unix +au BufRead *.mod call s:gofiletype_pre("gomod") +au BufReadPost *.mod call s:gofiletype_post() + " vim: sw=2 ts=2 et diff --git a/ftplugin/go/commands.vim b/ftplugin/go/commands.vim index ba29c59c07..99d81cf424 100644 --- a/ftplugin/go/commands.vim +++ b/ftplugin/go/commands.vim @@ -23,6 +23,9 @@ command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds() command! -nargs=* -range GoAddTags call go#tags#Add(, , , ) command! -nargs=* -range GoRemoveTags call go#tags#Remove(, , , ) +" -- mod +command! -nargs=0 -range GoModFmt call go#mod#Format() + " -- tool command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files() command! -nargs=0 GoDeps echo go#tool#Deps() diff --git a/ftplugin/gomod.vim b/ftplugin/gomod.vim new file mode 100644 index 0000000000..5f3dc412a9 --- /dev/null +++ b/ftplugin/gomod.vim @@ -0,0 +1,15 @@ +" gomod.vim: Vim filetype plugin for Go assembler. + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let b:undo_ftplugin = "setl fo< com< cms<" + +setlocal formatoptions-=t + +setlocal comments=s1:/*,mb:*,ex:*/,:// +setlocal commentstring=//\ %s + +" vim: sw=2 ts=2 et diff --git a/ftplugin/gomod/commands.vim b/ftplugin/gomod/commands.vim new file mode 100644 index 0000000000..d04fd50a81 --- /dev/null +++ b/ftplugin/gomod/commands.vim @@ -0,0 +1 @@ +command! -nargs=0 -range GoModFmt call go#mod#Format() diff --git a/ftplugin/gomod/mappings.vim b/ftplugin/gomod/mappings.vim new file mode 100644 index 0000000000..c8664f648b --- /dev/null +++ b/ftplugin/gomod/mappings.vim @@ -0,0 +1 @@ +nnoremap (go-mod-fmt) :call go#mod#Format() diff --git a/plugin/go.vim b/plugin/go.vim index 01e19b7d93..a19175e3bb 100644 --- a/plugin/go.vim +++ b/plugin/go.vim @@ -226,6 +226,13 @@ function! s:asmfmt_autosave() endif endfunction +function! s:modfmt_autosave() + " go.mod code formatting on save + if get(g:, "go_modfmt_autosave", 1) + call go#mod#Format() + endif +endfunction + function! s:metalinter_autosave() " run gometalinter on save if get(g:, "go_metalinter_autosave", 0) @@ -253,6 +260,7 @@ augroup vim-go endif autocmd BufWritePre *.go call s:fmt_autosave() + autocmd BufWritePre *.mod call s:modfmt_autosave() autocmd BufWritePre *.s call s:asmfmt_autosave() autocmd BufWritePost *.go call s:metalinter_autosave() autocmd BufNewFile *.go call s:template_autocreate() diff --git a/syntax/gomod.vim b/syntax/gomod.vim new file mode 100644 index 0000000000..969df5172f --- /dev/null +++ b/syntax/gomod.vim @@ -0,0 +1,45 @@ +" gomod.vim: Vim syntax file for go.mod file +" +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +syntax case match + +" match keywords +syntax keyword gomodModule module +syntax keyword gomodRequire require +syntax keyword gomodExclude exclude +syntax keyword gomodReplace replace + +" require, exclude and replace can be also grouped into block +syntax region gomodRequire start='require (' end=')' transparent contains=gomodRequire,gomodVersion +syntax region gomodExclude start='exclude (' end=')' transparent contains=gomodExclude,gomodVersion +syntax region gomodReplace start='replace (' end=')' transparent contains=gomodReplace,gomodVersion + +" set highlights +highlight default link gomodModule Keyword +highlight default link gomodRequire Keyword +highlight default link gomodExclude Keyword +highlight default link gomodReplace Keyword + +" comments are always in form of // ... +syntax region gomodComment start="//" end="$" contains=@Spell +highlight default link gomodComment Comment + +" make sure quoted import paths are higlighted +syntax region gomodString start=+"+ skip=+\\\\\|\\"+ end=+"+ +highlight default link gomodString String + +" replace operator is in the form of '=>' +syntax match gomodReplaceOperator "\v\=\>" +highlight default link gomodReplaceOperator Operator + + +" highlight semver, note that this is very simple. But it works for now +syntax match gomodVersion "v\d\.\d\.\d" +syntax match gomodVersion "v\d\.\d\.\d-.*" +highlight default link gomodVersion Identifier + +let b:current_syntax = "gomod" From 3b040723a94359f754a418cfa0f7afbfe112db11 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Thu, 30 Aug 2018 23:23:22 +0300 Subject: [PATCH 2/2] Fix review comments * Fix auto toggle command * Add guard agains Go versions lower than 1.11 * Add documentation * Fixed setting correct filetype * Fixed parsing errors * Changed variable names to be consistent --- autoload/go/config.vim | 8 ++++---- autoload/go/mod.vim | 32 ++++++++++++++++++++---------- doc/vim-go.txt | 39 +++++++++++++++++++++++++++++++++---- ftdetect/gofiletype.vim | 7 ++++--- ftplugin/gomod/commands.vim | 2 ++ plugin/go.vim | 2 +- syntax/gomod.vim | 4 ++-- 7 files changed, 70 insertions(+), 24 deletions(-) diff --git a/autoload/go/config.vim b/autoload/go/config.vim index 9a073eb6c8..1f7260a4dd 100644 --- a/autoload/go/config.vim +++ b/autoload/go/config.vim @@ -278,12 +278,12 @@ function! go#config#SetAsmfmtAutosave(value) abort let g:go_asmfmt_autosave = a:value endfunction -function! go#config#ModfmtAutosave() abort - return get(g:, "go_modfmt_autosave", 1) +function! go#config#ModFmtAutosave() abort + return get(g:, "go_mod_fmt_autosave", 1) endfunction -function! go#config#SetModfmtAutosave(value) abort - let g:go_modfmt_autosave = a:value +function! go#config#SetModFmtAutosave(value) abort + let g:go_mod_fmt_autosave = a:value endfunction function! go#config#DocMaxHeight() abort diff --git a/autoload/go/mod.vim b/autoload/go/mod.vim index 1c5d940685..0f4b5a85d7 100644 --- a/autoload/go/mod.vim +++ b/autoload/go/mod.vim @@ -1,11 +1,24 @@ +let s:go_major_version = "" + function! go#mod#Format() abort + " go mod only exists in `v1.11` + if empty(s:go_major_version) + let tokens = matchlist(go#util#System("go version"), '\d\+.\(\d\+\) ') + let s:go_major_version = str2nr(tokens[1]) + endif + + if s:go_major_version < "11" + call go#util#EchoError("Go v1.11 is required to format go.mod file") + return + endif + let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') " Save cursor position and many other things. let l:curw = winsaveview() " Write current unsaved buffer to a temp file - let l:tmpname = tempname() . '.go' + let l:tmpname = tempname() . '.mod' call writefile(go#util#GetLines(), l:tmpname) if go#util#IsWin() let l:tmpname = tr(l:tmpname, '\', '/') @@ -88,13 +101,12 @@ function! s:parse_errors(filename, content) abort " list of errors to be put into location list let errors = [] for line in splitted - let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)') + let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\s*\(.*\)') if !empty(tokens) call add(errors,{ \"filename": a:filename, \"lnum": tokens[2], - \"col": tokens[3], - \"text": tokens[4], + \"text": tokens[3], \ }) endif endfor @@ -108,7 +120,7 @@ function! s:show_errors(errors) abort let l:listtype = go#list#Type("GoModFmt") if !empty(a:errors) call go#list#Populate(l:listtype, a:errors, 'Format') - echohl Error | echomsg "GoModFmt returned error" | echohl None + call go#util#EchoError("GoModFmt returned error") endif " this closes the window if there are no errors or it opens @@ -116,13 +128,13 @@ function! s:show_errors(errors) abort call go#list#Window(l:listtype, len(a:errors)) endfunction -function! go#mod#ToggleModfmtAutoSave() abort - if go#config#ModfmtAutosave() - call go#config#SetModfmtAutosave(0) +function! go#mod#ToggleModFmtAutoSave() abort + if go#config#ModFmtAutosave() + call go#config#SetModFmtAutosave(0) call go#util#EchoProgress("auto mod fmt disabled") return end - call go#config#SetModfmtAutosave(1) - call go#util#EchoProgress("auto fmt enabled") + call go#config#SetModFmtAutosave(1) + call go#util#EchoProgress("auto mod fmt enabled") endfunction diff --git a/doc/vim-go.txt b/doc/vim-go.txt index 33015f40bb..31a3a76e61 100644 --- a/doc/vim-go.txt +++ b/doc/vim-go.txt @@ -806,6 +806,11 @@ CTRL-t Toggles |'g:go_fmt_autosave'|. + *:GoModFmtAutoSaveToggle* +:GoModFmtAutoSaveToggle + + Toggles |'g:go_mod_fmt_autosave'|. + *:GoAsmFmtAutoSaveToggle* :GoAsmFmtAutoSaveToggle @@ -880,6 +885,13 @@ CTRL-t } } < + *:GoModFmt* +:GoModFmt + + Filter the current go.mod buffer through "go mod edit -fmt" command. It + tries to preserve cursor position and avoids replacing the buffer with + stderr output. + ============================================================================== MAPPINGS *go-mappings* @@ -1097,6 +1109,10 @@ Calls `:GoImport` for the current package Generate if err != nil { return ... } automatically which infer the type of return values and the numbers. + *(go-mod-fmt)* + +Calls |:GoModFmt| for the current buffer + ============================================================================== TEXT OBJECTS *go-text-objects* @@ -1287,7 +1303,15 @@ doesn't break. However it's slows (creates/deletes a file for every save) and it's causing problems on some Vim versions. By default it's disabled. > let g:go_fmt_experimental = 0 + < + *'g:go_mod_fmt_autosave'* + +Use this option to auto |:GoModFmt| on save. By default it's enabled > + + let g:go_mod_fmt_autosave = 1 +< + *'g:go_doc_keywordprg_enabled'* Use this option to run `godoc` on words under the cursor with |K|; this will @@ -1497,10 +1521,10 @@ that was called. Supported values are "", "quickfix", and "locationlist". Specifies the type of list to use for command outputs (such as errors from builds, results from static analysis commands, etc...). When an expected key is not present in the dictionary, |'g:go_list_type'| will be used instead. -Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint", -"GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both -:GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported -values for each command are "quickfix" and "locationlist". +Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoModFmt", "GoInstall", +"GoLint", "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for +both :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". +Supported values for each command are "quickfix" and "locationlist". > let g:go_list_type_commands = {} < @@ -1874,6 +1898,13 @@ filetype. The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the `gotexttmpl` is never automatically set and needs to be set manually. +============================================================================== + *gomod* *ft-gomod-syntax* +go.mod file syntax~ + +The `gomod` 'filetype' provides syntax highlighting for Go's module file +`go.mod` + ============================================================================== DEBUGGER *go-debug* diff --git a/ftdetect/gofiletype.vim b/ftdetect/gofiletype.vim index 8cd1148d84..acb061d7db 100644 --- a/ftdetect/gofiletype.vim +++ b/ftdetect/gofiletype.vim @@ -31,8 +31,9 @@ au BufReadPost *.s call s:gofiletype_post() au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl -au BufNewFile *.mod setfiletype gomod | setlocal fileencoding=utf-8 fileformat=unix -au BufRead *.mod call s:gofiletype_pre("gomod") -au BufReadPost *.mod call s:gofiletype_post() +" make sure we explicitly look for a `go.mod` and the `module` starts from the +" beginning +au BufNewFile,BufRead go.mod + \ if getline(1) =~ '^module.*' | set filetype=gomod | endif " vim: sw=2 ts=2 et diff --git a/ftplugin/gomod/commands.vim b/ftplugin/gomod/commands.vim index d04fd50a81..d6a7285fce 100644 --- a/ftplugin/gomod/commands.vim +++ b/ftplugin/gomod/commands.vim @@ -1 +1,3 @@ command! -nargs=0 -range GoModFmt call go#mod#Format() + +command! -nargs=0 GoModFmtAutoSaveToggle call go#mod#ToggleModFmtAutoSave() diff --git a/plugin/go.vim b/plugin/go.vim index a19175e3bb..78f7e5b570 100644 --- a/plugin/go.vim +++ b/plugin/go.vim @@ -228,7 +228,7 @@ endfunction function! s:modfmt_autosave() " go.mod code formatting on save - if get(g:, "go_modfmt_autosave", 1) + if get(g:, "go_mod_fmt_autosave", 1) call go#mod#Format() endif endfunction diff --git a/syntax/gomod.vim b/syntax/gomod.vim index 969df5172f..3679e2cdf5 100644 --- a/syntax/gomod.vim +++ b/syntax/gomod.vim @@ -38,8 +38,8 @@ highlight default link gomodReplaceOperator Operator " highlight semver, note that this is very simple. But it works for now -syntax match gomodVersion "v\d\.\d\.\d" -syntax match gomodVersion "v\d\.\d\.\d-.*" +syntax match gomodVersion "v\d\+\.\d\+\.\d\+" +syntax match gomodVersion "v\d\+\.\d\+\.\d\+-.*" highlight default link gomodVersion Identifier let b:current_syntax = "gomod"