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

Updated plugins

This commit is contained in:
Amir Salihefendic
2019-03-27 16:08:56 +01:00
parent bf7b5985f1
commit 5a2572df03
73 changed files with 1924 additions and 598 deletions

View File

@ -65,7 +65,7 @@ function! go#auto#metalinter_autosave()
endif
" run gometalinter on save
call go#lint#Gometa(0, 1)
call go#lint#Gometa(!g:go_jump_to_error, 1)
endfunction
function! go#auto#modfmt_autosave()

View File

@ -219,12 +219,22 @@ function! s:trim_bracket(val) abort
return a:val
endfunction
let s:completions = ""
function! go#complete#Complete(findstart, base) abort
let s:completions = []
function! go#complete#GocodeComplete(findstart, base) abort
"findstart = 1 when we need to get the text length
if a:findstart == 1
execute "silent let s:completions = " . s:gocodeAutocomplete()
return col('.') - s:completions[0] - 1
let l:completions = []
execute "silent let l:completions = " . s:gocodeAutocomplete()
if len(l:completions) == 0 || len(l:completions) >= 2 && len(l:completions[1]) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
let s:completions = l:completions[1]
return col('.') - l:completions[0] - 1
"findstart = 0 when we need to return the list of completions
else
let s = getline(".")[col('.') - 1]
@ -236,6 +246,36 @@ function! go#complete#Complete(findstart, base) abort
endif
endfunction
function! go#complete#Complete(findstart, base) abort
let l:state = {'done': 0, 'matches': []}
function! s:handler(state, matches) abort dict
let a:state.matches = a:matches
let a:state.done = 1
endfunction
"findstart = 1 when we need to get the start of the match
if a:findstart == 1
call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state]))
while !l:state.done
sleep 10m
endwhile
let s:completions = l:state.matches
if len(l:state.matches) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
return col('.')
else "findstart = 0 when we need to return the list of completions
return s:completions
endif
endfunction
function! go#complete#ToggleAutoTypeInfo() abort
if go#config#AutoTypeInfo()
call go#config#SetAutoTypeInfo(0)

View File

@ -210,6 +210,12 @@ function! go#config#DebugCommands() abort
return g:go_debug_commands
endfunction
function! go#config#LspLog() abort
" make sure g:go_lsp_log is set so that it can be added to easily.
let g:go_lsp_log = get(g:, 'go_lsp_log', [])
return g:go_lsp_log
endfunction
function! go#config#SetDebugDiag(value) abort
let g:go_debug_diag = a:value
endfunction
@ -235,15 +241,27 @@ function! go#config#SetTemplateAutocreate(value) abort
endfunction
function! go#config#MetalinterCommand() abort
return get(g:, "go_metalinter_command", "")
return get(g:, "go_metalinter_command", "gometalinter")
endfunction
function! go#config#MetalinterAutosaveEnabled() abort
return get(g:, 'go_metalinter_autosave_enabled', ['vet', 'golint'])
let l:default_enabled = ["vet", "golint"]
if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif
return get(g:, "go_metalinter_autosave_enabled", default_enabled)
endfunction
function! go#config#MetalinterEnabled() abort
return get(g:, "go_metalinter_enabled", ['vet', 'golint', 'errcheck'])
let l:default_enabled = ["vet", "golint", "errcheck"]
if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif
return get(g:, "go_metalinter_enabled", default_enabled)
endfunction
function! go#config#MetalinterDisabled() abort
@ -444,7 +462,6 @@ function! go#config#EchoGoInfo() abort
return get(g:, "go_echo_go_info", 1)
endfunction
" Set the default value. A value of "1" is a shortcut for this, for
" compatibility reasons.
if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1

View File

@ -507,23 +507,7 @@ function! s:out_cb(ch, msg) abort
if has('nvim')
let s:state['data'] = []
let l:state = {'databuf': ''}
function! s:on_data(ch, data, event) dict abort closure
let l:data = self.databuf
for msg in a:data
let l:data .= l:msg
endfor
try
let l:res = json_decode(l:data)
let s:state['data'] = add(s:state['data'], l:res)
let self.databuf = ''
catch
" there isn't a complete message in databuf: buffer l:data and try
" again when more data comes in.
let self.databuf = l:data
finally
endtry
endfunction
" explicitly bind callback to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.on_data = function('s:on_data', [], l:state)
@ -560,6 +544,24 @@ function! s:out_cb(ch, msg) abort
endif
endfunction
function! s:on_data(ch, data, event) dict abort
let l:data = self.databuf
for l:msg in a:data
let l:data .= l:msg
endfor
try
let l:res = json_decode(l:data)
let s:state['data'] = add(s:state['data'], l:res)
let self.databuf = ''
catch
" there isn't a complete message in databuf: buffer l:data and try
" again when more data comes in.
let self.databuf = l:data
finally
endtry
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

View File

@ -5,7 +5,7 @@ set cpo&vim
let s:go_stack = []
let s:go_stack_level = 0
function! go#def#Jump(mode) abort
function! go#def#Jump(mode, type) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" so guru right now is slow for some people. previously we were using
@ -65,8 +65,18 @@ function! go#def#Jump(mode) abort
else
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif
elseif bin_name == 'gopls'
let [l:line, l:col] = getpos('.')[1:2]
" delegate to gopls, with an empty job object and an exit status of 0
" (they're irrelevant for gopls).
if a:type
call go#lsp#TypeDef(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0]))
else
call go#lsp#Definition(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0]))
endif
return
else
call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru]')
call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru, gopls]')
return
endif
@ -85,15 +95,19 @@ function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort
call go#def#jump_to_declaration(a:data[0], a:mode, a:bin_name)
" capture the active window so that after the exit_cb and close_cb callbacks
" can return to it when a:mode caused a split.
" capture the active window so that callbacks for jobs, exit_cb and
" close_cb, and callbacks for gopls can return to it when a:mode caused a
" split.
let self.winid = win_getid(winnr())
endfunction
" go#def#jump_to_declaration parses out (expected to be
" 'filename:line:col: message').
function! go#def#jump_to_declaration(out, mode, bin_name) abort
let final_out = a:out
if a:bin_name == "godef"
" append the type information to the same line so our we can parse it.
" append the type information to the same line so it will be parsed
" correctly using guru's output format.
" This makes it compatible with guru output.
let final_out = join(split(a:out, '\n'), ':')
endif

