1
0
mirror of https://github.com/golang/go synced 2024-11-18 23:54:41 -07:00
go/internal/lsp/source/completion_snippet.go

102 lines
3.0 KiB
Go
Raw Normal View History

// Copyright 2019 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.
package source
import (
"fmt"
"go/ast"
"golang.org/x/tools/internal/lsp/snippet"
)
// structFieldSnippets calculates the plain and placeholder snippets for struct literal field names.
func (c *completer) structFieldSnippets(label, detail string) (*snippet.Builder, *snippet.Builder) {
if !c.wantStructFieldCompletions() {
return nil, nil
}
// If we are in a deep completion then we can't be completing a field
// name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate
// a snippet).
if c.inDeepCompletion() {
return nil, nil
}
clInfo := c.enclosingCompositeLiteral
// If we are already in a key-value expression, we don't want a snippet.
if clInfo.kv != nil {
return nil, nil
}
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
label = fmt.Sprintf("%s: ", label)
// A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>".
plain.WriteText(label)
plain.WritePlaceholder(nil)
// A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
placeholder.WriteText(label)
placeholder.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(detail)
})
// If the cursor position is on a different line from the literal's opening brace,
// we are in a multiline literal.
if c.view.Session().Cache().FileSet().Position(c.pos).Line != c.view.Session().Cache().FileSet().Position(clInfo.cl.Lbrace).Line {
plain.WriteText(",")
placeholder.WriteText(",")
}
return plain, placeholder
}
// functionCallSnippets calculates the plain and placeholder snippets for function calls.
func (c *completer) functionCallSnippets(name string, params []string) (*snippet.Builder, *snippet.Builder) {
// If we are the left side (i.e. "Fun") part of a call expression,
// we don't want a snippet since there are already parens present.
if len(c.path) > 1 {
switch n := c.path[1].(type) {
case *ast.CallExpr:
internal/lsp: improve completions in go and defer statements Improve the existing fix-the-AST code to better identify the expression following the "go" or "defer" keywords: - Don't slurp the expression start outside the loop since the expression might only have a single token. - Set expression end to the position after the final token, not the position of the final token. - Track curly brace nesting to properly capture an entire "func() {}" expression. - Fix parent node detection to work when BadStmt isn't first statement of block. - Add special case to detect dangling period, e.g. "defer fmt.". We insert phantom "_" selectors like go/parser does to prevent the dangling "." from messing up the AST. - Use reflect in offsetPositions so it updates positions in all node types. This code shouldn't be called often, so I don't think performance is a concern. I also tweaked the function snippet code so it properly expands "defer" and "go" expressions to function calls. It thought it didn't have to expand since there was already a *ast.CallExpr, but the CallExpr was faked by us and the source doesn't actually contain the "()" calling parens. Note that this does not work for nested go/defer statements. For example, completions won't work properly in cases like this: go func() { defer fmt.<> } I think we can fix this as well with some more work. Change-Id: I8f9753fda76909b0e3a83489cdea69ad04ee237a Reviewed-on: https://go-review.googlesource.com/c/tools/+/193997 Reviewed-by: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-09-06 15:22:54 -06:00
// The Lparen != Rparen check detects fudged CallExprs we
// inserted when fixing the AST. In this case, we do still need
// to insert the calling "()" parens.
if n.Fun == c.path[0] && n.Lparen != n.Rparen {
return nil, nil
}
case *ast.SelectorExpr:
if len(c.path) > 2 {
internal/lsp: improve completions in go and defer statements Improve the existing fix-the-AST code to better identify the expression following the "go" or "defer" keywords: - Don't slurp the expression start outside the loop since the expression might only have a single token. - Set expression end to the position after the final token, not the position of the final token. - Track curly brace nesting to properly capture an entire "func() {}" expression. - Fix parent node detection to work when BadStmt isn't first statement of block. - Add special case to detect dangling period, e.g. "defer fmt.". We insert phantom "_" selectors like go/parser does to prevent the dangling "." from messing up the AST. - Use reflect in offsetPositions so it updates positions in all node types. This code shouldn't be called often, so I don't think performance is a concern. I also tweaked the function snippet code so it properly expands "defer" and "go" expressions to function calls. It thought it didn't have to expand since there was already a *ast.CallExpr, but the CallExpr was faked by us and the source doesn't actually contain the "()" calling parens. Note that this does not work for nested go/defer statements. For example, completions won't work properly in cases like this: go func() { defer fmt.<> } I think we can fix this as well with some more work. Change-Id: I8f9753fda76909b0e3a83489cdea69ad04ee237a Reviewed-on: https://go-review.googlesource.com/c/tools/+/193997 Reviewed-by: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-09-06 15:22:54 -06:00
if call, ok := c.path[2].(*ast.CallExpr); ok && call.Fun == c.path[1] && call.Lparen != call.Rparen {
return nil, nil
}
}
}
}
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
label := fmt.Sprintf("%s(", name)
// A plain snippet turns "someFun<>" into "someFunc(<>)".
plain.WriteText(label)
if len(params) > 0 {
plain.WritePlaceholder(nil)
}
plain.WriteText(")")
// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
placeholder.WriteText(label)
for i, p := range params {
if i > 0 {
placeholder.WriteText(", ")
}
placeholder.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(p)
})
}
placeholder.WriteText(")")
return plain, placeholder
}