wide/vendor/github.com/nsf/gocode/autocompletecontext.go

690 lines
18 KiB
Go
Raw Normal View History

2018-03-13 07:10:52 +03:00
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
)
//-------------------------------------------------------------------------
// out_buffers
//
// Temporary structure for writing autocomplete response.
//-------------------------------------------------------------------------
// fields must be exported for RPC
type candidate struct {
Name string
Type string
Class decl_class
}
type out_buffers struct {
tmpbuf *bytes.Buffer
candidates []candidate
ctx *auto_complete_context
tmpns map[string]bool
ignorecase bool
}
func new_out_buffers(ctx *auto_complete_context) *out_buffers {
b := new(out_buffers)
b.tmpbuf = bytes.NewBuffer(make([]byte, 0, 1024))
b.candidates = make([]candidate, 0, 64)
b.ctx = ctx
return b
}
func (b *out_buffers) Len() int {
return len(b.candidates)
}
func (b *out_buffers) Less(i, j int) bool {
x := b.candidates[i]
y := b.candidates[j]
if x.Class == y.Class {
return x.Name < y.Name
}
return x.Class < y.Class
}
func (b *out_buffers) Swap(i, j int) {
b.candidates[i], b.candidates[j] = b.candidates[j], b.candidates[i]
}
func (b *out_buffers) append_decl(p, name string, decl *decl, class decl_class) {
c1 := !g_config.ProposeBuiltins && decl.scope == g_universe_scope && decl.name != "Error"
c2 := class != decl_invalid && decl.class != class
c3 := class == decl_invalid && !has_prefix(name, p, b.ignorecase)
c4 := !decl.matches()
c5 := !check_type_expr(decl.typ)
if c1 || c2 || c3 || c4 || c5 {
return
}
decl.pretty_print_type(b.tmpbuf)
b.candidates = append(b.candidates, candidate{
Name: name,
Type: b.tmpbuf.String(),
Class: decl.class,
})
b.tmpbuf.Reset()
}
func (b *out_buffers) append_embedded(p string, decl *decl, class decl_class) {
if decl.embedded == nil {
return
}
first_level := false
if b.tmpns == nil {
// first level, create tmp namespace
b.tmpns = make(map[string]bool)
first_level = true
// add all children of the current decl to the namespace
for _, c := range decl.children {
b.tmpns[c.name] = true
}
}
for _, emb := range decl.embedded {
typedecl := type_to_decl(emb, decl.scope)
if typedecl == nil {
continue
}
// prevent infinite recursion here
if typedecl.flags&decl_visited != 0 {
continue
}
typedecl.flags |= decl_visited
defer typedecl.clear_visited()
for _, c := range typedecl.children {
if _, has := b.tmpns[c.name]; has {
continue
}
b.append_decl(p, c.name, c, class)
b.tmpns[c.name] = true
}
b.append_embedded(p, typedecl, class)
}
if first_level {
// remove tmp namespace
b.tmpns = nil
}
}
//-------------------------------------------------------------------------
// auto_complete_context
//
// Context that holds cache structures for autocompletion needs. It
// includes cache for packages and for main package files.
//-------------------------------------------------------------------------
type auto_complete_context struct {
current *auto_complete_file // currently edited file
others []*decl_file_cache // other files of the current package
pkg *scope
pcache package_cache // packages cache
declcache *decl_cache // top-level declarations cache
}
func new_auto_complete_context(pcache package_cache, declcache *decl_cache) *auto_complete_context {
c := new(auto_complete_context)
c.current = new_auto_complete_file("", declcache.context)
c.pcache = pcache
c.declcache = declcache
return c
}
func (c *auto_complete_context) update_caches() {
// temporary map for packages that we need to check for a cache expiration
// map is used as a set of unique items to prevent double checks
ps := make(map[string]*package_file_cache)
// collect import information from all of the files
c.pcache.append_packages(ps, c.current.packages)
c.others = get_other_package_files(c.current.name, c.current.package_name, c.declcache)
for _, other := range c.others {
c.pcache.append_packages(ps, other.packages)
}
update_packages(ps)
// fix imports for all files
fixup_packages(c.current.filescope, c.current.packages, c.pcache)
for _, f := range c.others {
fixup_packages(f.filescope, f.packages, c.pcache)
}
// At this point we have collected all top level declarations, now we need to
// merge them in the common package block.
c.merge_decls()
}
func (c *auto_complete_context) merge_decls() {
c.pkg = new_scope(g_universe_scope)
merge_decls(c.current.filescope, c.pkg, c.current.decls)
merge_decls_from_packages(c.pkg, c.current.packages, c.pcache)
for _, f := range c.others {
merge_decls(f.filescope, c.pkg, f.decls)
merge_decls_from_packages(c.pkg, f.packages, c.pcache)
}
}
func (c *auto_complete_context) make_decl_set(scope *scope) map[string]*decl {
set := make(map[string]*decl, len(c.pkg.entities)*2)
make_decl_set_recursive(set, scope)
return set
}
func (c *auto_complete_context) get_candidates_from_set(set map[string]*decl, partial string, class decl_class, b *out_buffers) {
for key, value := range set {
if value == nil {
continue
}
value.infer_type()
b.append_decl(partial, key, value, class)
}
}
func (c *auto_complete_context) get_candidates_from_decl(cc cursor_context, class decl_class, b *out_buffers) {
// propose all children of a subject declaration and
for _, decl := range cc.decl.children {
if cc.decl.class == decl_package && !ast.IsExported(decl.name) {
continue
}
if cc.struct_field {
// if we're autocompleting struct field init, skip all methods
if _, ok := decl.typ.(*ast.FuncType); ok {
continue
}
}
b.append_decl(cc.partial, decl.name, decl, class)
}
// propose all children of an underlying struct/interface type
adecl := advance_to_struct_or_interface(cc.decl)
if adecl != nil && adecl != cc.decl {
for _, decl := range adecl.children {
if decl.class == decl_var {
b.append_decl(cc.partial, decl.name, decl, class)
}
}
}
// propose all children of its embedded types
b.append_embedded(cc.partial, cc.decl, class)
}
func (c *auto_complete_context) get_import_candidates(partial string, b *out_buffers) {
pkgdirs := g_daemon.context.pkg_dirs()
resultSet := map[string]struct{}{}
for _, pkgdir := range pkgdirs {
// convert srcpath to pkgpath and get candidates
get_import_candidates_dir(pkgdir, filepath.FromSlash(partial), b.ignorecase, resultSet)
}
for k := range resultSet {
b.candidates = append(b.candidates, candidate{Name: k, Class: decl_import})
}
}
func get_import_candidates_dir(root, partial string, ignorecase bool, r map[string]struct{}) {
var fpath string
var match bool
if strings.HasSuffix(partial, "/") {
fpath = filepath.Join(root, partial)
} else {
fpath = filepath.Join(root, filepath.Dir(partial))
match = true
}
fi := readdir(fpath)
for i := range fi {
name := fi[i].Name()
rel, err := filepath.Rel(root, filepath.Join(fpath, name))
if err != nil {
panic(err)
}
if match && !has_prefix(rel, partial, ignorecase) {
continue
} else if fi[i].IsDir() {
get_import_candidates_dir(root, rel+string(filepath.Separator), ignorecase, r)
} else {
ext := filepath.Ext(name)
if ext != ".a" {
continue
} else {
rel = rel[0 : len(rel)-2]
}
r[vendorlessImportPath(filepath.ToSlash(rel))] = struct{}{}
}
}
}
// returns three slices of the same length containing:
// 1. apropos names
// 2. apropos types (pretty-printed)
// 3. apropos classes
// and length of the part that should be replaced (if any)
func (c *auto_complete_context) apropos(file []byte, filename string, cursor int) ([]candidate, int) {
c.current.cursor = cursor
c.current.name = filename
// Update caches and parse the current file.
// This process is quite complicated, because I was trying to design it in a
// concurrent fashion. Apparently I'm not really good at that. Hopefully
// will be better in future.
// Ugly hack, but it actually may help in some cases. Insert a
// semicolon right at the cursor location.
filesemi := make([]byte, len(file)+1)
copy(filesemi, file[:cursor])
filesemi[cursor] = ';'
copy(filesemi[cursor+1:], file[cursor:])
// Does full processing of the currently edited file (top-level declarations plus
// active function).
c.current.process_data(filesemi)
// Updates cache of other files and packages. See the function for details of
// the process. At the end merges all the top-level declarations into the package
// block.
c.update_caches()
// And we're ready to Go. ;)
b := new_out_buffers(c)
partial := 0
cc, ok := c.deduce_cursor_context(file, cursor)
if !ok {
var d *decl
if ident, ok := cc.expr.(*ast.Ident); ok && g_config.UnimportedPackages {
d = resolveKnownPackageIdent(ident.Name, c.current.name, c.current.context)
}
if d == nil {
return nil, 0
}
cc.decl = d
}
class := decl_invalid
switch cc.partial {
case "const":
class = decl_const
case "var":
class = decl_var
case "type":
class = decl_type
case "func":
class = decl_func
case "package":
class = decl_package
}
if cc.decl_import {
c.get_import_candidates(cc.partial, b)
if cc.partial != "" && len(b.candidates) == 0 {
// as a fallback, try case insensitive approach
b.ignorecase = true
c.get_import_candidates(cc.partial, b)
}
} else if cc.decl == nil {
// In case if no declaraion is a subject of completion, propose all:
set := c.make_decl_set(c.current.scope)
c.get_candidates_from_set(set, cc.partial, class, b)
if cc.partial != "" && len(b.candidates) == 0 {
// as a fallback, try case insensitive approach
b.ignorecase = true
c.get_candidates_from_set(set, cc.partial, class, b)
}
} else {
c.get_candidates_from_decl(cc, class, b)
if cc.partial != "" && len(b.candidates) == 0 {
// as a fallback, try case insensitive approach
b.ignorecase = true
c.get_candidates_from_decl(cc, class, b)
}
}
partial = len(cc.partial)
if len(b.candidates) == 0 {
return nil, 0
}
sort.Sort(b)
return b.candidates, partial
}
func update_packages(ps map[string]*package_file_cache) {
// initiate package cache update
done := make(chan bool)
for _, p := range ps {
go func(p *package_file_cache) {
defer func() {
if err := recover(); err != nil {
print_backtrace(err)
done <- false
}
}()
p.update_cache()
done <- true
}(p)
}
// wait for its completion
for _ = range ps {
if !<-done {
panic("One of the package cache updaters panicked")
}
}
}
func merge_decls(filescope *scope, pkg *scope, decls map[string]*decl) {
for _, d := range decls {
pkg.merge_decl(d)
}
filescope.parent = pkg
}
func merge_decls_from_packages(pkgscope *scope, pkgs []package_import, pcache package_cache) {
for _, p := range pkgs {
path, alias := p.path, p.alias
if alias != "." {
continue
}
p := pcache[path].main
if p == nil {
continue
}
for _, d := range p.children {
if ast.IsExported(d.name) {
pkgscope.merge_decl(d)
}
}
}
}
func fixup_packages(filescope *scope, pkgs []package_import, pcache package_cache) {
for _, p := range pkgs {
path, alias := p.path, p.alias
if alias == "" {
alias = pcache[path].defalias
}
// skip packages that will be merged to the package scope
if alias == "." {
continue
}
filescope.replace_decl(alias, pcache[path].main)
}
}
func get_other_package_files(filename, packageName string, declcache *decl_cache) []*decl_file_cache {
others := find_other_package_files(filename, packageName)
ret := make([]*decl_file_cache, len(others))
done := make(chan *decl_file_cache)
for _, nm := range others {
go func(name string) {
defer func() {
if err := recover(); err != nil {
print_backtrace(err)
done <- nil
}
}()
done <- declcache.get_and_update(name)
}(nm)
}
for i := range others {
ret[i] = <-done
if ret[i] == nil {
panic("One of the decl cache updaters panicked")
}
}
return ret
}
func find_other_package_files(filename, package_name string) []string {
if filename == "" {
return nil
}
dir, file := filepath.Split(filename)
files_in_dir, err := readdir_lstat(dir)
if err != nil {
panic(err)
}
count := 0
for _, stat := range files_in_dir {
ok, _ := filepath.Match("*.go", stat.Name())
if !ok || stat.Name() == file {
continue
}
count++
}
out := make([]string, 0, count)
for _, stat := range files_in_dir {
const non_regular = os.ModeDir | os.ModeSymlink |
os.ModeDevice | os.ModeNamedPipe | os.ModeSocket
ok, _ := filepath.Match("*.go", stat.Name())
if !ok || stat.Name() == file || stat.Mode()&non_regular != 0 {
continue
}
abspath := filepath.Join(dir, stat.Name())
if file_package_name(abspath) == package_name {
n := len(out)
out = out[:n+1]
out[n] = abspath
}
}
return out
}
func file_package_name(filename string) string {
file, _ := parser.ParseFile(token.NewFileSet(), filename, nil, parser.PackageClauseOnly)
return file.Name.Name
}
func make_decl_set_recursive(set map[string]*decl, scope *scope) {
for name, ent := range scope.entities {
if _, ok := set[name]; !ok {
set[name] = ent
}
}
if scope.parent != nil {
make_decl_set_recursive(set, scope.parent)
}
}
func check_func_field_list(f *ast.FieldList) bool {
if f == nil {
return true
}
for _, field := range f.List {
if !check_type_expr(field.Type) {
return false
}
}
return true
}
// checks for a type expression correctness, it the type expression has
// ast.BadExpr somewhere, returns false, otherwise true
func check_type_expr(e ast.Expr) bool {
switch t := e.(type) {
case *ast.StarExpr:
return check_type_expr(t.X)
case *ast.ArrayType:
return check_type_expr(t.Elt)
case *ast.SelectorExpr:
return check_type_expr(t.X)
case *ast.FuncType:
a := check_func_field_list(t.Params)
b := check_func_field_list(t.Results)
return a && b
case *ast.MapType:
a := check_type_expr(t.Key)
b := check_type_expr(t.Value)
return a && b
case *ast.Ellipsis:
return check_type_expr(t.Elt)
case *ast.ChanType:
return check_type_expr(t.Value)
case *ast.BadExpr:
return false
default:
return true
}
return true
}
//-------------------------------------------------------------------------
// Status output
//-------------------------------------------------------------------------
type decl_slice []*decl
func (s decl_slice) Less(i, j int) bool {
if s[i].class != s[j].class {
return s[i].name < s[j].name
}
return s[i].class < s[j].class
}
func (s decl_slice) Len() int { return len(s) }
func (s decl_slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
const (
color_red = "\033[0;31m"
color_red_bold = "\033[1;31m"
color_green = "\033[0;32m"
color_green_bold = "\033[1;32m"
color_yellow = "\033[0;33m"
color_yellow_bold = "\033[1;33m"
color_blue = "\033[0;34m"
color_blue_bold = "\033[1;34m"
color_magenta = "\033[0;35m"
color_magenta_bold = "\033[1;35m"
color_cyan = "\033[0;36m"
color_cyan_bold = "\033[1;36m"
color_white = "\033[0;37m"
color_white_bold = "\033[1;37m"
color_none = "\033[0m"
)
var g_decl_class_to_color = [...]string{
decl_const: color_white_bold,
decl_var: color_magenta,
decl_type: color_cyan,
decl_func: color_green,
decl_package: color_red,
decl_methods_stub: color_red,
}
var g_decl_class_to_string_status = [...]string{
decl_const: " const",
decl_var: " var",
decl_type: " type",
decl_func: " func",
decl_package: "package",
decl_methods_stub: " stub",
}
func (c *auto_complete_context) status() string {
buf := bytes.NewBuffer(make([]byte, 0, 4096))
fmt.Fprintf(buf, "Server's GOMAXPROCS == %d\n", runtime.GOMAXPROCS(0))
fmt.Fprintf(buf, "\nPackage cache contains %d entries\n", len(c.pcache))
fmt.Fprintf(buf, "\nListing these entries:\n")
for _, mod := range c.pcache {
fmt.Fprintf(buf, "\tname: %s (default alias: %s)\n", mod.name, mod.defalias)
fmt.Fprintf(buf, "\timports %d declarations and %d packages\n", len(mod.main.children), len(mod.others))
if mod.mtime == -1 {
fmt.Fprintf(buf, "\tthis package stays in cache forever (built-in package)\n")
} else {
mtime := time.Unix(0, mod.mtime)
fmt.Fprintf(buf, "\tlast modification time: %s\n", mtime)
}
fmt.Fprintf(buf, "\n")
}
if c.current.name != "" {
fmt.Fprintf(buf, "Last edited file: %s (package: %s)\n", c.current.name, c.current.package_name)
if len(c.others) > 0 {
fmt.Fprintf(buf, "\nOther files from the current package:\n")
}
for _, f := range c.others {
fmt.Fprintf(buf, "\t%s\n", f.name)
}
fmt.Fprintf(buf, "\nListing declarations from files:\n")
const status_decls = "\t%s%s" + color_none + " " + color_yellow + "%s" + color_none + "\n"
const status_decls_children = "\t%s%s" + color_none + " " + color_yellow + "%s" + color_none + " (%d)\n"
fmt.Fprintf(buf, "\n%s:\n", c.current.name)
ds := make(decl_slice, len(c.current.decls))
i := 0
for _, d := range c.current.decls {
ds[i] = d
i++
}
sort.Sort(ds)
for _, d := range ds {
if len(d.children) > 0 {
fmt.Fprintf(buf, status_decls_children,
g_decl_class_to_color[d.class],
g_decl_class_to_string_status[d.class],
d.name, len(d.children))
} else {
fmt.Fprintf(buf, status_decls,
g_decl_class_to_color[d.class],
g_decl_class_to_string_status[d.class],
d.name)
}
}
for _, f := range c.others {
fmt.Fprintf(buf, "\n%s:\n", f.name)
ds = make(decl_slice, len(f.decls))
i = 0
for _, d := range f.decls {
ds[i] = d
i++
}
sort.Sort(ds)
for _, d := range ds {
if len(d.children) > 0 {
fmt.Fprintf(buf, status_decls_children,
g_decl_class_to_color[d.class],
g_decl_class_to_string_status[d.class],
d.name, len(d.children))
} else {
fmt.Fprintf(buf, status_decls,
g_decl_class_to_color[d.class],
g_decl_class_to_string_status[d.class],
d.name)
}
}
}
}
return buf.String()
}