View File

@ -50,7 +50,7 @@ func! Test_Jump_leaves_lists() abort
let l:bufnr = bufnr('%')
call cursor(6, 7)
call go#def#Jump('')
call go#def#Jump('', 0)
let start = reltime()
while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10

View File

@ -128,10 +128,6 @@ function! s:async_guru(args) abort
\ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output"))
\ }
function! s:complete(job, exit_status, messages) dict abort
let output = join(a:messages, "\n")
call self.parse(a:exit_status, output, self.mode)
endfunction
" explicitly bind complete to state so that within it, self will
" always refer to state. See :help Partial for more information.
let state.complete = function('s:complete', [], state)
@ -164,6 +160,11 @@ function! s:async_guru(args) abort
endif
endfunc
function! s:complete(job, exit_status, messages) dict abort
let output = join(a:messages, "\n")
call self.parse(a:exit_status, output, self.mode)
endfunction
" run_guru runs the given guru argument
function! s:run_guru(args) abort
if go#util#has_job()
@ -239,91 +240,6 @@ function! go#guru#DescribeInfo(showstatus) abort
return
endif
function! s:info(exit_val, output, mode)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let result = json_decode(a:output)
if type(result) != type({})
call go#util#EchoError(printf("malformed output from guru: %s", a:output))
return
endif
if !has_key(result, 'detail')
" if there is no detail check if there is a description and print it
if has_key(result, "desc")
call go#util#EchoInfo(result["desc"])
return
endif
call go#util#EchoError("detail key is missing. Please open a bug report on vim-go repo.")
return
endif
let detail = result['detail']
let info = ""
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if detail == "value"
if !has_key(result, 'value')
call go#util#EchoError("value key is missing. Please open a bug report on vim-go repo.")
return
endif
let val = result["value"]
if !has_key(val, 'type')
call go#util#EchoError("type key is missing (value.type). Please open a bug report on vim-go repo.")
return
endif
let info = val["type"]
elseif detail == "type"
if !has_key(result, 'type')
call go#util#EchoError("type key is missing. Please open a bug report on vim-go repo.")
return
endif
let type = result["type"]
if !has_key(type, 'type')
call go#util#EchoError("type key is missing (type.type). Please open a bug report on vim-go repo.")
return
endif
let info = type["type"]
elseif detail == "package"
if !has_key(result, 'package')
call go#util#EchoError("package key is missing. Please open a bug report on vim-go repo.")
return
endif
let package = result["package"]
if !has_key(package, 'path')
call go#util#EchoError("path key is missing (package.path). Please open a bug report on vim-go repo.")
return
endif
let info = printf("package %s", package["path"])
elseif detail == "unknown"
let info = result["desc"]
else
call go#util#EchoError(printf("unknown detail mode found '%s'. Please open a bug report on vim-go repo", detail))
return
endif
call go#util#ShowInfo(info)
endfunction
let args = {
\ 'mode': 'describe',
\ 'format': 'json',
@ -336,6 +252,91 @@ function! go#guru#DescribeInfo(showstatus) abort
call s:run_guru(args)
endfunction
function! s:info(exit_val, output, mode)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let result = json_decode(a:output)
if type(result) != type({})
call go#util#EchoError(printf("malformed output from guru: %s", a:output))
return
endif
if !has_key(result, 'detail')
" if there is no detail check if there is a description and print it
if has_key(result, "desc")
call go#util#EchoInfo(result["desc"])
return
endif
call go#util#EchoError("detail key is missing. Please open a bug report on vim-go repo.")
return
endif
let detail = result['detail']
let info = ""
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if detail == "value"
if !has_key(result, 'value')
call go#util#EchoError("value key is missing. Please open a bug report on vim-go repo.")
return
endif
let val = result["value"]
if !has_key(val, 'type')
call go#util#EchoError("type key is missing (value.type). Please open a bug report on vim-go repo.")
return
endif
let info = val["type"]
elseif detail == "type"
if !has_key(result, 'type')
call go#util#EchoError("type key is missing. Please open a bug report on vim-go repo.")
return
endif
let type = result["type"]
if !has_key(type, 'type')
call go#util#EchoError("type key is missing (type.type). Please open a bug report on vim-go repo.")
return
endif
let info = type["type"]
elseif detail == "package"
if !has_key(result, 'package')
call go#util#EchoError("package key is missing. Please open a bug report on vim-go repo.")
return
endif
let package = result["package"]
if !has_key(package, 'path')
call go#util#EchoError("path key is missing (package.path). Please open a bug report on vim-go repo.")
return
endif
let info = printf("package %s", package["path"])
elseif detail == "unknown"
let info = result["desc"]
else
call go#util#EchoError(printf("unknown detail mode found '%s'. Please open a bug report on vim-go repo", detail))
return
endif
call go#util#ShowInfo(info)
endfunction
" Show possible targets of selected function call
function! go#guru#Callees(selected) abort
let args = {
@ -609,105 +610,6 @@ function! go#guru#DescribeBalloon() abort
return
endif
function! s:describe_balloon(exit_val, output, mode)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let l:result = json_decode(a:output)
if type(l:result) != type({})
call go#util#EchoError(printf('malformed output from guru: %s', a:output))
return
endif
let l:info = []
if has_key(l:result, 'desc')
if l:result['desc'] != 'identifier'
let l:info = add(l:info, l:result['desc'])
endif
endif
if has_key(l:result, 'detail')
let l:detail = l:result['detail']
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if l:detail == 'value'
if !has_key(l:result, 'value')
call go#util#EchoError('value key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:val = l:result['value']
if !has_key(l:val, 'type')
call go#util#EchoError('type key is missing (value.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:val['type']))
if has_key(l:val, 'value')
let l:info = add(l:info, printf('value: %s', l:val['value']))
endif
elseif l:detail == 'type'
if !has_key(l:result, 'type')
call go#util#EchoError('type key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:type = l:result['type']
if !has_key(l:type, 'type')
call go#util#EchoError('type key is missing (type.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:type['type']))
if has_key(l:type, 'methods')
let l:info = add(l:info, 'methods:')
for l:m in l:type.methods
let l:info = add(l:info, printf("\t%s", l:m['name']))
endfor
endif
elseif l:detail == 'package'
if !has_key(l:result, 'package')
call go#util#EchoError('package key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:package = result['package']
if !has_key(l:package, 'path')
call go#util#EchoError('path key is missing (package.path). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('package: %s', l:package["path"]))
elseif l:detail == 'unknown'
" the description is already included in l:info, and there's no other
" information on unknowns.
else
call go#util#EchoError(printf('unknown detail mode (%s) found. Please open a bug report on vim-go repo', l:detail))
return
endif
endif
if has('balloon_eval')
call balloon_show(join(l:info, "\n"))
return
endif
call balloon_show(l:info)
endfunction
" change the active window to the window where the cursor is.
let l:winid = win_getid(winnr())
call win_gotoid(v:beval_winid)
@ -730,6 +632,104 @@ function! go#guru#DescribeBalloon() abort
return ''
endfunction
function! s:describe_balloon(exit_val, output, mode)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let l:result = json_decode(a:output)
if type(l:result) != type({})
call go#util#EchoError(printf('malformed output from guru: %s', a:output))
return
endif
let l:info = []
if has_key(l:result, 'desc')
if l:result['desc'] != 'identifier'
let l:info = add(l:info, l:result['desc'])
endif
endif
if has_key(l:result, 'detail')
let l:detail = l:result['detail']
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if l:detail == 'value'
if !has_key(l:result, 'value')
call go#util#EchoError('value key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:val = l:result['value']
if !has_key(l:val, 'type')
call go#util#EchoError('type key is missing (value.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:val['type']))
if has_key(l:val, 'value')
let l:info = add(l:info, printf('value: %s', l:val['value']))
endif
elseif l:detail == 'type'
if !has_key(l:result, 'type')
call go#util#EchoError('type key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:type = l:result['type']
if !has_key(l:type, 'type')
call go#util#EchoError('type key is missing (type.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:type['type']))
if has_key(l:type, 'methods')
let l:info = add(l:info, 'methods:')
for l:m in l:type.methods
let l:info = add(l:info, printf("\t%s", l:m['name']))
endfor
endif
elseif l:detail == 'package'
if !has_key(l:result, 'package')
call go#util#EchoError('package key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:package = result['package']
if !has_key(l:package, 'path')
call go#util#EchoError('path key is missing (package.path). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('package: %s', l:package["path"]))
elseif l:detail == 'unknown'
" the description is already included in l:info, and there's no other
" information on unknowns.
else
call go#util#EchoError(printf('unknown detail mode (%s) found. Please open a bug report on vim-go repo', l:detail))
return
endif
endif
if has('balloon_eval')
call balloon_show(join(l:info, "\n"))
return
endif
call balloon_show(l:info)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -6,11 +6,14 @@ function! Test_gomodVersion_highlight() abort
try
syntax on
let l:dir= gotest#write_file('gomodtest/go.mod', [
let l:dir = gotest#write_file('gomodtest/go.mod', [
\ 'module github.com/fatih/vim-go',
\ '',
\ '\x1frequire (',
\ '\tversion/simple v1.0.0',
\ '\tversion/simple-pre-release v1.0.0-rc',
\ '\tversion/simple-pre-release v1.0.0+meta',
\ '\tversion/simple-pre-release v1.0.0-rc+meta',
\ '\tversion/pseudo/premajor v1.0.0-20060102150405-0123456789abcdef',
\ '\tversion/pseudo/prerelease v1.0.0-prerelease.0.20060102150405-0123456789abcdef',
\ '\tversion/pseudo/prepatch v1.0.1-0.20060102150405-0123456789abcdef',
@ -55,11 +58,10 @@ function! Test_gomodVersion_incompatible_highlight() abort
try
syntax on
let l:dir= gotest#write_file('gomodtest/go.mod', [
let l:dir = gotest#write_file('gomodtest/go.mod', [
\ 'module github.com/fatih/vim-go',
\ '',
\ '\x1frequire (',
\ '\tversion/invalid/incompatible v1.0.0+incompatible',
\ '\tversion/invalid/premajor/incompatible v1.0.0-20060102150405-0123456789abcdef+incompatible',
\ '\tversion/invalid/prerelease/incompatible v1.0.0-prerelease.0.20060102150405-0123456789abcdef+incompatible',
\ '\tversion/invalid/prepatch/incompatible v1.0.1-0.20060102150405-0123456789abcdef+incompatible',

View File

@ -5,7 +5,7 @@ set cpo&vim
let s:templatepath = go#util#Join(expand('<sfile>:p:h:h:h'), '.github', 'ISSUE_TEMPLATE.md')
function! go#issue#New() abort
let body = substitute(s:issuebody(), '[^A-Za-z0-9_.~-]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
let body = go#uriEncode(s:issuebody())
let url = "https://github.com/fatih/vim-go/issues/new?body=" . l:body
call go#util#OpenBrowser(l:url)
endfunction

View File

@ -145,23 +145,6 @@ function! go#job#Options(args)
let state.custom_complete = a:args.complete
endif
function! s:start(args) dict
if go#config#EchoCommandInfo() && self.statustype != ""
let prefix = '[' . self.statustype . '] '
call go#util#EchoSuccess(prefix . "dispatched")
endif
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
endfunction
" explicitly bind _start to state so that within it, self will
" always refer to state. See :help Partial for more information.
"
@ -169,35 +152,14 @@ function! go#job#Options(args)
" outside of this file.
let cbs._start = function('s:start', [''], state)
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! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
call self.show_status(a:job, a:exitval)
if self.closed || has('nvim')
call self.complete(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)
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)
@ -261,6 +223,48 @@ function! go#job#Options(args)
return cbs
endfunction
function! s:start(args) dict
if go#config#EchoCommandInfo() && self.statustype != ""
let prefix = '[' . self.statustype . '] '
call go#util#EchoSuccess(prefix . "dispatched")
endif
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
endfunction
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
call self.show_status(a:job, a:exitval)
if self.closed || has('nvim')
call self.complete(a:job, self.exit_status, self.messages)
endif
endfunction
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)
endif
endfunction
" go#job#Start runs a job. The options are expected to be the options
" suitable for Vim8 jobs. When called from Neovim, Vim8 options will be
" transformed to their Neovim equivalents.
@ -276,17 +280,24 @@ function! go#job#Start(cmd, options)
" early if the directory does not exist. This helps avoid errors when
" working with plugins that use virtual files that don't actually exist on
" the file system.
let filedir = expand("%:p:h")
let l:filedir = expand("%:p:h")
if has_key(l:options, 'cwd') && !isdirectory(l:options.cwd)
return
elseif !isdirectory(filedir)
elseif !isdirectory(l:filedir)
return
endif
let l:manualcd = 0
if !has_key(l:options, 'cwd')
" pre start
let l:manualcd = 1
let dir = getcwd()
execute l:cd fnameescape(filedir)
elseif !(has("patch-8.0.0902") || has('nvim'))
let l:manualcd = 1
let l:dir = l:options.cwd
execute l:cd fnameescape(l:dir)
call remove(l:options, 'cwd')
endif
if has_key(l:options, '_start')
@ -296,6 +307,11 @@ function! go#job#Start(cmd, options)
unlet l:options._start
endif
" noblock was added in 8.1.350; remove it if it's not supported.
if has_key(l:options, 'noblock') && (has('nvim') || !has("patch-8.1.350"))
call remove(l:options, 'noblock')
endif
if go#util#HasDebug('shell-commands')
call go#util#EchoInfo('job command: ' . string(a:cmd))
endif
@ -322,9 +338,9 @@ function! go#job#Start(cmd, options)
let job = job_start(l:cmd, l:options)
endif
if !has_key(l:options, 'cwd')
if l:manualcd
" post start
execute l:cd fnameescape(dir)
execute l:cd fnameescape(l:dir)
endif
return job
@ -337,6 +353,9 @@ function! s:neooptions(options)
let l:options['stdout_buf'] = ''
let l:options['stderr_buf'] = ''
let l:err_mode = get(a:options, 'err_mode', get(a:options, 'mode', ''))
let l:out_mode = get(a:options, 'out_mode', get(a:options, 'mode', ''))
for key in keys(a:options)
if key == 'cwd'
let l:options['cwd'] = a:options['cwd']
@ -347,17 +366,11 @@ function! s:neooptions(options)
let l:options['callback'] = a:options['callback']
if !has_key(a:options, 'out_cb')
function! s:callback2on_stdout(ch, data, event) dict
let self.stdout_buf = s:neocb(a:ch, self.stdout_buf, a:data, self.callback)
endfunction
let l:options['on_stdout'] = function('s:callback2on_stdout', [], l:options)
let l:options['on_stdout'] = function('s:callback2on_stdout', [l:out_mode], l:options)
endif
if !has_key(a:options, 'err_cb')
function! s:callback2on_stderr(ch, data, event) dict
let self.stderr_buf = s:neocb(a:ch, self.stderr_buf, a:data, self.callback)
endfunction
let l:options['on_stderr'] = function('s:callback2on_stderr', [], l:options)
let l:options['on_stderr'] = function('s:callback2on_stderr', [l:err_mode], l:options)
endif
continue
@ -365,29 +378,20 @@ function! s:neooptions(options)
if key == 'out_cb'
let l:options['out_cb'] = a:options['out_cb']
function! s:on_stdout(ch, data, event) dict
let self.stdout_buf = s:neocb(a:ch, self.stdout_buf, a:data, self.out_cb)
endfunction
let l:options['on_stdout'] = function('s:on_stdout', [], l:options)
let l:options['on_stdout'] = function('s:on_stdout', [l:out_mode], l:options)
continue
endif
if key == 'err_cb'
let l:options['err_cb'] = a:options['err_cb']
function! s:on_stderr(ch, data, event) dict
let self.stderr_buf = s:neocb(a:ch, self.stderr_buf, a:data, self.err_cb )
endfunction
let l:options['on_stderr'] = function('s:on_stderr', [], l:options)
let l:options['on_stderr'] = function('s:on_stderr', [l:err_mode], l:options)
continue
endif
if key == 'exit_cb'
let l:options['exit_cb'] = a:options['exit_cb']
function! s:on_exit(jobid, exitval, event) dict
call self.exit_cb(a:jobid, a:exitval)
endfunction
let l:options['on_exit'] = function('s:on_exit', [], l:options)
continue
@ -407,6 +411,26 @@ function! s:neooptions(options)
return l:options
endfunction
function! s:callback2on_stdout(mode, ch, data, event) dict
let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.callback)
endfunction
function! s:callback2on_stderr(mode, ch, data, event) dict
let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.callback)
endfunction
function! s:on_stdout(mode, ch, data, event) dict
let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.out_cb)
endfunction
function! s:on_stderr(mode, ch, data, event) dict
let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.err_cb )
endfunction
function! s:on_exit(jobid, exitval, event) dict
call self.exit_cb(a:jobid, a:exitval)
endfunction
function! go#job#Stop(job) abort
if has('nvim')
call jobstop(a:job)
@ -436,15 +460,34 @@ function! s:winjobarg(idx, val) abort
return a:val
endfunction
function! s:neocb(ch, buf, data, callback)
function! s:neocb(mode, ch, buf, data, callback)
" dealing with the channel lines of Neovim is awful. The docs (:help
" channel-lines) say:
" stream event handlers may receive partial (incomplete) lines. For a
" given invocation of on_stdout etc, `a:data` is not guaranteed to end
" with a newline.
" - `abcdefg` may arrive as `['abc']`, `['defg']`.
" - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`,
" `['','efg']`, or even `['ab']`, `['c','efg']`.
" stream event handlers may receive partial (incomplete) lines. For a
" given invocation of on_stdout etc, `a:data` is not guaranteed to end
" with a newline.
" - `abcdefg` may arrive as `['abc']`, `['defg']`.
" - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`,
" `['','efg']`, or even `['ab']`, `['c','efg']`.
"
" Thankfully, though, this is explained a bit better in an issue:
" https://github.com/neovim/neovim/issues/3555. Specifically in these two
" comments:
" * https://github.com/neovim/neovim/issues/3555#issuecomment-152290804
" * https://github.com/neovim/neovim/issues/3555#issuecomment-152588749
"
" The key is
" Every item in the list passed to job control callbacks represents a
" string after a newline(Except the first, of course). If the program
" outputs: "hello\nworld" the corresponding list is ["hello", "world"].
" If the program outputs "hello\nworld\n", the corresponding list is
" ["hello", "world", ""]. In other words, you can always determine if
" the last line received is complete or not.
" and
" for every list you receive in a callback, all items except the first
" represent newlines.
let l:buf = ''
" a single empty string means EOF was reached.
if len(a:data) == 1 && a:data[0] == ''
@ -455,20 +498,28 @@ function! s:neocb(ch, buf, data, callback)
endif
let l:data = [a:buf]
let l:buf = ''
else
let l:data = copy(a:data)
let l:data[0] = a:buf . l:data[0]
" The last element may be a partial line; save it for next time.
let l:buf = l:data[-1]
let l:data = l:data[:-2]
if a:mode != 'raw'
let l:buf = l:data[-1]
let l:data = l:data[:-2]
endif
endif
for l:msg in l:data
let l:i = 0
let l:last = len(l:data) - 1
while l:i <= l:last
let l:msg = l:data[l:i]
if a:mode == 'raw' && l:i < l:last
let l:msg = l:msg . "\n"
endif
call a:callback(a:ch, l:msg)
endfor
let l:i += 1
endwhile
return l:buf
endfunction

