1
0
mirror of https://github.com/amix/vimrc synced 2025-06-30 20:05:01 +08:00

Updated plugins

This commit is contained in:
Amir Salihefendic
2019-08-22 17:36:17 +02:00
parent 6711ae6453
commit 3aefdbd21a
244 changed files with 9486 additions and 3395 deletions

View File

@ -33,7 +33,7 @@ function! go#auto#echo_go_info()
endfunction
function! go#auto#auto_type_info()
if !go#config#AutoTypeInfo() || !filereadable(expand('%:p'))
if !go#config#AutoTypeInfo() || !isdirectory(expand('%:p:h'))
return
endif
@ -42,7 +42,7 @@ function! go#auto#auto_type_info()
endfunction
function! go#auto#auto_sameids()
if !go#config#AutoSameids() || !filereadable(expand('%:p'))
if !go#config#AutoSameids() || !isdirectory(expand('%:p:h'))
return
endif
@ -51,7 +51,7 @@ function! go#auto#auto_sameids()
endfunction
function! go#auto#fmt_autosave()
if !go#config#FmtAutosave() || !filereadable(expand('%:p'))
if !(go#config#FmtAutosave() && isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return
endif
@ -60,7 +60,7 @@ function! go#auto#fmt_autosave()
endfunction
function! go#auto#metalinter_autosave()
if !go#config#MetalinterAutosave() || !filereadable(expand('%:p'))
if !go#config#MetalinterAutosave() || !isdirectory(expand('%:p:h'))
return
endif
@ -69,7 +69,7 @@ function! go#auto#metalinter_autosave()
endfunction
function! go#auto#modfmt_autosave()
if !go#config#ModFmtAutosave() || !filereadable(expand('%:p'))
if !(go#config#ModFmtAutosave() && isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return
endif
@ -78,7 +78,7 @@ function! go#auto#modfmt_autosave()
endfunction
function! go#auto#asmfmt_autosave()
if !go#config#AsmfmtAutosave() || !filereadable(expand('%:p'))
if !(go#config#AsmfmtAutosave() && isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return
endif

View File

@ -9,8 +9,8 @@ function! go#cmd#autowrite() abort
for l:nr in range(0, bufnr('$'))
if buflisted(l:nr) && getbufvar(l:nr, '&modified')
" Sleep one second to make sure people see the message. Otherwise it is
" often immediacy overwritten by the async messages (which also don't
" invoke the "hit ENTER" prompt).
" often immediately overwritten by the async messages (which also
" doesn't invoke the "hit ENTER" prompt).
call go#util#EchoWarning('[No write since last change]')
sleep 1
return

View File

@ -79,7 +79,12 @@ endfunction
" go#complete#GoInfo returns the description of the identifier under the
" cursor.
function! go#complete#GetInfo() abort
return s:sync_info(0)
let l:mode = go#config#InfoMode()
if l:mode == 'gopls' && go#util#has_job()
return go#lsp#GetInfo()
else
return s:sync_info(0)
endif
endfunction
function! go#complete#Info(showstatus) abort
@ -216,6 +221,7 @@ function! s:info_complete(echo, result) abort
endfunction
function! s:trim_bracket(val) abort
echom a:val
let a:val.word = substitute(a:val.word, '[(){}\[\]]\+$', '', '')
return a:val
endfunction
@ -240,37 +246,44 @@ function! go#complete#GocodeComplete(findstart, base) abort
else
let s = getline(".")[col('.') - 1]
if s =~ '[(){}\{\}]'
return map(copy(s:completions[1]), 's:trim_bracket(v:val)')
return map(copy(s:completions), 's:trim_bracket(v:val)')
endif
return s:completions
endif
endfunction
function! go#complete#Complete(findstart, base) abort
let l:state = {'done': 0, 'matches': []}
let l:state = {'done': 0, 'matches': [], 'start': -1}
function! s:handler(state, matches) abort dict
function! s:handler(state, start, matches) abort dict
let a:state.start = a:start
let a:state.matches = a:matches
let a:state.done = 1
endfunction
"findstart = 1 when we need to get the start of the match
if a:findstart == 1
call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state]))
let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
let l:completion = go#lsp#Completion(expand('%:p'), l:line, l:col, funcref('s:handler', [l:state]))
if l:completion
return -3
endif
while !l:state.done
sleep 10m
endwhile
let s:completions = l:state.matches
if len(l:state.matches) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
return col('.')
let s:completions = l:state.matches
return go#lsp#lsp#PositionOf(getline(l:line+1), l:state.start-1)
else "findstart = 0 when we need to return the list of completions
return s:completions
endif

View File

@ -2,23 +2,33 @@
let s:cpo_save = &cpo
set cpo&vim
func! Test_GetInfo()
func! Test_GetInfo_gocode()
let g:go_info_mode = 'gocode'
call s:getinfo()
unlet g:go_info_mode
endfunction
func! Test_GetInfo_guru()
let g:go_info_mode = 'guru'
call s:getinfo()
unlet g:go_info_mode
endfunction
func! Test_GetInfo_gopls()
let g:go_info_mode = 'gopls'
call s:getinfo()
unlet g:go_info_mode
endfunction
func! s:getinfo()
let l:filename = 'complete/complete.go'
let l:tmp = gotest#load_fixture(l:filename)
call cursor(8, 3)
let g:go_info_mode = 'gocode'
let expected = 'func Example(s string)'
let actual = go#complete#GetInfo()
call assert_equal(expected, actual)
let g:go_info_mode = 'guru'
call go#config#InfoMode()
let actual = go#complete#GetInfo()
call assert_equal(expected, actual)
unlet g:go_info_mode
endfunction
" restore Vi compatibility settings

View File

@ -21,10 +21,12 @@ endfunction
function! go#config#SetBuildTags(value) abort
if a:value is ''
silent! unlet g:go_build_tags
call go#lsp#ResetWorkspaceDirectories()
return
endif
let g:go_build_tags = a:value
call go#lsp#ResetWorkspaceDirectories()
endfunction
function! go#config#TestTimeout() abort
@ -47,6 +49,14 @@ function! go#config#TermMode() abort
return get(g:, 'go_term_mode', 'vsplit')
endfunction
function! go#config#TermCloseOnExit() abort
return get(g:, 'go_term_close_on_exit', 1)
endfunction
function! go#config#SetTermCloseOnExit(value) abort
let g:go_term_close_on_exit = a:value
endfunction
function! go#config#TermEnabled() abort
return has('nvim') && get(g:, 'go_term_enabled', 0)
endfunction
@ -114,7 +124,7 @@ function! go#config#ListAutoclose() abort
endfunction
function! go#config#InfoMode() abort
return get(g:, 'go_info_mode', 'gocode')
return get(g:, 'go_info_mode', 'gopls')
endfunction
function! go#config#GuruScope() abort
@ -174,12 +184,15 @@ function! go#config#DocUrl() abort
return godoc_url
endfunction
function! go#config#DocPopupWindow() abort
return get(g:, 'go_doc_popup_window', 0)
endfunction
function! go#config#DefReuseBuffer() abort
return get(g:, 'go_def_reuse_buffer', 0)
endfunction
function! go#config#DefMode() abort
return get(g:, 'go_def_mode', 'guru')
return get(g:, 'go_def_mode', 'gopls')
endfunction
function! go#config#DeclsIncludes() abort
@ -268,10 +281,6 @@ function! go#config#MetalinterEnabled() abort
return get(g:, "go_metalinter_enabled", default_enabled)
endfunction
function! go#config#MetalinterDisabled() abort
return get(g:, "go_metalinter_disabled", [])
endfunction
function! go#config#GolintBin() abort
return get(g:, "go_golint_bin", "golint")
endfunction

View File

