Merge commit '2aa623055499f7996782cbf6a20ad1ab0abe6c1b' into main

main
Buddy Sandidge 7 years ago
commit 4a7f7bd422

@ -0,0 +1,12 @@
---
coverage:
status:
project:
default:
target: auto
threshold: 1
base: auto
comment: false
ignore:
- "!autoload/go/*.vim$"
- "autoload/go/*_test.vim$"

@ -1,5 +1,106 @@
## unplanned ## unplanned
## 1.17 - (March 27, 2018)
FEATURES:
* **Debugger support!** Add integrated support for the
[`delve`](https://github.com/derekparker/delve) debugger. Use
`:GoInstallBinaries` to install `dlv`, and see `:help go-debug` to get
started.
[[GH-1390]](https://github.com/fatih/vim-go/pull/1390)
IMPROVEMENTS:
* Add descriptions to neosnippet abbrevations.
[[GH-1639]](https://github.com/fatih/vim-go/pull/1639)
* Show messages in the location list instead of the quickfix list when
`gometalinter` is run automatically when saving a buffer. Whether the
location list or quickfix list is used can be customized in the usual ways.
[[GH-1652]](https://github.com/fatih/vim-go/pull/1652)
* Redraw the screen before executing blocking calls to gocode.
[[GH-1671]](https://github.com/fatih/vim-go/pull/1671)
* Add `fe` -> `fmt.Errorf()` snippet for NeoSnippet and UltiSnippets.
[[GH-1677]](https://github.com/fatih/vim-go/pull/1677)
* Use the async api when calling guru from neovim.
[[GH-1678]](https://github.com/fatih/vim-go/pull/1678)
* Use the async api when calling gocode to get type info.
[[GH-1697]](https://github.com/fatih/vim-go/pull/1697)
* Cache import path lookups to improve responsiveness.
[[GH-1713]](https://github.com/fatih/vim-go/pull/1713)
BUG FIXES:
* Create quickfix list correctly when tests timeout.
[[GH-1633]](https://github.com/fatih/vim-go/pull/1633)
* Apply `g:go_test_timeout` when running `:GoTestFunc`.
[[GH-1631]](https://github.com/fatih/vim-go/pull/1631)
* The user's configured `g:go_doc_url` variable wasn't working correctly in the
case when the "gogetdoc" command isn't installed.
[[GH-1629]](https://github.com/fatih/vim-go/pull/1629)
* Highlight format specifiers with an index (e.g. `%[2]d`).
[[GH-1634]](https://github.com/fatih/vim-go/pull/1634)
* Respect `g:go_test_show_name` change for `:GoTest` when it changes during a
Vim session.
[[GH-1641]](https://github.com/fatih/vim-go/pull/1641)
* Show `g:go_test_show_name` value for `:GoTest` failures if it's available.
[[GH-1641]](https://github.com/fatih/vim-go/pull/1641)
* Make sure linter errors for the file being saved are shown in vim74 and nvim.
[[GH-1640]](https://github.com/fatih/vim-go/pull/1640)
* Make sure only linter errors for the file being saved are shown in vim8.
Previously, all linter errors for all files in the current file's directory
were being shown.
[[GH-1640]](https://github.com/fatih/vim-go/pull/1640)
* Make sure gometalinter is run on the given directories when arguments are
given to :GoMetaLinter.
[[GH-1640]](https://github.com/fatih/vim-go/pull/1640)
* Do not run disabled linters with `gometalinter`.
[[GH-1648]](https://github.com/fatih/vim-go/pull/1648)
* Do not prompt user to press enter after when `gometalinter` is called in
autosave mode.
[[GH-1654]](https://github.com/fatih/vim-go/pull/1654)
* Fix potential race conditions when using vim8 jobs.
[[GH-1656]](https://github.com/fatih/vim-go/pull/1656)
* Treat `'autowriteall'` the same as `'autowrite'` when determining whether to
write a buffer before calling some commands.
[[GH-1653]](https://github.com/fatih/vim-go/pull/1653)
* Show the file location of test errors when the message is empty or begins
with a newline.
[[GH-1664]](https://github.com/fatih/vim-go/pull/1664)
* Fix minisnip on Windows.
[[GH-1698]](https://github.com/fatih/vim-go/pull/1698)
* Keep alternate filename when loading an autocreate template.
[[GH-1675]](https://github.com/fatih/vim-go/pull/1675)
* Parse the column number in errors correctly in vim8 and neovim.
[[GH-1716]](https://github.com/fatih/vim-go/pull/1716)
* Fix race conditions in the terminal handling for neovim.
[[GH-1721]](https://github.com/fatih/vim-go/pull/1721)
* Put the user back in the original window regardless of the value of
`splitright` after starting a neovim terminal window.
[[GH-1725]](https://github.com/fatih/vim-go/pull/1725)
BACKWARDS INCOMPATIBILITIES:
* Highlighting function and method declarations/calls is fixed. To fix it we
had to remove the meaning of the previous settings. The following setting is
removed:
* `go_highlight_methods`
in favor of the following settings and changes:
* `go_highlight_functions`: This highlights now all function and method
declarations (whereas previously it would also highlight function and
method calls, not anymore)
* `go_highlight_function_calls`: This higlights now all all function and
method calls.
[[GH-1557]](https://github.com/fatih/vim-go/pull/1557)
* Rename g`g:go_metalinter_excludes` to `g:go_metalinter_disabled`.
[[GH-1648]](https://github.com/fatih/vim-go/pull/1648)
* `:GoBuild` doesn't append the `-i` flag anymore due the recent Go 1.10
changes that introduced a build cache.
[[GH-1701]](https://github.com/fatih/vim-go/pull/1701)
## 1.16 - (December 29, 2017) ## 1.16 - (December 29, 2017)
FEATURES: FEATURES:

@ -12,13 +12,13 @@ This plugin adds Go language support for Vim, with the following main features:
with `:GoTest`. Run a single tests with `:GoTestFunc`). with `:GoTest`. Run a single tests with `:GoTestFunc`).
* Quickly execute your current file(s) with `:GoRun`. * Quickly execute your current file(s) with `:GoRun`.
* Improved syntax highlighting and folding. * Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`.
* Completion support via `gocode`. * Completion support via `gocode`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history. * `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`. * Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`. * Look up documentation with `:GoDoc` or `:GoDocBrowser`.
* Easily import packages via `:GoImport`, remove them via `:GoDrop`. * Easily import packages via `:GoImport`, remove them via `:GoDrop`.
* Automatic `GOPATH` detection which works with `gb` and `godep`. Change or * Precise type-safe renaming of identifiers with `:GoRename`.
display `GOPATH` with `:GoPath`.
* See which code is covered by tests with `:GoCoverage`. * See which code is covered by tests with `:GoCoverage`.
* Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`. * Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`.
* Call `gometalinter` with `:GoMetaLinter` to invoke all possible linters * Call `gometalinter` with `:GoMetaLinter` to invoke all possible linters
@ -28,7 +28,6 @@ This plugin adds Go language support for Vim, with the following main features:
errors, or make sure errors are checked with `:GoErrCheck`. errors, or make sure errors are checked with `:GoErrCheck`.
* Advanced source analysis tools utilizing `guru`, such as `:GoImplements`, * Advanced source analysis tools utilizing `guru`, such as `:GoImplements`,
`:GoCallees`, and `:GoReferrers`. `:GoCallees`, and `:GoReferrers`.
* Precise type-safe renaming of identifiers with `:GoRename`.
* ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more * ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more
information. information.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 KiB

@ -1,5 +1,5 @@
function! go#cmd#autowrite() abort function! go#cmd#autowrite() abort
if &autowrite == 1 if &autowrite == 1 || &autowriteall == 1
silent! wall silent! wall
endif endif
endfunction endfunction
@ -17,7 +17,7 @@ function! go#cmd#Build(bang, ...) abort
let args = let args =
\ ["build"] + \ ["build"] +
\ map(copy(a:000), "expand(v:val)") + \ map(copy(a:000), "expand(v:val)") +
\ ["-i", ".", "errors"] \ [".", "errors"]
" Vim async. " Vim async.
if go#util#has_job() if go#util#has_job()
@ -269,7 +269,7 @@ function s:cmd_job(args) abort
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
function! s:error_info_cb(job, exit_status, data) closure abort function! s:complete(job, exit_status, data) closure abort
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': a:args.cmd[1], \ 'type': a:args.cmd[1],
@ -288,12 +288,13 @@ function s:cmd_job(args) abort
call go#statusline#Update(status_dir, status) call go#statusline#Update(status_dir, status)
endfunction endfunction
let a:args.error_info_cb = funcref('s:error_info_cb') let a:args.complete = funcref('s:complete')
let callbacks = go#job#Spawn(a:args) let callbacks = go#job#Spawn(a:args)
let start_options = { let start_options = {
\ 'callback': callbacks.callback, \ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb, \ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ } \ }
" pre start " pre start

@ -0,0 +1,30 @@
func! Test_GoBuildErrors()
try
let l:filename = 'cmd/bad.go'
let l:tmp = gotest#load_fixture(l:filename)
exe 'cd ' . l:tmp . '/src/cmd'
" set the compiler type so that the errorformat option will be set
" correctly.
compiler go
let expected = [{'lnum': 4, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'undefined: notafunc'}]
" clear the quickfix lists
call setqflist([], 'r')
call go#cmd#Build(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, l:expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" vim: sw=2 ts=2 et

@ -1,58 +1,51 @@
let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix' let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix'
function! s:gocodeCurrentBuffer() abort function! s:gocodeCommand(cmd, args) abort
let file = tempname()
call writefile(go#util#GetLines(), file)
return file
endfunction
function! s:gocodeCommand(cmd, preargs, args) abort
for i in range(0, len(a:args) - 1)
let a:args[i] = go#util#Shellescape(a:args[i])
endfor
for i in range(0, len(a:preargs) - 1)
let a:preargs[i] = go#util#Shellescape(a:preargs[i])
endfor
let bin_path = go#path#CheckBinPath("gocode") let bin_path = go#path#CheckBinPath("gocode")
if empty(bin_path) if empty(bin_path)
return return []
endif endif
let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type)
let cmd = [bin_path]
let cmd = extend(cmd, ['-sock', socket_type])
let cmd = extend(cmd, ['-f', 'vim'])
let cmd = extend(cmd, [a:cmd])
let cmd = extend(cmd, a:args)
return cmd
endfunction
function! s:sync_gocode(cmd, args, input) abort
" We might hit cache problems, as gocode doesn't handle different GOPATHs " We might hit cache problems, as gocode doesn't handle different GOPATHs
" well. See: https://github.com/nsf/gocode/issues/239 " well. See: https://github.com/nsf/gocode/issues/239
let old_goroot = $GOROOT let old_goroot = $GOROOT
let $GOROOT = go#util#env("goroot") let $GOROOT = go#util#env("goroot")
try try
let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type) let cmd = s:gocodeCommand(a:cmd, a:args)
let cmd = printf('%s -sock %s %s %s %s', " gocode can sometimes be slow, so redraw now to avoid waiting for gocode
\ go#util#Shellescape(bin_path), " to return before redrawing automatically.
\ socket_type, redraw
\ join(a:preargs),
\ go#util#Shellescape(a:cmd), let [l:result, l:err] = go#util#Exec(cmd, a:input)
\ join(a:args)
\ )
let result = go#util#System(cmd)
finally finally
let $GOROOT = old_goroot let $GOROOT = old_goroot
endtry endtry
if go#util#ShellError() != 0 if l:err != 0
return "[\"0\", []]" return "[0, []]"
else endif
if &encoding != 'utf-8'
let result = iconv(result, 'utf-8', &encoding) if &encoding != 'utf-8'
endif let l:result = iconv(l:result, 'utf-8', &encoding)
return result
endif endif
endfunction
function! s:gocodeCurrentBufferOpt(filename) abort return l:result
return '-in=' . a:filename
endfunction endfunction
" TODO(bc): reset when gocode isn't running
let s:optionsEnabled = 0 let s:optionsEnabled = 0
function! s:gocodeEnableOptions() abort function! s:gocodeEnableOptions() abort
if s:optionsEnabled if s:optionsEnabled
@ -78,62 +71,161 @@ endfunction
function! s:gocodeAutocomplete() abort function! s:gocodeAutocomplete() abort
call s:gocodeEnableOptions() call s:gocodeEnableOptions()
let filename = s:gocodeCurrentBuffer() " use the offset as is, because the cursor position is the position for
let result = s:gocodeCommand('autocomplete', " which autocomplete candidates are needed.
\ [s:gocodeCurrentBufferOpt(filename), '-f=vim'], return s:sync_gocode('autocomplete',
\ [expand('%:p'), go#util#OffsetCursor()]) \ [expand('%:p'), go#util#OffsetCursor()],
call delete(filename) \ go#util#GetLines())
return result
endfunction endfunction
" go#complete#GoInfo returns the description of the identifier under the
" cursor.
function! go#complete#GetInfo() abort function! go#complete#GetInfo() abort
return s:sync_info(0)
endfunction
function! go#complete#Info(auto) abort
if go#util#has_job()
return s:async_info(a:auto)
else
return s:sync_info(a:auto)
endif
endfunction
function! s:async_info(auto)
if exists("s:async_info_job")
call job_stop(s:async_info_job)
unlet s:async_info_job
endif
let state = {
\ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'messages': [],
\ 'auto': a:auto
\ }
function! s:callback(chan, msg) dict
let l:msg = a:msg
if &encoding != 'utf-8'
let l:msg = iconv(l:msg, 'utf-8', &encoding)
endif
call add(self.messages, l:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
if self.closed
call self.complete()
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call self.complete()
endif
endfunction
function state.complete() dict
if self.exit_status != 0
return
endif
let result = s:info_filter(self.auto, join(self.messages, "\n"))
call s:info_complete(self.auto, result)
endfunction
" add 1 to the offset, so that the position at the cursor will be included
" in gocode's search
let offset = go#util#OffsetCursor()+1 let offset = go#util#OffsetCursor()+1
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete', " We might hit cache problems, as gocode doesn't handle different GOPATHs
\ [s:gocodeCurrentBufferOpt(filename), '-f=godit'], " well. See: https://github.com/nsf/gocode/issues/239
let env = {
\ "GOROOT": go#util#env("goroot")
\ }
let cmd = s:gocodeCommand('autocomplete',
\ [expand('%:p'), offset]) \ [expand('%:p'), offset])
call delete(filename)
" first line is: Charcount,,NumberOfCandidates, i.e: 8,,1 " TODO(bc): Don't write the buffer to a file; pass the buffer directrly to
" following lines are candiates, i.e: func foo(name string),,foo( " gocode's stdin. It shouldn't be necessary to use {in_io: 'file', in_name:
let out = split(result, '\n') " s:gocodeFile()}, but unfortunately {in_io: 'buffer', in_buf: bufnr('%')}
" should work.
let options = {
\ 'env': env,
\ 'in_io': 'file',
\ 'in_name': s:gocodeFile(),
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state)
\ }
" no candidates are found let s:async_info_job = job_start(cmd, options)
if len(out) == 1 endfunction
function! s:gocodeFile()
let file = tempname()
call writefile(go#util#GetLines(), file)
return file
endfunction
function! s:sync_info(auto)
" auto is true if we were called by g:go_auto_type_info's autocmd
" add 1 to the offset, so that the position at the cursor will be included
" in gocode's search
let offset = go#util#OffsetCursor()+1
let result = s:sync_gocode('autocomplete',
\ [expand('%:p'), offset],
\ go#util#GetLines())
let result = s:info_filter(a:auto, result)
call s:info_complete(a:auto, result)
endfunction
function! s:info_filter(auto, result) abort
if empty(a:result)
return "" return ""
endif endif
" only one candidate is found let l:result = eval(a:result)
if len(out) == 2 if len(l:result) != 2
return split(out[1], ',,')[0] return ""
endif endif
" to many candidates are available, pick one that maches the word under the let l:candidates = l:result[1]
" cursor if len(l:candidates) == 1
let infos = [] " When gocode panics in vim mode, it returns
for info in out[1:] " [0, [{'word': 'PANIC', 'abbr': 'PANIC PANIC PANIC', 'info': 'PANIC PANIC PANIC'}]]
call add(infos, split(info, ',,')[0]) if a:auto && l:candidates[0].info ==# "PANIC PANIC PANIC"
endfor return ""
endif
return l:candidates[0].info
endif
let filtered = []
let wordMatch = '\<' . expand("<cword>") . '\>' let wordMatch = '\<' . expand("<cword>") . '\>'
" escape single quotes in wordMatch before passing it to filter " escape single quotes in wordMatch before passing it to filter
let wordMatch = substitute(wordMatch, "'", "''", "g") let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(infos, "v:val =~ '".wordMatch."'") let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'")
if len(filtered) == 1 if len(l:filtered) != 1
return filtered[0] return ""
endif endif
return "" return l:filtered[0].info
endfunction endfunction
function! go#complete#Info(auto) abort function! s:info_complete(auto, result) abort
" auto is true if we were called by g:go_auto_type_info's autocmd if !empty(a:result)
let result = go#complete#GetInfo() echo "vim-go: " | echohl Function | echon a:result | echohl None
if !empty(result)
" if auto, and the result is a PANIC by gocode, hide it
if a:auto && result ==# 'PANIC PANIC PANIC' | return | endif
echo "vim-go: " | echohl Function | echon result | echohl None
endif endif
endfunction endfunction
@ -142,20 +234,22 @@ function! s:trim_bracket(val) abort
return a:val return a:val
endfunction endfunction
let s:completions = ""
function! go#complete#Complete(findstart, base) abort function! go#complete#Complete(findstart, base) abort
"findstart = 1 when we need to get the text length "findstart = 1 when we need to get the text length
if a:findstart == 1 if a:findstart == 1
execute "silent let g:gocomplete_completions = " . s:gocodeAutocomplete() execute "silent let s:completions = " . s:gocodeAutocomplete()
return col('.') - g:gocomplete_completions[0] - 1 return col('.') - s:completions[0] - 1
"findstart = 0 when we need to return the list of completions "findstart = 0 when we need to return the list of completions
else else
let s = getline(".")[col('.') - 1] let s = getline(".")[col('.') - 1]
if s =~ '[(){}\{\}]' if s =~ '[(){}\{\}]'
return map(copy(g:gocomplete_completions[1]), 's:trim_bracket(v:val)') return map(copy(s:completions[1]), 's:trim_bracket(v:val)')
endif endif
return g:gocomplete_completions[1]
return s:completions[1]
endif endif
endf endfunction
function! go#complete#ToggleAutoTypeInfo() abort function! go#complete#ToggleAutoTypeInfo() abort
if get(g:, "go_auto_type_info", 0) if get(g:, "go_auto_type_info", 0)
@ -168,5 +262,4 @@ function! go#complete#ToggleAutoTypeInfo() abort
call go#util#EchoProgress("auto type info enabled") call go#util#EchoProgress("auto type info enabled")
endfunction endfunction
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -45,13 +45,13 @@ function! go#coverage#Buffer(bang, ...) abort
let l:tmpname = tempname() let l:tmpname = tempname()
if get(g:, 'go_echo_command_info', 1) if get(g:, 'go_echo_command_info', 1)
echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None call go#util#EchoProgress("testing...")
endif endif
if go#util#has_job() if go#util#has_job()
call s:coverage_job({ call s:coverage_job({
\ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000, \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000,
\ 'custom_cb': function('s:coverage_callback', [l:tmpname]), \ 'complete': function('s:coverage_callback', [l:tmpname]),
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoTest', \ 'for': 'GoTest',
\ }) \ })
@ -107,7 +107,7 @@ function! go#coverage#Browser(bang, ...) abort
if go#util#has_job() if go#util#has_job()
call s:coverage_job({ call s:coverage_job({
\ 'cmd': ['go', 'test', '-coverprofile', l:tmpname], \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname],
\ 'custom_cb': function('s:coverage_browser_callback', [l:tmpname]), \ 'complete': function('s:coverage_browser_callback', [l:tmpname]),
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoTest', \ 'for': 'GoTest',
\ }) \ })
@ -278,7 +278,8 @@ function s:coverage_job(args)
call go#cmd#autowrite() call go#cmd#autowrite()
let status_dir = expand('%:p:h') let status_dir = expand('%:p:h')
function! s:error_info_cb(job, exit_status, data) closure let Complete = a:args.complete
function! s:complete(job, exit_status, data) closure
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': "coverage", \ 'type': "coverage",
@ -290,14 +291,16 @@ function s:coverage_job(args)
endif endif
call go#statusline#Update(status_dir, status) call go#statusline#Update(status_dir, status)
return Complete(a:job, a:exit_status, a:data)
endfunction endfunction
let a:args.error_info_cb = funcref('s:error_info_cb') let a:args.complete = funcref('s:complete')
let callbacks = go#job#Spawn(a:args) let callbacks = go#job#Spawn(a:args)
let start_options = { let start_options = {
\ 'callback': callbacks.callback, \ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb, \ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ } \ }
" pre start " pre start

@ -0,0 +1,904 @@
scriptencoding utf-8
if !exists('g:go_debug_windows')
let g:go_debug_windows = {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ }
endif
if !exists('g:go_debug_address')
let g:go_debug_address = '127.0.0.1:8181'
endif
if !exists('s:state')
let s:state = {
\ 'rpcid': 1,
\ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {},
\ 'localVars': {},
\ 'functionArgs': {},
\ 'message': [],
\ 'is_test': 0,
\}
if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state
endif
endif
if !exists('s:start_args')
let s:start_args = []
endif
function! s:groutineID() abort
return s:state['currentThread'].goroutineID
endfunction
function! s:exit(job, status) abort
if has_key(s:state, 'job')
call remove(s:state, 'job')
endif
call s:clearState()
if a:status > 0
call go#util#EchoError(s:state['message'])
endif
endfunction
function! s:logger(prefix, ch, msg) abort
let l:cur_win = bufwinnr('')
let l:log_win = bufwinnr(bufnr('__GODEBUG_OUTPUT__'))
if l:log_win == -1
return
endif
exe l:log_win 'wincmd w'
try
setlocal modifiable
if getline(1) == ''
call setline('$', a:prefix . a:msg)
else
call append('$', a:prefix . a:msg)
endif
normal! G
setlocal nomodifiable
finally
exe l:cur_win 'wincmd w'
endtry
endfunction
function! s:call_jsonrpc(method, ...) abort
if go#util#HasDebug('debugger-commands')
if !exists('g:go_debug_commands')
let g:go_debug_commands = []
endif
echom 'sending to dlv ' . a:method
endif
if len(a:000) > 0 && type(a:000[0]) == v:t_func
let Cb = a:000[0]
let args = a:000[1:]
else
let Cb = v:none
let args = a:000
endif
let s:state['rpcid'] += 1
let req_json = json_encode({
\ 'id': s:state['rpcid'],
\ 'method': a:method,
\ 'params': args,
\})
try
" Use callback
if type(Cb) == v:t_func
let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb})
call ch_sendraw(s:ch, req_json)
if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(g:go_debug_commands, {
\ 'request': req_json,
\ 'response': Cb,
\ })
endif
return
endif
let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000})
call ch_sendraw(ch, req_json)
let resp_json = ch_readraw(ch)
if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(g:go_debug_commands, {
\ 'request': req_json,
\ 'response': resp_json,
\ })
endif
let obj = json_decode(resp_json)
if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error)
throw obj.error
endif
return obj
catch
throw substitute(v:exception, '^Vim', '', '')
endtry
endfunction
" Update the location of the current breakpoint or line we're halted on based on
" response from dlv.
function! s:update_breakpoint(res) abort
if type(a:res) ==# v:t_none
return
endif
let state = a:res.result.State
if !has_key(state, 'currentThread')
return
endif
let s:state['currentThread'] = state.currentThread
let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
if len(bufs) == 0
return
endif
exe bufs[0][0] 'wincmd w'
let filename = state.currentThread.file
let linenr = state.currentThread.line
let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
if oldfile != filename
silent! exe 'edit' filename
endif
silent! exe 'norm!' linenr.'G'
silent! normal! zvzz
silent! sign unplace 9999
silent! exe 'sign place 9999 line=' . linenr . ' name=godebugcurline file=' . filename
endfunction
" Populate the stacktrace window.
function! s:show_stacktrace(res) abort
if !has_key(a:res, 'result')
return
endif
let l:stack_win = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
if l:stack_win == -1
return
endif
let l:cur_win = bufwinnr('')
exe l:stack_win 'wincmd w'
try
setlocal modifiable
silent %delete _
for i in range(len(a:res.result.Locations))
let loc = a:res.result.Locations[i]
call setline(i+1, printf('%s - %s:%d', loc.function.name, fnamemodify(loc.file, ':p'), loc.line))
endfor
finally
setlocal nomodifiable
exe l:cur_win 'wincmd w'
endtry
endfunction
" Populate the variable window.
function! s:show_variables() abort
let l:var_win = bufwinnr(bufnr('__GODEBUG_VARIABLES__'))
if l:var_win == -1
return
endif
let l:cur_win = bufwinnr('')
exe l:var_win 'wincmd w'
try
setlocal modifiable
silent %delete _
let v = []
let v += ['# Local Variables']
if type(get(s:state, 'localVars', [])) is type([])
for c in s:state['localVars']
let v += split(s:eval_tree(c, 0), "\n")
endfor
endif
let v += ['']
let v += ['# Function Arguments']
if type(get(s:state, 'functionArgs', [])) is type([])
for c in s:state['functionArgs']
let v += split(s:eval_tree(c, 0), "\n")
endfor
endif
call setline(1, v)
finally
setlocal nomodifiable
exe l:cur_win 'wincmd w'
endtry
endfunction
function! s:clearState() abort
let s:state['currentThread'] = {}
let s:state['localVars'] = {}
let s:state['functionArgs'] = {}
let s:state['message'] = []
silent! sign unplace 9999
endfunction
function! s:stop() abort
call s:clearState()
if has_key(s:state, 'job')
call job_stop(s:state['job'])
call remove(s:state, 'job')
endif
endfunction
function! go#debug#Stop() abort
" Remove signs.
for k in keys(s:state['breakpoint'])
let bt = s:state['breakpoint'][k]
if bt.id >= 0
silent exe 'sign unplace ' . bt.id
endif
endfor
" Remove all commands and add back the default commands.
for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
exe 'delcommand' k
endfor
command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, <f-args>)
command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, <f-args>)
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
" Remove all mappings.
for k in map(split(execute('map <Plug>(go-debug-'), "\n")[1:], 'matchstr(v:val, "^n\\s\\+\\zs\\S\\+")')
exe 'unmap' k
endfor
call s:stop()
let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
if len(bufs) > 0
exe bufs[0][0] 'wincmd w'
else
wincmd p
endif
silent! exe bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
set noballooneval
set balloonexpr=
endfunction
function! s:goto_file() abort
let m = matchlist(getline('.'), ' - \(.*\):\([0-9]\+\)$')
if m[1] == ''
return
endif
let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
if len(bufs) == 0
return
endif
exe bufs[0][0] 'wincmd w'
let filename = m[1]
let linenr = m[2]
let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
if oldfile != filename
silent! exe 'edit' filename
endif
silent! exe 'norm!' linenr.'G'
silent! normal! zvzz
endfunction
function! s:delete_expands()
let nr = line('.')
while 1
let l = getline(nr+1)
if empty(l) || l =~ '^\S'
return
endif
silent! exe (nr+1) . 'd _'
endwhile
silent! exe 'norm!' nr.'G'
endfunction
function! s:expand_var() abort
" Get name from struct line.
let name = matchstr(getline('.'), '^[^:]\+\ze: [a-zA-Z0-9\.·]\+{\.\.\.}$')
" Anonymous struct
if name == ''
let name = matchstr(getline('.'), '^[^:]\+\ze: struct {.\{-}}$')
endif
if name != ''
setlocal modifiable
let not_open = getline(line('.')+1) !~ '^ '
let l = line('.')
call s:delete_expands()
if not_open
call append(l, split(s:eval(name), "\n")[1:])
endif
silent! exe 'norm!' l.'G'
setlocal nomodifiable
return
endif
" Expand maps
let m = matchlist(getline('.'), '^[^:]\+\ze: map.\{-}\[\(\d\+\)\]$')
if len(m) > 0 && m[1] != ''
setlocal modifiable
let not_open = getline(line('.')+1) !~ '^ '
let l = line('.')
call s:delete_expands()
if not_open
" TODO: Not sure how to do this yet... Need to get keys of the map.
" let vs = ''
" for i in range(0, min([10, m[1]-1]))
" let vs .= ' ' . s:eval(printf("%s[%s]", m[0], ))
" endfor
" call append(l, split(vs, "\n"))
endif
silent! exe 'norm!' l.'G'
setlocal nomodifiable
return
endif
" Expand string.
let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(string\)\[\([0-9]\+\)\]\(: .\{-}\)\?$')
if len(m) > 0 && m[1] != ''
setlocal modifiable
let not_open = getline(line('.')+1) !~ '^ '
let l = line('.')
call s:delete_expands()
if not_open
let vs = ''
for i in range(0, min([10, m[3]-1]))
let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
endfor
call append(l, split(vs, "\n"))
endif
silent! exe 'norm!' l.'G'
setlocal nomodifiable
return
endif
" Expand slice.
let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(\[\]\w\{-}\)\[\([0-9]\+\)\]$')
if len(m) > 0 && m[1] != ''
setlocal modifiable
let not_open = getline(line('.')+1) !~ '^ '
let l = line('.')
call s:delete_expands()
if not_open
let vs = ''
for i in range(0, min([10, m[3]-1]))
let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
endfor
call append(l, split(vs, "\n"))
endif
silent! exe 'norm!' l.'G'
setlocal nomodifiable
return
endif
endfunction
function! s:start_cb(ch, json) abort
let res = json_decode(a:json)
if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
throw res.error
endif
if empty(res) || !has_key(res, 'result')
return
endif
for bt in res.result.Breakpoints
if bt.id >= 0
let s:state['breakpoint'][bt.id] = bt
exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
endif
endfor
let oldbuf = bufnr('%')
silent! only!
let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
if winnum != -1
return
endif
if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != ''
exe 'silent ' . g:go_debug_windows['stack']
silent file `='__GODEBUG_STACKTRACE__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugstacktrace
nmap <buffer> <cr> :<c-u>call <SID>goto_file()<cr>
nmap <buffer> q <Plug>(go-debug-stop)
endif
if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != ''
exe 'silent ' . g:go_debug_windows['out']
silent file `='__GODEBUG_OUTPUT__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugoutput
nmap <buffer> q <Plug>(go-debug-stop)
endif
if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != ''
exe 'silent ' . g:go_debug_windows['vars']
silent file `='__GODEBUG_VARIABLES__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugvariables
call append(0, ["# Local Variables", "", "# Function Arguments"])
nmap <buffer> <silent> <cr> :<c-u>call <SID>expand_var()<cr>
nmap <buffer> q <Plug>(go-debug-stop)
endif
silent! delcommand GoDebugStart
silent! delcommand GoDebugTest
command! -nargs=0 GoDebugContinue call go#debug#Stack('continue')
command! -nargs=0 GoDebugNext call go#debug#Stack('next')
command! -nargs=0 GoDebugStep call go#debug#Stack('step')
command! -nargs=0 GoDebugStepOut call go#debug#Stack('stepOut')
command! -nargs=0 GoDebugRestart call go#debug#Restart()
command! -nargs=0 GoDebugStop call go#debug#Stop()
command! -nargs=* GoDebugSet call go#debug#Set(<f-args>)
command! -nargs=1 GoDebugPrint call go#debug#Print(<q-args>)
nnoremap <silent> <Plug>(go-debug-breakpoint) :<C-u>call go#debug#Breakpoint()<CR>
nnoremap <silent> <Plug>(go-debug-next) :<C-u>call go#debug#Stack('next')<CR>
nnoremap <silent> <Plug>(go-debug-step) :<C-u>call go#debug#Stack('step')<CR>
nnoremap <silent> <Plug>(go-debug-stepout) :<C-u>call go#debug#Stack('stepout')<CR>
nnoremap <silent> <Plug>(go-debug-continue) :<C-u>call go#debug#Stack('continue')<CR>
nnoremap <silent> <Plug>(go-debug-stop) :<C-u>call go#debug#Stop()<CR>
nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR>
nmap <F5> <Plug>(go-debug-continue)
nmap <F6> <Plug>(go-debug-print)
nmap <F9> <Plug>(go-debug-breakpoint)
nmap <F10> <Plug>(go-debug-next)
nmap <F11> <Plug>(go-debug-step)
set balloonexpr=go#debug#BalloonExpr()
set ballooneval
exe bufwinnr(oldbuf) 'wincmd w'
endfunction
function! s:err_cb(ch, msg) abort
call go#util#EchoError(a:msg)
let s:state['message'] += [a:msg]
endfunction
function! s:out_cb(ch, msg) abort
call go#util#EchoProgress(a:msg)
let s:state['message'] += [a:msg]
" TODO: why do this in this callback?
if stridx(a:msg, g:go_debug_address) != -1
call ch_setoptions(a:ch, {
\ 'out_cb': function('s:logger', ['OUT: ']),
\ 'err_cb': function('s:logger', ['ERR: ']),
\})
" Tell dlv about the breakpoints that the user added before delve started.
let l:breaks = copy(s:state.breakpoint)
let s:state['breakpoint'] = {}
for l:bt in values(l:breaks)
call go#debug#Breakpoint(bt.line)
endfor
call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb'))
endif
endfunction
" Start the debug mode. The first argument is the package name to compile and
" debug, anything else will be passed to the running program.
function! go#debug#Start(is_test, ...) abort
if has('nvim')
call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(')
return
endif
if !go#util#has_job()
call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.')
return
endif
" It's already running.
if has_key(s:state, 'job') && job_status(s:state['job']) == 'run'
return
endif
let s:start_args = a:000
if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state
endif
" cd in to test directory; this is also what running "go test" does.
if a:is_test
lcd %:p:h
endif
let s:state.is_test = a:is_test
let dlv = go#path#CheckBinPath("dlv")
if empty(dlv)
return
endif
try
if len(a:000) > 0
let l:pkgname = a:1
" Expand .; otherwise this won't work from a tmp dir.
if l:pkgname[0] == '.'
let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:]
endif
else
let l:pkgname = go#package#FromPath(getcwd())
endif
let l:args = []
if len(a:000) > 1
let l:args = ['--'] + a:000[1:]
endif
let l:cmd = [
\ dlv,
\ (a:is_test ? 'test' : 'debug'),
\ '--output', tempname(),
\ '--headless',
\ '--api-version', '2',
\ '--log',
\ '--listen', g:go_debug_address,
\ '--accept-multiclient',
\]
if get(g:, 'go_build_tags', '') isnot ''
let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags]
endif
let l:cmd += l:args
call go#util#EchoProgress('Starting GoDebug...')
let s:state['message'] = []
let s:state['job'] = job_start(l:cmd, {
\ 'out_cb': function('s:out_cb'),
\ 'err_cb': function('s:err_cb'),
\ 'exit_cb': function('s:exit'),
\ 'stoponexit': 'kill',
\})
catch
call go#util#EchoError(v:exception)
endtry
endfunction
" Translate a reflect kind constant to a human string.
function! s:reflect_kind(k)
" Kind constants from Go's reflect package.
return [
\ 'Invalid Kind',
\ 'Bool',
\ 'Int',
\ 'Int8',
\ 'Int16',
\ 'Int32',
\ 'Int64',
\ 'Uint',
\ 'Uint8',
\ 'Uint16',
\ 'Uint32',
\ 'Uint64',
\ 'Uintptr',
\ 'Float32',
\ 'Float64',
\ 'Complex64',
\ 'Complex128',
\ 'Array',
\ 'Chan',
\ 'Func',
\ 'Interface',
\ 'Map',
\ 'Ptr',
\ 'Slice',
\ 'String',
\ 'Struct',
\ 'UnsafePointer',
\ ][a:k]
endfunction
function! s:eval_tree(var, nest) abort
if a:var.name =~ '^\~'
return ''
endif
let nest = a:nest
let v = ''
let kind = s:reflect_kind(a:var.kind)
if !empty(a:var.name)
let v .= repeat(' ', nest) . a:var.name . ': '
if kind == 'Bool'
let v .= printf("%s\n", a:var.value)
elseif kind == 'Struct'
" Anonymous struct
if a:var.type[:8] == 'struct { '
let v .= printf("%s\n", a:var.type)
else
let v .= printf("%s{...}\n", a:var.type)
endif
elseif kind == 'String'
let v .= printf("%s[%d]%s\n", a:var.type, a:var.len,
\ len(a:var.value) > 0 ? ': ' . a:var.value : '')
elseif kind == 'Slice' || kind == 'String' || kind == 'Map' || kind == 'Array'
let v .= printf("%s[%d]\n", a:var.type, a:var.len)
elseif kind == 'Chan' || kind == 'Func' || kind == 'Interface'
let v .= printf("%s\n", a:var.type)
elseif kind == 'Ptr'
" TODO: We can do something more useful here.
let v .= printf("%s\n", a:var.type)
elseif kind == 'Complex64' || kind == 'Complex128'
let v .= printf("%s%s\n", a:var.type, a:var.value)
" Int, Float
else
let v .= printf("%s(%s)\n", a:var.type, a:var.value)
endif
else
let nest -= 1
endif
if index(['Chan', 'Complex64', 'Complex128'], kind) == -1 && a:var.type != 'error'
for c in a:var.children
let v .= s:eval_tree(c, nest+1)
endfor
endif
return v
endfunction
function! s:eval(arg) abort
try
let res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID
let res = s:call_jsonrpc('RPCServer.Eval', {
\ 'expr': a:arg,
\ 'scope': {'GoroutineID': goroutineID}
\ })
return s:eval_tree(res.result.Variable, 0)
catch
call go#util#EchoError(v:exception)
return ''
endtry
endfunction
function! go#debug#BalloonExpr() abort
silent! let l:v = s:eval(v:beval_text)
return l:v
endfunction
function! go#debug#Print(arg) abort
try
echo substitute(s:eval(a:arg), "\n$", "", 0)
catch
call go#util#EchoError(v:exception)
endtry
endfunction
function! s:update_variables() abort
" FollowPointers requests pointers to be automatically dereferenced.
" MaxVariableRecurse is how far to recurse when evaluating nested types.
" MaxStringLen is the maximum number of bytes read from a string
" MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
" MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
let l:cfg = {
\ 'scope': {'GoroutineID': s:groutineID()},
\ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20}
\ }
try
let res = s:call_jsonrpc('RPCServer.ListLocalVars', l:cfg)
let s:state['localVars'] = res.result['Variables']
catch
call go#util#EchoError(v:exception)
endtry
try
let res = s:call_jsonrpc('RPCServer.ListFunctionArgs', l:cfg)
let s:state['functionArgs'] = res.result['Args']
catch
call go#util#EchoError(v:exception)
endtry
call s:show_variables()
endfunction
function! go#debug#Set(symbol, value) abort
try
let res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID
call s:call_jsonrpc('RPCServer.Set', {
\ 'symbol': a:symbol,
\ 'value': a:value,
\ 'scope': {'GoroutineID': goroutineID}
\ })
catch
call go#util#EchoError(v:exception)
endtry
call s:update_variables()
endfunction
function! s:update_stacktrace() abort
try
let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
call s:show_stacktrace(res)
catch
call go#util#EchoError(v:exception)
endtry
endfunction
function! s:stack_cb(ch, json) abort
let s:stack_name = ''
let res = json_decode(a:json)
if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
call go#util#EchoError(res.error)
call s:clearState()
call go#debug#Restart()
return
endif
if empty(res) || !has_key(res, 'result')
return
endif
call s:update_breakpoint(res)
call s:update_stacktrace()
call s:update_variables()
endfunction
" Send a command to change the cursor location to Delve.
"
" a:name must be one of continue, next, step, or stepOut.
function! go#debug#Stack(name) abort
let l:name = a:name
" Run continue if the program hasn't started yet.
if s:state.running is 0
let s:state.running = 1
let l:name = 'continue'
endif
" Add a breakpoint to the main.Main if the user didn't define any.
if len(s:state['breakpoint']) is 0
if go#debug#Breakpoint() isnot 0
let s:state.running = 0
return
endif
endif
try
" TODO: document why this is needed.
if l:name is# 'next' && get(s:, 'stack_name', '') is# 'next'
call s:call_jsonrpc('RPCServer.CancelNext')
endif
let s:stack_name = l:name
call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name})
catch
call go#util#EchoError(v:exception)
endtry
endfunction
function! go#debug#Restart() abort
try
call job_stop(s:state['job'])
while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run'
sleep 50m
endwhile
let l:breaks = s:state['breakpoint']
let s:state = {
\ 'rpcid': 1,
\ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {},
\ 'localVars': {},
\ 'functionArgs': {},
\ 'message': [],
\}
" Preserve breakpoints.
for bt in values(l:breaks)
" TODO: should use correct filename
exe 'sign unplace '. bt.id .' file=' . bt.file
call go#debug#Breakpoint(bt.line)
endfor
call call('go#debug#Start', s:start_args)
catch
call go#util#EchoError(v:exception)
endtry
endfunction
" Report if debugger mode is active.
function! s:isActive()
return len(s:state['message']) > 0
endfunction
" Toggle breakpoint. Returns 0 on success and 1 on failure.
function! go#debug#Breakpoint(...) abort
let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
" Get line number from argument.
if len(a:000) > 0
let linenr = str2nr(a:1)
if linenr is 0
call go#util#EchoError('not a number: ' . a:1)
return 0
endif
else
let linenr = line('.')
endif
try
" Check if we already have a breakpoint for this line.
let found = v:none
for k in keys(s:state.breakpoint)
let bt = s:state.breakpoint[k]
if bt.file == l:filename && bt.line == linenr
let found = bt
break
endif
endfor
" Remove breakpoint.
if type(found) == v:t_dict
call remove(s:state['breakpoint'], bt.id)
exe 'sign unplace '. found.id .' file=' . found.file
if s:isActive()
let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id})
endif
" Add breakpoint.
else
if s:isActive()
let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}})
let bt = res.result.Breakpoint
exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
let s:state['breakpoint'][bt.id] = bt
else
let id = len(s:state['breakpoint']) + 1
let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr}
exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename
endif
endif
catch
call go#util#EchoError(v:exception)
return 1
endtry
return 0
endfunction
sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent
fun! s:hi()
hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black
hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White
endfun
augroup vim-go-breakpoint
autocmd!
autocmd ColorScheme * call s:hi()
augroup end
call s:hi()
" vim: sw=2 ts=2 et

@ -53,7 +53,7 @@ function! go#def#Jump(mode) abort
if go#util#has_job() if go#util#has_job()
let l:spawn_args = { let l:spawn_args = {
\ 'cmd': cmd, \ 'cmd': cmd,
\ 'custom_cb': function('s:jump_to_declaration_cb', [a:mode, bin_name]), \ 'complete': function('s:jump_to_declaration_cb', [a:mode, bin_name]),
\ } \ }
if &modified if &modified
@ -292,16 +292,12 @@ function! go#def#Stack(...) abort
endfunction endfunction
function s:def_job(args) abort function s:def_job(args) abort
function! s:error_info_cb(job, exit_status, data) closure
" do not print anything during async definition search&jump
endfunction
let a:args.error_info_cb = funcref('s:error_info_cb')
let callbacks = go#job#Spawn(a:args) let callbacks = go#job#Spawn(a:args)
let start_options = { let start_options = {
\ 'callback': callbacks.callback, \ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb, \ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ } \ }
if &modified if &modified

@ -29,18 +29,7 @@ function! go#doc#OpenBrowser(...) abort
let name = out["name"] let name = out["name"]
let decl = out["decl"] let decl = out["decl"]
let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') let godoc_url = s:custom_godoc_url()
if godoc_url isnot 'https://godoc.org'
" strip last '/' character if available
let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/'
let godoc_url = strpart(godoc_url, 0, last_char)
endif
" custom godoc installations expects it
let godoc_url .= "/pkg"
endif
let godoc_url .= "/" . import let godoc_url .= "/" . import
if decl !~ "^package" if decl !~ "^package"
let godoc_url .= "#" . name let godoc_url .= "#" . name
@ -61,7 +50,7 @@ function! go#doc#OpenBrowser(...) abort
let exported_name = pkgs[1] let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set " example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = "https://godoc.org/" . pkg . "#" . exported_name let godoc_url = s:custom_godoc_url() . "/" . pkg . "#" . exported_name
call go#tool#OpenBrowser(godoc_url) call go#tool#OpenBrowser(godoc_url)
endfunction endfunction
@ -217,13 +206,18 @@ function! s:godocWord(args) abort
return [pkg, exported_name] return [pkg, exported_name]
endfunction endfunction
function! s:godocNotFound(content) abort function! s:custom_godoc_url() abort
if len(a:content) == 0 let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org')
return 1 if godoc_url isnot 'https://godoc.org'
" strip last '/' character if available
let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/'
let godoc_url = strpart(godoc_url, 0, last_char)
endif
" custom godoc installations expect /pkg before package names
let godoc_url .= "/pkg"
endif endif
return godoc_url
return a:content =~# '^.*: no such file or directory\n$'
endfunction endfunction
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -148,7 +148,6 @@ function! go#fmt#update_file(source, target)
if has_key(l:list_title, "title") && l:list_title['title'] == "Format" if has_key(l:list_title, "title") && l:list_title['title'] == "Format"
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
endif endif
endfunction endfunction

@ -78,7 +78,7 @@ function! s:guru_cmd(args) range abort
let scopes = go#util#StripTrailingSlash(scopes) let scopes = go#util#StripTrailingSlash(scopes)
" create shell-safe entries of the list " create shell-safe entries of the list
if !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif if !has("nvim") && !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif
" guru expect a comma-separated list of patterns, construct it " guru expect a comma-separated list of patterns, construct it
let l:scope = join(scopes, ",") let l:scope = join(scopes, ",")
@ -129,7 +129,7 @@ function! s:sync_guru(args) abort
endif endif
if has_key(a:args, 'custom_parse') if has_key(a:args, 'custom_parse')
call a:args.custom_parse(go#util#ShellError(), out) call a:args.custom_parse(go#util#ShellError(), out, a:args.mode)
else else
call s:parse_guru_output(go#util#ShellError(), out, a:args.mode) call s:parse_guru_output(go#util#ShellError(), out, a:args.mode)
endif endif
@ -137,6 +137,33 @@ function! s:sync_guru(args) abort
return out return out
endfunc endfunc
" use vim or neovim job api as appropriate
function! s:job_start(cmd, start_options) abort
if go#util#has_job()
return job_start(a:cmd, a:start_options)
endif
let opts = {'stdout_buffered': v:true, 'stderr_buffered': v:true}
function opts.on_stdout(job_id, data, event) closure
call a:start_options.callback(a:job_id, join(a:data, "\n"))
endfunction
function opts.on_stderr(job_id, data, event) closure
call a:start_options.callback(a:job_id, join(a:data, "\n"))
endfunction
function opts.on_exit(job_id, exit_code, event) closure
call a:start_options.exit_cb(a:job_id, a:exit_code)
call a:start_options.close_cb(a:job_id)
endfunction
" use a shell for input redirection if needed
let cmd = a:cmd
if has_key(a:start_options, 'in_io') && a:start_options.in_io ==# 'file' && !empty(a:start_options.in_name)
let cmd = ['/bin/sh', '-c', join(a:cmd, ' ') . ' <' . a:start_options.in_name]
endif
return jobstart(cmd, opts)
endfunction
" async_guru runs guru in async mode with the given arguments " async_guru runs guru in async mode with the given arguments
function! s:async_guru(args) abort function! s:async_guru(args) abort
let result = s:guru_cmd(a:args) let result = s:guru_cmd(a:args)
@ -145,8 +172,6 @@ function! s:async_guru(args) abort
return return
endif endif
let status_dir = expand('%:p:h')
let statusline_type = printf("%s", a:args.mode)
if !has_key(a:args, 'disable_progress') if !has_key(a:args, 'disable_progress')
if a:args.needs_scope if a:args.needs_scope
@ -155,44 +180,64 @@ function! s:async_guru(args) abort
endif endif
endif endif
let messages = [] let state = {
function! s:callback(chan, msg) closure \ 'status_dir': expand('%:p:h'),
call add(messages, a:msg) \ 'statusline_type': printf("%s", a:args.mode),
\ 'mode': a:args.mode,
\ 'status': {},
\ 'exitval': 0,
\ 'closed': 0,
\ 'exited': 0,
\ 'messages': [],
\ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output"))
\ }
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction endfunction
let status = {} function! s:exit_cb(job, exitval) dict
let exitval = 0 let self.exited = 1
function! s:exit_cb(job, exitval) closure
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': statusline_type, \ 'type': self.statusline_type,
\ 'state': "finished", \ 'state': "finished",
\ } \ }
if a:exitval if a:exitval
let exitval = a:exitval let self.exitval = a:exitval
let status.state = "failed" let status.state = "failed"
endif endif
call go#statusline#Update(status_dir, status) call go#statusline#Update(self.status_dir, status)
if self.closed
call self.complete()
endif
endfunction endfunction
function! s:close_cb(ch) closure function! s:close_cb(ch) dict
let out = join(messages, "\n") let self.closed = 1
if has_key(a:args, 'custom_parse') if self.exited
call a:args.custom_parse(exitval, out) call self.complete()
else
call s:parse_guru_output(exitval, out, a:args.mode)
endif endif
endfunction endfunction
function state.complete() dict
let out = join(self.messages, "\n")
call self.parse(self.exitval, out, self.mode)
endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = { let start_options = {
\ 'callback': funcref("s:callback"), \ 'callback': function('s:callback', [], state),
\ 'exit_cb': funcref("s:exit_cb"), \ 'exit_cb': function('s:exit_cb', [], state),
\ 'close_cb': funcref("s:close_cb"), \ 'close_cb': function('s:close_cb', [], state)
\ } \ }
if has_key(result, 'stdin_content') if has_key(result, 'stdin_content')
let l:tmpname = tempname() let l:tmpname = tempname()
@ -201,18 +246,18 @@ function! s:async_guru(args) abort
let l:start_options.in_name = l:tmpname let l:start_options.in_name = l:tmpname
endif endif
call go#statusline#Update(status_dir, { call go#statusline#Update(state.status_dir, {
\ 'desc': "current status", \ 'desc': "current status",
\ 'type': statusline_type, \ 'type': state.statusline_type,
\ 'state': "analysing", \ 'state': "analysing",
\}) \})
return job_start(result.cmd, start_options) return s:job_start(result.cmd, start_options)
endfunc endfunc
" run_guru runs the given guru argument " run_guru runs the given guru argument
function! s:run_guru(args) abort function! s:run_guru(args) abort
if go#util#has_job() if has('nvim') || go#util#has_job()
let res = s:async_guru(a:args) let res = s:async_guru(a:args)
else else
let res = s:sync_guru(a:args) let res = s:sync_guru(a:args)
@ -273,7 +318,7 @@ function! go#guru#DescribeInfo() abort
return return
endif endif
function! s:info(exit_val, output) function! s:info(exit_val, output, mode)
if a:exit_val != 0 if a:exit_val != 0
return return
endif endif
@ -448,10 +493,6 @@ function! go#guru#Referrers(selected) abort
call s:run_guru(args) call s:run_guru(args)
endfunction endfunction
function! go#guru#SameIdsTimer() abort
call timer_start(200, function('go#guru#SameIds'), {'repeat': -1})
endfunction
function! go#guru#SameIds() abort function! go#guru#SameIds() abort
" we use matchaddpos() which was introduce with 7.4.330, be sure we have " we use matchaddpos() which was introduce with 7.4.330, be sure we have
" it: http://ftp.vim.org/vim/patches/7.4/7.4.330 " it: http://ftp.vim.org/vim/patches/7.4/7.4.330
@ -479,7 +520,7 @@ function! go#guru#SameIds() abort
call s:run_guru(args) call s:run_guru(args)
endfunction endfunction
function! s:same_ids_highlight(exit_val, output) abort function! s:same_ids_highlight(exit_val, output, mode) abort
call go#guru#ClearSameIds() " run after calling guru to reduce flicker. call go#guru#ClearSameIds() " run after calling guru to reduce flicker.
if a:output[0] !=# '{' if a:output[0] !=# '{'

@ -1,9 +1,38 @@
" Spawn returns callbacks to be used with job_start. It's abstracted to be " Spawn returns callbacks to be used with job_start. It is abstracted to be
" used with various go command, such as build, test, install, etc.. This avoid " used with various go commands, such as build, test, install, etc.. This
" us to write the same callback over and over for some commands. It's fully " allows us to avoid writing the same callback over and over for some
" customizable so each command can change it to it's own logic. " commands. It's fully customizable so each command can change it to it's own
" logic.
"
" args is a dictionary with the these keys:
" 'cmd':
" The value to pass to job_start().
" 'bang':
" Set to 0 to jump to the first error in the error list.
" Defaults to 0.
" 'for':
" The g:go_list_type_command key to use to get the error list type to use.
" Defaults to '_job'
" 'complete':
" A function to call after the job exits and the channel is closed. The
" function will be passed three arguments: the job, its exit code, and the
" list of messages received from the channel. The default value will
" process the messages and manage the error list after the job exits and
" the channel is closed.
" The return value is a dictionary with these keys:
" 'callback':
" A function suitable to be passed as a job callback handler. See
" job-callback.
" 'exit_cb':
" A function suitable to be passed as a job exit_cb handler. See
" job-exit_cb.
" 'close_cb':
" A function suitable to be passed as a job close_cb handler. See
" job-close_cb.
function go#job#Spawn(args) function go#job#Spawn(args)
let cbs = { let cbs = {}
let state = {
\ 'winnr': winnr(), \ 'winnr': winnr(),
\ 'dir': getcwd(), \ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")), \ 'jobdir': fnameescape(expand("%:p:h")),
@ -11,34 +40,38 @@ function go#job#Spawn(args)
\ 'args': a:args.cmd, \ 'args': a:args.cmd,
\ 'bang': 0, \ 'bang': 0,
\ 'for': "_job", \ 'for': "_job",
\ } \ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'errorformat': &errorformat
\ }
if has_key(a:args, 'bang') if has_key(a:args, 'bang')
let cbs.bang = a:args.bang let state.bang = a:args.bang
endif endif
if has_key(a:args, 'for') if has_key(a:args, 'for')
let cbs.for = a:args.for let state.for = a:args.for
endif endif
" add final callback to be called if async job is finished " do nothing in state.complete by default.
" The signature should be in form: func(job, exit_status, messages) function state.complete(job, exit_status, data)
if has_key(a:args, 'custom_cb') endfunction
let cbs.custom_cb = a:args.custom_cb
endif
if has_key(a:args, 'error_info_cb') if has_key(a:args, 'complete')
let cbs.error_info_cb = a:args.error_info_cb let state.complete = a:args.complete
endif endif
function cbs.callback(chan, msg) dict function! s:callback(chan, msg) dict
call add(self.messages, a:msg) call add(self.messages, a:msg)
endfunction endfunction
" explicitly bind callback to state so that within it, self will
" always refer to state. See :help Partial for more information.
let cbs.callback = function('s:callback', [], state)
function cbs.exit_cb(job, exitval) dict function! s:exit_cb(job, exitval) dict
if has_key(self, 'error_info_cb') let self.exit_status = a:exitval
call self.error_info_cb(a:job, a:exitval, self.messages) let self.exited = 1
endif
if get(g:, 'go_echo_command_info', 1) if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0 if a:exitval == 0
@ -48,55 +81,69 @@ function go#job#Spawn(args)
endif endif
endif endif
if has_key(self, 'custom_cb') if self.closed
call self.custom_cb(a:job, a:exitval, self.messages) call self.complete(a:job, self.exit_status, self.messages)
call self.show_errors(a:job, self.exit_status, self.messages)
endif
endfunction
" explicitly bind exit_cb to state so that within it, self will always refer
" to state. See :help Partial for more information.
let cbs.exit_cb = function('s:exit_cb', [], state)
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
let job = ch_getjob(a:ch)
call self.complete(job, self.exit_status, self.messages)
call self.show_errors(job, self.exit_status, self.messages)
endif endif
endfunction
" explicitly bind close_cb to state so that within it, self will
" always refer to state. See :help Partial for more information.
let cbs.close_cb = function('s:close_cb', [], state)
function state.show_errors(job, exit_status, data)
let l:listtype = go#list#Type(self.for) let l:listtype = go#list#Type(self.for)
if a:exitval == 0 if a:exit_status == 0
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
return return
endif endif
call self.show_errors(l:listtype) let l:listtype = go#list#Type(self.for)
endfunction if len(a:data) == 0
call go#list#Clean(l:listtype)
return
endif
let out = join(self.messages, "\n")
function cbs.show_errors(listtype) dict
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
try try
" parse the errors relative to self.jobdir
execute cd self.jobdir execute cd self.jobdir
let errors = go#tool#ParseErrors(self.messages) call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for)
let errors = go#tool#FilterValids(errors) let errors = go#list#Get(l:listtype)
finally finally
execute cd . fnameescape(self.dir) execute cd . fnameescape(self.dir)
endtry endtry
if !len(errors)
if empty(errors)
" failed to parse errors, output the original content " failed to parse errors, output the original content
call go#util#EchoError(self.messages + [self.dir]) call go#util#EchoError(self.messages + [self.dir])
return return
endif endif
if self.winnr == winnr() if self.winnr == winnr()
call go#list#Populate(a:listtype, errors, join(self.args)) call go#list#Window(l:listtype, len(errors))
call go#list#Window(a:listtype, len(errors)) if !self.bang
if !empty(errors) && !self.bang call go#list#JumpToFirst(l:listtype)
call go#list#JumpToFirst(a:listtype)
endif endif
endif endif
endfunction endfunction
" override callback handler if user provided it
if has_key(a:args, 'callback')
let cbs.callback = a:args.callback
endif
" override exit callback handler if user provided it
if has_key(a:args, 'exit_cb')
let cbs.exit_cb = a:args.exit_cb
endif
return cbs return cbs
endfunction endfunction
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -62,6 +62,7 @@ function! s:spawn(bang, desc, for, args) abort
\ 'status_dir' : status_dir, \ 'status_dir' : status_dir,
\ 'started_at' : started_at, \ 'started_at' : started_at,
\ 'for' : a:for, \ 'for' : a:for,
\ 'errorformat': &errorformat,
\ } \ }
" execute go build in the files directory " execute go build in the files directory
@ -125,7 +126,6 @@ function! s:on_exit(job_id, exit_status, event) dict abort
let l:listtype = go#list#Type(self.for) let l:listtype = go#list#Type(self.for)
if a:exit_status == 0 if a:exit_status == 0
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
let self.state = "SUCCESS" let self.state = "SUCCESS"
@ -143,8 +143,9 @@ function! s:on_exit(job_id, exit_status, event) dict abort
call go#util#EchoError("[" . self.status_type . "] FAILED") call go#util#EchoError("[" . self.status_type . "] FAILED")
endif endif
let errors = go#tool#ParseErrors(std_combined) " parse the errors relative to self.jobdir
let errors = go#tool#FilterValids(errors) call go#list#ParseFormat(l:listtype, self.errorformat, std_combined, self.for)
let errors = go#list#Get(l:listtype)
execute cd . fnameescape(dir) execute cd . fnameescape(dir)
@ -156,7 +157,6 @@ function! s:on_exit(job_id, exit_status, event) dict abort
" if we are still in the same windows show the list " if we are still in the same windows show the list
if self.winnr == winnr() if self.winnr == winnr()
call go#list#Populate(l:listtype, errors, self.desc)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !self.bang if !empty(errors) && !self.bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)

@ -10,8 +10,8 @@ if !exists("g:go_metalinter_enabled")
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck'] let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
endif endif
if !exists("g:go_metalinter_excludes") if !exists("g:go_metalinter_disabled")
let g:go_metalinter_excludes = [] let g:go_metalinter_disabled = []
endif endif
if !exists("g:go_golint_bin") if !exists("g:go_golint_bin")
@ -24,9 +24,9 @@ endif
function! go#lint#Gometa(autosave, ...) abort function! go#lint#Gometa(autosave, ...) abort
if a:0 == 0 if a:0 == 0
let goargs = shellescape(expand('%:p:h')) let goargs = [expand('%:p:h')]
else else
let goargs = go#util#Shelljoin(a:000) let goargs = a:000
endif endif
let bin_path = go#path#CheckBinPath("gometalinter") let bin_path = go#path#CheckBinPath("gometalinter")
@ -44,8 +44,8 @@ function! go#lint#Gometa(autosave, ...) abort
let cmd += ["--enable=".linter] let cmd += ["--enable=".linter]
endfor endfor
for exclude in g:go_metalinter_excludes for linter in g:go_metalinter_disabled
let cmd += ["--exclude=".exclude] let cmd += ["--disable=".linter]
endfor endfor
" gometalinter has a --tests flag to tell its linters whether to run " gometalinter has a --tests flag to tell its linters whether to run
@ -54,14 +54,20 @@ function! go#lint#Gometa(autosave, ...) abort
" test files. One example of a linter that will not run against tests if " test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck. " we do not specify this flag is errcheck.
let cmd += ["--tests"] let cmd += ["--tests"]
" path
let cmd += [expand('%:p:h')]
else else
" the user wants something else, let us use it. " the user wants something else, let us use it.
let cmd += split(g:go_metalinter_command, " ") let cmd += split(g:go_metalinter_command, " ")
endif endif
if a:autosave
" redraw so that any messages that were displayed while writing the file
" will be cleared
redraw
" Include only messages for the active buffer for autosave.
let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
endif
" gometalinter has a default deadline of 5 seconds. " gometalinter has a default deadline of 5 seconds.
" "
" For async mode (s:lint_job), we want to override the default deadline only " For async mode (s:lint_job), we want to override the default deadline only
@ -78,29 +84,27 @@ function! go#lint#Gometa(autosave, ...) abort
let cmd += ["--deadline=" . deadline] let cmd += ["--deadline=" . deadline]
endif endif
call s:lint_job({'cmd': cmd}) let cmd += goargs
call s:lint_job({'cmd': cmd}, a:autosave)
return return
endif endif
" We're calling gometalinter synchronously. " We're calling gometalinter synchronously.
let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")] let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")]
if a:autosave let cmd += goargs
" include only messages for the active buffer
let cmd += ["--include='^" . expand('%:p') . ".*$'"]
endif
let [l:out, l:err] = go#util#Exec(cmd)
let meta_command = join(cmd, " ") if a:autosave
let l:listtype = go#list#Type("GoMetaLinterAutoSave")
let out = go#util#System(meta_command) else
let l:listtype = go#list#Type("GoMetaLinter")
endif
let l:listtype = go#list#Type("GoMetaLinter") if l:err == 0
if go#util#ShellError() == 0
redraw | echo
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
else else
" GoMetaLinter can output one of the two, so we look for both: " GoMetaLinter can output one of the two, so we look for both:
@ -142,7 +146,7 @@ function! go#lint#Golint(...) abort
endif endif
let l:listtype = go#list#Type("GoLint") let l:listtype = go#list#Type("GoLint")
call go#list#Parse(l:listtype, out) call go#list#Parse(l:listtype, out, "GoLint")
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
@ -161,8 +165,9 @@ function! go#lint#Vet(bang, ...) abort
let l:listtype = go#list#Type("GoVet") let l:listtype = go#list#Type("GoVet")
if go#util#ShellError() != 0 if go#util#ShellError() != 0
let errors = go#tool#ParseErrors(split(out, '\n')) let errorformat="%-Gexit status %\\d%\\+," . &errorformat
call go#list#Populate(l:listtype, errors, 'Vet') call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet")
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
@ -170,7 +175,6 @@ function! go#lint#Vet(bang, ...) abort
echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
endif endif
endfunction endfunction
@ -223,7 +227,6 @@ function! go#lint#Errcheck(...) abort
endif endif
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None
endif endif
@ -240,11 +243,19 @@ function! go#lint#ToggleMetaLinterAutoSave() abort
call go#util#EchoProgress("auto metalinter enabled") call go#util#EchoProgress("auto metalinter enabled")
endfunction endfunction
function s:lint_job(args) function! s:lint_job(args, autosave)
let status_dir = expand('%:p:h') let state = {
let started_at = reltime() \ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime(),
call go#statusline#Update(status_dir, { \ 'messages': [],
\ 'exited': 0,
\ 'closed': 0,
\ 'exit_status': 0,
\ 'winnr': winnr(),
\ 'autosave': a:autosave
\ }
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status", \ 'desc': "current status",
\ 'type': "gometalinter", \ 'type': "gometalinter",
\ 'state': "analysing", \ 'state': "analysing",
@ -253,32 +264,20 @@ function s:lint_job(args)
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
let l:listtype = go#list#Type("GoMetaLinter") if a:autosave
let l:errformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m' let state.listtype = go#list#Type("GoMetaLinterAutoSave")
else
function! s:callback(chan, msg) closure let state.listtype = go#list#Type("GoMetaLinter")
let old_errorformat = &errorformat endif
let &errorformat = l:errformat
try
if l:listtype == "locationlist"
lad a:msg
elseif l:listtype == "quickfix"
caddexpr a:msg
endif
finally
let &errorformat = old_errorformat
endtry
" TODO(jinleileiking): give a configure to jump or not
let l:winnr = winnr()
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
exe l:winnr . "wincmd w" function! s:callback(chan, msg) dict closure
call add(self.messages, a:msg)
endfunction endfunction
function! s:exit_cb(job, exitval) closure function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exit_status = a:exitval
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': "gometaliner", \ 'type': "gometaliner",
@ -289,22 +288,50 @@ function s:lint_job(args)
let status.state = "failed" let status.state = "failed"
endif endif
let elapsed_time = reltimestr(reltime(started_at)) let elapsed_time = reltimestr(reltime(self.started_at))
" strip whitespace " strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time) let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(status_dir, status) call go#statusline#Update(self.status_dir, status)
let errors = go#list#Get(l:listtype) if self.closed
if empty(errors) call self.show_errors()
call go#list#Window(l:listtype, len(errors)) endif
elseif has("patch-7.4.2200") endfunction
if l:listtype == 'quickfix'
call setqflist([], 'a', {'title': 'GoMetaLinter'}) function! s:close_cb(ch) dict
else let self.closed = 1
call setloclist(0, [], 'a', {'title': 'GoMetaLinter'})
endif if self.exited
call self.show_errors()
endif
endfunction
function state.show_errors()
let l:winnr = winnr()
" make sure the current window is the window from which gometalinter was
" run when the listtype is locationlist so that the location list for the
" correct window will be populated.
if self.listtype == 'locationlist'
exe self.winnr . "wincmd w"
endif
let l:errorformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m'
call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter')
let errors = go#list#Get(self.listtype)
call go#list#Window(self.listtype, len(errors))
" move to the window that was active before processing the errors, because
" the user may have moved around within the window or even moved to a
" different window since saving. Moving back to current window as of the
" start of this function avoids the perception that the quickfix window
" steals focus when linting takes a while.
if self.autosave
exe l:winnr . "wincmd w"
endif endif
if get(g:, 'go_echo_command_info', 1) if get(g:, 'go_echo_command_info', 1)
@ -312,15 +339,16 @@ function s:lint_job(args)
endif endif
endfunction endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = { let start_options = {
\ 'callback': funcref("s:callback"), \ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb"), \ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state),
\ } \ }
call job_start(a:args.cmd, start_options) call job_start(a:args.cmd, start_options)
call go#list#Clean(l:listtype)
if get(g:, 'go_echo_command_info', 1) if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("linting started ...") call go#util#EchoProgress("linting started ...")
endif endif

@ -0,0 +1,131 @@
func! Test_Gometa() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
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')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_enabled will be set so
" we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let orig_go_metalinter_enabled = g:go_metalinter_enabled
let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(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)
let g:go_metalinter_enabled = orig_go_metalinter_enabled
endfunc
func! Test_GometaWithDisabled() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
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')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_disabled will be set so
" we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let orig_go_metalinter_disabled = g:go_metalinter_disabled
let g:go_metalinter_disabled = ['vet']
call go#lint#Gometa(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)
let g:go_metalinter_disabled = orig_go_metalinter_disabled
endfunc
func! Test_GometaAutoSave() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
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)'}
\ ]
let winnr = winnr()
" clear the location lists
call setloclist(l:winnr, [], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_autosave_enabled will be
" set so we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let orig_go_metalinter_autosave_enabled = g:go_metalinter_autosave_enabled
let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(1)
let actual = getloclist(l:winnr)
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(l:winnr)
endwhile
call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_autosave_enabled = orig_go_metalinter_autosave_enabled
endfunc
func! Test_Vet()
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
compiler go
let expected = [
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'arg str for printf verb %d 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)
endfunc
" vim: sw=2 ts=2 et

@ -18,14 +18,7 @@ function! go#list#Window(listtype, ...) abort
" location list increases/decreases, cwindow will not resize when a new " location list increases/decreases, cwindow will not resize when a new
" updated height is passed. lopen in the other hand resizes the screen. " updated height is passed. lopen in the other hand resizes the screen.
if !a:0 || a:1 == 0 if !a:0 || a:1 == 0
let autoclose_window = get(g:, 'go_list_autoclose', 1) call go#list#Close(a:listtype)
if autoclose_window
if a:listtype == "locationlist"
lclose
else
cclose
endif
endif
return return
endif endif
@ -79,13 +72,7 @@ function! go#list#ParseFormat(listtype, errformat, items, title) abort
" parse and populate the location list " parse and populate the location list
let &errorformat = a:errformat let &errorformat = a:errformat
try try
if a:listtype == "locationlist" call go#list#Parse(a:listtype, a:items, a:title)
lgetexpr a:items
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
else
cgetexpr a:items
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif
endif
finally finally
"restore back "restore back
let &errorformat = old_errorformat let &errorformat = old_errorformat
@ -94,11 +81,13 @@ endfunction
" Parse parses the given items based on the global errorformat and " Parse parses the given items based on the global errorformat and
" populates the list. " populates the list.
function! go#list#Parse(listtype, items) abort function! go#list#Parse(listtype, items, title) abort
if a:listtype == "locationlist" if a:listtype == "locationlist"
lgetexpr a:items lgetexpr a:items
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
else else
cgetexpr a:items cgetexpr a:items
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif
endif endif
endfunction endfunction
@ -111,13 +100,29 @@ function! go#list#JumpToFirst(listtype) abort
endif endif
endfunction endfunction
" Clean cleans the location list " Clean cleans and closes the location list
function! go#list#Clean(listtype) abort function! go#list#Clean(listtype) abort
if a:listtype == "locationlist" if a:listtype == "locationlist"
lex [] lex []
else else
cex [] cex []
endif endif
call go#list#Close(a:listtype)
endfunction
" Close closes the location list
function! go#list#Close(listtype) abort
let autoclose_window = get(g:, 'go_list_autoclose', 1)
if !autoclose_window
return
endif
if a:listtype == "locationlist"
lclose
else
cclose
endif
endfunction endfunction
function! s:listtype(listtype) abort function! s:listtype(listtype) abort
@ -137,21 +142,22 @@ endfunction
" single file or buffer. Keys that begin with an underscore are not supported " single file or buffer. Keys that begin with an underscore are not supported
" in g:go_list_type_commands. " in g:go_list_type_commands.
let s:default_list_type_commands = { let s:default_list_type_commands = {
\ "GoBuild": "quickfix", \ "GoBuild": "quickfix",
\ "GoErrCheck": "quickfix", \ "GoErrCheck": "quickfix",
\ "GoFmt": "locationlist", \ "GoFmt": "locationlist",
\ "GoGenerate": "quickfix", \ "GoGenerate": "quickfix",
\ "GoInstall": "quickfix", \ "GoInstall": "quickfix",
\ "GoLint": "quickfix", \ "GoLint": "quickfix",
\ "GoMetaLinter": "quickfix", \ "GoMetaLinter": "quickfix",
\ "GoModifyTags": "locationlist", \ "GoMetaLinterAutoSave": "locationlist",
\ "GoRename": "quickfix", \ "GoModifyTags": "locationlist",
\ "GoRun": "quickfix", \ "GoRename": "quickfix",
\ "GoTest": "quickfix", \ "GoRun": "quickfix",
\ "GoVet": "quickfix", \ "GoTest": "quickfix",
\ "_guru": "locationlist", \ "GoVet": "quickfix",
\ "_term": "locationlist", \ "_guru": "locationlist",
\ "_job": "locationlist", \ "_term": "locationlist",
\ "_job": "locationlist",
\ } \ }
function! go#list#Type(for) abort function! go#list#Type(for) abort

@ -54,8 +54,14 @@ function! go#package#Paths() abort
return dirs return dirs
endfunction endfunction
let s:import_paths = {}
" ImportPath returns the import path in the current directory it was executed " ImportPath returns the import path in the current directory it was executed
function! go#package#ImportPath() abort function! go#package#ImportPath() abort
let dir = expand("%:p:h")
if has_key(s:import_paths, dir)
return s:import_paths[dir]
endif
let out = go#tool#ExecuteInDir("go list") let out = go#tool#ExecuteInDir("go list")
if go#util#ShellError() != 0 if go#util#ShellError() != 0
return -1 return -1
@ -69,6 +75,8 @@ function! go#package#ImportPath() abort
return -1 return -1
endif endif
let s:import_paths[dir] = import_path
return import_path return import_path
endfunction endfunction

@ -45,9 +45,9 @@ function! go#path#Default() abort
return $GOPATH return $GOPATH
endfunction endfunction
" HasPath checks whether the given path exists in GOPATH environment variable " s:HasPath checks whether the given path exists in GOPATH environment variable
" or not " or not
function! go#path#HasPath(path) abort function! s:HasPath(path) abort
let go_paths = split(go#path#Default(), go#util#PathListSep()) let go_paths = split(go#path#Default(), go#util#PathListSep())
let last_char = strlen(a:path) - 1 let last_char = strlen(a:path) - 1
@ -94,11 +94,11 @@ function! go#path#Detect() abort
" gb vendor plugin " gb vendor plugin
" (https://github.com/constabulary/gb/tree/master/cmd/gb-vendor) " (https://github.com/constabulary/gb/tree/master/cmd/gb-vendor)
let gb_vendor_root = src_path . "vendor" . go#util#PathSep() let gb_vendor_root = src_path . "vendor" . go#util#PathSep()
if isdirectory(gb_vendor_root) && !go#path#HasPath(gb_vendor_root) if isdirectory(gb_vendor_root) && !s:HasPath(gb_vendor_root)
let gopath = gb_vendor_root . go#util#PathListSep() . gopath let gopath = gb_vendor_root . go#util#PathListSep() . gopath
endif endif
if !go#path#HasPath(src_path) if !s:HasPath(src_path)
let gopath = src_path . go#util#PathListSep() . gopath let gopath = src_path . go#util#PathListSep() . gopath
endif endif
endif endif
@ -108,7 +108,7 @@ function! go#path#Detect() abort
if !empty(godeps_root) if !empty(godeps_root)
let godeps_path = join([fnamemodify(godeps_root, ':p:h:h'), "Godeps", "_workspace" ], go#util#PathSep()) let godeps_path = join([fnamemodify(godeps_root, ':p:h:h'), "Godeps", "_workspace" ], go#util#PathSep())
if !go#path#HasPath(godeps_path) if !s:HasPath(godeps_path)
let gopath = godeps_path . go#util#PathListSep() . gopath let gopath = godeps_path . go#util#PathListSep() . gopath
endif endif
endif endif
@ -164,7 +164,7 @@ function! go#path#CheckBinPath(binpath) abort
let $PATH = old_path let $PATH = old_path
if go#util#IsUsingCygwinShell() == 1 if go#util#IsUsingCygwinShell() == 1
return go#path#CygwinPath(binpath) return s:CygwinPath(binpath)
endif endif
return binpath return binpath
@ -183,13 +183,13 @@ function! go#path#CheckBinPath(binpath) abort
let $PATH = old_path let $PATH = old_path
if go#util#IsUsingCygwinShell() == 1 if go#util#IsUsingCygwinShell() == 1
return go#path#CygwinPath(a:binpath) return s:CygwinPath(a:binpath)
endif endif
return go_bin_path . go#util#PathSep() . basename return go_bin_path . go#util#PathSep() . basename
endfunction endfunction
function! go#path#CygwinPath(path) function! s:CygwinPath(path)
return substitute(a:path, '\\', '/', "g") return substitute(a:path, '\\', '/', "g")
endfunction endfunction

@ -72,14 +72,23 @@ function! go#rename#Rename(bang, ...) abort
endfunction endfunction
function s:rename_job(args) function s:rename_job(args)
let messages = [] let state = {
function! s:callback(chan, msg) closure \ 'exited': 0,
call add(messages, a:msg) \ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'status_dir': expand('%:p:h'),
\ 'bang': a:args.bang
\ }
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction endfunction
let status_dir = expand('%:p:h') function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
function! s:exit_cb(job, exitval) closure
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': "gorename", \ 'type': "gorename",
@ -90,17 +99,30 @@ function s:rename_job(args)
let status.state = "failed" let status.state = "failed"
endif endif
call go#statusline#Update(status_dir, status) call go#statusline#Update(self.status_dir, status)
call s:parse_errors(a:exitval, a:args.bang, messages) if self.closed
call s:parse_errors(self.exitval, self.bang, self.messages)
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call s:parse_errors(self.exitval, self.bang, self.messages)
endif
endfunction endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = { let start_options = {
\ 'callback': funcref("s:callback"), \ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb"), \ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state),
\ } \ }
call go#statusline#Update(status_dir, { call go#statusline#Update(state.status_dir, {
\ 'desc': "current status", \ 'desc': "current status",
\ 'type': "gorename", \ 'type': "gorename",
\ 'state': "started", \ 'state': "started",
@ -138,7 +160,6 @@ function s:parse_errors(exit_val, bang, out)
" strip out newline on the end that gorename puts. If we don't remove, it " strip out newline on the end that gorename puts. If we don't remove, it
" will trigger the 'Hit ENTER to continue' prompt " will trigger the 'Hit ENTER to continue' prompt
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
call go#util#EchoSuccess(a:out[0]) call go#util#EchoSuccess(a:out[0])
" refresh the buffer so we can see the new content " refresh the buffer so we can see the new content

@ -24,7 +24,7 @@ function! go#template#create() abort
let l:template_file = get(g:, 'go_template_file', "hello_world.go") let l:template_file = get(g:, 'go_template_file', "hello_world.go")
endif endif
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file) let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
silent exe '0r ' . fnameescape(l:template_path) silent exe 'keepalt 0r ' . fnameescape(l:template_path)
elseif l:package_name == -1 && l:go_template_use_pkg == 1 elseif l:package_name == -1 && l:go_template_use_pkg == 1
" cwd is now the dir of the package " cwd is now the dir of the package
let l:path = fnamemodify(getcwd(), ':t') let l:path = fnamemodify(getcwd(), ':t')

@ -2,9 +2,6 @@ if has('nvim') && !exists("g:go_term_mode")
let g:go_term_mode = 'vsplit' let g:go_term_mode = 'vsplit'
endif endif
" s:jobs is a global reference to all jobs started with new()
let s:jobs = {}
" new creates a new terminal with the given command. Mode is set based on the " new creates a new terminal with the given command. Mode is set based on the
" global variable g:go_term_mode, which is by default set to :vsplit " global variable g:go_term_mode, which is by default set to :vsplit
function! go#term#new(bang, cmd) abort function! go#term#new(bang, cmd) abort
@ -18,8 +15,14 @@ function! go#term#newmode(bang, cmd, mode) abort
let mode = g:go_term_mode let mode = g:go_term_mode
endif endif
let state = {
\ 'cmd': a:cmd,
\ 'bang' : a:bang,
\ 'winid': win_getid(winnr()),
\ 'stdout': []
\ }
" execute go build in the files directory " execute go build in the files directory
let l:winnr = winnr()
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd() let dir = getcwd()
@ -33,30 +36,27 @@ function! go#term#newmode(bang, cmd, mode) abort
setlocal noswapfile setlocal noswapfile
setlocal nobuflisted setlocal nobuflisted
" explicitly bind callbacks to state so that within them, self will always
" refer to state. See :help Partial for more information.
"
" Don't set an on_stderr, because it will be passed the same data as
" on_stdout. See https://github.com/neovim/neovim/issues/2836
let job = { let job = {
\ 'stderr' : [], \ 'on_stdout': function('s:on_stdout', [], state),
\ 'stdout' : [], \ 'on_exit' : function('s:on_exit', [], state),
\ 'bang' : a:bang, \ }
\ 'on_stdout': function('s:on_stdout'),
\ 'on_stderr': function('s:on_stderr'),
\ 'on_exit' : function('s:on_exit'),
\ }
let id = termopen(a:cmd, job) let state.id = termopen(a:cmd, job)
let state.termwinid = win_getid(winnr())
execute cd . fnameescape(dir) execute cd . fnameescape(dir)
let job.id = id
let job.cmd = a:cmd
startinsert
" resize new term if needed. " resize new term if needed.
let height = get(g:, 'go_term_height', winheight(0)) let height = get(g:, 'go_term_height', winheight(0))
let width = get(g:, 'go_term_width', winwidth(0)) let width = get(g:, 'go_term_width', winwidth(0))
" we are careful how to resize. for example it's vsplit we don't change " Adjust the window width or height depending on whether it's a vertical or
" the height. The below command resizes the buffer " horizontal split.
if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew" if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew"
exe 'vertical resize ' . width exe 'vertical resize ' . width
elseif mode =~ "split" || mode =~ "new" elseif mode =~ "split" || mode =~ "new"
@ -64,77 +64,56 @@ function! go#term#newmode(bang, cmd, mode) abort
endif endif
" we also need to resize the pty, so there you go... " we also need to resize the pty, so there you go...
call jobresize(id, width, height) call jobresize(state.id, width, height)
let s:jobs[id] = job call win_gotoid(state.winid)
stopinsert
if l:winnr !=# winnr() return state.id
exe l:winnr . "wincmd w"
endif
return id
endfunction endfunction
function! s:on_stdout(job_id, data, event) dict abort function! s:on_stdout(job_id, data, event) dict abort
if !has_key(s:jobs, a:job_id) call extend(self.stdout, a:data)
return
endif
let job = s:jobs[a:job_id]
call extend(job.stdout, a:data)
endfunction
function! s:on_stderr(job_id, data, event) dict abort
if !has_key(s:jobs, a:job_id)
return
endif
let job = s:jobs[a:job_id]
call extend(job.stderr, a:data)
endfunction endfunction
function! s:on_exit(job_id, exit_status, event) dict abort function! s:on_exit(job_id, exit_status, event) dict abort
if !has_key(s:jobs, a:job_id)
return
endif
let job = s:jobs[a:job_id]
let l:listtype = go#list#Type("_term") let l:listtype = go#list#Type("_term")
" usually there is always output so never branch into this clause " usually there is always output so never branch into this clause
if empty(job.stdout) if empty(self.stdout)
call go#list#Clean(l:listtype) call s:cleanlist(self.winid, l:listtype)
call go#list#Window(l:listtype)
unlet s:jobs[a:job_id]
return return
endif endif
let errors = go#tool#ParseErrors(job.stdout) let errors = go#tool#ParseErrors(self.stdout)
let errors = go#tool#FilterValids(errors) let errors = go#tool#FilterValids(errors)
if !empty(errors) if !empty(errors)
" close terminal we don't need it anymore " close terminal; we don't need it anymore
call win_gotoid(self.termwinid)
close close
call go#list#Populate(l:listtype, errors, job.cmd) call win_gotoid(self.winid)
call go#list#Populate(l:listtype, errors, self.cmd)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !self.bang if !self.bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
endif endif
unlet s:jobs[a:job_id]
return return
endif endif
" tests are passing clean the list and close the list. But we only can call s:cleanlist(self.winid, l:listtype)
" close them from a normal view, so jump back, close the list and then endfunction
" again jump back to the terminal
wincmd p
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
wincmd p
unlet s:jobs[a:job_id] function! s:cleanlist(winid, listtype) abort
" There are no errors. Clean and close the list. Jump to the window to which
" the location list is attached, close the list, and then jump back to the
" current window.
let winid = win_getid(winnr())
call win_gotoid(a:winid)
call go#list#Clean(a:listtype)
call win_gotoid(l:winid)
endfunction endfunction
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -0,0 +1,50 @@
func! Test_GoTermNewMode()
if !has('nvim')
return
endif
try
let l:filename = 'term/term.go'
let l:tmp = gotest#load_fixture(l:filename)
exe 'cd ' . l:tmp . '/src/term'
let expected = expand('%:p')
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set nosplitright
call go#term#newmode(0, cmd, '')
let actual = expand('%:p')
call assert_equal(actual, l:expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_GoTermNewMode_SplitRight()
if !has('nvim')
return
endif
try
let l:filename = 'term/term.go'
let l:tmp = gotest#load_fixture(l:filename)
exe 'cd ' . l:tmp . '/src/term'
let expected = expand('%:p')
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set splitright
call go#term#newmode(0, cmd, '')
let actual = expand('%:p')
call assert_equal(actual, l:expected)
finally
call delete(l:tmp, 'rf')
set nosplitright
endtry
endfunc
" vim: sw=2 ts=2 et

@ -0,0 +1,5 @@
package main
func main() {
notafunc()
}

@ -0,0 +1,7 @@
package foo
import "fmt"
func MissingFooDoc() {
fmt.Println("missing doc")
}

@ -0,0 +1,7 @@
package lint
import "fmt"
func MissingDoc() {
fmt.Println("missing doc")
}

@ -0,0 +1,7 @@
package lint
import "fmt"
func AlsoMissingDoc() {
fmt.Println("missing doc")
}

@ -0,0 +1,8 @@
package main
import "fmt"
func main() {
str := "hello world!"
fmt.Printf("%d\n", str)
}

@ -0,0 +1,5 @@
package main
func main() {
println("hello, world")
}

@ -18,6 +18,7 @@ func TestTopSubHelper(t *testing.T) {
func TestMultiline(t *testing.T) { func TestMultiline(t *testing.T) {
t.Error("this is an error\nand a second line, too") t.Error("this is an error\nand a second line, too")
t.Error("\nthis is another error")
} }
func TestSub(t *testing.T) { func TestSub(t *testing.T) {

@ -0,0 +1,11 @@
package main
import "testing"
func TestHelloWorld(t *testing.T) {
t.Error("so long")
t.Run("sub", func(t *testing.T) {
t.Error("thanks for all the fish")
})
}

@ -0,0 +1,47 @@
// Run a few parallel tests, all in parallel, using multiple techniques for
// causing the test to take a while so that the stacktraces resulting from a
// test timeout will contain several goroutines to avoid giving a false sense
// of confidence or creating error formats that don't account for the more
// complex scenarios that can occur with timeouts.
package main
import (
"testing"
"time"
)
func TestSleep(t *testing.T) {
t.Parallel()
time.Sleep(15 * time.Second)
t.Log("expected panic if run with timeout < 15s")
}
func TestRunning(t *testing.T) {
t.Parallel()
c := time.After(15 * time.Second)
Loop:
for {
select {
case <-c:
break Loop
default:
}
}
t.Log("expected panic if run with timeout < 15s")
}
func TestRunningAlso(t *testing.T) {
t.Parallel()
c := time.After(15 * time.Second)
Loop:
for {
select {
case <-c:
break Loop
default:
}
}
t.Log("expected panic if run with timeout < 15s")
}

@ -94,7 +94,6 @@ function! go#test#Test(bang, compile, ...) abort
call go#util#EchoError("[test] FAIL") call go#util#EchoError("[test] FAIL")
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
if a:compile if a:compile
call go#util#EchoSuccess("[test] SUCCESS") call go#util#EchoSuccess("[test] SUCCESS")
@ -129,15 +128,16 @@ function! go#test#Func(bang, ...) abort
if a:0 if a:0
call extend(args, a:000) call extend(args, a:000)
else
" only add this if no custom flags are passed
let timeout = get(g:, 'go_test_timeout', '10s')
call add(args, printf("-timeout=%s", timeout))
endif endif
call call('go#test#Test', args) call call('go#test#Test', args)
endfunction endfunction
function! s:test_job(args) abort function! s:test_job(args) abort
let status_dir = expand('%:p:h')
let started_at = reltime()
let status = { let status = {
\ 'desc': 'current status', \ 'desc': 'current status',
\ 'type': "test", \ 'type': "test",
@ -148,24 +148,37 @@ function! s:test_job(args) abort
let status.state = "compiling" let status.state = "compiling"
endif endif
call go#statusline#Update(status_dir, status)
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
let messages = [] let state = {
function! s:callback(chan, msg) closure \ 'exited': 0,
call add(messages, a:msg) \ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'args': a:args,
\ 'compile_test': a:args.compile_test,
\ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime()
\ }
call go#statusline#Update(state.status_dir, status)
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction endfunction
function! s:exit_cb(job, exitval) closure function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
let status = { let status = {
\ 'desc': 'last status', \ 'desc': 'last status',
\ 'type': "test", \ 'type': "test",
\ 'state': "pass", \ 'state': "pass",
\ } \ }
if a:args.compile_test if self.compile_test
let status.state = "success" let status.state = "success"
endif endif
@ -175,7 +188,7 @@ function! s:test_job(args) abort
if get(g:, 'go_echo_command_info', 1) if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0 if a:exitval == 0
if a:args.compile_test if self.compile_test
call go#util#EchoSuccess("[test] SUCCESS") call go#util#EchoSuccess("[test] SUCCESS")
else else
call go#util#EchoSuccess("[test] PASS") call go#util#EchoSuccess("[test] PASS")
@ -185,29 +198,33 @@ function! s:test_job(args) abort
endif endif
endif endif
let elapsed_time = reltimestr(reltime(started_at)) let elapsed_time = reltimestr(reltime(self.started_at))
" strip whitespace " strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time) let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(status_dir, status) call go#statusline#Update(self.status_dir, status)
let l:listtype = go#list#Type("GoTest") if self.closed
if a:exitval == 0 call s:show_errors(self.args, self.exitval, self.messages)
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
return
endif endif
endfunction
" TODO(bc): When messages is JSON, the JSON should be run through a function! s:close_cb(ch) dict
" filter to produce lines that are more easily described by errorformat. let self.closed = 1
call s:show_errors(a:args, a:exitval, messages)
if self.exited
call s:show_errors(self.args, self.exitval, self.messages)
endif
endfunction endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = { let start_options = {
\ 'callback': funcref("s:callback"), \ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb"), \ 'exit_cb': funcref("s:exit_cb", [], state),
\ } \ 'close_cb': funcref("s:close_cb", [], state)
\ }
" pre start " pre start
let dir = getcwd() let dir = getcwd()
@ -225,6 +242,15 @@ endfunction
" a quickfix compatible list of errors. It's intended to be used only for go " a quickfix compatible list of errors. It's intended to be used only for go
" test output. " test output.
function! s:show_errors(args, exit_val, messages) abort function! s:show_errors(args, exit_val, messages) abort
let l:listtype = go#list#Type("GoTest")
if a:exit_val == 0
call go#list#Clean(l:listtype)
return
endif
" TODO(bc): When messages is JSON, the JSON should be run through a
" filter to produce lines that are more easily described by errorformat.
let l:listtype = go#list#Type("GoTest") let l:listtype = go#list#Type("GoTest")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
@ -253,6 +279,7 @@ endfunction
let s:efm= "" let s:efm= ""
let s:go_test_show_name=0
function! s:errorformat() abort function! s:errorformat() abort
" NOTE(arslan): once we get JSON output everything will be easier :). " NOTE(arslan): once we get JSON output everything will be easier :).
@ -261,14 +288,16 @@ function! s:errorformat() abort
" https://github.com/golang/go/issues/2981. " https://github.com/golang/go/issues/2981.
let goroot = go#util#goroot() let goroot = go#util#goroot()
if s:efm != "" let show_name=get(g:, 'go_test_show_name', 0)
if s:efm != "" && s:go_test_show_name == show_name
return s:efm return s:efm
endif endif
let s:go_test_show_name = show_name
" each level of test indents the test output 4 spaces. " each level of test indents the test output 4 spaces. Capturing groups
" TODO(bc): figure out how to use 0 or more groups of four spaces for the " (e.g. \(\)) cannot be used in an errorformat, but non-capturing groups can
" indentation. '%\\( %\\)%#' should work, but doesn't. " (e.g. \%(\)).
let indent = " %#" let indent = '%\\%( %\\)%#'
" match compiler errors " match compiler errors
let format = "%f:%l:%c: %m" let format = "%f:%l:%c: %m"
@ -285,8 +314,8 @@ function! s:errorformat() abort
" "
" e.g.: " e.g.:
" '--- FAIL: TestSomething (0.00s)' " '--- FAIL: TestSomething (0.00s)'
if get(g:, 'go_test_show_name', 0) if show_name
let format .= ",%+G" . indent . "--- FAIL: %.%#" let format .= ",%G" . indent . "--- FAIL: %m (%.%#)"
else else
let format .= ",%-G" . indent . "--- FAIL: %.%#" let format .= ",%-G" . indent . "--- FAIL: %.%#"
endif endif
@ -298,6 +327,12 @@ function! s:errorformat() abort
" message. e.g.: " message. e.g.:
" '\ttime_test.go:30: Likely problem: the time zone files have not been installed.' " '\ttime_test.go:30: Likely problem: the time zone files have not been installed.'
let format .= ",%A" . indent . "%\\t%\\+%f:%l: %m" let format .= ",%A" . indent . "%\\t%\\+%f:%l: %m"
" also match lines that don't have a message (i.e. the message begins with a
" newline or is the empty string):
" e.g.:
" t.Errorf("\ngot %v; want %v", actual, expected)
" t.Error("")
let format .= ",%A" . indent . "%\\t%\\+%f:%l: "
" Match the 2nd and later lines of multi-line output. These lines are " Match the 2nd and later lines of multi-line output. These lines are
" indented the number of spaces for the level of nesting of the test, " indented the number of spaces for the level of nesting of the test,
@ -314,6 +349,10 @@ function! s:errorformat() abort
" set the format for panics. " set the format for panics.
" handle panics from test timeouts
let format .= ",%+Gpanic: test timed out after %.%\\+"
" handle non-timeout panics
" In addition to 'panic', check for 'fatal error' to support older versions " In addition to 'panic', check for 'fatal error' to support older versions
" of Go that used 'fatal error'. " of Go that used 'fatal error'.
" "

@ -6,18 +6,19 @@ func! Test_GoTest() abort
\ {'lnum': 16, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'helper badness'}, \ {'lnum': 16, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'helper badness'},
\ {'lnum': 20, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is an error'}, \ {'lnum': 20, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is an error'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'},
\ {'lnum': 25, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is a sub-test error'}, \ {'lnum': 21, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ''},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is another error'},
\ {'lnum': 26, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is a sub-test error'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'},
\ {'lnum': 6, 'bufnr': 3, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'another package badness'}, \ {'lnum': 6, 'bufnr': 3, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'another package badness'},
\ {'lnum': 42, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: worst ever [recovered]'} \ {'lnum': 43, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: worst ever [recovered]'}
\ ] \ ]
call s:test('play/play_test.go', expected) call s:test('play/play_test.go', expected)
endfunc endfunc
func! Test_GoTestConcurrentPanic() func! Test_GoTestConcurrentPanic()
let expected = [ let expected = [
\ {'lnum': 49, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: concurrent fail'} \ {'lnum': 50, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: concurrent fail'}
\ ] \ ]
call s:test('play/play_test.go', expected, "-run", "TestConcurrentPanic") call s:test('play/play_test.go', expected, "-run", "TestConcurrentPanic")
endfunc endfunc
@ -30,11 +31,13 @@ func! Test_GoTestVerbose() abort
\ {'lnum': 16, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'helper badness'}, \ {'lnum': 16, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'helper badness'},
\ {'lnum': 20, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is an error'}, \ {'lnum': 20, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is an error'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'},
\ {'lnum': 25, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is a sub-test error'}, \ {'lnum': 21, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ''},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is another error'},
\ {'lnum': 26, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'this is a sub-test error'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'and a second line, too'},
\ {'lnum': 31, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'goodness'}, \ {'lnum': 32, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'goodness'},
\ {'lnum': 6, 'bufnr': 3, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'another package badness'}, \ {'lnum': 6, 'bufnr': 3, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'another package badness'},
\ {'lnum': 42, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: worst ever [recovered]'} \ {'lnum': 43, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: worst ever [recovered]'}
\ ] \ ]
call s:test('play/play_test.go', expected, "-v") call s:test('play/play_test.go', expected, "-v")
endfunc endfunc
@ -43,10 +46,32 @@ func! Test_GoTestCompilerError() abort
let expected = [ let expected = [
\ {'lnum': 6, 'bufnr': 6, 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'syntax error: unexpected newline, expecting comma or )'} \ {'lnum': 6, 'bufnr': 6, 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'syntax error: unexpected newline, expecting comma or )'}
\ ] \ ]
call s:test('compilerror/compilerror_test.go', expected) call s:test('compilerror/compilerror_test.go', expected)
endfunc endfunc
func! Test_GoTestTimeout() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'panic: test timed out after 500ms'}
\ ]
let g:go_test_timeout="500ms"
call s:test('timeout/timeout_test.go', expected)
unlet g:go_test_timeout
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': 9, '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': 9, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ ]
let g:go_test_show_name=1
call s:test('showname/showname_test.go', expected)
let g:go_test_show_name=0
endfunc
func! s:test(file, expected, ...) abort func! s:test(file, expected, ...) abort
if has('nvim') if has('nvim')
" nvim mostly shows test errors correctly, but the the expected errors are " nvim mostly shows test errors correctly, but the the expected errors are
@ -57,7 +82,7 @@ func! s:test(file, expected, ...) abort
" the tests will run for Neovim, too. " the tests will run for Neovim, too.
return return
endif endif
let $GOPATH = fnameescape(expand("%:p:h")) . '/test-fixtures/test' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file silent exe 'e ' . $GOPATH . '/src/' . a:file
" clear the quickfix lists " clear the quickfix lists
@ -78,42 +103,15 @@ func! s:test(file, expected, ...) abort
let actual = getqflist() let actual = getqflist()
endwhile endwhile
" for some reason, when run headless, the quickfix lists includes a line for item in actual
" that should have been filtered out; remove it manually. The line is not let item.text = s:normalize_durations(item.text)
" present when run manually. endfor
let i = 0
while i < len(actual)
if actual[i].text =~# '^=== RUN .*'
call remove(actual, i)
endif
let i += 1
endwhile
call assert_equal(len(a:expected), len(actual), "number of errors") for item in a:expected
if len(a:expected) != len(actual) let item.text = s:normalize_durations(item.text)
return endfor
endif
let i = 0 call gotest#assert_quickfix(actual, a:expected)
while i < len(a:expected)
let expected_item = a:expected[i]
let actual_item = actual[i]
let i += 1
call assert_equal(expected_item.bufnr, actual_item.bufnr, "bufnr")
call assert_equal(expected_item.lnum, actual_item.lnum, "lnum")
call assert_equal(expected_item.col, actual_item.col, "col")
call assert_equal(expected_item.vcol, actual_item.vcol, "vcol")
call assert_equal(expected_item.nr, actual_item.nr, "nr")
call assert_equal(expected_item.pattern, actual_item.pattern, "pattern")
let expected_text = s:normalize_durations(expected_item.text)
let actual_text = s:normalize_durations(actual_item.text)
call assert_equal(expected_text, actual_text, "text")
call assert_equal(expected_item.type, actual_item.type, "type")
call assert_equal(expected_item.valid, actual_item.valid, "valid")
endwhile
endfunc endfunc
func! s:normalize_durations(str) abort func! s:normalize_durations(str) abort

@ -17,6 +17,7 @@ endif
" < > " < >
" t for tag " t for tag
" Select a function in visual mode.
function! go#textobj#Function(mode) abort function! go#textobj#Function(mode) abort
let offset = go#util#OffsetCursor() let offset = go#util#OffsetCursor()
@ -98,23 +99,8 @@ function! go#textobj#Function(mode) abort
call cursor(info.rbrace.line-1, 1) call cursor(info.rbrace.line-1, 1)
endfunction endfunction
function! go#textobj#FunctionJump(mode, direction) abort " Get the location of the previous or next function.
" get count of the motion. This should be done before all the normal function! go#textobj#FunctionLocation(direction, cnt) abort
" expressions below as those reset this value(because they have zero
" count!). We abstract -1 because the index starts from 0 in motion.
let l:cnt = v:count1 - 1
" set context mark so we can jump back with '' or ``
normal! m'
" select already previously selected visual content and continue from there.
" If it's the first time starts with the visual mode. This is needed so
" after selecting something in visual mode, every consecutive motion
" continues.
if a:mode == 'v'
normal! gv
endif
let offset = go#util#OffsetCursor() let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p")) let fname = shellescape(expand("%:p"))
@ -131,7 +117,7 @@ function! go#textobj#FunctionJump(mode, direction) abort
endif endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset)
let command .= ' -shift ' . l:cnt let command .= ' -shift ' . a:cnt
if a:direction == 'next' if a:direction == 'next'
let command .= ' -mode next' let command .= ' -mode next'
@ -154,9 +140,33 @@ function! go#textobj#FunctionJump(mode, direction) abort
call delete(l:tmpname) call delete(l:tmpname)
endif endif
" convert our string dict representation into native Vim dictionary type let l:result = json_decode(out)
let result = eval(out) if type(l:result) != 4 || !has_key(l:result, 'fn')
if type(result) != 4 || !has_key(result, 'fn') return 0
endif
return l:result
endfunction
function! go#textobj#FunctionJump(mode, direction) abort
" get count of the motion. This should be done before all the normal
" expressions below as those reset this value(because they have zero
" count!). We abstract -1 because the index starts from 0 in motion.
let l:cnt = v:count1 - 1
" set context mark so we can jump back with '' or ``
normal! m'
" select already previously selected visual content and continue from there.
" If it's the first time starts with the visual mode. This is needed so
" after selecting something in visual mode, every consecutive motion
" continues.
if a:mode == 'v'
normal! gv
endif
let l:result = go#textobj#FunctionLocation(a:direction, l:cnt)
if l:result is 0
return return
endif endif

@ -330,6 +330,7 @@ function! go#util#EchoWarning(msg)
call s:echo(a:msg, 'WarningMsg') call s:echo(a:msg, 'WarningMsg')
endfunction endfunction
function! go#util#EchoProgress(msg) function! go#util#EchoProgress(msg)
redraw
call s:echo(a:msg, 'Identifier') call s:echo(a:msg, 'Identifier')
endfunction endfunction
function! go#util#EchoInfo(msg) function! go#util#EchoInfo(msg)
@ -362,7 +363,6 @@ function! go#util#archive()
return expand("%:p:gs!\\!/!") . "\n" . strlen(l:buffer) . "\n" . l:buffer return expand("%:p:gs!\\!/!") . "\n" . strlen(l:buffer) . "\n" . l:buffer
endfunction endfunction
" Make a named temporary directory which starts with "prefix". " Make a named temporary directory which starts with "prefix".
" "
" Unfortunately Vim's tempname() is not portable enough across various systems; " Unfortunately Vim's tempname() is not portable enough across various systems;
@ -384,7 +384,7 @@ function! go#util#tempdir(prefix) abort
endfor endfor
if l:dir == '' if l:dir == ''
echoerr 'Unable to find directory to store temporary directory in' call go#util#EchoError('Unable to find directory to store temporary directory in')
return return
endif endif
@ -395,4 +395,9 @@ function! go#util#tempdir(prefix) abort
return l:tmp return l:tmp
endfunction endfunction
" Report if the user enabled a debug flag in g:go_debug.
function! go#util#HasDebug(flag)
return index(get(g:, 'go_debug', []), a:flag) >= 0
endfunction
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -102,4 +102,29 @@ fun! gotest#assert_fixture(path) abort
call gotest#assert_buffer(0, l:want) call gotest#assert_buffer(0, l:want)
endfun endfun
func! gotest#assert_quickfix(got, want) abort
call assert_equal(len(a:want), len(a:got), "number of errors")
if len(a:want) != len(a:got)
call assert_equal(a:want, a:got)
return
endif
let i = 0
while i < len(a:want)
let want_item = a:want[i]
let got_item = a:got[i]
let i += 1
call assert_equal(want_item.bufnr, got_item.bufnr, "bufnr")
call assert_equal(want_item.lnum, got_item.lnum, "lnum")
call assert_equal(want_item.col, got_item.col, "col")
call assert_equal(want_item.vcol, got_item.vcol, "vcol")
call assert_equal(want_item.nr, got_item.nr, "nr")
call assert_equal(want_item.pattern, got_item.pattern, "pattern")
call assert_equal(want_item.text, got_item.text, "text")
call assert_equal(want_item.type, got_item.type, "type")
call assert_equal(want_item.valid, got_item.valid, "valid")
endwhile
endfunc
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -22,10 +22,11 @@ CONTENTS *go-contents*
6. Functions....................................|go-functions| 6. Functions....................................|go-functions|
7. Settings.....................................|go-settings| 7. Settings.....................................|go-settings|
8. Syntax highlighting..........................|go-syntax| 8. Syntax highlighting..........................|go-syntax|
9. FAQ/Troubleshooting..........................|go-troubleshooting| 9. Debugger.....................................|go-debug|
10. Development..................................|go-development| 10. FAQ/Troubleshooting..........................|go-troubleshooting|
11. Donation.....................................|go-donation| 11. Development..................................|go-development|
12. Credits......................................|go-credits| 12. Donation.....................................|go-donation|
13. Credits......................................|go-credits|
============================================================================== ==============================================================================
INTRO *go-intro* INTRO *go-intro*
@ -40,13 +41,13 @@ tools developed by the Go community to provide a seamless Vim experience.
test it with |:GoTest|. Run a single tests with |:GoTestFunc|). test it with |:GoTest|. Run a single tests with |:GoTestFunc|).
* Quickly execute your current file(s) with |:GoRun|. * Quickly execute your current file(s) with |:GoRun|.
* Improved syntax highlighting and folding. * Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with |:GoDebugStart|.
* Completion support via `gocode`. * Completion support via `gocode`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history. * `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with |:GoDef|. * Go to symbol/declaration with |:GoDef|.
* Look up documentation with |:GoDoc| or |:GoDocBrowser|. * Look up documentation with |:GoDoc| or |:GoDocBrowser|.
* Easily import packages via |:GoImport|, remove them via |:GoDrop|. * Easily import packages via |:GoImport|, remove them via |:GoDrop|.
* Automatic `GOPATH` detection which works with `gb` and `godep`. Change or * Precise type-safe renaming of identifiers with |:GoRename|.
display `GOPATH` with |:GoPath|.
* See which code is covered by tests with |:GoCoverage|. * See which code is covered by tests with |:GoCoverage|.
* Add or remove tags on struct fields with |:GoAddTags| and |:GoRemoveTags|. * Add or remove tags on struct fields with |:GoAddTags| and |:GoRemoveTags|.
* Call `gometalinter` with |:GoMetaLinter| to invoke all possible linters * Call `gometalinter` with |:GoMetaLinter| to invoke all possible linters
@ -56,7 +57,8 @@ tools developed by the Go community to provide a seamless Vim experience.
static errors, or make sure errors are checked with |:GoErrCheck|. static errors, or make sure errors are checked with |:GoErrCheck|.
* Advanced source analysis tools utilizing `guru`, such as |:GoImplements|, * Advanced source analysis tools utilizing `guru`, such as |:GoImplements|,
|:GoCallees|, and |:GoReferrers|. |:GoCallees|, and |:GoReferrers|.
* Precise type-safe renaming of identifiers with |:GoRename|. * Automatic `GOPATH` detection which works with `gb` and `godep`. Change or
display `GOPATH` with |:GoPath|.
* Integrated and improved snippets, supporting `ultisnips`, `neosnippet`, * Integrated and improved snippets, supporting `ultisnips`, `neosnippet`,
and `vim-minisnip`. and `vim-minisnip`.
* Share your current code to play.golang.org with |:GoPlay|. * Share your current code to play.golang.org with |:GoPlay|.
@ -797,6 +799,9 @@ CTRL-t
Toggles |'g:go_metalinter_autosave'|. Toggles |'g:go_metalinter_autosave'|.
By default, `gometalinter` messages will be shown in the |location-list|
window. The list to use can be set using |'g:go_list_type_commands'|.
*:GoTemplateAutoCreateToggle* *:GoTemplateAutoCreateToggle*
:GoTemplateAutoCreateToggle :GoTemplateAutoCreateToggle
@ -1097,7 +1102,7 @@ cleaned for each package after `60` seconds. This can be changed with the
*go#complete#GetInfo()* *go#complete#GetInfo()*
Returns the description of the identifer under the cursor. Can be used to plug Returns the description of the identifer under the cursor. Can be used to plug
into the statusline. This function is also used for |'g:go_auto_type_info'|. into the statusline.
============================================================================== ==============================================================================
SETTINGS *go-settings* SETTINGS *go-settings*
@ -1371,8 +1376,12 @@ function when using the `af` text object. By default it's enabled. >
*'g:go_metalinter_autosave'* *'g:go_metalinter_autosave'*
Use this option to auto |:GoMetaLinter| on save. Only linter messages for Use this option to auto |:GoMetaLinter| on save. Only linter messages for
the active buffer will be shown. By default it's disabled > the active buffer will be shown.
By default, `gometalinter` messages will be shown in the |location-list|
window. The list to use can be set using |'g:go_list_type_commands'|.
By default it's disabled >
let g:go_metalinter_autosave = 0 let g:go_metalinter_autosave = 0
< <
*'g:go_metalinter_autosave_enabled'* *'g:go_metalinter_autosave_enabled'*
@ -1384,17 +1393,17 @@ default it's using `vet` and `golint`.
< <
*'g:go_metalinter_enabled'* *'g:go_metalinter_enabled'*
Specifies the currently enabled linters for the |:GoMetaLinter| command. By Specifies the linters to enable for the |:GoMetaLinter| command. By default
default it's using `vet`, `golint` and `errcheck`. it's using `vet`, `golint` and `errcheck`.
> >
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck'] let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
< <
*'g:go_metalinter_excludes'* *'g:go_metalinter_disabled'*
Specifies the linters to be excluded from the |:GoMetaLinter| command. By Specifies the linters to disable for the |:GoMetaLinter| command. By default
default it's empty it's empty
> >
let g:go_metalinter_excludes = [] let g:go_metalinter_disabled = []
< <
*'g:go_metalinter_command'* *'g:go_metalinter_command'*
@ -1437,9 +1446,9 @@ Specifies the type of list to use for command outputs (such as errors from
builds, results from static analysis commands, etc...). When an expected key builds, results from static analysis commands, etc...). When an expected key
is not present in the dictionary, |'g:go_list_type'| will be used instead. is not present in the dictionary, |'g:go_list_type'| will be used instead.
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint", Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint",
"GoMetaLinter", "GoModifyTags" (used for both :GoAddTags and :GoRemoveTags), "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both
"GoRename", "GoRun", and "GoTest". Supported values for each command are :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported
"quickfix" and "locationlist". values for each command are "quickfix" and "locationlist".
> >
let g:go_list_type_commands = {} let g:go_list_type_commands = {}
< <
@ -1651,6 +1660,18 @@ By default "snakecase" is used. Current values are: ["snakecase",
> >
let g:go_addtags_transform = 'snakecase' let g:go_addtags_transform = 'snakecase'
< <
*'g:go_debug'*
A list of options to debug; useful for development and/or reporting bugs.
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`.
>
let g:go_debug = []
<
============================================================================== ==============================================================================
SYNTAX HIGHLIGHTING *ft-go-syntax* *go-syntax* SYNTAX HIGHLIGHTING *ft-go-syntax* *go-syntax*
@ -1725,7 +1746,7 @@ Highlight operators such as `:=` , `==`, `-=`, etc.
< <
*'g:go_highlight_functions'* *'g:go_highlight_functions'*
Highlight function names. Highlight function and method declarations.
> >
let g:go_highlight_functions = 0 let g:go_highlight_functions = 0
< <
@ -1737,11 +1758,11 @@ declarations. Setting this implies the functionality from
> >
let g:go_highlight_function_arguments = 0 let g:go_highlight_function_arguments = 0
< <
*'g:go_highlight_methods'* *'g:go_highlight_function_calls'*
Highlight method names. Highlight function and method calls.
> >
let g:go_highlight_methods = 0 let g:go_highlight_function_calls = 0
< <
*'g:go_highlight_types'* *'g:go_highlight_types'*
@ -1808,6 +1829,212 @@ The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the
`gotexttmpl` is never automatically set and needs to be set manually. `gotexttmpl` is never automatically set and needs to be set manually.
==============================================================================
DEBUGGER *go-debug*
Vim-go comes with a special "debugger mode". This starts a `dlv` process in
the background and provides various commands to communicate with it.
This debugger is similar to Visual Studio or Eclipse and has the following
features:
* Show stack trace and jumps.
* List local variables.
* List function arguments.
* Expand values of struct or array/slice.
* Show balloon on the symbol.
* Show output of stdout/stderr.
* Toggle breakpoint.
* Stack operation continue/next/step out.
This feature requires Vim 8.0.0087 or newer with the |+job| feature. Neovim
does _not_ work (yet).
This requires Delve 1.0.0 or newer, and it is recommended to use Go 1.10 or
newer, as its new caching will speed up recompiles.
*go-debug-intro*
GETTING STARTED WITH THE DEBUGGER~
Use |:GoDebugStart| or |:GoDebugTest| to start the debugger. The first
argument is the package name, and any arguments after that will be passed on
to the program; for example:
>
:GoDebugStart . -someflag value
<
This may take few seconds. After the code is compiled you'll see three new
windows: the stack trace on left side, the variable list on the bottom-left,
and program output at the bottom.
You can add breakpoints with |:GoDebugBreakpoint| (<F9>) and run your program
with |:GoDebugContinue| (<F5>).
The program will halt on the breakpoint, at which point you can inspect the
program state. You can go to the next line with |:GoDebugNext| (<F10>) or step
in with |:GoDebugStep| (<F11>).
The variable window in the bottom left (`GODEBUG_VARIABLES`) will display all
local variables. Struct values are displayed as `{...}`, array/slices as
`[4]`. Use <CR> on the variable name to expand the values.
The `GODEBUG_OUTPUT` window displays output from the program and the Delve
debugger.
The `GODEBUG_STACKTRACE` window can be used to jump to different places in the
call stack.
When you're done use |:GoDebugStop| to close the debugging windows and halt
the `dlv` process, or |:GoDebugRestart| to recompile the code.
*go-debug-commands*
DEBUGGER COMMANDS~
Only |:GoDebugStart| and |:GoDebugBreakpoint| are available by default; the
rest of the commands and mappings become available after starting debug mode.
*:GoDebugStart*
:GoDebugStart [pkg] [program-args]
Start the debug mode for [pkg]; this does several things:
* Setup the debug windows according to |'g:go_debug_windows'|.
* Make the `:GoDebug*` commands and `(go-debug-*)` mappings available.
The current directory is used if [pkg] is empty. Any other arguments will
be passed to the program.
Use |:GoDebugStop| to stop `dlv` and exit debugging mode.
*:GoDebugTest*
:GoDebugTest [pkg] [program-args]
Behaves the same as |:GoDebugStart| but runs `dlv test` instead of
`dlv debug` so you can debug tests.
Use `-test.flag` to pass flags to `go test` when debugging a test; for
example `-test.v` or `-test.run TestFoo`
*:GoDebugRestart*
:GoDebugRestart
Stop the program (if running) and restart `dlv` to recompile the package.
The current window layout and breakpoints will be left intact.
*:GoDebugStop*
*(go-debug-stop)*
:GoDebugStop
Stop `dlv` and remove all debug-specific commands, mappings, and windows.
*:GoDebugBreakpoint*
*(go-debug-breakpoint)*
:GoDebugBreakpoint [linenr]
Toggle breakpoint for the [linenr]. [linenr] defaults to the current line
if it is omitted. A line with a breakpoint will have the
{godebugbreakpoint} |:sign| placed on it. The line the program is
currently halted on will have the {godebugcurline} sign.
*hl-GoDebugCurrent* *hl-GoDebugBreakpoint*
A line with a breakpoint will be highlighted with the {GoDebugBreakpoint}
group; the line the program is currently halted on will be highlighted
with {GoDebugCurrent}.
Mapped to <F9> by default.
*:GoDebugContinue*
*(go-debug-continue)*
:GoDebugContinue
Continue execution until breakpoint or program termination. It will start
the program if it hasn't been started yet.
Mapped to <F5> by default.
*:GoDebugNext*
*(go-debug-next)*
:GoDebugNext
Advance execution by one line, also called "step over" by some other
debuggers.
It will behave as |:GoDebugContinue| if the program isn't started.
Mapped to <F10> by default.
*:GoDebugStep*
*(go-debug-step)*
:GoDebugStep
Advance execution by one step, stopping at the next line of code that will
be executed (regardless of location).
It will behave as |:GoDebugContinue| if the program isn't started.
Mapped to <F11> by default.
*:GoDebugStepOut*
*(go-debug-stepout)*
:GoDebugStepOut
Run all the code in the current function and halt when the function
returns ("step out of the current function").
It will behave as |:GoDebugContinue| if the program isn't started.
*:GoDebugSet*
:GoDebugSet {var} {value}
Set the variable {var} to {value}. Example:
>
:GoDebugSet truth 42
<
This only works for `float`, `int` and variants, `uint` and variants,
`bool`, and pointers (this is a `delve` limitation, not a vim-go
limitation).
*:GoDebugPrint*
*(go-debug-print)*
:GoDebugPrint {expr}
Print the result of a Go expression.
>
:GoDebugPrint truth == 42
truth == 42 true
<
Mapped to <F6> by default, which will evaluate the <cword> under the
cursor.
*go-debug-settings*
DEBUGGER SETTINGS~
*'g:go_debug_windows'*
Controls the window layout for debugging mode. This is a |dict| with three
possible keys: "stack", "out", and "vars"; the windows will created in that
order with the commands in the value.
A window will not be created if a key is missing or empty.
Defaults:
>
let g:go_debug_windows = {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ }
<
Show only variables on the right-hand side: >
let g:go_debug_windows = {
\ 'vars': 'rightbelow 60vnew',
\ }
<
*'g:go_debug_address'*
Server address `dlv` will listen on; must be in `hostname:port` format.
Defaults to `127.0.0.1:8181`:
>
let g:go_debug_address = '127.0.0.1:8181'
<
============================================================================== ==============================================================================
FAQ TROUBLESHOOTING *go-troubleshooting* FAQ TROUBLESHOOTING *go-troubleshooting*

@ -1,3 +1,5 @@
" vint: -ProhibitAutocmdWithNoGroup
" We take care to preserve the user's fileencodings and fileformats, " We take care to preserve the user's fileencodings and fileformats,
" because those settings are global (not buffer local), yet we want " because those settings are global (not buffer local), yet we want
" to override them for loading Go files, which are defined to be UTF-8. " to override them for loading Go files, which are defined to be UTF-8.
@ -18,17 +20,15 @@ function! s:gofiletype_post()
let &g:fileencodings = s:current_fileencodings let &g:fileencodings = s:current_fileencodings
endfunction endfunction
augroup vim-go-filetype " Note: should not use augroup in ftdetect (see :help ftdetect)
autocmd! au BufNewFile *.go setfiletype go | setlocal fileencoding=utf-8 fileformat=unix
au BufNewFile *.go setfiletype go | setlocal fileencoding=utf-8 fileformat=unix au BufRead *.go call s:gofiletype_pre("go")
au BufRead *.go call s:gofiletype_pre("go") au BufReadPost *.go call s:gofiletype_post()
au BufReadPost *.go call s:gofiletype_post()
au BufNewFile *.s setfiletype asm | setlocal fileencoding=utf-8 fileformat=unix au BufNewFile *.s setfiletype asm | setlocal fileencoding=utf-8 fileformat=unix
au BufRead *.s call s:gofiletype_pre("asm") au BufRead *.s call s:gofiletype_pre("asm")
au BufReadPost *.s call s:gofiletype_post() au BufReadPost *.s call s:gofiletype_post()
au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl
augroup end
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -98,4 +98,11 @@ command! -nargs=0 GoKeyify call go#keyify#Keyify()
" -- fillstruct " -- fillstruct
command! -nargs=0 GoFillStruct call go#fillstruct#FillStruct() command! -nargs=0 GoFillStruct call go#fillstruct#FillStruct()
" -- debug
if !exists(':GoDebugStart')
command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, <f-args>)
command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, <f-args>)
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
endif
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

@ -40,7 +40,7 @@ function! s:GoMinisnip() abort
endif endif
if exists('g:minisnip_dir') if exists('g:minisnip_dir')
let g:minisnip_dir .= ':' . globpath(&rtp, 'gosnippets/minisnip') let g:minisnip_dir .= go#util#PathListSep() . globpath(&rtp, 'gosnippets/minisnip')
else else
let g:minisnip_dir = globpath(&rtp, 'gosnippets/minisnip') let g:minisnip_dir = globpath(&rtp, 'gosnippets/minisnip')
endif endif

@ -252,6 +252,11 @@ snippet fn "fmt.Println(...)"
fmt.Println("${1:${VISUAL}}") fmt.Println("${1:${VISUAL}}")
endsnippet endsnippet
# Fmt Errorf debug
snippet fe "fmt.Errorf(...)"
fmt.Errorf("${1:${VISUAL}}")
endsnippet
# log printf # log printf
snippet lf "log.Printf(...)" snippet lf "log.Printf(...)"
log.Printf("${1:${VISUAL}} = %+v\n", $1) log.Printf("${1:${VISUAL}} = %+v\n", $1)

@ -128,21 +128,21 @@ abbr if err := ...; err != nil { ... }
# error snippet # error snippet
snippet errn snippet errn
abbr if err != nil { ... } abbr if err != nil { return err }
if err != nil { if err != nil {
return err return err
} }
${0} ${0}
# error snippet in TestFunc # error snippet in TestFunc
snippet errt snippet errt
abbr if err != nil { ... } abbr if err != nil { t.Fatal(err) }
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
# error snippet in log.Fatal # error snippet in log.Fatal
snippet errl snippet errl
abbr if err != nil { ... } abbr if err != nil { log.Fatal(err) }
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -157,7 +157,7 @@ abbr if err != nil { return [...], err }
# error snippet handle and return # error snippet handle and return
snippet errh snippet errh
abbr if err != nil { return } abbr if err != nil { ... return }
if err != nil { if err != nil {
${1} ${1}
return return
@ -166,7 +166,7 @@ abbr if err != nil { return }
# error snippet with panic # error snippet with panic
snippet errp snippet errp
abbr if err != nil { ... } abbr if err != nil { panic(...) }
if err != nil { if err != nil {
panic(${1}) panic(${1})
} }
@ -219,6 +219,10 @@ abbr fmt.Printf(...)
snippet fn snippet fn
abbr fmt.Println(...) abbr fmt.Println(...)
fmt.Println("${1}") fmt.Println("${1}")
# Fmt Errorf
snippet fe
abbr fmt.Errorf(...)
fmt.Errorf("${1}")
# log printf # log printf
snippet lf snippet lf
abbr log.Printf(...) abbr log.Printf(...)

@ -31,6 +31,7 @@ endif
" needed by the user with GoInstallBinaries " needed by the user with GoInstallBinaries
let s:packages = { let s:packages = {
\ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt'], \ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt'],
\ 'dlv': ['github.com/derekparker/delve/cmd/dlv'],
\ 'errcheck': ['github.com/kisielk/errcheck'], \ 'errcheck': ['github.com/kisielk/errcheck'],
\ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct'], \ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct'],
\ 'gocode': ['github.com/nsf/gocode', {'windows': '-ldflags -H=windowsgui'}], \ 'gocode': ['github.com/nsf/gocode', {'windows': '-ldflags -H=windowsgui'}],

@ -42,8 +42,8 @@ if !exists("g:go_highlight_function_arguments")
let g:go_highlight_function_arguments = 0 let g:go_highlight_function_arguments = 0
endif endif
if !exists("g:go_highlight_methods") if !exists("g:go_highlight_function_calls")
let g:go_highlight_methods = 0 let g:go_highlight_function_calls = 0
endif endif
if !exists("g:go_highlight_fields") if !exists("g:go_highlight_fields")
@ -202,7 +202,19 @@ else
endif endif
if g:go_highlight_format_strings != 0 if g:go_highlight_format_strings != 0
syn match goFormatSpecifier /\([^%]\(%%\)*\)\@<=%[-#0 +]*\%(\*\|\d\+\)\=\%(\.\%(\*\|\d\+\)\)*[vTtbcdoqxXUeEfgGsp]/ contained containedin=goString " [n] notation is valid for specifying explicit argument indexes
" 1. Match a literal % not preceded by a %.
" 2. Match any number of -, #, 0, space, or +
" 3. Match * or [n]* or any number or nothing before a .
" 4. Match * or [n]* or any number or nothing after a .
" 5. Match [n] or nothing before a verb
" 6. Match a formatting verb
syn match goFormatSpecifier /\
\([^%]\(%%\)*\)\
\@<=%[-#0 +]*\
\%(\%(\%(\[\d\+\]\)\=\*\)\|\d\+\)\=\
\%(\.\%(\%(\%(\[\d\+\]\)\=\*\)\|\d\+\)\=\)\=\
\%(\[\d\+\]\)\=[vTtbcdoqxXUeEfFgGsp]/ contained containedin=goString,goRawString
hi def link goFormatSpecifier goSpecialString hi def link goFormatSpecifier goSpecialString
endif endif
@ -348,7 +360,6 @@ hi def link goOperator Operator
" Functions; " Functions;
if g:go_highlight_functions isnot 0 || g:go_highlight_function_arguments isnot 0 if g:go_highlight_functions isnot 0 || g:go_highlight_function_arguments isnot 0
syn match goFunctionCall /\w\+\ze(/ contains=goBuiltins,goDeclaration
syn match goDeclaration /\<func\>/ nextgroup=goReceiver,goFunction,goSimpleArguments skipwhite skipnl syn match goDeclaration /\<func\>/ nextgroup=goReceiver,goFunction,goSimpleArguments skipwhite skipnl
syn match goReceiverVar /\w\+\ze\s\+\(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained syn match goReceiverVar /\w\+\ze\s\+\(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained
syn match goPointerOperator /\*/ nextgroup=goReceiverType contained skipwhite skipnl syn match goPointerOperator /\*/ nextgroup=goReceiverType contained skipwhite skipnl
@ -367,13 +378,12 @@ else
syn keyword goDeclaration func syn keyword goDeclaration func
endif endif
hi def link goFunction Function hi def link goFunction Function
hi def link goFunctionCall Type
" Methods; " Function calls;
if g:go_highlight_methods != 0 if g:go_highlight_function_calls != 0
syn match goMethodCall /\.\w\+\ze(/hs=s+1 syn match goFunctionCall /\w\+\ze(/ contains=goBuiltins,goDeclaration
endif endif
hi def link goMethodCall Type hi def link goFunctionCall Type
" Fields; " Fields;
if g:go_highlight_fields != 0 if g:go_highlight_fields != 0
@ -443,7 +453,7 @@ if g:go_highlight_build_constraints != 0 || s:fold_package_comment
\ . ' contains=@goCommentGroup,@Spell' \ . ' contains=@goCommentGroup,@Spell'
\ . (s:fold_package_comment ? ' fold' : '') \ . (s:fold_package_comment ? ' fold' : '')
exe 'syn region goPackageComment start=/\v\/\*.*\n(.*\n)*\s*\*\/\npackage/' exe 'syn region goPackageComment start=/\v\/\*.*\n(.*\n)*\s*\*\/\npackage/'
\ . ' end=/\v\n\s*package/he=e-7,me=e-7,re=e-7' \ . ' end=/\v\*\/\n\s*package/he=e-7,me=e-7,re=e-7'
\ . ' contains=@goCommentGroup,@Spell' \ . ' contains=@goCommentGroup,@Spell'
\ . (s:fold_package_comment ? ' fold' : '') \ . (s:fold_package_comment ? ' fold' : '')
hi def link goPackageComment Comment hi def link goPackageComment Comment

@ -0,0 +1,13 @@
if exists("b:current_syntax")
finish
endif
syn match godebugOutputErr '^ERR:.*'
syn match godebugOutputOut '^OUT:.*'
let b:current_syntax = "godebugoutput"
hi def link godebugOutputErr Comment
hi def link godebugOutputOut Normal
" vim: sw=2 ts=2 et

@ -0,0 +1,11 @@
if exists("b:current_syntax")
finish
endif
syn match godebugStacktrace '^\S\+'
let b:current_syntax = "godebugoutput"
hi def link godebugStacktrace SpecialKey
" vim: sw=2 ts=2 et

@ -0,0 +1,23 @@
if exists("b:current_syntax")
finish
endif
syn match godebugTitle '^#.*'
syn match godebugVariables '^\s*\S\+\ze:'
syn keyword goType chan map bool string error
syn keyword goSignedInts int int8 int16 int32 int64 rune
syn keyword goUnsignedInts byte uint uint8 uint16 uint32 uint64 uintptr
syn keyword goFloats float32 float64
syn keyword goComplexes complex64 complex128
syn keyword goBoolean true false
let b:current_syntax = "godebugvariables"
hi def link godebugTitle Underlined
hi def link godebugVariables Statement
hi def link goType Type
hi def link goBoolean Boolean
" vim: sw=2 ts=2 et
Loading…
Cancel
Save