Skip to content

Commit c1dd611

Browse files
authored
Merge pull request #1931 from fatih/go-modules-syntax
go.mod file support
2 parents b6381dd + 3b04072 commit c1dd611

File tree

10 files changed

+263
-4
lines changed

10 files changed

+263
-4
lines changed

autoload/go/config.vim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ function! go#config#SetAsmfmtAutosave(value) abort
278278
let g:go_asmfmt_autosave = a:value
279279
endfunction
280280

281+
function! go#config#ModFmtAutosave() abort
282+
return get(g:, "go_mod_fmt_autosave", 1)
283+
endfunction
284+
285+
function! go#config#SetModFmtAutosave(value) abort
286+
let g:go_mod_fmt_autosave = a:value
287+
endfunction
288+
281289
function! go#config#DocMaxHeight() abort
282290
return get(g:, "go_doc_max_height", 20)
283291
endfunction

autoload/go/mod.vim

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
let s:go_major_version = ""
2+
3+
function! go#mod#Format() abort
4+
" go mod only exists in `v1.11`
5+
if empty(s:go_major_version)
6+
let tokens = matchlist(go#util#System("go version"), '\d\+.\(\d\+\) ')
7+
let s:go_major_version = str2nr(tokens[1])
8+
endif
9+
10+
if s:go_major_version < "11"
11+
call go#util#EchoError("Go v1.11 is required to format go.mod file")
12+
return
13+
endif
14+
15+
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
16+
17+
" Save cursor position and many other things.
18+
let l:curw = winsaveview()
19+
20+
" Write current unsaved buffer to a temp file
21+
let l:tmpname = tempname() . '.mod'
22+
call writefile(go#util#GetLines(), l:tmpname)
23+
if go#util#IsWin()
24+
let l:tmpname = tr(l:tmpname, '\', '/')
25+
endif
26+
27+
let current_col = col('.')
28+
let l:args = ['go', 'mod', 'edit', '--fmt', l:tmpname]
29+
let [l:out, l:err] = go#util#Exec(l:args)
30+
let diff_offset = len(readfile(l:tmpname)) - line('$')
31+
32+
if l:err == 0
33+
call go#mod#update_file(l:tmpname, fname)
34+
else
35+
let errors = s:parse_errors(fname, l:out)
36+
call s:show_errors(errors)
37+
endif
38+
39+
" We didn't use the temp file, so clean up
40+
call delete(l:tmpname)
41+
42+
" Restore our cursor/windows positions.
43+
call winrestview(l:curw)
44+
45+
" be smart and jump to the line the new statement was added/removed
46+
call cursor(line('.') + diff_offset, current_col)
47+
48+
" Syntax highlighting breaks less often.
49+
syntax sync fromstart
50+
endfunction
51+
52+
" update_file updates the target file with the given formatted source
53+
function! go#mod#update_file(source, target)
54+
" remove undo point caused via BufWritePre
55+
try | silent undojoin | catch | endtry
56+
57+
let old_fileformat = &fileformat
58+
if exists("*getfperm")
59+
" save file permissions
60+
let original_fperm = getfperm(a:target)
61+
endif
62+
63+
call rename(a:source, a:target)
64+
65+
" restore file permissions
66+
if exists("*setfperm") && original_fperm != ''
67+
call setfperm(a:target , original_fperm)
68+
endif
69+
70+
" reload buffer to reflect latest changes
71+
silent edit!
72+
73+
let &fileformat = old_fileformat
74+
let &syntax = &syntax
75+
76+
let l:listtype = go#list#Type("GoModFmt")
77+
78+
" the title information was introduced with 7.4-2200
79+
" https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640
80+
if has('patch-7.4.2200')
81+
" clean up previous list
82+
if l:listtype == "quickfix"
83+
let l:list_title = getqflist({'title': 1})
84+
else
85+
let l:list_title = getloclist(0, {'title': 1})
86+
endif
87+
else
88+
" can't check the title, so assume that the list was for go fmt.
89+
let l:list_title = {'title': 'Format'}
90+
endif
91+
92+
if has_key(l:list_title, "title") && l:list_title['title'] == "Format"
93+
call go#list#Clean(l:listtype)
94+
endif
95+
endfunction
96+
97+
" parse_errors parses the given errors and returns a list of parsed errors
98+
function! s:parse_errors(filename, content) abort
99+
let splitted = split(a:content, '\n')
100+
101+
" list of errors to be put into location list
102+
let errors = []
103+
for line in splitted
104+
let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\s*\(.*\)')
105+
if !empty(tokens)
106+
call add(errors,{
107+
\"filename": a:filename,
108+
\"lnum": tokens[2],
109+
\"text": tokens[3],
110+
\ })
111+
endif
112+
endfor
113+
114+
return errors
115+
endfunction
116+
117+
" show_errors opens a location list and shows the given errors. If the given
118+
" errors is empty, it closes the the location list
119+
function! s:show_errors(errors) abort
120+
let l:listtype = go#list#Type("GoModFmt")
121+
if !empty(a:errors)
122+
call go#list#Populate(l:listtype, a:errors, 'Format')
123+
call go#util#EchoError("GoModFmt returned error")
124+
endif
125+
126+
" this closes the window if there are no errors or it opens
127+
" it if there is any
128+
call go#list#Window(l:listtype, len(a:errors))
129+
endfunction
130+
131+
function! go#mod#ToggleModFmtAutoSave() abort
132+
if go#config#ModFmtAutosave()
133+
call go#config#SetModFmtAutosave(0)
134+
call go#util#EchoProgress("auto mod fmt disabled")
135+
return
136+
end
137+
138+
call go#config#SetModFmtAutosave(1)
139+
call go#util#EchoProgress("auto mod fmt enabled")
140+
endfunction

doc/vim-go.txt

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,11 @@ CTRL-t
806806

807807
Toggles |'g:go_fmt_autosave'|.
808808

809+
*:GoModFmtAutoSaveToggle*
810+
:GoModFmtAutoSaveToggle
811+
812+
Toggles |'g:go_mod_fmt_autosave'|.
813+
809814
*:GoAsmFmtAutoSaveToggle*
810815
:GoAsmFmtAutoSaveToggle
811816

@@ -880,6 +885,13 @@ CTRL-t
880885
}
881886
}
882887
<
888+
*:GoModFmt*
889+
:GoModFmt
890+
891+
Filter the current go.mod buffer through "go mod edit -fmt" command. It
892+
tries to preserve cursor position and avoids replacing the buffer with
893+
stderr output.
894+
883895
==============================================================================
884896
MAPPINGS *go-mappings*
885897

@@ -1097,6 +1109,10 @@ Calls `:GoImport` for the current package
10971109
Generate if err != nil { return ... } automatically which infer the type of
10981110
return values and the numbers.
10991111

1112+
*(go-mod-fmt)*
1113+
1114+
Calls |:GoModFmt| for the current buffer
1115+
11001116
==============================================================================
11011117
TEXT OBJECTS *go-text-objects*
11021118

@@ -1287,7 +1303,15 @@ doesn't break. However it's slows (creates/deletes a file for every save) and
12871303
it's causing problems on some Vim versions. By default it's disabled. >
12881304
12891305
let g:go_fmt_experimental = 0
1306+
12901307
<
1308+
*'g:go_mod_fmt_autosave'*
1309+
1310+
Use this option to auto |:GoModFmt| on save. By default it's enabled >
1311+
1312+
let g:go_mod_fmt_autosave = 1
1313+
<
1314+
12911315
*'g:go_doc_keywordprg_enabled'*
12921316

12931317
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".
14971521
Specifies the type of list to use for command outputs (such as errors from
14981522
builds, results from static analysis commands, etc...). When an expected key
14991523
is not present in the dictionary, |'g:go_list_type'| will be used instead.
1500-
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint",
1501-
"GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both
1502-
:GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported
1503-
values for each command are "quickfix" and "locationlist".
1524+
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoModFmt", "GoInstall",
1525+
"GoLint", "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for
1526+
both :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest".
1527+
Supported values for each command are "quickfix" and "locationlist".
15041528
>
15051529
let g:go_list_type_commands = {}
15061530
<
@@ -1874,6 +1898,13 @@ filetype.
18741898
The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the
18751899
`gotexttmpl` is never automatically set and needs to be set manually.
18761900

1901+
==============================================================================
1902+
*gomod* *ft-gomod-syntax*
1903+
go.mod file syntax~
1904+
1905+
The `gomod` 'filetype' provides syntax highlighting for Go's module file
1906+
`go.mod`
1907+
18771908

18781909
==============================================================================
18791910
DEBUGGER *go-debug*

ftdetect/gofiletype.vim

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ au BufReadPost *.s call s:gofiletype_post()
3131

3232
au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl
3333

34+
" make sure we explicitly look for a `go.mod` and the `module` starts from the
35+
" beginning
36+
au BufNewFile,BufRead go.mod
37+
\ if getline(1) =~ '^module.*' | set filetype=gomod | endif
38+
3439
" vim: sw=2 ts=2 et

ftplugin/go/commands.vim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds()
2323
command! -nargs=* -range GoAddTags call go#tags#Add(<line1>, <line2>, <count>, <f-args>)
2424
command! -nargs=* -range GoRemoveTags call go#tags#Remove(<line1>, <line2>, <count>, <f-args>)
2525

26+
" -- mod
27+
command! -nargs=0 -range GoModFmt call go#mod#Format()
28+
2629
" -- tool
2730
command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files(<f-args>)
2831
command! -nargs=0 GoDeps echo go#tool#Deps()

ftplugin/gomod.vim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
" gomod.vim: Vim filetype plugin for Go assembler.
2+
3+
if exists("b:did_ftplugin")
4+
finish
5+
endif
6+
let b:did_ftplugin = 1
7+
8+
let b:undo_ftplugin = "setl fo< com< cms<"
9+
10+
setlocal formatoptions-=t
11+
12+
setlocal comments=s1:/*,mb:*,ex:*/,://
13+
setlocal commentstring=//\ %s
14+
15+
" vim: sw=2 ts=2 et

ftplugin/gomod/commands.vim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
command! -nargs=0 -range GoModFmt call go#mod#Format()
2+
3+
command! -nargs=0 GoModFmtAutoSaveToggle call go#mod#ToggleModFmtAutoSave()

ftplugin/gomod/mappings.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nnoremap <silent> <Plug>(go-mod-fmt) :<C-u>call go#mod#Format()<CR>

plugin/go.vim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,13 @@ function! s:asmfmt_autosave()
226226
endif
227227
endfunction
228228

229+
function! s:modfmt_autosave()
230+
" go.mod code formatting on save
231+
if get(g:, "go_mod_fmt_autosave", 1)
232+
call go#mod#Format()
233+
endif
234+
endfunction
235+
229236
function! s:metalinter_autosave()
230237
" run gometalinter on save
231238
if get(g:, "go_metalinter_autosave", 0)
@@ -253,6 +260,7 @@ augroup vim-go
253260
endif
254261

255262
autocmd BufWritePre *.go call s:fmt_autosave()
263+
autocmd BufWritePre *.mod call s:modfmt_autosave()
256264
autocmd BufWritePre *.s call s:asmfmt_autosave()
257265
autocmd BufWritePost *.go call s:metalinter_autosave()
258266
autocmd BufNewFile *.go call s:template_autocreate()

syntax/gomod.vim

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
" gomod.vim: Vim syntax file for go.mod file
2+
"
3+
" Quit when a (custom) syntax file was already loaded
4+
if exists("b:current_syntax")
5+
finish
6+
endif
7+
8+
syntax case match
9+
10+
" match keywords
11+
syntax keyword gomodModule module
12+
syntax keyword gomodRequire require
13+
syntax keyword gomodExclude exclude
14+
syntax keyword gomodReplace replace
15+
16+
" require, exclude and replace can be also grouped into block
17+
syntax region gomodRequire start='require (' end=')' transparent contains=gomodRequire,gomodVersion
18+
syntax region gomodExclude start='exclude (' end=')' transparent contains=gomodExclude,gomodVersion
19+
syntax region gomodReplace start='replace (' end=')' transparent contains=gomodReplace,gomodVersion
20+
21+
" set highlights
22+
highlight default link gomodModule Keyword
23+
highlight default link gomodRequire Keyword
24+
highlight default link gomodExclude Keyword
25+
highlight default link gomodReplace Keyword
26+
27+
" comments are always in form of // ...
28+
syntax region gomodComment start="//" end="$" contains=@Spell
29+
highlight default link gomodComment Comment
30+
31+
" make sure quoted import paths are higlighted
32+
syntax region gomodString start=+"+ skip=+\\\\\|\\"+ end=+"+
33+
highlight default link gomodString String
34+
35+
" replace operator is in the form of '=>'
36+
syntax match gomodReplaceOperator "\v\=\>"
37+
highlight default link gomodReplaceOperator Operator
38+
39+
40+
" highlight semver, note that this is very simple. But it works for now
41+
syntax match gomodVersion "v\d\+\.\d\+\.\d\+"
42+
syntax match gomodVersion "v\d\+\.\d\+\.\d\+-.*"
43+
highlight default link gomodVersion Identifier
44+
45+
let b:current_syntax = "gomod"

0 commit comments

Comments
 (0)