1
0
mirror of https://github.com/amix/vimrc synced 2025-06-16 09:35:01 +08:00

Updated plugins

This commit is contained in:
Amir Salihefendic
2018-03-31 11:56:26 -03:00
parent 7c643a2d9c
commit 02572caa95
84 changed files with 4588 additions and 1749 deletions

View File

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

View File

@ -1,5 +1,15 @@
## 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.
@ -8,6 +18,16 @@ IMPROVEMENTS:
`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:
@ -47,7 +67,17 @@ BUG FIXES:
* 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:
@ -57,7 +87,7 @@ BACKWARDS INCOMPATIBILITIES:
* `go_highlight_methods`
in favor of the following settings and changes:
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
@ -67,6 +97,9 @@ BACKWARDS INCOMPATIBILITIES:
[[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)

View File

@ -9,16 +9,16 @@
This plugin adds Go language support for Vim, with the following main features:
* Compile your package with `:GoBuild`, install it with `:GoInstall` or test it
with `:GoTest`. Run a single tests with `:GoTestFunc`).
with `:GoTest`. Run a single test with `:GoTestFunc`).
* Quickly execute your current file(s) with `:GoRun`.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`.
* Completion support via `gocode`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`.
* Easily import packages via `:GoImport`, remove them via `:GoDrop`.
* Automatic `GOPATH` detection which works with `gb` and `godep`. Change or
display `GOPATH` with `:GoPath`.
* Precise type-safe renaming of identifiers with `:GoRename`.
* See which code is covered by tests with `:GoCoverage`.
* Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`.
* 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`.
* Advanced source analysis tools utilizing `guru`, such as `:GoImplements`,
`: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
information.

View File

@ -17,7 +17,7 @@ function! go#cmd#Build(bang, ...) abort
let args =
\ ["build"] +
\ map(copy(a:000), "expand(v:val)") +
\ ["-i", ".", "errors"]
\ [".", "errors"]
" Vim async.
if go#util#has_job()

View File

@ -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

View File

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

View File

@ -45,7 +45,7 @@ function! go#coverage#Buffer(bang, ...) abort
let l:tmpname = tempname()
if get(g:, 'go_echo_command_info', 1)
echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None
call go#util#EchoProgress("testing...")
endif
if go#util#has_job()

View File

@ -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

View File

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

View File