View File

@ -9,22 +9,14 @@ function! go#lint#Gometa(bang, autosave, ...) abort
let goargs = a:000
endif
if empty(go#config#MetalinterCommand())
let bin_path = go#path#CheckBinPath("gometalinter")
if empty(bin_path)
let l:metalinter = go#config#MetalinterCommand()
if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint'
let cmd = s:metalintercmd(l:metalinter)
if empty(cmd)
return
endif
let cmd = [bin_path]
let cmd += ["--disable-all"]
" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]
" linters
let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
for linter in linters
@ -44,15 +36,19 @@ function! go#lint#Gometa(bang, autosave, ...) abort
" will be cleared
redraw
" Include only messages for the active buffer for autosave.
let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
if l:metalinter == "gometalinter"
" Include only messages for the active buffer for autosave.
let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
endif
let cmd += include
elseif l:metalinter == "golangci-lint"
let goargs[0] = expand('%:p')
endif
let cmd += include
endif
" Call gometalinter asynchronously.
" Call metalinter asynchronously.
let deadline = go#config#MetalinterDeadline()
if deadline != ''
let cmd += ["--deadline=" . deadline]
@ -60,8 +56,21 @@ function! go#lint#Gometa(bang, autosave, ...) abort
let cmd += goargs
if l:metalinter == "gometalinter"
" Gometalinter can output one of the two, so we look for both:
" <file>:<line>:<column>:<severity>: <message> (<linter>)
" <file>:<line>::<severity>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
else
" Golangci-lint can output the following:
" <file>:<line>:<column>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:\ %m"
endif
if go#util#has_job()
call s:lint_job({'cmd': cmd}, a:bang, a:autosave)
call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave)
return
endif
@ -77,12 +86,6 @@ function! go#lint#Gometa(bang, autosave, ...) abort
call go#list#Clean(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:
" <file>:<line>:<column>:<severity>: <message> (<linter>)
" <file>:<line>::<severity>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
" Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
@ -205,8 +208,8 @@ endfunction
function! s:lint_job(args, bang, autosave)
let l:opts = {
\ 'statustype': "gometalinter",
\ 'errorformat': '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m',
\ 'statustype': a:args.statustype,
\ 'errorformat': a:args.errformat,
\ 'for': "GoMetaLinter",
\ 'bang': a:bang,
\ }
@ -221,6 +224,45 @@ function! s:lint_job(args, bang, autosave)
call go#job#Spawn(a:args.cmd, l:opts)
endfunction
function! s:metalintercmd(metalinter)
let l:cmd = []
let bin_path = go#path#CheckBinPath(a:metalinter)
if !empty(bin_path)
if a:metalinter == "gometalinter"
let l:cmd = s:gometalintercmd(bin_path)
elseif a:metalinter == "golangci-lint"
let l:cmd = s:golangcilintcmd(bin_path)
endif
endif
return cmd
endfunction
function! s:gometalintercmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["--disable-all"]
" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]
return cmd
endfunction
function! s:golangcilintcmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["run"]
let cmd += ["--print-issued-lines=false"]
let cmd += ["--disable-all"]
" do not use the default exclude patterns, because doing so causes golint
" problems about missing doc strings to be ignored and other things that
" golint identifies.
let cmd += ["--exclude-use-default=false"]
return cmd
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -3,92 +3,128 @@ let s:cpo_save = &cpo
set cpo&vim
func! Test_Gometa() abort
call s:gometa('gometaliner')
endfunc
func! Test_GometaGolangciLint() abort
call s:gometa('golangci-lint')
endfunc
func! s:gometa(metalinter) 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)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')
let g:go_metalinter_enabled = ['golint']
let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
unlet g:go_metalinter_enabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_enabled
endtry
endfunc
func! Test_GometaWithDisabled() abort
call s:gometawithdisabled('gometalinter')
endfunc
func! Test_GometaWithDisabledGolangciLint() abort
call s:gometawithdisabled('golangci-lint')
endfunc
func! s:gometawithdisabled(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
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)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')
let g:go_metalinter_disabled = ['vet']
let g:go_metalinter_disabled = ['vet']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
unlet g:go_metalinter_disabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_disabled
endtry
endfunc
func! Test_GometaAutoSave() abort
call s:gometaautosave('gometalinter')
endfunc
func! Test_GometaAutoSaveGolangciLint() abort
call s:gometaautosave('golangci-lint')
endfunc
func! s:gometaautosave(metalinter) 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)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]
let winnr = winnr()
let winnr = winnr()
" clear the location lists
call setloclist(l:winnr, [], 'r')
" clear the location lists
call setloclist(l:winnr, [], 'r')
let g:go_metalinter_autosave_enabled = ['golint']
let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(0, 1)
call go#lint#Gometa(0, 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
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)
unlet g:go_metalinter_autosave_enabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_autosave_enabled
endtry
endfunc
func! Test_Vet()
func! Test_Vet() abort
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': '',
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '',
\ 'text': 'Printf format %d has arg str of wrong type string'}
\ ]

