diff --git a/gopls/internal/lsp/source/completion/allowcommand_test.go b/gopls/internal/lsp/source/completion/allowcommand_test.go new file mode 100644 index 00000000000..d1dc9ab56b5 --- /dev/null +++ b/gopls/internal/lsp/source/completion/allowcommand_test.go @@ -0,0 +1,62 @@ +package completion + +import ( + "fmt" + "testing" + + "github.com/goplus/gop/ast" + "github.com/goplus/gop/parser" + "github.com/goplus/gop/token" + "golang.org/x/tools/gop/ast/astutil" +) + +func TestAllowCommand(t *testing.T) { + testdata := []struct { + src string + pos int + result bool + }{ + {`println`, 7, true}, + {`a := add`, 8, false}, + {`onStart => { println }`, 20, true}, + {`println info`, 12, false}, + {`fmt.println`, 11, true}, + {`a := pkg.info.get`, 17, false}, + {`pkg.info.get`, 12, true}, + {`run "get /p/$id", => { + get "http://foo.com/p/${id}" + ret 200 +}`, 57, true}, + {`run "get /p/$id", => { + get "http://foo.com/p/${id}" + ret 200 + json { + "id": getId, + } +}`, 83, false}, + } + for i, data := range testdata { + r, err := testCheckAllowCommand(data.src, data.pos) + if err != nil { + t.Fatalf("check index %v error: %v", i, err) + } + if r != data.result { + t.Fatalf("check index %v result failed. got %v want %v", i, r, data.result) + } + } +} + +func testCheckAllowCommand(src string, pos int) (bool, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "main.gop", src, 0) + if err != nil { + return false, err + } + paths, _ := astutil.PathEnclosingInterval(f, token.Pos(pos), token.Pos(pos)) + switch node := paths[0].(type) { + case *ast.Ident, *ast.SelectorExpr: + default: + return false, fmt.Errorf("not found ident or selector expr, got %T", node) + } + return checkAllowCommand(paths), nil +} diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go index 541f5a6c381..1efeaa81093 100644 --- a/gopls/internal/lsp/source/completion/completion.go +++ b/gopls/internal/lsp/source/completion/completion.go @@ -3424,17 +3424,7 @@ func (c *gopCompleter) quickParse(ctx context.Context, cMu *sync.Mutex, enough * // goxls func alias if tok == token.FUNC { if alias, ok := hasAliasName(id.Name); ok { - var noSnip bool - switch len(fn.Type.Params.List) { - case 0: - noSnip = true - case 1: - if fn.Recv != nil { - if _, ok := fn.Type.Params.List[0].Type.(*ast.Ellipsis); ok { - noSnip = true - } - } - } + noSnip := (len(fn.Type.Params.List) == 0) || c.allowCommand c.items = append(c.items, cloneAliasItem(item, id.Name, alias, 0.0001, noSnip)) } } diff --git a/gopls/internal/lsp/source/completion/completion_gox.go b/gopls/internal/lsp/source/completion/completion_gox.go index f137be3478c..5b753325e74 100644 --- a/gopls/internal/lsp/source/completion/completion_gox.go +++ b/gopls/internal/lsp/source/completion/completion_gox.go @@ -122,6 +122,9 @@ type gopCompleter struct { // also includes our package scope and the universal scope at the // end. scopes []*types.Scope + + // allowCommand is allow Go+ command style function + allowCommand bool } // gopFuncInfo holds info about a function object. @@ -274,6 +277,32 @@ func gopEnclosingFunction(path []ast.Node, info *typesutil.Info) *gopFuncInfo { return nil } +// checkAllowCommand check Go+ command style by ident/sel +func checkAllowCommand(paths []ast.Node) bool { + var pos token.Pos + switch paths[0].(type) { + case *ast.Ident, *ast.SelectorExpr: + pos = paths[0].Pos() + default: + return false + } + var i int = 1 + for ; i < len(paths); i++ { + if _, ok := paths[i].(*ast.SelectorExpr); !ok { + break + } + pos = paths[i].Pos() + } + var stmtPos token.Pos + for ; i < len(paths); i++ { + if _, ok := paths[i].(*ast.ExprStmt); ok { + stmtPos = paths[i].Pos() + break + } + } + return pos == stmtPos +} + // GopCompletion returns a list of possible candidates for completion, given a // a file and a position. // @@ -404,6 +433,7 @@ func GopCompletion(ctx context.Context, snapshot source.Snapshot, fh source.File methodSetCache: make(map[methodSetKey]*types.MethodSet), mapper: pgf.Mapper, scopes: scopes, + allowCommand: checkAllowCommand(path), } ctx, cancel := context.WithCancel(ctx) @@ -1288,17 +1318,7 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro // goxls func alias if tok == token.FUNC { if alias, ok := hasAliasName(id.Name); ok { - var noSnip bool - switch len(fn.Type.Params.List) { - case 0: - noSnip = true - case 1: - if fn.Recv != nil { - if _, ok := fn.Type.Params.List[0].Type.(*ast.Ellipsis); ok { - noSnip = true - } - } - } + noSnip := (len(fn.Type.Params.List) == 0) || c.allowCommand c.items = append(c.items, cloneAliasItem(item, id.Name, alias, 0.0001, noSnip)) } } diff --git a/gopls/internal/lsp/source/completion/deep_completion_gox.go b/gopls/internal/lsp/source/completion/deep_completion_gox.go index 676fcf32a3c..28e98812cf1 100644 --- a/gopls/internal/lsp/source/completion/deep_completion_gox.go +++ b/gopls/internal/lsp/source/completion/deep_completion_gox.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/goplus/gogen" "github.com/qiniu/x/log" "golang.org/x/tools/gopls/internal/goxls" "golang.org/x/tools/gopls/internal/lsp/protocol" @@ -176,12 +177,8 @@ func (c *gopCompleter) addCandidate(ctx context.Context, cand *candidate) { } // Lower score of method calls so we prefer fields and vars over calls. - var aliasNoSnip bool if cand.hasMod(invoke) { if sig, ok := obj.Type().Underlying().(*types.Signature); ok { - if sig.Params() == nil || (sig.Recv() != nil && sig.Variadic() && sig.Params().Len() == 1) { - aliasNoSnip = true - } if sig.Recv() != nil { cand.score *= 0.9 } @@ -206,19 +203,33 @@ func (c *gopCompleter) addCandidate(ctx context.Context, cand *candidate) { cand.score = 0 } + objIsFunc := isFunc(obj) var aliasName string - cand.name, aliasName = gopDeepCandName(cand, c.pkg.GetTypes()) + if objIsFunc { + cand.name, aliasName = gopDeepCandNameEx(cand, c.pkg.GetTypes()) + } else { + cand.name = gopDeepCandName(cand) + } if item, err := c.item(ctx, *cand); err == nil { - c.items = append(c.items, item) - if aliasName != cand.name { - c.items = append(c.items, cloneAliasItem(item, cand.name, aliasName, 0.0001, aliasNoSnip)) + if objIsFunc && aliasName != cand.name { + noSnip := gogen.HasAutoProperty(obj.Type()) || c.allowCommand + c.items = append(c.items, cloneAliasItem(item, cand.name, aliasName, 0.0001, noSnip)) + } + if (objIsFunc || isBuiltin(obj)) && c.allowCommand { + item.snippet = nil } + c.items = append(c.items, item) } else if false && goxls.DbgCompletion { log.Println("gopCompleter.addCandidate item:", err) log.SingleStack() } } +func isBuiltin(obj types.Object) bool { + _, ok := obj.(*types.Builtin) + return ok +} + func cloneAliasItem(item CompletionItem, name string, alias string, score float64, noSnip bool) CompletionItem { aliasItem := item if showGopStyle { @@ -243,7 +254,36 @@ func cloneAliasItem(item CompletionItem, name string, alias string, score float6 // deepCandName produces the full candidate name including any // ancestor objects. For example, "foo.bar().baz" for candidate "baz". -func gopDeepCandName(cand *candidate, this *types.Package) (name string, alias string) { +func gopDeepCandName(cand *candidate) (name string) { + totalLen := len(cand.obj.Name()) + for i, obj := range cand.path { + n := len(obj.Name()) + 1 + totalLen += n + if cand.pathInvokeMask&(1< 0 { + totalLen += 2 + } + } + + var buf strings.Builder + buf.Grow(totalLen) + + for i, obj := range cand.path { + buf.WriteString(obj.Name()) + if cand.pathInvokeMask&(1< 0 { + buf.WriteByte('(') + buf.WriteByte(')') + } + buf.WriteByte('.') + } + + buf.WriteString(cand.obj.Name()) + + return buf.String() +} + +// deepCandNameEx produces the full candidate name and alias including any +// ancestor objects. For example, "foo.bar().baz" for candidate "baz". +func gopDeepCandNameEx(cand *candidate, this *types.Package) (name string, alias string) { totalLen := len(cand.obj.Name()) totalLen2 := totalLen for i, obj := range cand.path {