@ -78,7 +78,7 @@ function! s:guru_cmd(args) range abort
let scopes = go#util#StripTrailingSlash(scopes)
" 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
let l:scope = join(scopes, ",")
@ -129,7 +129,7 @@ function! s:sync_guru(args) abort
endif
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
call s:parse_guru_output(go#util#ShellError(), out, a:args.mode)
endif
@ -137,6 +137,33 @@ function! s:sync_guru(args) abort
return out
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
function! s:async_guru(args) abort
let result = s:guru_cmd(a:args)
@ -145,8 +172,6 @@ function! s:async_guru(args) abort
return
endif
let status_dir = expand('%:p:h')
let statusline_type = printf("%s", a:args.mode)
if !has_key(a:args, 'disable_progress')
if a:args.needs_scope
@ -155,60 +180,64 @@ function! s:async_guru(args) abort
endif
endif
let messages = []
function! s:callback(chan, msg) closure
call add(messages, a:msg)
let state = {
\ 'status_dir': expand('%:p:h'),
\ '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
let status = {}
let exitval = 0
let closed = 0
let exited = 0
function! s:exit_cb(job, exitval) closure
let exited = 1
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let status = {
\ 'desc': 'last status',
\ 'type': statusline_type,
\ 'type': self.statusline_type,
\ 'state': "finished",
\ }
if a:exitval
let exitval = a:exitval
let self.exitval = a:exitval
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status)
call go#statusline#Update(self.status_dir, status)
if closed
call s:complete()
if self.closed
call self.complete()
endif
endfunction
function! s:close_cb(ch) closure
let closed = 1
function! s:close_cb(ch) dict
let self.closed = 1
if exited
call s:complete()
if self.exited
call self.complete()
endif
endfunction
function! s:complete() closure
let out = join(messages, "\n")
function state.complete() dict
let out = join(self.messages, "\n")
if has_key(a:args, 'custom_parse')
call a:args.custom_parse(exitval, out)
else
call s:parse_guru_output(exitval, out, a:args.mode)
endif
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 = {
\ 'callback': funcref("s:callback"),
\ 'exit_cb': funcref("s:exit_cb"),
\ 'close_cb': funcref("s:close_cb"),
\ }
\ 'callback': function('s:callback', [], state),
\ 'exit_cb': function('s:exit_cb', [], state),
\ 'close_cb': function('s:close_cb', [], state)
\ }
if has_key(result, 'stdin_content')
let l:tmpname = tempname()
@ -217,18 +246,18 @@ function! s:async_guru(args) abort
let l:start_options.in_name = l:tmpname
endif
call go#statusline#Update(status_dir, {
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status",
\ 'type': statusline_type,
\ 'type': state.statusline_type,
\ 'state': "analysing",
\})
return job_start(result.cmd, start_options)
return s:job_start(result.cmd, start_options)
endfunc
" run_guru runs the given guru argument
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)
else
let res = s:sync_guru(a:args)
@ -289,7 +318,7 @@ function! go#guru#DescribeInfo() abort
return
endif
function! s:info(exit_val, output)
function! s:info(exit_val, output, mode)
if a:exit_val != 0
return
endif
@ -464,10 +493,6 @@ function! go#guru#Referrers(selected) abort
call s:run_guru(args)
endfunction
function! go#guru#SameIdsTimer() abort
call timer_start(200, function('go#guru#SameIds'), {'repeat': -1})
endfunction
function! go#guru#SameIds() abort
" 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
@ -495,7 +520,7 @@ function! go#guru#SameIds() abort
call s:run_guru(args)
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.
if a:output[0] !=# '{'

View File

@ -19,10 +19,6 @@
" 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.
" 'callback':
" A function to call when there is a message to read from the job's
" channel. The function will be passed two arguments: the channel and a
" message. See job-callback.
" The return value is a dictionary with these keys:
" 'callback':
@ -36,43 +32,46 @@
" job-close_cb.
function go#job#Spawn(args)
let cbs = {}
let winnr = winnr()
let dir = getcwd()
let jobdir = fnameescape(expand("%:p:h"))
let messages = []
let args = a:args.cmd
let bang = 0
let for = "_job"
let state = {
\ 'winnr': winnr(),
\ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")),
\ 'messages': [],
\ 'args': a:args.cmd,
\ 'bang': 0,
\ 'for': "_job",
\ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'errorformat': &errorformat
\ }
if has_key(a:args, 'bang')
let l:bang = a:args.bang
let state.bang = a:args.bang
endif
if has_key(a:args, 'for')
let l:for = a:args.for
let state.for = a:args.for
endif
let l:exited = 0
let l:exit_status = 0
let l:closed = 0
function! s:NopComplete(job, exit_status, data)
" do nothing in state.complete by default.
function state.complete(job, exit_status, data)
endfunction
let Complete = funcref('s:NopComplete')
if has_key(a:args, 'complete')
let Complete = a:args.complete
let state.complete = a:args.complete
endif
function cbs.callback(chan, msg) dict closure
call add(messages, a:msg)
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
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 closure
let exit_status = a:exitval
let exited = 1
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0
@ -82,56 +81,63 @@ function go#job#Spawn(args)
endif
endif
if closed
call Complete(a:job, exit_status, messages)
call s:show_errors(a:job, exit_status, messages)
if self.closed
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 cbs.close_cb(ch) dict closure
let closed = 1
function! s:close_cb(ch) dict
let self.closed = 1
if exited
if self.exited
let job = ch_getjob(a:ch)
call Complete(job, exit_status, messages)
call s:show_errors(job, exit_status, messages)
call self.complete(job, self.exit_status, self.messages)
call self.show_errors(job, self.exit_status, self.messages)
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! s:show_errors(job, exit_status, data) closure
let l:listtype = go#list#Type(for)
function state.show_errors(job, exit_status, data)
let l:listtype = go#list#Type(self.for)
if a:exit_status == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
return
endif
let l:listtype = go#list#Type(for)
let l:listtype = go#list#Type(self.for)
if len(a:data) == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
return
endif
let out = join(self.messages, "\n")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
try
execute cd jobdir
let errors = go#tool#ParseErrors(a:data)
let errors = go#tool#FilterValids(errors)
" parse the errors relative to self.jobdir
execute cd self.jobdir
call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for)
let errors = go#list#Get(l:listtype)
finally
execute cd . fnameescape(dir)
execute cd . fnameescape(self.dir)
endtry
if empty(errors)
" failed to parse errors, output the original content
call go#util#EchoError(messages + [dir])
call go#util#EchoError(self.messages + [self.dir])
return
endif
if winnr == winnr()
call go#list#Populate(l:listtype, errors, join(args))
if self.winnr == winnr()
call go#list#Window(l:listtype, len(errors))
if !bang
if !self.bang
call go#list#JumpToFirst(l:listtype)
endif
endif

