Skip to content

Commit

Permalink
Merge pull request #1697 from bhcleek/async-info
Browse files Browse the repository at this point in the history
get info from gocode asynchronously
  • Loading branch information
bhcleek authored Mar 7, 2018
2 parents 945b636 + 2499155 commit dcbfdf3
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 77 deletions.
241 changes: 165 additions & 76 deletions autoload/go/complete.vim
Original file line number Diff line number Diff line change
@@ -1,62 +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
endfunction

function! s:gocodeCurrentBufferOpt(filename) abort
return '-in=' . a:filename
return l:result
endfunction

" TODO(bc): reset when gocode isn't running
let s:optionsEnabled = 0
function! s:gocodeEnableOptions() abort
if s:optionsEnabled
Expand All @@ -82,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
return s:sync_info(0)
endfunction

function! go#complete#Info(auto) abort
if go#util#has_job()
return s:async_info(a:auto)
else
return s:sync_info(a:auto)
endif
endfunction

function! s:async_info(auto)
if exists("s:async_info_job")
call job_stop(s:async_info_job)
unlet s:async_info_job
endif

let state = {
\ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'messages': [],
\ 'auto': a:auto
\ }

function! s:callback(chan, msg) dict
let l:msg = a:msg
if &encoding != 'utf-8'
let l:msg = iconv(l:msg, 'utf-8', &encoding)
endif
call add(self.messages, l:msg)
endfunction

function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1

if self.closed
call self.complete()
endif
endfunction

function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call self.complete()
endif
endfunction

function state.complete() dict
if self.exit_status != 0
return
endif

let result = s:info_filter(self.auto, join(self.messages, "\n"))
call s:info_complete(self.auto, result)
endfunction

" add 1 to the offset, so that the position at the cursor will be included
" in gocode's search
let offset = go#util#OffsetCursor()+1
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete',
\ [s:gocodeCurrentBufferOpt(filename), '-f=godit'],

" 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])
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')
" 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)
\ }

" no candidates are found
if len(out) == 1
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

" 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

" only one candidate is found
if len(out) == 2
return split(out[1], ',,')[0]
let l:result = eval(a:result)
if len(l:result) != 2
return ""
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 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(infos, "v:val =~ '".wordMatch."'")
let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'")

if len(filtered) == 1
return filtered[0]
if len(l:filtered) != 1
return ""
endif

return ""
return l:filtered[0].info
endfunction

function! go#complete#Info(auto) abort
" 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
function! s:info_complete(auto, result) abort
if !empty(a:result)
echo "vim-go: " | echohl Function | echon a:result | echohl None
endif
endfunction

Expand All @@ -146,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)
Expand All @@ -172,5 +262,4 @@ function! go#complete#ToggleAutoTypeInfo() abort
call go#util#EchoProgress("auto type info enabled")
endfunction


" vim: sw=2 ts=2 et
2 changes: 1 addition & 1 deletion doc/vim-go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1102,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*
Expand Down

0 comments on commit dcbfdf3

Please sign in to comment.