Skip to content

Commit

Permalink
go.mod file support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
fatih committed Aug 30, 2018
1 parent b6381dd commit 3d725eb
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 0 deletions.
8 changes: 8 additions & 0 deletions autoload/go/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
128 changes: 128 additions & 0 deletions autoload/go/mod.vim
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions ftdetect/gofiletype.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions ftplugin/go/commands.vim
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds()
command! -nargs=* -range GoAddTags call go#tags#Add(<line1>, <line2>, <count>, <f-args>)
command! -nargs=* -range GoRemoveTags call go#tags#Remove(<line1>, <line2>, <count>, <f-args>)

" -- mod
command! -nargs=0 -range GoModFmt call go#mod#Format()

" -- tool
command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files(<f-args>)
command! -nargs=0 GoDeps echo go#tool#Deps()
Expand Down
15 changes: 15 additions & 0 deletions ftplugin/gomod.vim
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions ftplugin/gomod/commands.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
command! -nargs=0 -range GoModFmt call go#mod#Format()
1 change: 1 addition & 0 deletions ftplugin/gomod/mappings.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nnoremap <silent> <Plug>(go-mod-fmt) :<C-u>call go#mod#Format()<CR>
8 changes: 8 additions & 0 deletions plugin/go.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
45 changes: 45 additions & 0 deletions syntax/gomod.vim
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit 3d725eb

Please sign in to comment.