View File

@ -62,6 +62,7 @@ function! s:spawn(bang, desc, for, args) abort
\ 'status_dir' : status_dir,
\ 'started_at' : started_at,
\ 'for' : a:for,
\ 'errorformat': &errorformat,
\ }
" 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)
if a:exit_status == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
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")
endif
let errors = go#tool#ParseErrors(std_combined)
let errors = go#tool#FilterValids(errors)
" parse the errors relative to self.jobdir
call go#list#ParseFormat(l:listtype, self.errorformat, std_combined, self.for)
let errors = go#list#Get(l:listtype)
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 self.winnr == winnr()
call go#list#Populate(l:listtype, errors, self.desc)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !self.bang
call go#list#JumpToFirst(l:listtype)

View File

@ -105,7 +105,6 @@ function! go#lint#Gometa(autosave, ...) abort
if l:err == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
else
" GoMetaLinter can output one of the two, so we look for both:
@ -147,7 +146,7 @@ function! go#lint#Golint(...) abort
endif
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)
call go#list#Window(l:listtype, len(errors))
call go#list#JumpToFirst(l:listtype)
@ -166,8 +165,9 @@ function! go#lint#Vet(bang, ...) abort
let l:listtype = go#list#Type("GoVet")
if go#util#ShellError() != 0
let errors = go#tool#ParseErrors(split(out, '\n'))
call go#list#Populate(l:listtype, errors, 'Vet')
let errorformat="%-Gexit status %\\d%\\+," . &errorformat
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))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
@ -175,7 +175,6 @@ function! go#lint#Vet(bang, ...) abort
echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
endif
endfunction
@ -228,7 +227,6 @@ function! go#lint#Errcheck(...) abort
endif
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None
endif
@ -246,10 +244,18 @@ function! go#lint#ToggleMetaLinterAutoSave() abort
endfunction
function! s:lint_job(args, autosave)
let status_dir = expand('%:p:h')
let started_at = reltime()
let state = {
\ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime(),
\ 'messages': [],
\ 'exited': 0,
\ 'closed': 0,
\ 'exit_status': 0,
\ 'winnr': winnr(),
\ 'autosave': a:autosave
\ }
call go#statusline#Update(status_dir, {
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status",
\ 'type': "gometalinter",
\ 'state': "analysing",
@ -259,26 +265,18 @@ function! s:lint_job(args, autosave)
call go#cmd#autowrite()
if a:autosave
let l:listtype = go#list#Type("GoMetaLinterAutoSave")
let state.listtype = go#list#Type("GoMetaLinterAutoSave")
else
let l:listtype = go#list#Type("GoMetaLinter")
let state.listtype = go#list#Type("GoMetaLinter")
endif
let l:errformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m'
let l:messages = []
let l:exited = 0
let l:closed = 0
let l:exit_status = 0
let l:winnr = winnr()
function! s:callback(chan, msg) closure
call add(messages, a:msg)
function! s:callback(chan, msg) dict closure
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) closure
let exited = 1
let exit_status = a:exitval
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exit_status = a:exitval
let status = {
\ 'desc': 'last status',
@ -290,50 +288,63 @@ function! s:lint_job(args, autosave)
let status.state = "failed"
endif
let elapsed_time = reltimestr(reltime(started_at))
let elapsed_time = reltimestr(reltime(self.started_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(status_dir, status)
call go#statusline#Update(self.status_dir, status)
if closed
call s:show_errors()
if self.closed
call self.show_errors()
endif
endfunction
function! s:close_cb(ch) closure
let closed = 1
function! s:close_cb(ch) dict
let self.closed = 1
if exited
call s:show_errors()
if self.exited
call self.show_errors()
endif
endfunction
function! s:show_errors() closure
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 l:listtype == 'locationlist'
exe l:winnr . "wincmd w"
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(l:listtype, l:errorformat, messages, 'GoMetaLinter')
call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter')
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
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
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoSuccess("linting finished")
endif
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 = {
\ 'callback': funcref("s:callback"),
\ 'exit_cb': funcref("s:exit_cb"),
\ 'close_cb': funcref("s:close_cb"),
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state),
\ }
call job_start(a:args.cmd, start_options)

View File

@ -3,7 +3,7 @@ func! Test_Gometa() abort
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': 3, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ {'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
@ -37,7 +37,7 @@ func! Test_GometaWithDisabled() abort
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': 3, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ {'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
@ -71,7 +71,7 @@ func! Test_GometaAutoSave() abort
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': 2, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ {'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()
@ -102,4 +102,30 @@ func! Test_GometaAutoSave() abort
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

View File

@ -18,14 +18,7 @@ function! go#list#Window(listtype, ...) abort
" location list increases/decreases, cwindow will not resize when a new
" updated height is passed. lopen in the other hand resizes the screen.
if !a:0 || a:1 == 0
let autoclose_window = get(g:, 'go_list_autoclose', 1)
if autoclose_window
if a:listtype == "locationlist"
lclose
else
cclose
endif
endif
call go#list#Close(a:listtype)
return
endif
@ -79,13 +72,7 @@ function! go#list#ParseFormat(listtype, errformat, items, title) abort
" parse and populate the location list
let &errorformat = a:errformat
try
if a:listtype == "locationlist"
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
call go#list#Parse(a:listtype, a:items, a:title)
finally
"restore back
let &errorformat = old_errorformat
@ -94,11 +81,13 @@ endfunction
" Parse parses the given items based on the global errorformat and
" populates the list.
function! go#list#Parse(listtype, items) abort
function! go#list#Parse(listtype, items, title) abort
if a:listtype == "locationlist"
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
endfunction
@ -111,13 +100,29 @@ function! go#list#JumpToFirst(listtype) abort
endif
endfunction
" Clean cleans the location list
" Clean cleans and closes the location list
function! go#list#Clean(listtype) abort
if a:listtype == "locationlist"
lex []
else
cex []
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
function! s:listtype(listtype) abort

View File

@ -54,8 +54,14 @@ function! go#package#Paths() abort
return dirs
endfunction
let s:import_paths = {}
" ImportPath returns the import path in the current directory it was executed
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")
if go#util#ShellError() != 0
return -1
@ -69,6 +75,8 @@ function! go#package#ImportPath() abort
return -1
endif
let s:import_paths[dir] = import_path
return import_path
endfunction

View File

@ -72,20 +72,22 @@ function! go#rename#Rename(bang, ...) abort
endfunction
function s:rename_job(args)
let exited = 0
let closed = 0
let exitval = 0
let messages = []
let state = {
\ 'exited': 0,
\ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'status_dir': expand('%:p:h'),
\ 'bang': a:args.bang
\ }
function! s:callback(chan, msg) closure
call add(messages, a:msg)
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
let status_dir = expand('%:p:h')
function! s:exit_cb(job, exitval) closure
let exited = 1
let exitval = a:exitval
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
let status = {
\ 'desc': 'last status',
@ -97,28 +99,30 @@ function s:rename_job(args)
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status)
call go#statusline#Update(self.status_dir, status)
if closed
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) closure
let closed = 1
function! s:close_cb(ch) dict
let self.closed = 1
if exited
call s:parse_errors(exitval, a:args.bang, messages)
if self.exited
call s:parse_errors(self.exitval, self.bang, self.messages)
endif
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 = {
\ 'callback': funcref("s:callback"),
\ 'exit_cb': funcref("s:exit_cb"),
\ 'close_cb': funcref("s:close_cb"),
\ 'callback': funcref("s:callback", [], state),
\ '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",
\ 'type': "gorename",
\ 'state': "started",
@ -156,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
" will trigger the 'Hit ENTER to continue' prompt
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
call go#util#EchoSuccess(a:out[0])
" refresh the buffer so we can see the new content

View File

@ -24,7 +24,7 @@ function! go#template#create() abort
let l:template_file = get(g:, 'go_template_file', "hello_world.go")
endif
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
" cwd is now the dir of the package
let l:path = fnamemodify(getcwd(), ':t')

View File

@ -2,9 +2,6 @@ if has('nvim') && !exists("g:go_term_mode")
let g:go_term_mode = 'vsplit'
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
" global variable g:go_term_mode, which is by default set to :vsplit
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
endif
let state = {
\ 'cmd': a:cmd,
\ 'bang' : a:bang,
\ 'winid': win_getid(winnr()),
\ 'stdout': []
\ }
" execute go build in the files directory
let l:winnr = winnr()
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
@ -33,30 +36,27 @@ function! go#term#newmode(bang, cmd, mode) abort
setlocal noswapfile
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 = {
\ 'stderr' : [],
\ 'stdout' : [],
\ 'bang' : a:bang,
\ 'on_stdout': function('s:on_stdout'),
\ 'on_stderr': function('s:on_stderr'),
\ 'on_exit' : function('s:on_exit'),
\ }
\ 'on_stdout': function('s:on_stdout', [], state),
\ 'on_exit' : function('s:on_exit', [], state),
\ }
let id = termopen(a:cmd, job)
let state.id = termopen(a:cmd, job)
let state.termwinid = win_getid(winnr())
execute cd . fnameescape(dir)
let job.id = id
let job.cmd = a:cmd
startinsert
" resize new term if needed.
let height = get(g:, 'go_term_height', winheight(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
" the height. The below command resizes the buffer
" Adjust the window width or height depending on whether it's a vertical or
" horizontal split.
if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew"
exe 'vertical resize ' . width
elseif mode =~ "split" || mode =~ "new"
@ -64,77 +64,56 @@ function! go#term#newmode(bang, cmd, mode) abort
endif
" 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
stopinsert
call win_gotoid(state.winid)
if l:winnr !=# winnr()
exe l:winnr . "wincmd w"
endif
return id
return state.id
endfunction
function! s:on_stdout(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.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)
call extend(self.stdout, a:data)
endfunction
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")
" usually there is always output so never branch into this clause
if empty(job.stdout)
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
unlet s:jobs[a:job_id]
if empty(self.stdout)
call s:cleanlist(self.winid, l:listtype)
return
endif
let errors = go#tool#ParseErrors(job.stdout)
let errors = go#tool#ParseErrors(self.stdout)
let errors = go#tool#FilterValids(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
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))
if !self.bang
call go#list#JumpToFirst(l:listtype)
endif
unlet s:jobs[a:job_id]
return
endif
" tests are passing clean the list and close the list. But we only can
" close them from a normal view, so jump back, close the list and then
" again jump back to the terminal
wincmd p
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
wincmd p
call s:cleanlist(self.winid, l:listtype)
endfunction
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
" vim: sw=2 ts=2 et

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -94,7 +94,6 @@ function! go#test#Test(bang, compile, ...) abort
call go#util#EchoError("[test] FAIL")
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
if a:compile
call go#util#EchoSuccess("[test] SUCCESS")
@ -139,9 +138,6 @@ function! go#test#Func(bang, ...) abort
endfunction
function! s:test_job(args) abort
let status_dir = expand('%:p:h')
let started_at = reltime()
let status = {
\ 'desc': 'current status',
\ 'type': "test",
@ -152,23 +148,29 @@ function! s:test_job(args) abort
let status.state = "compiling"
endif
call go#statusline#Update(status_dir, status)
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let l:exited = 0
let l:closed = 0
let l:exitval = 0
let messages = []
let state = {
\ 'exited': 0,
\ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'args': a:args,
\ 'compile_test': a:args.compile_test,
\ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime()
\ }
function! s:callback(chan, msg) closure
call add(messages, a:msg)
call go#statusline#Update(state.status_dir, status)
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) closure
let exited = 1
let exitval = a:exitval
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
let status = {
\ 'desc': 'last status',
@ -176,7 +178,7 @@ function! s:test_job(args) abort
\ 'state': "pass",
\ }
if a:args.compile_test
if self.compile_test
let status.state = "success"
endif
@ -186,7 +188,7 @@ function! s:test_job(args) abort
if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0
if a:args.compile_test
if self.compile_test
call go#util#EchoSuccess("[test] SUCCESS")
else
call go#util#EchoSuccess("[test] PASS")
@ -196,31 +198,33 @@ function! s:test_job(args) abort
endif
endif
let elapsed_time = reltimestr(reltime(started_at))
let elapsed_time = reltimestr(reltime(self.started_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(status_dir, status)
call go#statusline#Update(self.status_dir, status)
if closed
call s:show_errors(a:args, l:exitval, messages)
if self.closed
call s:show_errors(self.args, self.exitval, self.messages)
endif
endfunction
function! s:close_cb(ch) closure
let closed = 1
function! s:close_cb(ch) dict
let self.closed = 1
if exited
call s:show_errors(a:args, l:exitval, messages)
if self.exited
call s:show_errors(self.args, self.exitval, self.messages)
endif
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 = {
\ 'callback': funcref("s:callback"),
\ 'exit_cb': funcref("s:exit_cb"),
\ 'close_cb': funcref("s:close_cb"),
\ }
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state)
\ }
" pre start
let dir = getcwd()
@ -241,7 +245,6 @@ 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)
call go#list#Window(l:listtype)
return
endif