View File

@ -0,0 +1,444 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
let s:lspfactory = {}
function! s:lspfactory.get() dict abort
if !has_key(self, 'current')
" TODO(bc): check that the lsp is still running.
let self.current = s:newlsp()
endif
return self.current
endfunction
function! s:lspfactory.reset() dict abort
if has_key(self, 'current')
call remove(self, 'current')
endif
endfunction
function! s:newlsp()
if !go#util#has_job()
" TODO(bc): start the server in the background using a shell that waits for the right output before returning.
call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.')
return
endif
" job is the job used to talk to the backing instance of gopls.
" ready is 0 until the initialize response has been received. 1 afterwards.
" queue is messages to send after initialization
" last_request_id is id of the most recently sent request.
" buf is unprocessed/incomplete responses
" handlers is a mapping of request ids to dictionaries of functions.
" request id -> {start, requestComplete, handleResult, error}
" * start is a function that takes no arguments
" * requestComplete is a function that takes 1 argument. The parameter will be 1
" if the call was succesful.
" * handleResult takes a single argument, the result message received from gopls
" * error takes a single argument, the error message received from gopls.
" The error method is optional.
let l:lsp = {
\ 'job': '',
\ 'ready': 0,
\ 'queue': [],
\ 'last_request_id': 0,
\ 'buf': '',
\ 'handlers': {},
\ }
function! l:lsp.readMessage(data) dict abort
let l:responses = []
let l:rest = a:data
while 1
" Look for the end of the HTTP headers
let l:body_start_idx = matchend(l:rest, "\r\n\r\n")
if l:body_start_idx < 0
" incomplete header
break
endif
" Parse the Content-Length header.
let l:header = l:rest[:l:body_start_idx - 4]
let l:length_match = matchlist(
\ l:header,
\ '\vContent-Length: *(\d+)'
\)
if empty(l:length_match)
" TODO(bc): shutdown gopls?
throw "invalid JSON-RPC header:\n" . l:header
endif
" get the start of the rest
let l:rest_start_idx = l:body_start_idx + str2nr(l:length_match[1])
if len(l:rest) < l:rest_start_idx
" incomplete response body
break
endif
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-\n" . l:rest[:l:rest_start_idx - 1])
endif
let l:body = l:rest[l:body_start_idx : l:rest_start_idx - 1]
let l:rest = l:rest[l:rest_start_idx :]
try
" add the json body to the list.
call add(l:responses, json_decode(l:body))
catch
" TODO(bc): log the message and/or show an error message.
finally
" intentionally left blank.
endtry
endwhile
return [l:rest, l:responses]
endfunction
function! l:lsp.handleMessage(ch, data) dict abort
let self.buf .= a:data
let [self.buf, l:responses] = self.readMessage(self.buf)
" TODO(bc): handle notifications (e.g. window/showMessage).
for l:response in l:responses
if has_key(l:response, 'id') && has_key(self.handlers, l:response.id)
try
let l:handler = self.handlers[l:response.id]
if has_key(l:response, 'error')
call l:handler.requestComplete(0)
call go#util#EchoError(l:response.error.message)
if has_key(l:handler, 'error')
call call(l:handler.error, [l:response.error.message])
endif
return
endif
call l:handler.requestComplete(1)
call call(l:handler.handleResult, [l:response.result])
finally
call remove(self.handlers, l:response.id)
endtry
endif
endfor
endfunction
function! l:lsp.handleInitializeResult(result) dict abort
let self.ready = 1
" TODO(bc): send initialized message to the server?
" send messages queued while waiting for ready.
for l:item in self.queue
call self.sendMessage(l:item.data, l:item.handler)
endfor
" reset the queue
let self.queue = []
endfunction
function! l:lsp.sendMessage(data, handler) dict abort
if !self.last_request_id
" TODO(bc): run a server per module and one per GOPATH? (may need to
" keep track of servers by rootUri).
let l:msg = self.newMessage(go#lsp#message#Initialize(getcwd()))
let l:state = s:newHandlerState('gopls')
let l:state.handleResult = funcref('self.handleInitializeResult', [], l:self)
let self.handlers[l:msg.id] = l:state
call l:state.start()
call self.write(l:msg)
endif
if !self.ready
call add(self.queue, {'data': a:data, 'handler': a:handler})
return
endif
let l:msg = self.newMessage(a:data)
if has_key(l:msg, 'id')
let self.handlers[l:msg.id] = a:handler
endif
call a:handler.start()
call self.write(l:msg)
endfunction
" newMessage returns a message constructed from data. data should be a dict
" with 2 or 3 keys: notification, method, and optionally params.
function! l:lsp.newMessage(data) dict abort
let l:msg = {
\ 'method': a:data.method,
\ 'jsonrpc': '2.0',
\ }
if !a:data.notification
let self.last_request_id += 1
let l:msg.id = self.last_request_id
endif
if has_key(a:data, 'params')
let l:msg.params = a:data.params
endif
return l:msg
endfunction
function! l:lsp.write(msg) dict abort
let l:body = json_encode(a:msg)
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "->\n" . l:data)
endif
if has('nvim')
call chansend(self.job, l:data)
return
endif
call ch_sendraw(self.job, l:data)
endfunction
function! l:lsp.exit_cb(job, exit_status) dict abort
call s:lspfactory.reset()
endfunction
" explicitly bind close_cb to state so that within it, self will always refer
function! l:lsp.close_cb(ch) dict abort
" TODO(bc): does anything need to be done here?
endfunction
function! l:lsp.err_cb(ch, msg) dict abort
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-stderr\n" . a:msg)
endif
endfunction
" explicitly bind callbacks to l:lsp so that within it, self will always refer
" to l:lsp instead of l:opts. See :help Partial for more information.
let l:opts = {
\ 'in_mode': 'raw',
\ 'out_mode': 'raw',
\ 'err_mode': 'nl',
\ 'noblock': 1,
\ 'err_cb': funcref('l:lsp.err_cb', [], l:lsp),
\ 'out_cb': funcref('l:lsp.handleMessage', [], l:lsp),
\ 'close_cb': funcref('l:lsp.close_cb', [], l:lsp),
\ 'exit_cb': funcref('l:lsp.exit_cb', [], l:lsp),
\ 'cwd': getcwd(),
\}
let l:bin_path = go#path#CheckBinPath("gopls")
if empty(l:bin_path)
return
endif
" TODO(bc): output a message indicating which directory lsp is going to
" start in.
let l:lsp.job = go#job#Start([l:bin_path], l:opts)
" TODO(bc): send the initialize message now?
return l:lsp
endfunction
function! s:noop()
endfunction
function! s:newHandlerState(statustype)
let l:state = {
\ 'winid': win_getid(winnr()),
\ 'statustype': a:statustype,
\ 'jobdir': getcwd(),
\ }
" explicitly bind requestComplete to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.requestComplete = funcref('s:requestComplete', [], l:state)
" explicitly bind start to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.start = funcref('s:start', [], l:state)
return l:state
endfunction
function! s:requestComplete(ok) abort dict
if self.statustype == ''
return
endif
if go#config#EchoCommandInfo()
let prefix = '[' . self.statustype . '] '
if a:ok
call go#util#EchoSuccess(prefix . "SUCCESS")
else
call go#util#EchoError(prefix . "FAIL")
endif
endif
let status = {
\ 'desc': 'last status',
\ 'type': self.statustype,
\ 'state': "success",
\ }
if !a:ok
let status.state = "failed"
endif
if has_key(self, '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)
endif
call go#statusline#Update(self.jobdir, status)
endfunction
function! s:start() abort dict
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
endfunction
" go#lsp#Definition calls gopls to get the definition of the identifier at
" line and col in fname. handler should be a dictionary function that takes a
" list of strings in the form 'file:line:col: message'. handler will be
" attached to a dictionary that manages state (statuslines, sets the winid,
" etc.)
function! go#lsp#Definition(fname, line, col, handler)
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('definition')
let l:state.handleResult = funcref('s:definitionHandler', [function(a:handler, [], l:state)], l:state)
let l:msg = go#lsp#message#Definition(fnamemodify(a:fname, ':p'), a:line, a:col)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:definitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
" go#lsp#Type calls gopls to get the type definition of the identifier at
" line and col in fname. handler should be a dictionary function that takes a
" list of strings in the form 'file:line:col: message'. handler will be
" attached to a dictionary that manages state (statuslines, sets the winid,
" etc.)
function! go#lsp#TypeDef(fname, line, col, handler)
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('type definition')
let l:msg = go#lsp#message#TypeDefinition(fnamemodify(a:fname, ':p'), a:line, a:col)
let l:state.handleResult = funcref('s:typeDefinitionHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:typeDefinitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
function! go#lsp#DidOpen(fname)
if get(b:, 'go_lsp_did_open', 0)
return
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidOpen(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
let b:go_lsp_did_open = 1
endfunction
function! go#lsp#DidChange(fname)
if get(b:, 'go_lsp_did_open', 0)
return go#lsp#DidOpen(a:fname)
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidChange(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidClose(fname)
if !get(b:, 'go_lsp_did_open', 0)
return
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidClose(fnamemodify(a:fname, ':p'))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
let b:go_lsp_did_open = 0
endfunction
function! go#lsp#Completion(fname, line, col, handler)
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Completion(a:fname, a:line, a:col)
let l:state = s:newHandlerState('completion')
let l:state.handleResult = funcref('s:completionHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:completionErrorHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:completionHandler(next, msg) abort dict
" gopls returns a CompletionList.
let l:matches = []
for l:item in a:msg.items
let l:match = {'abbr': l:item.label, 'word': l:item.textEdit.newText, 'info': '', 'kind': go#lsp#completionitemkind#Vim(l:item.kind)}
if has_key(l:item, 'detail')
let l:item.info = l:item.detail
endif
if has_key(l:item, 'documentation')
let l:match.info .= "\n\n" . l:item.documentation
endif
let l:matches = add(l:matches, l:match)
endfor
let l:args = [l:matches]
call call(a:next, l:args)
endfunction
function! s:completionErrorHandler(next, error) abort dict
call call(a:next, [[]])
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,47 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:Text = 1
let s:Method = 2
let s:Function = 3
let s:Constructor = 4
let s:Field = 5
let s:Variable = 6
let s:Class = 7
let s:Interface = 8
let s:Module = 9
let s:Property = 10
let s:Unit = 11
let s:Value = 12
let s:Enum = 13
let s:Keyword = 14
let s:Snippet = 15
let s:Color = 16
let s:File = 17
let s:Reference = 18
let s:Folder = 19
let s:EnumMember = 20
let s:Constant = 21
let s:Struct = 22
let s:Event = 23
let s:Operator = 24
let s:TypeParameter = 25
function! go#lsp#completionitemkind#Vim(kind)
if a:kind == s:Method || a:kind == s:Function || a:kind == s:Constructor
return 'f'
elseif a:kind == s:Variable || a:kind == s:Constant
return 'v'
elseif a:kind == s:Field || a:kind == s:Property
return 'm'
elseif a:kind == s:Class || a:kind == s:Interface || a:kind == s:Struct
return 't'
endif
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,111 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#lsp#message#Initialize(wd)
return {
\ 'notification': 0,
\ 'method': 'initialize',
\ 'params': {
\ 'processId': getpid(),
\ 'rootUri': go#path#ToURI(a:wd),
\ 'capabilities': {
\ 'workspace': {},
\ 'textDocument': {}
\ }
\ }
\ }
endfunction
function! go#lsp#message#Definition(file, line, col)
return {
\ 'notification': 0,
\ 'method': 'textDocument/definition',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col)
\ }
\ }
endfunction
function! go#lsp#message#TypeDefinition(file, line, col)
return {
\ 'notification': 0,
\ 'method': 'textDocument/typeDefinition',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col)
\ }
\ }
endfunction
function! go#lsp#message#DidOpen(file, content)
return {
\ 'notification': 1,
\ 'method': 'textDocument/didOpen',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ 'languageId': 'go',
\ 'text': a:content,
\ }
\ }
\ }
endfunction
function! go#lsp#message#DidChange(file, content)
return {
\ 'notification': 1,
\ 'method': 'textDocument/didChange',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ },
\ 'contentChanges': [
\ {
\ 'text': a:content,
\ }
\ ]
\ }
\ }
endfunction
function! go#lsp#message#DidClose(file)
return {
\ 'notification': 1,
\ 'method': 'textDocument/didClose',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ }
\ }
\ }
endfunction
function! go#lsp#message#Completion(file, line, col)
return {
\ 'notification': 0,
\ 'method': 'textDocument/completion',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col),
\ }
\ }
endfunction
function! s:position(line, col)
return {'line': a:line - 1, 'character': a:col-1}
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View File