@ -576,7 +576,7 @@ function! go#debug#Start(is_test, ...) abort
return s:state['job']
endif
let s:start_args = a:000
let s:start_args = [a:is_test] + a:000
if go#util#HasDebug('debugger-state')
call go#config#SetDebugDiag(s:state)
@ -595,10 +595,23 @@ function! go#debug#Start(is_test, ...) abort
" append the package when it's given.
if len(a:000) > 0
let l:pkgname = go#package#FromPath(a:1)
if l:pkgname is -1
call go#util#EchoError('could not determine package name')
return
let l:pkgname = a:1
if l:pkgname[0] == '.'
let l:pkgabspath = fnamemodify(l:pkgname, ':p')
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:dir = getcwd()
execute l:cd fnameescape(expand('%:p:h'))
try
let l:pkgname = go#package#FromPath(l:pkgabspath)
if type(l:pkgname) == type(0)
call go#util#EchoError('could not determine package name')
return
endif
finally
execute l:cd fnameescape(l:dir)
endtry
endif
let l:cmd += [l:pkgname]

View File

@ -10,6 +10,10 @@ function! Test_GoDebugStart_RelativePackage() abort
call s:debug('./debug/debugmain')
endfunction
function! Test_GoDebugStart_RelativePackage_NullModule() abort
call s:debug('./debug/debugmain', 1)
endfunction
function! Test_GoDebugStart_Package() abort
call s:debug('debug/debugmain')
endfunction
@ -52,14 +56,22 @@ function! Test_GoDebugStart_Errors() abort
endtry
endfunction
" s:debug takes 2 optional arguments. The first is a package to debug. The
" second is a flag to indicate whether to reset GOPATH after
" gotest#load_fixture is called in order to test behavior outside of GOPATH.
function! s:debug(...) abort
if !go#util#has_job()
return
endif
try
let $oldgopath = $GOPATH
let l:tmp = gotest#load_fixture('debug/debugmain/debugmain.go')
if a:0 > 1 && a:2 == 1
let $GOPATH = $oldgopath
endif
call go#debug#Breakpoint(6)
call assert_false(exists(':GoDebugStop'))

View File

@ -6,7 +6,7 @@ let s:go_stack = []
let s:go_stack_level = 0
function! go#def#Jump(mode, type) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
let l:fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" so guru right now is slow for some people. previously we were using
" godef which also has it's own quirks. But this issue come up so many
@ -66,7 +66,7 @@ function! go#def#Jump(mode, type) abort
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif
elseif bin_name == 'gopls'
let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position()
" delegate to gopls, with an empty job object and an exit status of 0
" (they're irrelevant for gopls).
if a:type

View File

