1
0
mirror of https://github.com/amix/vimrc synced 2025-06-29 02:55: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

@ -0,0 +1 @@
patreon: bhcleek

View File

@ -6,10 +6,8 @@ If possible, please provide clear steps for reproducing the problem.
### What did you expect to happen?
### What happened instead?
### Configuration (**MUST** fill this out):
#### vim-go version:
@ -20,10 +18,13 @@ If possible, please provide clear steps for reproducing the problem.
</pre></details>
#### Vim version (first three lines from `:version`):
<!-- :version -->
#### Go version (`go version`):
#### Go version (`go version`):
<!-- go version -->
#### Go environment
<details><summary><code>go env</code> Output:</summary><br><pre>
<!-- go env -->
</pre></details>

View File

@ -1,5 +1,9 @@
## unplanned
BACKWARDS INCOMPATABILITIES:
* `g:go_metalinter_disabled` has been removed.
[[GH-2375]](https://github.com/fatih/vim-go/pull/2117)
IMPROVEMENTS:
* Add a new option, `g:go_code_completion_enabled`, to control whether omnifunc
is set.
@ -8,6 +12,53 @@ IMPROVEMENTS:
[[GH-2261]](https://github.com/fatih/vim-go/pull/2261)
* Allow debugging of packages outside of GOPATH without a go.mod file.
[[GH-2269]](https://github.com/fatih/vim-go/pull/2269)
* Show which example failed when Example tests fail
[[GH-2277]](https://github.com/fatih/vim-go/pull/2277)
* Show function signature and return types in preview window when autocompleting functions and methods.
[[GH-2289]](https://github.com/fatih/vim-go/pull/2289)
* Improve the user experience when using null modules.
[[GH-2300]](https://github.com/fatih/vim-go/pull/2300)
* Modify `:GoReportGitHubIssue` to include vim-go configuration values
[[GH-2323]](https://github.com/fatih/vim-go/pull/2323)
* Respect `g:go_info_mode='gopls'` in go#complete#GetInfo.
[[GH-2313]](https://github.com/fatih/vim-go/pull/2313)
* Allow `:GoLint`, `:GoErrCheck`, and `:GoDebug` to work in null modules.
[[GH-2335]](https://github.com/fatih/vim-go/pull/2335)
* Change default value for `g:go_info_mode` and `g:go_def_mode` to `'gopls'`.
[[GH-2329]](https://github.com/fatih/vim-go/pull/2329)
* Add a new option, `g:go_doc_popup_window` to optionally use a popup window
for godoc in Vim 8.1.1513 and later.
[[GH-2347]](https://github.com/fatih/vim-go/pull/2347)
* Add `:GoAddWorkspace` function to support multiple workspaces with gopls.
[[GH-2356]](https://github.com/fatih/vim-go/pull/2356)
* Install gopls from its stable package.
[[GH-2360]](https://github.com/fatih/vim-go/pull/2360)
* Disambiguate progress message when initializing gopls.
[[GH-2369]](https://github.com/fatih/vim-go/pull/2369)
* Calculate LSP position correctly when on a line that contains multi-byte
characters before the position.
[[GH-2389]](https://github.com/fatih/vim-go/pull/2389)
* Calculate Vim position correctly from LSP text position.
[[GH-2395]](https://github.com/fatih/vim-go/pull/2395)
* Use the statusline to display gopls initialization status messages and only
echo the statuses when `g:go_echo_command_info` is set.
[[GH-2422]](https://github.com/fatih/vim-go/pull/2422)
* Send configuration to gopls so that build tags will be considered and hover
content won't have documentation.
[[GH-2429]](https://github.com/fatih/vim-go/pull/2429)
* Add a new option, `g:go_term_close_on_exit`, to control whether jobs run in a
terminal window will close the terminal window when the job exits.
[[GH-2409]](https://github.com/fatih/vim-go/pull/2409)
* Allow `g:go_template_file` and `g:go_template_test_files` to reside outside
of vim-go's template directory.
[[GH-2434]](https://github.com/fatih/vim-go/pull/2434)
* Add a new command, `:GoLSPDebugBrowser`, to open a browser to gopls debugging
view.
[[GH-2436]](https://github.com/fatih/vim-go/pull/2436)
* Restart gopls automatically when it is updated via `:GoUpdateBinaries`.
[[GH-2453]](https://github.com/fatih/vim-go/pull/2453)
* Reset `'more'` while installing binaries to avoid unnecessary more prompts.
[[GH-2457]](https://github.com/fatih/vim-go/pull/2457)
BUG FIXES:
* display info about function and function types whose parameters are
@ -26,6 +77,44 @@ BUG FIXES:
[[GH-2268]](https://github.com/fatih/vim-go/pull/2268)
* Set the anchor for method documentation correctly.
[[GH-2276]](https://github.com/fatih/vim-go/pull/2276)
* Respect the LSP information for determining where candidate matches start.
[[GH-2291]](https://github.com/fatih/vim-go/pull/2291)
* Restore environment variables with backslashes correctly.
[[GH-2292]](https://github.com/fatih/vim-go/pull/2292)
* Modify handling of gopls output for `:GoInfo` to ensure the value will be
displayed.
[[GH-2311]](https://github.com/fatih/vim-go/pull/2311)
* Run `:GoLint` successfully in null modules.
[[GH-2318]](https://github.com/fatih/vim-go/pull/2318)
* Ensure actions on save work in new buffers that have not yet been persisted to disk.
[[GH-2319]](https://github.com/fatih/vim-go/pull/2319)
* Restore population of information in `:GoReportGitHubIssue`.
[[GH-2312]](https://github.com/fatih/vim-go/pull/2312)
* Do not jump back to the originating window when jumping to definitions with
`g:go_def_mode='gopls'`.
[[GH-2327]](https://github.com/fatih/vim-go/pull/2327)
* Fix getting information about a valid identifier for which gopls returns no
information (e.g. calling `:GoInfo` on a package identifier).
[[GH-2339]](https://github.com/fatih/vim-go/pull/2339)
* Fix tab completion of package names on the cmdline in null modules.
[[GH-2342]](https://github.com/fatih/vim-go/pull/2342)
* Display identifier info correctly when the identifier has no godoc.
[[GH-2373]](https://github.com/fatih/vim-go/pull/2373)
* Fix false positives when saving a buffer and `g:go_metalinter_command` is
`golangci-lint`.
[[GH-2367]](https://github.com/fatih/vim-go/pull/2367)
* Fix `:GoDebugRestart`.
[[GH-2390]](https://github.com/fatih/vim-go/pull/2390)
* Do not execute tests twice in terminal mode.
[[GH-2397]](https://github.com/fatih/vim-go/pull/2397)
* Do not open a new buffer in Neovim when there are compilation errors and
terminal mode is enabled.
[[GH-2401]](https://github.com/fatih/vim-go/pull/2401)
* Fix error due to typo in implementation of `:GoAddWorkspace`.
[[GH-2415]](https://github.com/fatih/vim-go/pull/2401)
* Do not format the file automatically when `g:go_format_autosave` is set and
the file being written is not the current file.
[[GH-2442]](https://github.com/fatih/vim-go/pull/2401)
## 1.20 - (April 22, 2019)

View File

@ -68,7 +68,16 @@ Depending on your installation method, you may have to generate the plugin's
[`help tags`](http://vimhelp.appspot.com/helphelp.txt.html#%3Ahelptags)
manually (e.g. `:helptags ALL`).
We also have an [official vim-go tutorial](https://github.com/fatih/vim-go-tutorial).
We also have an [official vim-go tutorial](https://github.com/fatih/vim-go/wiki).
## FAQ and troubleshooting
The FAQ and troubleshooting tips are in the documentation and can be quickly
accessed using `:help go-troubleshooting`. If you believe you've found a bug or
shortcoming in vim-go that is neither addressed by help nor in [existing
issues](https://github.com/fatih/vim-go/issues), please open an issue with
clear reproduction steps. `:GoReportGitHubIssue` can be used pre-populate a lot
of the information needed when creating a new issue.
## License

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

View File

@ -12,6 +12,9 @@ set cpo&vim
" The full path to the created directory is returned, it is the caller's
" responsibility to clean that up!
fun! gotest#write_file(path, contents) abort
if go#util#has_job()
call go#lsp#CleanWorkspaces()
endif
let l:dir = go#util#tempdir("vim-go-test/testrun/")
let $GOPATH .= ':' . l:dir
let l:full_path = l:dir . '/src/' . a:path
@ -19,15 +22,23 @@ fun! gotest#write_file(path, contents) abort
call mkdir(fnamemodify(l:full_path, ':h'), 'p')
call writefile(a:contents, l:full_path)
exe 'cd ' . l:dir . '/src'
if go#util#has_job()
call go#lsp#AddWorkspaceDirectory(fnamemodify(l:full_path, ':p:h'))
endif
silent exe 'e! ' . a:path
" Set cursor.
let l:lnum = 1
for l:line in a:contents
let l:m = match(l:line, "\x1f")
let l:m = stridx(l:line, "\x1f")
if l:m > -1
call setpos('.', [0, l:lnum, l:m, 0])
let l:byte = line2byte(l:lnum) + l:m
exe 'goto '. l:byte
call setline('.', substitute(getline('.'), "\x1f", '', ''))
silent noautocmd w!
break
endif
@ -42,6 +53,9 @@ endfun
" The file will be copied to a new GOPATH-compliant temporary directory and
" loaded as the current buffer.
fun! gotest#load_fixture(path) abort
if go#util#has_job()
call go#lsp#CleanWorkspaces()
endif
let l:dir = go#util#tempdir("vim-go-test/testrun/")
let $GOPATH .= ':' . l:dir
let l:full_path = l:dir . '/src/' . a:path
@ -51,6 +65,9 @@ fun! gotest#load_fixture(path) abort
silent exe 'noautocmd e ' . a:path
silent exe printf('read %s/test-fixtures/%s', g:vim_go_root, a:path)
silent noautocmd w!
if go#util#has_job()
call go#lsp#AddWorkspaceDirectory(fnamemodify(l:full_path, ':p:h'))
endif
return l:dir
endfun

View File

@ -94,26 +94,26 @@ For Pathogen or Vim |packages|, just clone the repo. For other plugin managers
you may also need to add the lines to your vimrc to execute the plugin
manager's install command.
* Vim 8 |packages|
>
* Vim 8 |packages| >
git clone https://github.com/fatih/vim-go.git \
~/.vim/pack/plugins/start/vim-go
<
* https://github.com/tpope/vim-pathogen >
git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go
<
* https://github.com/junegunn/vim-plug >
Plug 'fatih/vim-go'
Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
<
* https://github.com/Shougo/neobundle.vim >
NeoBundle 'fatih/vim-go'
<
* https://github.com/gmarik/vundle >
Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
Plugin 'fatih/vim-go'
<
* Manual (not recommended) >
@ -376,8 +376,8 @@ CTRL-t
:GoInfo
Show type information about the identifier under the cursor. For example
putting it above a function call is going to show the full function
signature. By default it uses `gocode` to get the type informations. To
change the underlying tool from `gocode` to another tool, see
signature. By default it uses `gopls` to get the type informations. To
change the underlying tool from `gopls` to another tool, see
|'g:go_info_mode'|.
@ -536,7 +536,7 @@ CTRL-t
*:GoGuruScope*
:GoGuruScope [pattern] [pattern2] ... [patternN]
:GoGuruScope [pattern] ...
Changes the custom |'g:go_guru_scope'| setting and overrides it with the
given package patterns. The custom scope is cleared (unset) if `""` is
@ -906,6 +906,16 @@ CTRL-t
tries to preserve cursor position and avoids replacing the buffer with
stderr output.
*:GoAddWorkspace*
:GoAddWorkspace [dir] ...
Add directories to the `gopls` workspace.
*:GoLSPDebugBrowser*
:GoLSPDebugBrowser
Open a browser to see gopls debugging information.
==============================================================================
MAPPINGS *go-mappings*
@ -1204,6 +1214,14 @@ balloonexpr`.
==============================================================================
SETTINGS *go-settings*
*'g:go_version_warning'*
Enable warning when using an unsupported version of Vim. By default it is
enabled.
>
let g:go_version_warning = 1
<
*'g:go_code_completion_enabled'*
Enable code completion with |'omnifunc'|. By default it is enabled.
@ -1228,9 +1246,9 @@ set to 10 seconds . >
<
*'g:go_play_browser_command'*
Browser to use for |:GoPlay| or |:GoDocBrowser|. The url must be added with
`%URL%`, and it's advisable to include `&` to make sure the shell returns. For
example:
Browser to use for |:GoPlay|, |:GoDocBrowser|, and |:GoLSPDebugBrowser|. The
url must be added with `%URL%`, and it's advisable to include `&` to make sure
the shell returns. For example:
>
let g:go_play_browser_command = 'firefox-developer %URL% &'
<
@ -1259,11 +1277,11 @@ updated. By default it's disabled. The delay can be configured with the
*'g:go_info_mode'*
Use this option to define the command to be used for |:GoInfo|. By default
`gocode` is being used as it's the fastest option. But one might also use
`gopls` or `guru` as they cover more cases and are more accurate. Current
valid options are: `[gocode, guru, gopls]` >
let g:go_info_mode = 'gocode'
`gopls` is used, because it is the fastest and is known to be highly accurate.
One might also use `guru` for its accuracy or `gocode` for its performance.
Valid options are `gocode`, `gopls`, and `guru`.
>
let g:go_info_mode = 'gopls'
<
*'g:go_auto_sameids'*
@ -1375,10 +1393,11 @@ a private internal service. Default is 'https://godoc.org'.
*'g:go_def_mode'*
Use this option to define the command to be used for |:GoDef|. By default
`guru` is being used as it covers all edge cases. But one might also use
`godef` as it's faster. Current valid options are: `[guru, godef, gopls]` >
let g:go_def_mode = 'guru'
`gopls` is used, because it is the fastest. One might also use `guru` for its
accuracy or `godef` for its performance. Valid options are `godef`, `gopls`,
and `guru`.
>
let g:go_def_mode = 'gopls'
<
*'g:go_def_mapping_enabled'*
@ -1621,6 +1640,13 @@ according to |'g:go_term_mode'|, otherwise it will run them in the background
just like `:GoBuild`. By default it is disabled.
>
let g:go_term_enabled = 0
<
*'g:go_term_close_on_exit'*
This option is Neovim only. If set to 1 it closes the terminal after the
command run in it exits. By default it is enabled.
>
let g:go_term_close_on_exit = 1
<
*'g:go_alternate_mode'*
@ -1676,7 +1702,8 @@ Specifies whether `gocode` should use a different socket type. By default
When a new Go file is created, vim-go automatically fills the buffer content
with a Go code template. By default, the templates under the `templates`
folder are used. This can be changed with the |'g:go_template_file'| and
|'g:go_template_test_file'| settings.
|'g:go_template_test_file'| settings to either use a different file in the
same `templates` folder, or to use a file stored elsewhere.
If the new file is created in an already prepopulated package (with other Go
files), in this case a Go code template with only the Go package declaration
@ -1691,17 +1718,23 @@ By default it is enabled.
<
*'g:go_template_file'*
Specifies the file under the `templates` folder that is used if a new Go file
is created. Checkout |'g:go_template_autocreate'| for more info. By default
the `hello_world.go` file is used.
Specifies either the file under the `templates` folder that is used if a new
Go file is created. Checkout |'g:go_template_autocreate'| for more info. By
default the `hello_world.go` file is used.
This variable can be set to an absolute path, so the template files don't have
to be stored inside the vim-go directory structure. Useful when you want to
use different templates for different projects.
>
let g:go_template_file = "hello_world.go"
<
*'g:go_template_test_file'*
Specifies the file under the `templates` folder that is used if a new Go test
file is created. Checkout |'g:go_template_autocreate'| for more info. By
default the `hello_world_test.go` file is used.
Like with |'g:go_template_file'|, this specifies the file to use for test
tempaltes. The template file should be under the `templates` folder,
alternatively absolute paths can be used, too. Checkout
|'g:go_template_autocreate'| for more info. By default, the
`hello_world_test.go` file is used.
>
let g:go_template_test_file = "hello_world_test.go"
<
@ -1788,7 +1821,10 @@ Currently accepted values:
debugger-state Expose debugger state in 'g:go_debug_diag'.
debugger-commands Echo communication between vim-go and `dlv`; requests and
responses are recorded in `g:go_debug_commands`.
lsp Record lsp requests and responses in g:go_lsp_log.
lsp Echo communication between vim-go and `gopls`. All
communication is shown in a dedicated window. When
enabled before gopls is started, |:GoLSPDebugBrowser| can
be used to open a browser window to help debug gopls.
>
let g:go_debug = []
<
@ -2027,8 +2063,7 @@ rest of the commands and mappings become available after starting debug mode.
* Make the `:GoDebug*` commands and `(go-debug-*)` mappings available.
The directory of the current buffer is used if [pkg] is empty. Any other
arguments will be passed to the program. When [pkg] is relative, it will
be interpreted relative to the directory of the current buffer.
arguments will be passed to the program.
Use |:GoDebugStop| to stop `dlv` and exit debugging mode.
@ -2183,6 +2218,22 @@ Highlight the current line and breakpoints in the debugger.
==============================================================================
FAQ TROUBLESHOOTING *go-troubleshooting*
How do I troubleshoot problems?~
One of the best ways to understand what vim-go is doing and the output from
the tools to which it delegates is to use leverage the features described in
|'g:go_debug'|.
Completion and other functions that use `gopls` don't work~
Vim-go is heavily reliant on `gopls` for completion and other functionality.
Many of the features that use `gopls` (e.g. completion, jumping to
definitions, showing identifier information, et al.) can be configured to
delegate to other tools. e.g. completion via |'omnifunc'|, |'g:go_info_mode'|
and |'g:go_def_mode'| can be set to use other tools for now (though some of
the alternatives to `gopls` are effectively at their end of life and support
for them from within vim-go may be removed soon).
I get "Unknown function: go#config#..." error when I open a Go file.~
This often happens to vim-polyglot users when new config options are added to

View File

@ -116,4 +116,11 @@ command! -nargs=0 GoReportGitHubIssue call go#issue#New()
" -- iferr
command! -nargs=0 GoIfErr call go#iferr#Generate()
" -- lsp
command! -nargs=+ -complete=dir GoAddWorkspace call go#lsp#AddWorkspaceDirectory(<f-args>)
command! -nargs=0 GoLSPDebugBrowser call go#lsp#DebugBrowser()
" -- term
command! GoToggleTermCloseOnExit call go#term#ToggleCloseOnExit()
" vim: sw=2 ts=2 et

View File

@ -151,7 +151,7 @@ if err := ${1:condition}; err != nil {
endsnippet
# error snippet
snippet errn "Error return " !b
snippet errn "Error return" !b
if err != nil {
return err
}
@ -326,7 +326,7 @@ endsnippet
# struct
snippet st "type T struct { ... }"
type ${1:Type} struct {
${0}
${0}
}
endsnippet

View File

@ -20,19 +20,19 @@ function! s:checkVersion() abort
let l:unsupported = 0
if go#config#VersionWarning() != 0
if has('nvim')
let l:unsupported = !has('nvim-0.3.1')
let l:unsupported = !has('nvim-0.3.2')
else
let l:unsupported = (v:version < 704 || (v:version == 704 && !has('patch2009')))
endif
if l:unsupported == 1
echohl Error
echom "vim-go requires Vim 7.4.2009 or Neovim 0.3.1, but you're using an older version."
echom "vim-go requires Vim 7.4.2009 or Neovim 0.3.2, but you're using an older version."
echom "Please update your Vim for the best vim-go experience."
echom "If you really want to continue you can set this to make the error go away:"
echom " let g:go_version_warning = 0"
echom "Note that some features may error out or behave incorrectly."
echom "Please do not report bugs unless you're using Vim 7.4.2009 or newer or Neovim 0.3.1."
echom "Please do not report bugs unless you're using Vim 7.4.2009 or newer or Neovim 0.3.2."
echohl None
" Make sure people see this.
@ -56,7 +56,7 @@ let s:packages = {
\ 'gogetdoc': ['github.com/zmb3/gogetdoc'],
\ 'goimports': ['golang.org/x/tools/cmd/goimports'],
\ 'golint': ['golang.org/x/lint/golint'],
\ 'gopls': ['golang.org/x/tools/cmd/gopls'],
\ 'gopls': ['golang.org/x/tools/gopls@latest', {}, {'after': function('go#lsp#Restart', [])}],
\ 'gometalinter': ['github.com/alecthomas/gometalinter'],
\ 'golangci-lint': ['github.com/golangci/golangci-lint/cmd/golangci-lint'],
\ 'gomodifytags': ['github.com/fatih/gomodifytags'],
@ -104,9 +104,6 @@ function! s:GoInstallBinaries(updateBinaries, ...)
" vim's executable path is looking in PATH so add our go_bin path to it
let Restore_path = go#util#SetEnv('PATH', go_bin_path . go#util#PathListSep() . $PATH)
" GO111MODULE must be off to install golanci-lint and gometalinter
let Restore_modules = go#util#SetEnv('GO111MODULE', 'off')
" when shellslash is set on MS-* systems, shellescape puts single quotes
" around the output string. cmd on Windows does not handle single quotes
" correctly. Unsetting shellslash forces shellescape to use double quotes
@ -117,10 +114,7 @@ function! s:GoInstallBinaries(updateBinaries, ...)
set noshellslash
endif
let l:dl_cmd = ['go', 'get', '-v', '-d']
if get(g:, "go_get_update", 1) != 0
let l:dl_cmd += ['-u']
endif
let l:get_base_cmd = ['go', 'get', '-v']
" Filter packages from arguments (if any).
let l:packages = {}
@ -142,51 +136,93 @@ function! s:GoInstallBinaries(updateBinaries, ...)
let l:platform = 'windows'
endif
for [binary, pkg] in items(l:packages)
let l:importPath = pkg[0]
let l:oldmore = &more
let &more = 0
let l:run_cmd = copy(l:dl_cmd)
if len(l:pkg) > 1 && get(l:pkg[1], l:platform, '') isnot ''
let l:run_cmd += get(l:pkg[1], l:platform, '')
endif
for [l:binary, l:pkg] in items(l:packages)
let l:importPath = l:pkg[0]
let bin_setting_name = "go_" . binary . "_bin"
" TODO(bc): how to support this with modules? Do we have to clone and then
" install manually? Probably not. I suspect that we can just use GOPATH
" mode and then do the legacy method.
let bin_setting_name = "go_" . l:binary . "_bin"
if exists("g:{bin_setting_name}")
let bin = g:{bin_setting_name}
else
if go#util#IsWin()
let bin = binary . '.exe'
let bin = l:binary . '.exe'
else
let bin = binary
let bin = l:binary
endif
endif
if !executable(bin) || a:updateBinaries == 1
if a:updateBinaries == 1
echo "vim-go: Updating " . binary . ". Reinstalling ". importPath . " to folder " . go_bin_path
echo "vim-go: Updating " . l:binary . ". Reinstalling ". importPath . " to folder " . go_bin_path
else
echo "vim-go: ". binary ." not found. Installing ". importPath . " to folder " . go_bin_path
echo "vim-go: ". l:binary ." not found. Installing ". importPath . " to folder " . go_bin_path
endif
" first download the binary
let [l:out, l:err] = go#util#Exec(l:run_cmd + [l:importPath])
if l:err
echom "Error downloading " . l:importPath . ": " . l:out
if l:importPath =~ "@"
let Restore_modules = go#util#SetEnv('GO111MODULE', 'on')
let l:tmpdir = go#util#tempdir('vim-go')
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:dir = getcwd()
try
execute l:cd . fnameescape(l:tmpdir)
let l:get_cmd = copy(l:get_base_cmd)
" first download the binary
let [l:out, l:err] = go#util#Exec(l:get_cmd + [l:importPath])
if l:err
echom "Error installing " . l:importPath . ": " . l:out
endif
call call(Restore_modules, [])
finally
execute l:cd . fnameescape(l:dir)
endtry
call call(Restore_modules, [])
else
let l:get_cmd = copy(l:get_base_cmd)
let l:get_cmd += ['-d']
if get(g:, "go_get_update", 1) != 0
let l:get_cmd += ['-u']
endif
" GO111MODULE must be off to install gometalinter.
let Restore_modules = go#util#SetEnv('GO111MODULE', 'off')
" first download the binary
let [l:out, l:err] = go#util#Exec(l:get_cmd + [l:importPath])
if l:err
echom "Error downloading " . l:importPath . ": " . l:out
endif
" and then build and install it
let l:build_cmd = ['go', 'build', '-o', go_bin_path . go#util#PathSep() . bin, l:importPath]
if len(l:pkg) > 1 && get(l:pkg[1], l:platform, '') isnot ''
let l:build_cmd += get(l:pkg[1], l:platform, '')
endif
let [l:out, l:err] = go#util#Exec(l:build_cmd)
if l:err
echom "Error installing " . l:importPath . ": " . l:out
endif
call call(Restore_modules, [])
endif
" and then build and install it
let l:build_cmd = ['go', 'build', '-o', go_bin_path . go#util#PathSep() . bin, l:importPath]
let [l:out, l:err] = go#util#Exec(l:build_cmd)
if l:err
echom "Error installing " . l:importPath . ": " . l:out
if len(l:pkg) > 2
call call(get(l:pkg[2], 'after', function('s:noop', [])), [])
endif
endif
endfor
" restore back!
call call(Restore_path, [])
call call(Restore_modules, [])
if resetshellslash
set shellslash
@ -197,6 +233,8 @@ function! s:GoInstallBinaries(updateBinaries, ...)
else
call go#util#EchoInfo('installing finished!')
endif
let &more = l:oldmore
endfunction
" CheckBinaries checks if the necessary binaries to install the Go tool

View File

@ -30,7 +30,7 @@ case "$vim" in
"nvim")
# Use latest stable version.
tag="v0.3.1"
tag="v0.3.2"
giturl="https://github.com/neovim/neovim"
;;
@ -62,7 +62,7 @@ cd "$srcdir"
if [ "$1" = "nvim" ]; then
# TODO: Use macOS binaries on macOS
curl -Ls https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz |
curl -Ls https://github.com/neovim/neovim/releases/download/$tag/nvim-linux64.tar.gz |
tar xzf - -C /tmp/vim-go-test/
mv /tmp/vim-go-test/nvim-linux64 /tmp/vim-go-test/nvim-install
mkdir -p "$installdir/share/nvim/runtime/pack/vim-go/start"

View File

@ -64,6 +64,12 @@ for s:test in sort(s:tests)
endif
try
exe 'call ' . s:test
" sleep to give events a chance to be processed. This is especially
" important for the LSP code to have a chance to run before Vim exits, in
" order to avoid errors trying to write to the gopls channels since Vim
" would otherwise stop gopls before the event handlers were run and result
" in 'stream closed' errors when the events were run _after_ gopls exited.
sleep 50m
catch
let v:errors += [v:exception]
endtry
@ -76,17 +82,17 @@ for s:test in sort(s:tests)
let s:elapsed_time = substitute(reltimestr(reltime(s:started)), '^\s*\(.\{-}\)\s*$', '\1', '')
let s:done += 1
call s:logmessages()
if len(v:errors) > 0
let s:fail += 1
call add(s:logs, printf("--- FAIL %s (%ss)", s:test[:-3], s:elapsed_time))
call s:logmessages()
call extend(s:logs, map(v:errors, '" ". v:val'))
" Reset so we can capture failures of the next test.
let v:errors = []
else
if g:test_verbose is 1
call s:logmessages()
call add(s:logs, printf("--- PASS %s (%ss)", s:test[:-3], s:elapsed_time))
endif
endif