@ -205,6 +205,37 @@ function! s:CygwinPath(path)
return substitute(a:path, '\\', '/', "g")
endfunction
" go#path#ToURI converts path to a file URI. path should be an absolute path.
" Relative paths cannot be properly converted to a URI; when path is a
" relative path, the file scheme will not be prepended.
function! go#path#ToURI(path)
let l:path = a:path
if l:path[1:2] is# ':\'
let l:path = '/' . l:path[0:1] . l:path[3:]
endif
return substitute(
\ (l:path[0] is# '/' ? 'file://' : '') . go#uri#EncodePath(l:path),
\ '\\',
\ '/',
\ 'g',
\)
endfunction
function! go#path#FromURI(uri) abort
let l:i = len('file://')
let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri
let l:path = go#uri#Decode(l:encoded_path)
" If the path is like /C:/foo/bar, it should be C:\foo\bar instead.
if l:path =~# '^/[a-zA-Z]:'
let l:path = substitute(l:path[1:], '/', '\\', 'g')
endif
return l:path
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save

View File

@ -241,7 +241,7 @@ function! s:errorformat() abort
" e.g.:
" '\t/usr/local/go/src/time.go:1313 +0x5d'
" panicaddress, and readyaddress are identical except for
" panicaddress and readyaddress are identical except for
" panicaddress sets the filename and line number.
let panicaddress = "%\\t%f:%l +0x%[0-9A-Fa-f]%\\+"
let readyaddress = "%\\t%\\f%\\+:%\\d%\\+ +0x%[0-9A-Fa-f]%\\+"
@ -264,6 +264,11 @@ function! s:errorformat() abort
" the running goroutine's stack.
let format .= ",%Z" . panicaddress
" Match and ignore errors from runtime.goparkunlock(). These started
" appearing in stack traces from Go 1.12 test timeouts.
let format .= ",%-Gruntime.goparkunlock(%.%#"
let format .= ",%-G%\\t" . goroot . "%\\f%\\+:%\\d%\\+"
" Match and ignore panic address without being part of a multi-line message.
" This is to catch those lines that come after the top most non-standard
" library line in stack traces.
@ -290,8 +295,8 @@ function! s:errorformat() abort
" It would be nice if this weren't necessary, but panic lines from tests are
" prefixed with a single leading tab, making them very similar to 2nd and
" later lines of a multi-line compiler error. Swallow it so that it doesn't
" cause a quickfix entry since the next entry can add a quickfix entry for
" 2nd and later lines of a multi-line compiler error.
" cause a quickfix entry since the next %G entry can add a quickfix entry
" for 2nd and later lines of a multi-line compiler error.
let format .= ",%-C%\\tpanic: %.%#"
let format .= ",%G%\\t%m"

View File

@ -78,7 +78,7 @@ endfunc
func! Test_GoTestVet() abort
let expected = [
\ {'lnum': 6, 'bufnr': 16, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
\ {'lnum': 6, 'bufnr': 16, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
\ ]
call s:test('veterror/veterror.go', expected)
endfunc

View File

@ -0,0 +1,34 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#uri#Encode(value) abort
return s:encode(a:value, '[^A-Za-z0-9_.~-]')
endfunction
function! go#uri#EncodePath(value) abort
return s:encode(a:value, '[^/A-Za-z0-9_.~-]')
endfunction
function! s:encode(value, unreserved)
return substitute(
\ a:value,
\ a:unreserved,
\ '\="%".printf(''%02X'', char2nr(submatch(0)))',
\ 'g'
\)
endfunction
function! go#uri#Decode(value) abort
return substitute(
\ a:value,
\ '%\(\x\x\)',
\ '\=nr2char(''0X'' . submatch(1))',
\ 'g'
\)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et