@ -2,19 +2,21 @@
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
func! Test_jump_to_declaration_guru() abort
try
let l:filename = 'def/jump.go'
let lnum = 5
let col = 6
let l:lnum = 5
let l:col = 6
let l:tmp = gotest#load_fixture(l:filename)
let guru_out = printf("%s:%d:%d: defined here as func main", filename, lnum, col)
call go#def#jump_to_declaration(guru_out, "", 'guru')
let l:guru_out = printf("%s:%d:%d: defined here as func main", l:filename, l:lnum, l:col)
call go#def#jump_to_declaration(l:guru_out, "", 'guru')
call assert_equal(filename, bufname("%"))
call assert_equal(lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2])
call assert_equal(l:filename, bufname("%"))
call assert_equal(l:lnum, getcurpos()[1])
call assert_equal(l:col, getcurpos()[2])
finally
call delete(l:tmp, 'rf')
endtry
@ -22,17 +24,17 @@ endfunc
func! Test_jump_to_declaration_godef() abort
try
let filename = 'def/jump.go'
let lnum = 5
let col = 6
let l:filename = 'def/jump.go'
let l:lnum = 5
let l:col = 6
let l:tmp = gotest#load_fixture(l:filename)
let godef_out = printf("%s:%d:%d\ndefined here as func main", filename, lnum, col)
let l:godef_out = printf("%s:%d:%d\ndefined here as func main", l:filename, l:lnum, l:col)
call go#def#jump_to_declaration(godef_out, "", 'godef')
call assert_equal(filename, bufname("%"))
call assert_equal(lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2])
call assert_equal(l:filename, bufname("%"))
call assert_equal(l:lnum, getcurpos()[1])
call assert_equal(l:col, getcurpos()[2])
finally
call delete(l:tmp, 'rf')
endtry
@ -40,33 +42,180 @@ endfunc
func! Test_Jump_leaves_lists() abort
try
let filename = 'def/jump.go'
let l:filename = 'def/jump.go'
let l:tmp = gotest#load_fixture(l:filename)
let expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}]
let l:expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}]
call setloclist(winnr(), copy(expected), 'r' )
call setqflist(copy(expected), 'r' )
call setloclist(winnr(), copy(l:expected), 'r' )
call setqflist(copy(l:expected), 'r' )
let l:bufnr = bufnr('%')
call cursor(6, 7)
if !go#util#has_job()
let g:go_def_mode='godef'
endif
call go#def#Jump('', 0)
let start = reltime()
while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10
if !go#util#has_job()
unlet g:go_def_mode
endif
let l:start = reltime()
while bufnr('%') == l:bufnr && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
let actual = getloclist(winnr())
call gotest#assert_quickfix(actual, expected)
let l:actual = getloclist(winnr())
call gotest#assert_quickfix(l:actual, l:expected)
let actual = getqflist()
call gotest#assert_quickfix(actual, expected)
let l:actual = getqflist()
call gotest#assert_quickfix(l:actual, l:expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_DefJump_gopls_simple_first() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('simple/firstposition/firstposition.go', [
\ 'package firstposition',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" . '"id:", id)',
\ '}',
\ ] )
let l:expected = [0, 4, 2, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_simple_last() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('simple/lastposition/lastposition.go', [
\ 'package lastposition',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" . '"id:", id)',
\ '}',
\ ] )
let l:expected = [0, 4, 2, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_MultipleCodeUnit_first() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('multiplecodeunit/firstposition/firstposition.go', [
\ 'package firstposition',
\ '',
\ 'func Example() {',
\ "\t𐐀, id := " . '"foo", "bar"',
\ "\tprintln(" . '"(𐐀, id):", 𐐀, id)',
\ '}',
\ ] )
let l:expected = [0, 4, 8, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_MultipleCodeUnit_last() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('multiplecodeunit/lastposition/lastposition.go', [
\ 'package lastposition',
\ '',
\ 'func Example() {',
\ "\t𐐀, id := " . '"foo", "bar"',
\ "\tprintln(" . '"(𐐀, id):", 𐐀, id)',
\ '}',
\ ] )
let l:expected = [0, 4, 8, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -76,6 +76,18 @@ function! go#doc#Open(newmode, mode, ...) abort
endfunction
function! s:GodocView(newposition, position, content) abort
" popup window
if go#config#DocPopupWindow() && exists('*popup_atcursor') && exists('*popup_clear')
call popup_clear()
call popup_atcursor(split(a:content, '\n'), {
\ 'padding': [1, 1, 1, 1],
\ 'borderchars': ['-','|','-','|','+','+','+','+'],
\ "border": [1, 1, 1, 1],
\ })
return
endif
" reuse existing buffer window if it exists otherwise create a new one
let is_visible = bufexists(s:buf_nr) && bufwinnr(s:buf_nr) != -1
if !bufexists(s:buf_nr)

View File

@ -18,20 +18,30 @@ function! s:issuebody() abort
for l in lines
let body = add(body, l)
if l =~ '^\* Vim version'
if l =~ '^<!-- :version'
redir => out
silent version
redir END
let body = extend(body, split(out, "\n")[0:2])
elseif l =~ '^\* Go version'
elseif l =~ '^<!-- go version -->'
let [out, err] = go#util#Exec(['go', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
elseif l =~ '^\* Go environment'
elseif l =~ '^<!-- go env -->'
let [out, err] = go#util#Exec(['go', 'env'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
endif
endfor
let body = add(body, "#### vim-go configuration:\n<details><summary>vim-go configuration</summary><br><pre>")
for k in keys(g:)
if k =~ '^go_'
let body = add(body, 'g:' . k . ' = ' . string(get(g:, k)))
endif
endfor
let body = add(body, '</pre></details>')
return join(body, "\n")
endfunction

View File

@ -22,10 +22,6 @@ function! go#lint#Gometa(bang, autosave, ...) abort
for linter in linters
let cmd += ["--enable=".linter]
endfor
for linter in go#config#MetalinterDisabled()
let cmd += ["--disable=".linter]
endfor
else
" the user wants something else, let us use it.
let cmd = split(go#config#MetalinterCommand(), " ")
@ -44,7 +40,7 @@ function! go#lint#Gometa(bang, autosave, ...) abort
endif
let cmd += include
elseif l:metalinter == "golangci-lint"
let goargs[0] = expand('%:p')
let goargs[0] = expand('%:p:h')
endif
endif
@ -88,7 +84,13 @@ function! go#lint#Gometa(bang, autosave, ...) abort
else
let l:winid = win_getid(winnr())
" Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
let l:messages = split(out, "\n")
if a:autosave
call s:metalinterautosavecomplete(fnamemodify(expand('%:p'), ":."), 0, 1, l:messages)
endif
call go#list#ParseFormat(l:listtype, errformat, l:messages, 'GoMetaLinter')
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
@ -105,7 +107,7 @@ endfunction
" the location list
function! go#lint#Golint(bang, ...) abort
if a:0 == 0
let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), go#package#ImportPath()])
let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), expand('%:p:h')])
else
let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000)
endif
@ -141,7 +143,7 @@ function! go#lint#Vet(bang, ...) abort
if a:0 == 0
let [l:out, l:err] = go#util#Exec(['go', 'vet', go#package#ImportPath()])
else
let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000)
let [l:out, l:err] = go#util#ExecInDir(['go', 'tool', 'vet'] + a:000)
endif
let l:listtype = go#list#Type("GoVet")
@ -230,6 +232,7 @@ function! s:lint_job(args, bang, autosave)
if a:autosave
let l:opts.for = "GoMetaLinterAutoSave"
let l:opts.complete = funcref('s:metalinterautosavecomplete', [expand('%:p:t')])
endif
" autowrite is not enabled for jobs
@ -279,6 +282,21 @@ function! s:golangcilintcmd(bin_path)
return cmd
endfunction
function! s:metalinterautosavecomplete(filepath, job, exit_code, messages)
if len(a:messages) == 0
return
endif
let l:file = expand('%:p:t')
let l:idx = len(a:messages) - 1
while l:idx >= 0
if a:messages[l:idx] !~# '^' . a:filepath . ':'
call remove(a:messages, l:idx)
endif
let l:idx -= 1
endwhile
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -3,7 +3,7 @@ let s:cpo_save = &cpo
set cpo&vim
func! Test_Gometa() abort
call s:gometa('gometaliner')
call s:gometa('gometalinter')
endfunc
func! Test_GometaGolangciLint() abort
@ -11,14 +11,19 @@ func! Test_GometaGolangciLint() abort
endfunc
func! s:gometa(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try
let g:go_metalinter_comand = a:metalinter
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
if a:metalinter == 'golangci-lint'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function `MissingFooDoc` should have comment or be unexported (golint)'}
\ ]
endif
" clear the quickfix lists
call setqflist([], 'r')
@ -36,48 +41,11 @@ func! s:gometa(metalinter) abort
call gotest#assert_quickfix(actual, expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled
endtry
endfunc
func! Test_GometaWithDisabled() abort
call s:gometawithdisabled('gometalinter')
endfunc
func! Test_GometaWithDisabledGolangciLint() abort
call s:gometawithdisabled('golangci-lint')
endfunc
func! s:gometawithdisabled(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
" clear the quickfix lists
call setqflist([], 'r')
let g:go_metalinter_disabled = ['vet']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_disabled
endtry
endfunc
func! Test_GometaAutoSave() abort
call s:gometaautosave('gometalinter')
endfunc
@ -87,14 +55,19 @@ func! Test_GometaAutoSaveGolangciLint() abort
endfunc
func! s:gometaautosave(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try
let g:go_metalinter_comand = a:metalinter
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]
if a:metalinter == 'golangci-lint'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function `MissingDoc` should have comment or be unexported (golint)'}
\ ]
endif
let winnr = winnr()
@ -114,18 +87,50 @@ func! s:gometaautosave(metalinter) abort
call gotest#assert_quickfix(actual, expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_autosave_enabled
endtry
endfunc
func! Test_Vet() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
let l:tmp = gotest#load_fixture('lint/src/vet/vet.go')
try
let expected = [
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '',
\ 'text': 'Printf format %d has arg str of wrong type string'}
\ ]
let winnr = winnr()
" clear the location lists
call setqflist([], 'r')
call go#lint#Vet(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Lint_GOPATH() abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
compiler go
let expected = [
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '',
\ 'text': 'Printf format %d has arg str of wrong type string'}
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported'},
\ {'lnum': 5, 'bufnr': 6, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function AlsoMissingDoc should have comment or be unexported'}
\ ]
let winnr = winnr()
@ -133,7 +138,35 @@ func! Test_Vet() abort
" clear the location lists
call setqflist([], 'r')
call go#lint#Vet(1)
call go#lint#Golint(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
call call(RestoreGOPATH, [])
endfunc
func! Test_Lint_NullModule() abort
silent exe 'e ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint/src/lint/lint.go'
compiler go
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported'},
\ {'lnum': 5, 'bufnr': 6, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function AlsoMissingDoc should have comment or be unexported'}
\ ]
let winnr = winnr()
" clear the location lists
call setqflist([], 'r')
call go#lint#Golint(1)
let actual = getqflist()
let start = reltime()

View File