View File

@ -17,6 +17,7 @@ endif
" < >
" t for tag
" Select a function in visual mode.
function! go#textobj#Function(mode) abort
let offset = go#util#OffsetCursor()
@ -98,23 +99,8 @@ function! go#textobj#Function(mode) abort
call cursor(info.rbrace.line-1, 1)
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
" Get the location of the previous or next function.
function! go#textobj#FunctionLocation(direction, cnt) abort
let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p"))
@ -131,7 +117,7 @@ function! go#textobj#FunctionJump(mode, direction) abort
endif
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'
let command .= ' -mode next'
@ -154,9 +140,33 @@ function! go#textobj#FunctionJump(mode, direction) abort
call delete(l:tmpname)
endif
" convert our string dict representation into native Vim dictionary type
let result = eval(out)
if type(result) != 4 || !has_key(result, 'fn')
let l:result = json_decode(out)
if type(l:result) != 4 || !has_key(l: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
endif

View File

@ -330,6 +330,7 @@ function! go#util#EchoWarning(msg)
call s:echo(a:msg, 'WarningMsg')
endfunction
function! go#util#EchoProgress(msg)
redraw
call s:echo(a:msg, 'Identifier')
endfunction
function! go#util#EchoInfo(msg)
@ -362,7 +363,6 @@ function! go#util#archive()
return expand("%:p:gs!\\!/!") . "\n" . strlen(l:buffer) . "\n" . l:buffer
endfunction
" Make a named temporary directory which starts with "prefix".
"
" Unfortunately Vim's tempname() is not portable enough across various systems;
@ -384,7 +384,7 @@ function! go#util#tempdir(prefix) abort
endfor
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
endif
@ -395,4 +395,9 @@ function! go#util#tempdir(prefix) abort
return l:tmp
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

View File

@ -22,10 +22,11 @@ CONTENTS *go-contents*
6. Functions....................................|go-functions|
7. Settings.....................................|go-settings|
8. Syntax highlighting..........................|go-syntax|
9. FAQ/Troubleshooting..........................|go-troubleshooting|
10. Development..................................|go-development|
11. Donation.....................................|go-donation|
12. Credits......................................|go-credits|
9. Debugger.....................................|go-debug|
10. FAQ/Troubleshooting..........................|go-troubleshooting|
11. Development..................................|go-development|
12. Donation.....................................|go-donation|
13. Credits......................................|go-credits|
==============================================================================
INTRO *go-intro*
@ -37,16 +38,16 @@ and individual features can be toggled easily. vim-go leverages a number of
tools developed by the Go community to provide a seamless Vim experience.
* Compile your package with |:GoBuild|, install it with |:GoInstall| or
test it with |:GoTest|. Run a single tests with |:GoTestFunc|).
test it with |:GoTest|. Run a single test with |:GoTestFunc|).
* Quickly execute your current file(s) with |:GoRun|.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with |:GoDebugStart|.
* Completion support via `gocode`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with |:GoDef|.
* Look up documentation with |:GoDoc| or |:GoDocBrowser|.
* Easily import packages via |:GoImport|, remove them via |:GoDrop|.
* Automatic `GOPATH` detection which works with `gb` and `godep`. Change or
display `GOPATH` with |:GoPath|.
* Precise type-safe renaming of identifiers with |:GoRename|.
* See which code is covered by tests with |:GoCoverage|.
* Add or remove tags on struct fields with |:GoAddTags| and |:GoRemoveTags|.
* 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|.
* Advanced source analysis tools utilizing `guru`, such as |:GoImplements|,
|: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`,
and `vim-minisnip`.
* Share your current code to play.golang.org with |:GoPlay|.
@ -1100,7 +1102,7 @@ cleaned for each package after `60` seconds. This can be changed with the
*go#complete#GetInfo()*
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*
@ -1658,6 +1660,18 @@ By default "snakecase" is used. Current values are: ["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*
@ -1815,6 +1829,212 @@ The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the
`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*

View File

@ -98,4 +98,11 @@ command! -nargs=0 GoKeyify call go#keyify#Keyify()
" -- 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

View File

@ -40,7 +40,7 @@ function! s:GoMinisnip() abort
endif
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
let g:minisnip_dir = globpath(&rtp, 'gosnippets/minisnip')
endif

View File

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

View File

@ -219,6 +219,10 @@ abbr fmt.Printf(...)
snippet fn
abbr fmt.Println(...)
fmt.Println("${1}")
# Fmt Errorf
snippet fe
abbr fmt.Errorf(...)
fmt.Errorf("${1}")
# log printf
snippet lf
abbr log.Printf(...)

View File

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

View File

@ -453,7 +453,7 @@ if g:go_highlight_build_constraints != 0 || s:fold_package_comment
\ . ' contains=@goCommentGroup,@Spell'
\ . (s:fold_package_comment ? ' fold' : '')
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'
\ . (s:fold_package_comment ? ' fold' : '')
hi def link goPackageComment Comment

View File

@ -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

View File

@ -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

View File

@ -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