347 lines
9.5 KiB
VimL
347 lines
9.5 KiB
VimL
|
" Copyright 2011 The Go Authors. All rights reserved.
|
||
|
" Use of this source code is governed by a BSD-style
|
||
|
" license that can be found in the LICENSE file.
|
||
|
"
|
||
|
" This file provides a utility function that performs auto-completion of
|
||
|
" package names, for use by other commands.
|
||
|
|
||
|
" don't spam the user when Vim is started in Vi compatibility mode
|
||
|
let s:cpo_save = &cpo
|
||
|
set cpo&vim
|
||
|
|
||
|
let s:goos = $GOOS
|
||
|
let s:goarch = $GOARCH
|
||
|
|
||
|
if len(s:goos) == 0
|
||
|
if exists('g:golang_goos')
|
||
|
let s:goos = g:golang_goos
|
||
|
elseif has('win32') || has('win64')
|
||
|
let s:goos = 'windows'
|
||
|
elseif has('macunix')
|
||
|
let s:goos = 'darwin'
|
||
|
else
|
||
|
let s:goos = '*'
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
if len(s:goarch) == 0
|
||
|
if exists('g:golang_goarch')
|
||
|
let s:goarch = g:golang_goarch
|
||
|
else
|
||
|
let s:goarch = '*'
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
function! s:paths() abort
|
||
|
let dirs = []
|
||
|
|
||
|
if !exists("s:goroot")
|
||
|
if executable('go')
|
||
|
let s:goroot = go#util#env("goroot")
|
||
|
if go#util#ShellError() != 0
|
||
|
call go#util#EchoError('`go env GOROOT` failed')
|
||
|
endif
|
||
|
else
|
||
|
let s:goroot = $GOROOT
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
if len(s:goroot) != 0 && isdirectory(s:goroot)
|
||
|
let dirs += [s:goroot]
|
||
|
endif
|
||
|
|
||
|
let workspaces = split(go#path#Default(), go#util#PathListSep())
|
||
|
if workspaces != []
|
||
|
let dirs += workspaces
|
||
|
endif
|
||
|
|
||
|
return dirs
|
||
|
endfunction
|
||
|
|
||
|
function! s:module() abort
|
||
|
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Dir}}'])
|
||
|
if l:err != 0
|
||
|
return {}
|
||
|
endif
|
||
|
let l:dir = split(l:out, '\n')[0]
|
||
|
|
||
|
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Path}}'])
|
||
|
if l:err != 0
|
||
|
return {}
|
||
|
endif
|
||
|
let l:path = split(l:out, '\n')[0]
|
||
|
|
||
|
return {'dir': l:dir, 'path': l:path}
|
||
|
endfunction
|
||
|
|
||
|
function! s:vendordirs() abort
|
||
|
let l:vendorsuffix = go#util#PathSep() . 'vendor'
|
||
|
let l:module = s:module()
|
||
|
if empty(l:module)
|
||
|
let [l:root, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Root}}'])
|
||
|
if l:err != 0
|
||
|
return []
|
||
|
endif
|
||
|
if empty(l:root)
|
||
|
return []
|
||
|
endif
|
||
|
|
||
|
let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src'
|
||
|
|
||
|
let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}'])
|
||
|
if l:err != 0
|
||
|
return []
|
||
|
endif
|
||
|
let l:dir = split(l:dir, '\n')[0]
|
||
|
|
||
|
let l:vendordirs = []
|
||
|
while l:dir != l:root
|
||
|
let l:vendordir = l:dir . l:vendorsuffix
|
||
|
if isdirectory(l:vendordir)
|
||
|
let l:vendordirs = add(l:vendordirs, l:vendordir)
|
||
|
endif
|
||
|
|
||
|
let l:dir = fnamemodify(l:dir, ':h')
|
||
|
endwhile
|
||
|
|
||
|
return l:vendordirs
|
||
|
endif
|
||
|
|
||
|
let l:vendordir = l:module.dir . l:vendorsuffix
|
||
|
if !isdirectory(l:vendordir)
|
||
|
return []
|
||
|
endif
|
||
|
return [l:vendordir]
|
||
|
endfunction
|
||
|
|
||
|
let s:import_paths = {}
|
||
|
" ImportPath returns the import path of the package for current buffer. It
|
||
|
" returns -1 if the import path cannot be determined.
|
||
|
function! go#package#ImportPath() abort
|
||
|
let l:dir = expand("%:p:h")
|
||
|
if has_key(s:import_paths, dir)
|
||
|
return s:import_paths[l:dir]
|
||
|
endif
|
||
|
|
||
|
let l:importpath = go#package#FromPath(l:dir)
|
||
|
if type(l:importpath) == type(0)
|
||
|
return -1
|
||
|
endif
|
||
|
|
||
|
let s:import_paths[l:dir] = l:importpath
|
||
|
|
||
|
return l:importpath
|
||
|
endfunction
|
||
|
|
||
|
let s:in_gopath = {}
|
||
|
" InGOPATH returns TRUE when the package of the current buffer is within
|
||
|
" GOPATH.
|
||
|
function! go#package#InGOPATH() abort
|
||
|
let l:dir = expand("%:p:h")
|
||
|
if has_key(s:in_gopath, dir)
|
||
|
return s:in_gopath[l:dir][0] !=# '_'
|
||
|
endif
|
||
|
|
||
|
try
|
||
|
" turn off module support so that `go list` will report the package name
|
||
|
" with a leading '_' when the current buffer is not within GOPATH.
|
||
|
let Restore_modules = go#util#SetEnv('GO111MODULE', 'off')
|
||
|
let [l:out, l:err] = go#util#ExecInDir(['go', 'list'])
|
||
|
if l:err != 0
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
let l:importpath = split(l:out, '\n')[0]
|
||
|
if len(l:importpath) > 0
|
||
|
let s:in_gopath[l:dir] = l:importpath
|
||
|
endif
|
||
|
finally
|
||
|
call call(Restore_modules, [])
|
||
|
endtry
|
||
|
|
||
|
return len(l:importpath) > 0 && l:importpath[0] !=# '_'
|
||
|
endfunction
|
||
|
|
||
|
" go#package#FromPath returns the import path of arg. -1 is returned when arg
|
||
|
" does not specify a package. -2 is returned when arg is a relative path
|
||
|
" outside of GOPATH, not in a module, and not below the current working
|
||
|
" directory. A relative path is returned when in a null module at or below the
|
||
|
" current working directory..
|
||
|
function! go#package#FromPath(arg) abort
|
||
|
let l:path = fnamemodify(a:arg, ':p')
|
||
|
if !isdirectory(l:path)
|
||
|
let l:path = fnamemodify(l:path, ':h')
|
||
|
endif
|
||
|
|
||
|
let l:dir = go#util#Chdir(l:path)
|
||
|
try
|
||
|
if glob("*.go") == ""
|
||
|
" There's no Go code in this directory. We might be in a module directory
|
||
|
" which doesn't have any code at this level. To avoid `go list` making a
|
||
|
" bunch of HTTP requests to fetch dependencies, short-circuit `go list`
|
||
|
" and return -1 immediately.
|
||
|
if !empty(s:module())
|
||
|
return -1
|
||
|
endif
|
||
|
endif
|
||
|
let [l:out, l:err] = go#util#Exec(['go', 'list'])
|
||
|
if l:err != 0
|
||
|
return -1
|
||
|
endif
|
||
|
|
||
|
let l:importpath = split(l:out, '\n')[0]
|
||
|
finally
|
||
|
call go#util#Chdir(l:dir)
|
||
|
endtry
|
||
|
|
||
|
" go list returns '_CURRENTDIRECTORY' if the directory is in a null module
|
||
|
" (i.e. neither in GOPATH nor in a module). Return a relative import path
|
||
|
" if possible or an error if that is the case.
|
||
|
if l:importpath[0] ==# '_'
|
||
|
let l:relativeimportpath = fnamemodify(l:importpath[1:], ':.')
|
||
|
if go#util#IsWin()
|
||
|
let l:relativeimportpath = substitute(l:relativeimportpath, '\\', '/', 'g')
|
||
|
endif
|
||
|
|
||
|
if l:relativeimportpath == l:importpath[1:]
|
||
|
return '.'
|
||
|
endif
|
||
|
|
||
|
if l:relativeimportpath[0] == '/'
|
||
|
return -2
|
||
|
endif
|
||
|
|
||
|
let l:importpath= printf('./%s', l:relativeimportpath)
|
||
|
endif
|
||
|
|
||
|
return l:importpath
|
||
|
endfunction
|
||
|
|
||
|
function! go#package#CompleteMembers(package, member) abort
|
||
|
let [l:content, l:err] = go#util#Exec(['go', 'doc', a:package])
|
||
|
if l:err || !len(content)
|
||
|
return []
|
||
|
endif
|
||
|
|
||
|
let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
|
||
|
try
|
||
|
let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*'
|
||
|
let mx2 = '^\%(const\|var\|type\|func\) \([A-Z][^ (]\+\).*'
|
||
|
let candidates = map(filter(copy(lines), 'v:val =~ mx1'),
|
||
|
\ 'substitute(v:val, mx1, "\\1", "")')
|
||
|
\ + map(filter(copy(lines), 'v:val =~ mx2'),
|
||
|
\ 'substitute(v:val, mx2, "\\1", "")')
|
||
|
return filter(candidates, '!stridx(v:val, a:member)')
|
||
|
catch
|
||
|
return []
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
|
||
|
let words = split(a:CmdLine, '\s\+', 1)
|
||
|
|
||
|
" do not complete package members for these commands
|
||
|
let neglect_commands = ["GoImportAs", "GoGuruScope"]
|
||
|
|
||
|
if len(words) > 2 && index(neglect_commands, words[0]) == -1
|
||
|
" Complete package members
|
||
|
return go#package#CompleteMembers(words[1], words[2])
|
||
|
endif
|
||
|
|
||
|
let dirs = s:paths()
|
||
|
let module = s:module()
|
||
|
|
||
|
if len(dirs) == 0 && empty(module)
|
||
|
" should not happen
|
||
|
return []
|
||
|
endif
|
||
|
|
||
|
let vendordirs = s:vendordirs()
|
||
|
|
||
|
let l:modcache = go#util#env('gomodcache')
|
||
|
|
||
|
let ret = {}
|
||
|
for dir in dirs
|
||
|
" this may expand to multiple lines
|
||
|
let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
|
||
|
if l:modcache != ''
|
||
|
let root = add(root, l:modcache)
|
||
|
else
|
||
|
let root = add(root, expand(dir . '/pkg/mod'))
|
||
|
endif
|
||
|
let root = add(root, expand(dir . '/src'), )
|
||
|
let root = extend(root, vendordirs)
|
||
|
let root = add(root, module)
|
||
|
for item in root
|
||
|
" item may be a dictionary when operating in a module.
|
||
|
if type(item) == type({})
|
||
|
if empty(item)
|
||
|
continue
|
||
|
endif
|
||
|
let dir = item.dir
|
||
|
let path = item.path
|
||
|
else
|
||
|
let dir = item
|
||
|
let path = item
|
||
|
endif
|
||
|
|
||
|
if !empty(module) && dir ==# module.dir
|
||
|
if stridx(a:ArgLead, module.path) == 0
|
||
|
if len(a:ArgLead) != len(module.path)
|
||
|
let glob = globpath(module.dir, substitute(a:ArgLead, module.path . '/\?', '', '').'*')
|
||
|
else
|
||
|
let glob = module.dir
|
||
|
endif
|
||
|
elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0
|
||
|
" use the module directory when module.path begins wih a:ArgLead and
|
||
|
" module.path does not have any path segments after a:ArgLead.
|
||
|
let glob = module.dir
|
||
|
else
|
||
|
continue
|
||
|
endif
|
||
|
else
|
||
|
let glob = globpath(dir, a:ArgLead.'*')
|
||
|
endif
|
||
|
for candidate in split(glob)
|
||
|
if isdirectory(candidate)
|
||
|
" TODO(bc): use wildignore instead of filtering out vendor
|
||
|
" directories manually?
|
||
|
if fnamemodify(candidate, ':t') == 'vendor'
|
||
|
continue
|
||
|
endif
|
||
|
" if path contains version info, strip it out
|
||
|
let vidx = strridx(candidate, '@')
|
||
|
if vidx >= 0
|
||
|
let candidate = strpart(candidate, 0, vidx)
|
||
|
endif
|
||
|
let candidate .= '/'
|
||
|
elseif candidate !~ '\.a$'
|
||
|
continue
|
||
|
endif
|
||
|
|
||
|
if dir !=# path
|
||
|
let candidate = substitute(candidate, '^' . dir, path, 'g')
|
||
|
else
|
||
|
let candidate = candidate[len(dir)+1:]
|
||
|
endif
|
||
|
" replace a backslash with a forward slash and drop .a suffixes
|
||
|
let candidate = substitute(substitute(candidate, '[\\]', '/', 'g'),
|
||
|
\ '\.a$', '', 'g')
|
||
|
|
||
|
" without this the result can have duplicates in form of
|
||
|
" 'encoding/json' and '/encoding/json/'
|
||
|
let candidate = go#util#StripPathSep(candidate)
|
||
|
|
||
|
let ret[candidate] = candidate
|
||
|
endfor
|
||
|
endfor
|
||
|
endfor
|
||
|
return sort(keys(ret))
|
||
|
endfunction
|
||
|
|
||
|
" restore Vi compatibility settings
|
||
|
let &cpo = s:cpo_save
|
||
|
unlet s:cpo_save
|
||
|
|
||
|
" vim: sw=2 ts=2 et
|