@ -7,7 +7,7 @@ scriptencoding utf-8
let s:lspfactory = {}
function! s:lspfactory.get() dict abort
if !has_key(self, 'current') || empty(self.current)
if empty(get(self, 'current', {})) || empty(get(self.current, 'job', {}))
let self.current = s:newlsp()
endif
@ -22,9 +22,16 @@ endfunction
function! s:newlsp() abort
if !go#util#has_job()
" TODO(bc): start the server in the background using a shell that waits for the right output before returning.
call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.')
return
let l:oldshortmess=&shortmess
if has('nvim')
set shortmess-=F
endif
call go#util#EchoWarning('Features that rely on gopls will not work without either Vim 8.0.0087 or newer with +job or Neovim')
" Sleep one second to make sure people see the message. Otherwise it is
" often immediately overwritten by an async message.
sleep 1
let &shortmess=l:oldshortmess
return {'sendMessage': funcref('s:noop')}
endif
" job is the job used to talk to the backing instance of gopls.
@ -47,6 +54,8 @@ function! s:newlsp() abort
\ 'last_request_id': 0,
\ 'buf': '',
\ 'handlers': {},
\ 'workspaceDirectories': [],
\ 'wd' : '',
\ }
function! l:lsp.readMessage(data) dict abort
@ -75,19 +84,17 @@ function! s:newlsp() abort
endif
" get the start of the rest
let l:rest_start_idx = l:body_start_idx + str2nr(l:length_match[1])
let l:next_start_idx = l:body_start_idx + str2nr(l:length_match[1])
if len(l:rest) < l:rest_start_idx
if len(l:rest) < l:next_start_idx
" incomplete response body
break
endif
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-\n" . l:rest[:l:rest_start_idx - 1])
endif
call s:debug('received', l:rest[:l:next_start_idx - 1])
let l:body = l:rest[l:body_start_idx : l:rest_start_idx - 1]
let l:rest = l:rest[l:rest_start_idx :]
let l:body = l:rest[l:body_start_idx : l:next_start_idx - 1]
let l:rest = l:rest[l:next_start_idx :]
try
" add the json body to the list.
@ -105,46 +112,99 @@ function! s:newlsp() abort
function! l:lsp.handleMessage(ch, data) dict abort
let self.buf .= a:data
let [self.buf, l:responses] = self.readMessage(self.buf)
let [self.buf, l:messages] = self.readMessage(self.buf)
" TODO(bc): handle notifications (e.g. window/showMessage).
for l:response in l:responses
if has_key(l:response, 'id') && has_key(self.handlers, l:response.id)
try
let l:handler = self.handlers[l:response.id]
let l:winid = win_getid(winnr())
" Always set the active window to the window that was active when
" the request was sent. Among other things, this makes sure that
" the correct window's location list will be populated when the
" list type is 'location' and the user has moved windows since
" sending the reques.
call win_gotoid(l:handler.winid)
if has_key(l:response, 'error')
call l:handler.requestComplete(0)
if has_key(l:handler, 'error')
call call(l:handler.error, [l:response.error.message])
else
call go#util#EchoError(l:response.error.message)
endif
call win_gotoid(l:winid)
return
endif
call l:handler.requestComplete(1)
call call(l:handler.handleResult, [l:response.result])
call win_gotoid(l:winid)
finally
call remove(self.handlers, l:response.id)
endtry
for l:message in l:messages
if has_key(l:message, 'method')
if has_key(l:message, 'id')
call self.handleRequest(l:message)
else
call self.handleNotification(l:message)
endif
elseif has_key(l:message, 'result') || has_key(l:message, 'error')
call self.handleResponse(l:message)
endif
endfor
endfunction
function! l:lsp.handleRequest(req) dict abort
if a:req.method == 'workspace/workspaceFolders'
let l:resp = go#lsp#message#WorkspaceFoldersResult(self.workspaceDirectories)
elseif a:req.method == 'workspace/configuration' && has_key(a:req, 'params') && has_key(a:req.params, 'items')
let l:resp = go#lsp#message#ConfigurationResult(a:req.params.items)
elseif a:req.method == 'client/registerCapability' && has_key(a:req, 'params') && has_key(a:req.params, 'registrations')
let l:resp = v:null
else
return
endif
if get(self, 'exited', 0)
return
endif
let l:msg = self.newResponse(a:req.id, l:resp)
call self.write(l:msg)
endfunction
function! l:lsp.handleNotification(req) dict abort
" TODO(bc): handle notifications (e.g. window/showMessage).
endfunction
function! l:lsp.handleResponse(resp) dict abort
if has_key(a:resp, 'id') && has_key(self.handlers, a:resp.id)
try
let l:handler = self.handlers[a:resp.id]
let l:winid = win_getid(winnr())
" Always set the active window to the window that was active when
" the request was sent. Among other things, this makes sure that
" the correct window's location list will be populated when the
" list type is 'location' and the user has moved windows since
" sending the request.
call win_gotoid(l:handler.winid)
if has_key(a:resp, 'error')
call l:handler.requestComplete(0)
if has_key(l:handler, 'error')
call call(l:handler.error, [a:resp.error.message])
else
call go#util#EchoError(a:resp.error.message)
endif
call win_gotoid(l:winid)
return
endif
call l:handler.requestComplete(1)
let l:winidBeforeHandler = l:handler.winid
call call(l:handler.handleResult, [a:resp.result])
" change the window back to the window that was active when
" starting to handle the message _only_ if the handler didn't
" update the winid, so that handlers can set the winid if needed
" (e.g. :GoDef).
if l:handler.winid == l:winidBeforeHandler
call win_gotoid(l:winid)
endif
finally
call remove(self.handlers, a:resp.id)
endtry
endif
endfunction
function! l:lsp.handleInitializeResult(result) dict abort
if go#config#EchoCommandInfo()
call go#util#EchoProgress("initialized gopls")
endif
let status = {
\ 'desc': '',
\ 'type': 'gopls',
\ 'state': 'initialized',
\ }
call go#statusline#Update(self.wd, status)
let self.ready = 1
" TODO(bc): send initialized message to the server?
let l:msg = self.newMessage(go#lsp#message#Initialized())
call self.write(l:msg)
" send messages queued while waiting for ready.
for l:item in self.queue
@ -157,22 +217,34 @@ function! s:newlsp() abort
function! l:lsp.sendMessage(data, handler) dict abort
if !self.last_request_id
" TODO(bc): run a server per module and one per GOPATH? (may need to
" keep track of servers by rootUri).
let l:wd = go#util#ModuleRoot()
if l:wd == -1
call go#util#EchoError('could not determine appropriate working directory for gopls')
return
return -1
endif
if l:wd == ''
let l:wd = getcwd()
endif
let self.wd = l:wd
if go#config#EchoCommandInfo()
call go#util#EchoProgress("initializing gopls")
endif
let l:status = {
\ 'desc': '',
\ 'type': 'gopls',
\ 'state': 'initializing',
\ }
call go#statusline#Update(l:wd, l:status)
let self.workspaceDirectories = add(self.workspaceDirectories, l:wd)
let l:msg = self.newMessage(go#lsp#message#Initialize(l:wd))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('self.handleInitializeResult', [], l:self)
let self.handlers[l:msg.id] = l:state
call l:state.start()
@ -199,7 +271,7 @@ function! s:newlsp() abort
let l:msg = {
\ 'method': a:data.method,
\ 'jsonrpc': '2.0',
\ }
\ }
if !a:data.notification
let self.last_request_id += 1
@ -213,13 +285,21 @@ function! s:newlsp() abort
return l:msg
endfunction
function! l:lsp.write(msg) dict abort
let l:body = json_encode(a:msg)
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
function l:lsp.newResponse(id, result) dict abort
let l:msg = {
\ 'jsonrpc': '2.0',
\ 'id': a:id,
\ 'result': a:result,
\ }
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "->\n" . l:data)
endif
return l:msg
endfunction
function! l:lsp.write(msg) dict abort
let l:body = json_encode(a:msg)
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
call s:debug('sent', l:data)
if has('nvim')
call chansend(self.job, l:data)
@ -229,19 +309,39 @@ function! s:newlsp() abort
call ch_sendraw(self.job, l:data)
endfunction
function! l:lsp.exit_cb(job, exit_status) dict abort
function! l:lsp.exit_cb(job, exit_status) dict
let self.exited = 1
if !get(self, 'restarting', 0)
return
endif
let l:queue = self.queue
let l:workspaces = self.workspaceDirectories
call s:lspfactory.reset()
let l:lsp = s:lspfactory.get()
" restore workspaces
call call('go#lsp#AddWorkspaceDirectory', l:workspaces)
" * send DidOpen messages for all buffers that have b:did_lsp_open set
" TODO(bc): check modifiable and filetype, too?
bufdo if get(b:, 'go_lsp_did_open', 0) | if &modified | call go#lsp#DidOpen(expand('%:p')) | else | call go#lsp#DidChange(expand('%:p')) | endif | endif
let l:lsp.queue = extend(l:lsp.queue, l:queue)
return
endfunction
" explicitly bind close_cb to state so that within it, self will always refer
function! l:lsp.close_cb(ch) dict abort
" TODO(bc): does anything need to be done here?
" TODO(bc): remove the buffer variables that indicate that gopls has been
" informed that the file is open
endfunction
function! l:lsp.err_cb(ch, msg) dict abort
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-stderr\n" . a:msg)
if a:msg =~ '^\tPort = \d\+$' && !get(self, 'debugport', 0)
let self.debugport = substitute(a:msg, '^\tPort = \(\d\+\).*$', '\1', '')
endif
call s:debug('stderr', a:msg)
endfunction
" explicitly bind callbacks to l:lsp so that within it, self will always refer
@ -263,11 +363,13 @@ function! s:newlsp() abort
return
endif
" TODO(bc): output a message indicating which directory lsp is going to
" start in.
let l:lsp.job = go#job#Start([l:bin_path], l:opts)
let l:cmd = [l:bin_path]
if go#util#HasDebug('lsp')
let l:cmd = extend(l:cmd, ['-debug', 'localhost:0'])
endif
let l:lsp.job = go#job#Start(l:cmd, l:opts)
" TODO(bc): send the initialize message now?
return l:lsp
endfunction
@ -327,16 +429,17 @@ function! s:requestComplete(ok) abort dict
endfunction
function! s:start() abort dict
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
if self.statustype == ''
return
endif
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endfunction
" go#lsp#Definition calls gopls to get the definition of the identifier at
@ -351,13 +454,13 @@ function! go#lsp#Definition(fname, line, col, handler) abort
let l:state = s:newHandlerState('definition')
let l:state.handleResult = funcref('s:definitionHandler', [function(a:handler, [], l:state)], l:state)
let l:msg = go#lsp#message#Definition(fnamemodify(a:fname, ':p'), a:line, a:col)
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:definitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, go#lsp#lsp#PositionOf(getline(l:msg.range.start.line+1), l:msg.range.start.character), 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
@ -373,13 +476,13 @@ function! go#lsp#TypeDef(fname, line, col, handler) abort
let l:state = s:newHandlerState('type definition')
let l:msg = go#lsp#message#TypeDefinition(fnamemodify(a:fname, ':p'), a:line, a:col)
let l:state.handleResult = funcref('s:typeDefinitionHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:typeDefinitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, go#lsp#lsp#PositionOf(getline(l:msg.range.start.line+1), l:msg.range.start.character), 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
@ -396,9 +499,13 @@ function! go#lsp#DidOpen(fname) abort
let l:msg = go#lsp#message#DidOpen(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
" TODO(bc): setting a buffer level variable here assumes that a:fname is the
" current buffer. Change to a:fname first before setting it and then change
" back to active buffer.
let b:go_lsp_did_open = 1
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidChange(fname) abort
@ -409,17 +516,17 @@ function! go#lsp#DidChange(fname) abort
return
endif
call go#lsp#DidOpen(a:fname)
if !filereadable(a:fname)
return
endif
call go#lsp#DidOpen(a:fname)
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidChange(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidClose(fname) abort
@ -435,9 +542,12 @@ function! go#lsp#DidClose(fname) abort
let l:msg = go#lsp#message#DidClose(fnamemodify(a:fname, ':p'))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
" TODO(bc): setting a buffer level variable here assumes that a:fname is the
" current buffer. Change to a:fname first before setting it and then change
" back to active buffer.
let b:go_lsp_did_open = 0
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#Completion(fname, line, col, handler) abort
@ -448,16 +558,33 @@ function! go#lsp#Completion(fname, line, col, handler) abort
let l:state = s:newHandlerState('completion')
let l:state.handleResult = funcref('s:completionHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:completionErrorHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:completionHandler(next, msg) abort dict
" gopls returns a CompletionList.
let l:matches = []
let l:start = -1
for l:item in a:msg.items
let l:start = l:item.textEdit.range.start.character
let l:match = {'abbr': l:item.label, 'word': l:item.textEdit.newText, 'info': '', 'kind': go#lsp#completionitemkind#Vim(l:item.kind)}
if has_key(l:item, 'detail')
let l:match.info = l:item.detail
let l:match.menu = l:item.detail
if go#lsp#completionitemkind#IsFunction(l:item.kind) || go#lsp#completionitemkind#IsMethod(l:item.kind)
let l:match.info = printf('%s %s', l:item.label, l:item.detail)
" The detail provided by gopls hasn't always provided the the full
" signature including the return value. The label used to be the
" function signature and the detail was the return value. Handle
" that case for backward compatibility. This can be removed in the
" future once it's likely that the majority of users are on a recent
" version of gopls.
if l:item.detail !~ '^func'
let l:match.info = printf('func %s %s', l:item.label, l:item.detail)
endif
endif
endif
if has_key(l:item, 'documentation')
@ -466,12 +593,12 @@ function! s:completionHandler(next, msg) abort dict
let l:matches = add(l:matches, l:match)
endfor
let l:args = [l:matches]
let l:args = [l:start, l:matches]
call call(a:next, l:args)
endfunction
function! s:completionErrorHandler(next, error) abort dict
call call(a:next, [[]])
call call(a:next, [-1, []])
endfunction
function! go#lsp#Hover(fname, line, col, handler) abort
@ -482,7 +609,7 @@ function! go#lsp#Hover(fname, line, col, handler) abort
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:hoverHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:hoverHandler(next, msg) abort dict
@ -499,7 +626,7 @@ endfunction
function! go#lsp#Info(showstatus)
let l:fname = expand('%:p')
let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position()
call go#lsp#DidChange(l:fname)
@ -511,10 +638,29 @@ function! go#lsp#Info(showstatus)
let l:state = s:newHandlerState('')
endif
let l:state.handleResult = funcref('s:infoDefinitionHandler', [function('s:info', []), a:showstatus], l:state)
let l:state.handleResult = funcref('s:infoDefinitionHandler', [function('s:info', [1], l:state), a:showstatus], l:state)
let l:state.error = funcref('s:noop')
let l:msg = go#lsp#message#Definition(l:fname, l:line, l:col)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#GetInfo()
let l:fname = expand('%:p')
let [l:line, l:col] = go#lsp#lsp#Position()
call go#lsp#DidChange(l:fname)
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('')
let l:info = go#promise#New(function('s:info', [0], l:state), 10000, '')
let l:state.handleResult = funcref('s:infoDefinitionHandler', [l:info.wrapper, 0], l:state)
let l:state.error = funcref('s:noop')
let l:msg = go#lsp#message#Definition(l:fname, l:line, l:col)
call l:lsp.sendMessage(l:msg, l:state)
return l:info.await()
endfunction
function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
@ -522,8 +668,8 @@ function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
let l:msg = a:msg[0]
let l:fname = go#path#FromURI(l:msg.uri)
let l:line = l:msg.range.start.line+1
let l:col = l:msg.range.start.character+1
let l:line = l:msg.range.start.line
let l:col = l:msg.range.start.character
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
@ -534,18 +680,166 @@ function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
let l:state = s:newHandlerState('')
endif
let l:state.handleResult = funcref('s:hoverHandler', [function('s:info', [], l:state)], l:state)
let l:state.handleResult = funcref('s:hoverHandler', [a:next], l:state)
let l:state.error = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
return l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:info(content) abort dict
function! s:info(show, content) abort dict
let l:content = s:infoFromHoverContent(a:content)
if a:show
call go#util#ShowInfo(l:content)
endif
return l:content
endfunction
function! s:infoFromHoverContent(content) abort
if len(a:content) < 1
return ''
endif
let l:content = a:content[0]
" strip off the method set and fields of structs and interfaces.
if l:content =~# '^type [^ ]\+ \(struct\|interface\)'
if l:content =~# '^\(type \)\?[^ ]\+ \(struct\|interface\)'
let l:content = substitute(l:content, '{.*', '', '')
endif
call go#util#ShowInfo(l:content)
return l:content
endfunction
function! go#lsp#AddWorkspaceDirectory(...) abort
if a:0 == 0
return
endif
call go#lsp#CleanWorkspaces()
let l:workspaces = []
for l:dir in a:000
let l:dir = fnamemodify(l:dir, ':p')
if !isdirectory(l:dir)
continue
endif
let l:workspaces = add(l:workspaces, l:dir)
endfor
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
let l:lsp.workspaceDirectories = extend(l:lsp.workspaceDirectories, l:workspaces)
let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:workspaces, [])
call l:lsp.sendMessage(l:msg, l:state)
return 0
endfunction
function! go#lsp#CleanWorkspaces() abort
let l:workspaces = []
let l:lsp = s:lspfactory.get()
let l:i = 0
let l:missing = []
for l:dir in l:lsp.workspaceDirectories
if !isdirectory(l:dir)
let l:dir = add(l:missing, l:dir)
call remove(l:lsp.workspaceDirectories, l:i)
continue
endif
let l:i += 1
endfor
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
let l:msg = go#lsp#message#ChangeWorkspaceFolders([], l:missing)
call l:lsp.sendMessage(l:msg, l:state)
return 0
endfunction
" go#lsp#ResetWorkspaceDiretories removes and then re-adds all workspace
" folders to cause gopls to send configuration requests for all of them again.
" This is useful, for instance, when build tags have been added and gopls
" needs to use them.
function! go#lsp#ResetWorkspaceDirectories() abort
call go#lsp#CleanWorkspaces()
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:lsp.workspaceDirectories, l:lsp.workspaceDirectories)
call l:lsp.sendMessage(l:msg, l:state)
return 0
endfunction
function! go#lsp#DebugBrowser() abort
let l:lsp = s:lspfactory.get()
let l:port = get(l:lsp, 'debugport', 0)
if !l:port
call go#util#EchoError("gopls was not started with debugging enabled. See :help g:go_debug.")
return
endif
call go#util#OpenBrowser(printf('http://localhost:%d', l:port))
endfunction
function! go#lsp#Restart() abort
if !go#util#has_job() || len(s:lspfactory) == 0 || !has_key(s:lspfactory, 'current')
return
endif
let l:lsp = s:lspfactory.get()
let l:lsp.restarting = 1
let l:state = s:newHandlerState('exit')
let l:msg = go#lsp#message#Shutdown()
let l:state.handleResult = funcref('s:noop')
let l:retval = l:lsp.sendMessage(l:msg, l:state)
let l:msg = go#lsp#message#Exit()
let l:retval = l:lsp.sendMessage(l:msg, l:state)
return l:retval
endfunction
function! s:debug(event, data) abort
if !go#util#HasDebug('lsp')
return
endif
let l:winid = win_getid()
let l:name = '__GOLSP_LOG__'
let l:log_winid = bufwinid(l:name)
if l:log_winid == -1
silent keepalt botright 10new
silent file `='__GOLSP_LOG__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=golsplog
else
call win_gotoid(l:log_winid)
endif
try
setlocal modifiable
if getline(1) == ''
call setline('$', printf('%s: %s', a:event, a:data))
else
call append('$', printf('%s: %s', a:event, a:data))
endif
normal! G
setlocal nomodifiable
finally
call win_gotoid(l:winid)
endtry
endfunction
" restore Vi compatibility settings

View File

@ -28,7 +28,7 @@ let s:Event = 23
let s:Operator = 24
let s:TypeParameter = 25
function! go#lsp#completionitemkind#Vim(kind)
function! go#lsp#completionitemkind#Vim(kind) abort
if a:kind == s:Method || a:kind == s:Function || a:kind == s:Constructor
return 'f'
elseif a:kind == s:Variable || a:kind == s:Constant
@ -40,6 +40,22 @@ function! go#lsp#completionitemkind#Vim(kind)
endif
endfunction
function! go#lsp#completionitemkind#IsFunction(kind) abort
if a:kind == s:Function
return 1
endif
return 0
endfunction
function! go#lsp#completionitemkind#IsMethod(kind) abort
if a:kind == s:Method
return 1
endif
return 0
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -0,0 +1,58 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" go#lsp#lsp#Position returns the LSP text position. If no arguments are
" provided, the cursor position is assumed. Otherwise, there should be two
" arguments: the line and the column.
function! go#lsp#lsp#Position(...)
if a:0 < 2
let [l:line, l:col] = getpos('.')[1:2]
else
let l:line = a:1
let l:col = a:2
endif
let l:content = getline(l:line)
" LSP uses 0-based lines.
return [l:line - 1, s:character(l:line, l:col-1)]
endfunction
function! s:strlen(str) abort
let l:runes = split(a:str, '\zs')
return len(l:runes) + len(filter(l:runes, 'char2nr(v:val)>=0x10000'))
endfunction
function! s:character(line, col) abort
return s:strlen(getline(a:line)[:col([a:line, a:col - 1])])
endfunction
" go#lsp#PositionOf returns len(content[0:units]) where units is utf-16 code
" units. This is mostly useful for converting LSP text position to vim
" position.
function! go#lsp#lsp#PositionOf(content, units) abort
if a:units == 0
return 1
endif
let l:remaining = a:units
let l:str = ""
for l:rune in split(a:content, '\zs')
if l:remaining < 0
break
endif
let l:remaining -= 1
if char2nr(l:rune) >= 0x10000
let l:remaining -= 1
endif
let l:str = l:str . l:rune
endfor
return len(l:str)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,32 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
function! Test_PositionOf_Simple()
let l:actual = go#lsp#lsp#PositionOf("just ascii", 3)
call assert_equal(4, l:actual)
endfunc
function! Test_PositionOf_MultiByte()
" ⌘ is U+2318, which encodes to three bytes in utf-8 and 1 code unit in
" utf-16.
let l:actual = go#lsp#lsp#PositionOf("⌘⌘ foo", 3)
call assert_equal(8, l:actual)
endfunc
function! Test_PositionOf_MultipleCodeUnit()
" 𐐀 is U+10400, which encodes to 4 bytes in utf-8 and 2 code units in
" utf-16.
let l:actual = go#lsp#lsp#PositionOf("𐐀 bar", 3)
call assert_equal(6, l:actual)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -10,17 +10,50 @@ function! go#lsp#message#Initialize(wd) abort
\ 'processId': getpid(),
\ 'rootUri': go#path#ToURI(a:wd),
\ 'capabilities': {
\ 'workspace': {},
\ 'workspace': {
\ 'workspaceFolders': v:true,
\ 'didChangeConfiguration': {
\ 'dynamicRegistration': v:true,
\ },
\ 'configuration': v:true,
\ },
\ 'textDocument': {
\ 'hover': {
\ 'contentFormat': ['plaintext'],
\ },
\ }
\ }
\ },
\ 'workspaceFolders': [s:workspaceFolder(0, a:wd)],
\ }
\ }
endfunction
function! go#lsp#message#Initialized() abort
return {
\ 'notification': 1,
\ 'method': 'initialized',
\ 'params': {},
\ }
endfunction
function! go#lsp#message#Shutdown() abort
return {
\ 'notification': 0,
\ 'method': 'shutdown',
\ }
endfunction
function! go#lsp#message#Exit() abort
return {
\ 'notification': 1,
\ 'method': 'exit',
\ }
endfunction
function! go#lsp#message#WorkspaceFoldersResult(dirs) abort
return map(copy(a:dirs), function('s:workspaceFolder', []))
endfunction
function! go#lsp#message#Definition(file, line, col) abort
return {
\ 'notification': 0,
@ -116,8 +149,49 @@ function! go#lsp#message#Hover(file, line, col) abort
\ }
endfunction
function! go#lsp#message#ChangeWorkspaceFolders(add, remove) abort
let l:addDirs = map(copy(a:add), function('s:workspaceFolder', []))
let l:removeDirs = map(copy(a:add), function('s:workspaceFolder', []))
return {
\ 'notification': 1,
\ 'method': 'workspace/didChangeWorkspaceFolders',
\ 'params': {
\ 'event': {
\ 'removed': l:removeDirs,
\ 'added': l:addDirs,
\ },
\ }
\ }
endfunction
function! go#lsp#message#ConfigurationResult(items) abort
let l:result = []
" results must be in the same order as the items
for l:item in a:items
let l:config = {
\ 'buildFlags': [],
\ 'hoverKind': 'NoDocumentation',
\ }
let l:buildtags = go#config#BuildTags()
if buildtags isnot ''
let l:config.buildFlags = extend(l:config.buildFlags, ['-tags', go#config#BuildTags()])
endif
let l:result = add(l:result, l:config)
endfor
return l:result
endfunction
function s:workspaceFolder(key, val) abort
return {'uri': go#path#ToURI(a:val), 'name': a:val}
endfunction
function! s:position(line, col) abort
return {'line': a:line - 1, 'character': a:col-1}
return {'line': a:line, 'character': a:col}
endfunction
" restore Vi compatibility settings

View File

@ -0,0 +1,49 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
function! Test_GetSimpleTextPosition()
call s:getinfo('lsp text position should align with cursor position after ascii only', 'ascii')
endfunction
function! Test_GetMultiByteTextPosition()
call s:getinfo('lsp text position should align with cursor position after two place of interest symbols ⌘⌘', 'multi-byte')
endfunction
function! Test_GetMultipleCodeUnitTextPosition()
call s:getinfo('lsp text position should align with cursor position after Deseret Capital Letter Long I 𐐀', 'multi-code-units')
endfunction
function! s:getinfo(str, name)
if !go#util#has_job()
return
endif
try
let g:go_info_mode = 'gopls'
let l:tmp = gotest#write_file(a:name . '/position/position.go', [
\ 'package position',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" .'"' . a:str . '", id)',
\ '}',
\ ] )
let l:expected = 'var id string'
let l:actual = go#lsp#GetInfo()
call assert_equal(l:expected, l:actual)
finally
"call delete(l:tmp, 'rf')
unlet g:go_info_mode
endtry
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -8,10 +8,14 @@ function! go#mod#Format() abort
" go mod only exists in `v1.11`
if empty(s:go_major_version)
let tokens = matchlist(go#util#Exec(['go', 'version']), '\d\+.\(\d\+\)\(\.\d\+\)\? ')
let s:go_major_version = str2nr(tokens[1])
if len(tokens) > 0
let s:go_major_version = str2nr(tokens[1])
else
let s:go_major_version = ""
endif
endif
if s:go_major_version < "11"
if !empty(s:go_major_version) && s:go_major_version < "11"
call go#util#EchoError("Go v1.11 is required to format go.mod file")
return
endif

View File

@ -82,6 +82,10 @@ function! s:vendordirs() abort
if l:err != 0
return []
endif
if empty(l:root)
return []
endif
let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src'
let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}'])
@ -111,57 +115,77 @@ function! s:vendordirs() abort
endfunction
let s:import_paths = {}
" ImportPath returns the import path of the package for current buffer.
" ImportPath returns the import path of the package for current buffer. It
" returns -1 if the import path cannot be determined.
function! go#package#ImportPath() abort
let dir = expand("%:p:h")
let l:dir = expand("%:p:h")
if has_key(s:import_paths, dir)
return s:import_paths[dir]
return s:import_paths[l:dir]
endif
let [l:out, l:err] = go#util#ExecInDir(['go', 'list'])
if l:err != 0
let l:importpath = go#package#FromPath(l:dir)
if type(l:importpath) == type(0)
return -1
endif
let l:importpath = split(out, '\n')[0]
" go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
" Check it and retun an error if that is the case
if l:importpath[0] ==# '_'
return -1
endif
let s:import_paths[dir] = l:importpath
let s:import_paths[l:dir] = l:importpath
return l:importpath
endfunction
" go#package#FromPath returns the import path of arg. -1 is returned when arg
" does not specify a package. -2 is returned when arg is a relative path
" outside of GOPATH and not in a module.
" outside of GOPATH, not in a module, and not below the current working
" directory. A relative path is returned when in a null module at or below the
" current working directory..
function! go#package#FromPath(arg) abort
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:dir = getcwd()
let l:path = a:arg
let l:path = fnamemodify(a:arg, ':p')
if !isdirectory(l:path)
let l:path = fnamemodify(l:path, ':h')
endif
execute l:cd fnameescape(l:path)
let [l:out, l:err] = go#util#Exec(['go', 'list'])
execute l:cd fnameescape(l:dir)
if l:err != 0
return -1
endif
try
if glob("*.go") == ""
" There's no Go code in this directory. We might be in a module directory
" which doesn't have any code at this level. To avoid `go list` making a
" bunch of HTTP requests to fetch dependencies, short-circuit `go list`
" and return -1 immediately.
if !empty(s:module())
return -1
endif
endif
let [l:out, l:err] = go#util#Exec(['go', 'list'])
if l:err != 0
return -1
endif
let l:importpath = split(l:out, '\n')[0]
let l:importpath = split(l:out, '\n')[0]
finally
execute l:cd fnameescape(l:dir)
endtry
" go list returns '_CURRENTDIRECTORY' if the directory is neither in GOPATH
" nor in a module. Check it and retun an error if that is the case
" go list returns '_CURRENTDIRECTORY' if the directory is in a null module
" (i.e. neither in GOPATH nor in a module). Return a relative import path
" if possible or an error if that is the case.
if l:importpath[0] ==# '_'
return -2
let l:relativeimportpath = fnamemodify(l:importpath[1:], ':.')
if go#util#IsWin()
let l:relativeimportpath = substitute(l:relativeimportpath, '\\', '/', 'g')
endif
if l:relativeimportpath == l:importpath[1:]
return '.'
endif
if l:relativeimportpath[0] == '/'
return -2
endif
let l:importpath= printf('./%s', l:relativeimportpath)
endif
return l:importpath

View File

@ -0,0 +1,50 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
" New returns a promise. A promise's primary purpose is to make async jobs
" synchronous by awaiting fn.
"
" A promise is a dictionary with two keys:
" 'wrapper':
" A function that wraps fn. It can be used in place of fn.
" 'await':
" A function that waits for wrapper to be called and returns the value
" returned by fn. Returns default if timeout expires.
function! go#promise#New(fn, timeout, default) abort
let l:state = {}
" explicitly bind to state so that within l:promise's methods, self will
" always refer to state. See :help Partial for more information.
return {
\ 'wrapper': function('s:wrapper', [a:fn], l:state),
\ 'await': function('s:await', [a:timeout, a:default], l:state),
\ }
endfunction
function! s:wrapper(fn, ...) dict
let self.retval = call(a:fn, a:000)
return self.retval
endfunction
function! s:await(timeout, default) dict
let l:timer = timer_start(a:timeout, function('s:setretval', [a:default], self))
while !has_key(self, 'retval')
sleep 50m
endwhile
call timer_stop(l:timer)
return self.retval
endfunction
function! s:setretval(val, timer) dict
let self.retval = a:val
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,41 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_PromiseNew() abort
let l:sut = go#promise#New(function('s:work', []), 100, -1)
call assert_true(has_key(l:sut, 'wrapper'))
call assert_true(has_key(l:sut, 'await'))
endfunc
func! Test_PromiseAwait() abort
let l:expected = 1
let l:default = -1
let l:sut = go#promise#New(function('s:work', [l:expected]), 100, l:default)
call timer_start(10, l:sut.wrapper)
let l:actual = call(l:sut.await, [])
call assert_equal(l:expected, l:actual)
endfunc
func! Test_PromiseAwait_Timeout() abort
let l:desired = 1
let l:expected = -1
let l:sut = go#promise#New(function('s:work', [l:desired]), 10, l:expected)
call timer_start(100, l:sut.wrapper)
let l:actual = call(l:sut.await, [])
call assert_equal(l:expected, l:actual)
endfunc
func! s:work(val, timer)
return a:val
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -27,9 +27,8 @@ let s:last_status = ""
" if not it returns an empty string. This function should be plugged directly
" into the statusline.
function! go#statusline#Show() abort
" lazy initialiation of the cleaner
" lazy initialization of the cleaner
if !s:timer_id
" clean every 60 seconds all statuses
let interval = go#config#StatuslineDuration()
let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif
@ -57,9 +56,9 @@ function! go#statusline#Show() abort
" only update highlight if status has changed.
if status_text != s:last_status
if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass"
if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass" || status.state =~ 'initialized'
hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 guibg=#5fd700 guifg=#005f00
elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling"
elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling" || status.state =~ 'initializing'
hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 guibg=#ff8700 guifg=#870000
elseif status.state =~ "failed"
hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 guibg=#ff0000 guifg=#5f0000
@ -83,10 +82,11 @@ function! go#statusline#Update(status_dir, status) abort
" before we stop the timer, check if we have any previous jobs to be cleaned
" up. Otherwise every job will reset the timer when this function is called
" and thus old jobs will never be cleaned
call go#statusline#Clear(0)
call s:clear()
" also reset the timer, so the user has time to see it in the statusline.
" Setting the timer_id to 0 will trigger a new cleaner routine.
" Setting the timer_id to 0 will cause a new timer to be created the next
" time the go#statusline#Show() is called.
call timer_stop(s:timer_id)
let s:timer_id = 0
endfunction
@ -94,6 +94,10 @@ endfunction
" Clear clears all currently stored statusline data. The timer_id argument is
" just a placeholder so we can pass it to a timer_start() function if needed.
function! go#statusline#Clear(timer_id) abort
call s:clear()
endfunction
function! s:clear()
for [status_dir, status] in items(s:statuses)
let elapsed_time = reltimestr(reltime(status.created_at))
" strip whitespace

View File

@ -24,8 +24,13 @@ function! go#template#create() abort
else
let l:template_file = go#config#TemplateFile()
endif
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
silent exe 'keepalt 0r ' . fnameescape(l:template_path)
" If template_file is an absolute path, use it as-is. This is to support
" overrides pointing to templates outside of the vim-go plugin dir
if fnamemodify(l:template_file, ':p') != l:template_file
let l:template_file = go#util#Join(l:root_dir, "templates", l:template_file)
endif
silent exe 'keepalt 0r ' . fnameescape(l:template_file)
endif
else
let l:content = printf("package %s", l:package_name)

View File

@ -96,6 +96,13 @@ function! s:on_stdout(job_id, data, event) dict abort
endfunction
function! s:on_exit(job_id, exit_status, event) dict abort
" change to directory where test were run. if we do not do this
" the quickfix items will have the incorrect paths.
" see: https://github.com/fatih/vim-go/issues/2400
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:dir = getcwd()
execute l:cd . fnameescape(expand("%:p:h"))
let l:winid = win_getid(winnr())
call win_gotoid(self.winid)
let l:listtype = go#list#Type("_term")
@ -130,8 +137,10 @@ function! s:on_exit(job_id, exit_status, event) dict abort
endif
" close terminal; we don't need it anymore
call win_gotoid(self.termwinid)
close!
if go#config#TermCloseOnExit()
call win_gotoid(self.termwinid)
close!
endif
if self.bang
call win_gotoid(l:winid)
@ -140,6 +149,21 @@ function! s:on_exit(job_id, exit_status, event) dict abort
call win_gotoid(self.winid)
call go#list#JumpToFirst(l:listtype)
" change back to original working directory
execute l:cd l:dir
endfunction
function! go#term#ToggleCloseOnExit() abort
if go#config#TermCloseOnExit()
call go#config#SetTermCloseOnExit(0)
call go#util#EchoProgress("term close on exit disabled")
return
endif
call go#config#SetTermCloseOnExit(1)
call go#util#EchoProgress("term close on exit enabled")
return
endfunction
" restore Vi compatibility settings

View File

@ -22,6 +22,7 @@ func! Test_GoTermNewMode()
call assert_equal(actual, l:expected)
finally
sleep 50m
call delete(l:tmp, 'rf')
endtry
endfunc
@ -46,6 +47,7 @@ func! Test_GoTermNewMode_SplitRight()
call assert_equal(actual, l:expected)
finally
sleep 50m
call delete(l:tmp, 'rf')
set nosplitright
endtry

View File

@ -0,0 +1,10 @@
package main
import (
"fmt"
)
func ExampleHelloWorld() {
fmt.Println("Hello, World")
// Output: What's shakin
}

View File

@ -32,6 +32,7 @@ function! go#test#Test(bang, compile, ...) abort
if go#config#TermEnabled()
call go#term#new(a:bang, ["go"] + args, s:errorformat())
return
endif
if go#util#has_job()
@ -166,9 +167,17 @@ function! s:errorformat() abort
let format .= ",%-G" . indent . "%#--- PASS: %.%#"
" Match failure lines.
"
" Example failures start with '--- FAIL: ', followed by the example name
" followed by a space , followed by the duration of the example in
" parantheses. They aren't nested, though, so don't check for indentation.
" The errors from them also aren't indented and don't report file location
" or line numbers, so those won't show up. This will at least let the user
" know which example failed, though.
let format .= ',%G--- FAIL: %\\%(Example%\\)%\\@=%m (%.%#)'
" Test failures start with '--- FAIL: ', followed by the test name followed
" by a space the duration of the test in parentheses
" by a space, followed by the duration of the test in parentheses.
"
" e.g.:
" '--- FAIL: TestSomething (0.00s)'

View File

@ -66,9 +66,9 @@ endfunc
func! Test_GoTestShowName() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'},
\ {'lnum': 6, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'},
\ {'lnum': 6, 'bufnr': 8, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld/sub'},
\ {'lnum': 9, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ {'lnum': 9, 'bufnr': 8, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ ]
let g:go_test_show_name=1
@ -78,20 +78,27 @@ endfunc
func! Test_GoTestVet() abort
let expected = [
\ {'lnum': 6, 'bufnr': 10, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
\ {'lnum': 6, 'bufnr': 11, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
\ ]
call s:test('veterror/veterror.go', expected)
endfunc
func! Test_GoTestTestCompilerError() abort
let expected = [
\ {'lnum': 10, 'bufnr': 8, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'},
\ {'lnum': 10, 'bufnr': 9, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'struct {} does not implement io.Reader (missing Read method)'}
\ ]
call s:test('testcompilerror/testcompilerror_test.go', expected)
endfunc
func! Test_GoTestExample() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'ExampleHelloWorld'}
\ ]
call s:test('example/example_test.go', expected)
endfunc
func! s:test(file, expected, ...) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file

View File

@ -115,7 +115,15 @@ endfunction
function! go#tool#DescribeBalloon()
let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p')
call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', []))
let l:winid = win_getid()
call win_gotoid(bufwinid(v:beval_bufnr))
let [l:line, l:col] = go#lsp#lsp#Position(v:beval_lnum, v:beval_col)
call go#lsp#Hover(l:fname, l:line, l:col, funcref('s:balloon', []))
call win_gotoid(l:winid)
return ''
endfunction

View File

@ -36,15 +36,9 @@ function! go#util#Join(...) abort
endfunction
" IsWin returns 1 if current OS is Windows or 0 otherwise
" Note that has('win32') is always 1 when has('win64') is 1, so has('win32') is enough.
function! go#util#IsWin() abort
let win = ['win16', 'win32', 'win64', 'win95']
for w in win
if (has(w))
return 1
endif
endfor
return 0
return has('win32')
endfunction
" IsMac returns 1 if current OS is macOS or 0 otherwise.
@ -468,7 +462,7 @@ function! go#util#tempdir(prefix) abort
endif
" Not great randomness, but "good enough" for our purpose here.
let l:rnd = sha256(printf('%s%s', localtime(), fnamemodify(bufname(''), ":p")))
let l:rnd = sha256(printf('%s%s', reltimestr(reltime()), fnamemodify(bufname(''), ":p")))
let l:tmp = printf("%s/%s%s", l:dir, a:prefix, l:rnd)
call mkdir(l:tmp, 'p', 0700)
return l:tmp
@ -557,7 +551,9 @@ function! go#util#SetEnv(name, value) abort
let l:remove = 1
endif
call execute('let $' . a:name . ' = "' . a:value . '"')
" wrap the value in single quotes so that it will work on windows when there
" are backslashes present in the value (e.g. $PATH).
call execute('let $' . a:name . " = '" . a:value . "'")
if l:remove
function! s:remove(name) abort