Skip to content

Instantly share code, notes, and snippets.

@koron
Last active July 6, 2020 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koron/a5857248323628bf4f633001636b576e to your computer and use it in GitHub Desktop.
Save koron/a5857248323628bf4f633001636b576e to your computer and use it in GitHub Desktop.
Vimでgo.modを考慮した module & package 名の補完をする実験作

Getting started

  1. copy gocomplete.vim to your autoload folder. (ex: ~/.vim/autoload)

  2. setup completion for your command

    example for :Godoc command

    command! -nargs=* -range -complete=customlist,gocomplete#packages Godoc call s:Godoc(<f-args>)

Limitation

  • can't complete standard packages (for now)
  • it would be slow with large module (ex: github.com/aws/aws-sdk-go)
  • modify go.sum sometime
  • stop to complete sometime
scriptencoding utf-8
let s:cache = {}
let s:cache_enable = 1
function! s:get_cache(key, ttl, valueFn) abort
if !s:cache_enable
return a:valueFn(a:key)
endif
let l:now = localtime()
let l:entry = get(s:cache, a:key, {})
if len(l:entry) >= 2 && l:entry['expiresAt'] > l:now
return l:entry['value']->copy()
endif
" get new value for a key.
let l:entry = { 'value': a:valueFn(a:key), 'expiresAt': l:now + a:ttl }
let s:cache[a:key] = l:entry
return l:entry['value']->copy()
endfunction
" gocomplete#_cache returns a dictionary of complement cache.
function! gocomplete#_cache() abort
return s:cache
endfunction
" gocomplete#_cache_reset resets complement cache.
function! gocomplete#_cache_reset() abort
let s:cache = {}
endfunction
" gocomplete#_cache_enable
function! gocomplete#_cache_enable(v) abort
let s:cache_enable = a:v
endfunction
function! s:goroot() abort
return s:get_cache('ENV:GOROOT', 3600, {_ -> systemlist('go env GOROOT')[0]})
endfunction
function! s:system_jsonl(cmd) abort
let l:out = systemlist(a:cmd)
let l:out = map(l:out, {_, line -> line == '}' ? line .. ',' : line})
let l:out = join(l:out)
let l:out = json_decode('[' .. l:out .. ']')
return l:out
endfunction
function! s:match_module(modpath, lead) abort
if len(a:lead) > len(a:modpath)
return stridx(a:lead, a:modpath .. '/') == 0
endif
return stridx(a:modpath, a:lead) == 0
endfunction
function! s:match_package(pkgpath, lead) abort
return stridx(a:pkgpath, a:lead) == 0
endfunction
function! s:filter_set(set, pat, fn) abort
let l:list = copy(a:set)
let l:list = map(l:list, {_, v -> a:fn(v)})
let l:list = map(l:list, {_, v -> matchstr(v, a:pat)})
let l:list = filter(l:list, {_, v -> v != ''})
let l:list = sort(l:list)
let l:list = uniq(l:list)
return l:list
endfunction
function! s:filter_modules(mods, pat) abort
return s:filter_set(a:mods, a:pat, {m -> m.Path})
endfunction
function! s:filter_packages(pkgs, pat) abort
return s:filter_set(a:pkgs, a:pat, {p -> p.ImportPath})
endfunction
" gocomplete#list_packages obtains all packages in the module. the module is
" specified by its path.
function! gocomplete#list_packages(modpath = '', modver = '') abort
if a:modpath != ''
let l:key = 'PACKAGES:' .. a:modpath .. (a:modver != '' ? '@' .. a:modver : '')
let l:ttl = a:modver != '' ? 3600 : 300
return s:get_cache(l:key, l:ttl, {_ -> s:system_jsonl('go list -find -json ' .. a:modpath .. '/...')})
endif
" list standard packages.
return s:get_cache('STANDARD_PACKAGES', 86400, {_ -> s:system_jsonl('go list -find -json std')})
endfunction
" gocomplete#list_modules obtains all modules which the project depends on.
" the project is specified by directory, default is current directory.
function! gocomplete#list_modules(dir = '') abort
let l:olddir = ''
if a:dir != ''
let l:olddir = chdir(a:dir)
endif
try
return s:get_cache('MODULES:' .. a:dir, 900, {_ -> s:system_jsonl('go list -m -json all')})
finally
if l:olddir != ''
call chdir(l:olddir)
endif
endtry
endfunction
" gocomplete#find_modules finds and returns modules which match with a lead.
function! gocomplete#find_modules(lead) abort
let l:mods = gocomplete#list_modules(getcwd())
return l:mods->filter({_, m -> s:match_module(m.Path, a:lead)})
endfunction
" gocomplete#find_packages finds and returns packages which match with a lead
" in a module.
function! gocomplete#find_packages(lead, modpath, modver = '') abort
let l:pkgs = gocomplete#list_packages(a:modpath, a:modver)
return l:pkgs->filter({_, p -> s:match_package(p.ImportPath, a:lead)})
endfunction
" gocomplete#packages completes packages with a lead. it can be used in Vim's
" command completion.
function! gocomplete#packages(lead, line = 0, pos = 0) abort
let l:mods = gocomplete#find_modules(a:lead)
if len(l:mods) == 0
return []
elseif len(l:mods) > 1
let l:list = s:filter_modules(l:mods, a:lead .. '[^/]*')
if len(l:list) > 1
return l:list
endif
return s:filter_modules(l:mods, a:lead .. '[^/]*\%(/[^/]*\)\?')
endif
" case: len(l:mods) == 1, enumerate packages in the module.
let [l:modpath, l:modver] = [l:mods[0].Path, get(l:mods[0], 'Version', '')]
let l:pkgs = gocomplete#find_packages(a:lead, l:modpath, l:modver)
if len(l:pkgs) == 0
return [a:lead, l:modpath]
endif
let l:pat = len(a:lead) > len(l:modpath) ? a:lead : l:modpath
let l:list = s:filter_packages(l:pkgs, l:pat .. '[^/]*')
if len(l:list) == 0
return [a:lead, l:modpath]
elseif len(l:list) > 1
return l:list
endif
" case: candidate is just only a package, check its sub packages.
return s:filter_packages(l:pkgs, l:pat .. '[^/]*\%(/[^/]*\)\?')
endfunction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment