diff options
Diffstat (limited to 'gopls/internal/lsp/template/symbols.go')
-rw-r--r-- | gopls/internal/lsp/template/symbols.go | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/gopls/internal/lsp/template/symbols.go b/gopls/internal/lsp/template/symbols.go new file mode 100644 index 000000000..24f9604c1 --- /dev/null +++ b/gopls/internal/lsp/template/symbols.go @@ -0,0 +1,230 @@ +// Copyright 2021 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 template + +import ( + "bytes" + "context" + "fmt" + "text/template/parse" + "unicode/utf8" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/source" +) + +// in local coordinates, to be translated to protocol.DocumentSymbol +type symbol struct { + start int // for sorting + length int // in runes (unicode code points) + name string + kind protocol.SymbolKind + vardef bool // is this a variable definition? + // do we care about selection range, or children? + // no children yet, and selection range is the same as range +} + +func (s symbol) String() string { + return fmt.Sprintf("{%d,%d,%s,%s,%v}", s.start, s.length, s.name, s.kind, s.vardef) +} + +// for FieldNode or VariableNode (or ChainNode?) +func (p *Parsed) fields(flds []string, x parse.Node) []symbol { + ans := []symbol{} + // guessing that there are no embedded blanks allowed. The doc is unclear + lookfor := "" + switch x.(type) { + case *parse.FieldNode: + for _, f := range flds { + lookfor += "." + f // quadratic, but probably ok + } + case *parse.VariableNode: + lookfor = flds[0] + for i := 1; i < len(flds); i++ { + lookfor += "." + flds[i] + } + case *parse.ChainNode: // PJW, what are these? + for _, f := range flds { + lookfor += "." + f // quadratic, but probably ok + } + default: + // If these happen they will happen even if gopls is restarted + // and the users does the same thing, so it is better not to panic. + // context.Background() is used because we don't have access + // to any other context. [we could, but it would be complicated] + event.Log(context.Background(), fmt.Sprintf("%T unexpected in fields()", x)) + return nil + } + if len(lookfor) == 0 { + event.Log(context.Background(), fmt.Sprintf("no strings in fields() %#v", x)) + return nil + } + startsAt := int(x.Position()) + ix := bytes.Index(p.buf[startsAt:], []byte(lookfor)) // HasPrefix? PJW? + if ix < 0 || ix > len(lookfor) { // lookfor expected to be at start (or so) + // probably golang.go/#43388, so back up + startsAt -= len(flds[0]) + 1 + ix = bytes.Index(p.buf[startsAt:], []byte(lookfor)) // ix might be 1? PJW + if ix < 0 { + return ans + } + } + at := ix + startsAt + for _, f := range flds { + at += 1 // . + kind := protocol.Method + if f[0] == '$' { + kind = protocol.Variable + } + sym := symbol{name: f, kind: kind, start: at, length: utf8.RuneCount([]byte(f))} + if kind == protocol.Variable && len(p.stack) > 1 { + if pipe, ok := p.stack[len(p.stack)-2].(*parse.PipeNode); ok { + for _, y := range pipe.Decl { + if x == y { + sym.vardef = true + } + } + } + } + ans = append(ans, sym) + at += len(f) + } + return ans +} + +func (p *Parsed) findSymbols() { + if len(p.stack) == 0 { + return + } + n := p.stack[len(p.stack)-1] + pop := func() { + p.stack = p.stack[:len(p.stack)-1] + } + if n == nil { // allowing nil simplifies the code + pop() + return + } + nxt := func(nd parse.Node) { + p.stack = append(p.stack, nd) + p.findSymbols() + } + switch x := n.(type) { + case *parse.ActionNode: + nxt(x.Pipe) + case *parse.BoolNode: + // need to compute the length from the value + msg := fmt.Sprintf("%v", x.True) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(msg), kind: protocol.Boolean}) + case *parse.BranchNode: + nxt(x.Pipe) + nxt(x.List) + nxt(x.ElseList) + case *parse.ChainNode: + p.symbols = append(p.symbols, p.fields(x.Field, x)...) + nxt(x.Node) + case *parse.CommandNode: + for _, a := range x.Args { + nxt(a) + } + //case *parse.CommentNode: // go 1.16 + // log.Printf("implement %d", x.Type()) + case *parse.DotNode: + sym := symbol{name: "dot", kind: protocol.Variable, start: int(x.Pos), length: 1} + p.symbols = append(p.symbols, sym) + case *parse.FieldNode: + p.symbols = append(p.symbols, p.fields(x.Ident, x)...) + case *parse.IdentifierNode: + sym := symbol{name: x.Ident, kind: protocol.Function, start: int(x.Pos), + length: utf8.RuneCount([]byte(x.Ident))} + p.symbols = append(p.symbols, sym) + case *parse.IfNode: + nxt(&x.BranchNode) + case *parse.ListNode: + if x != nil { // wretched typed nils. Node should have an IfNil + for _, nd := range x.Nodes { + nxt(nd) + } + } + case *parse.NilNode: + sym := symbol{name: "nil", kind: protocol.Constant, start: int(x.Pos), length: 3} + p.symbols = append(p.symbols, sym) + case *parse.NumberNode: + // no name; ascii + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(x.Text), kind: protocol.Number}) + case *parse.PipeNode: + if x == nil { // {{template "foo"}} + return + } + for _, d := range x.Decl { + nxt(d) + } + for _, c := range x.Cmds { + nxt(c) + } + case *parse.RangeNode: + nxt(&x.BranchNode) + case *parse.StringNode: + // no name + sz := utf8.RuneCount([]byte(x.Text)) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.String}) + case *parse.TemplateNode: // invoking a template + // x.Pos points to the quote before the name + p.symbols = append(p.symbols, symbol{name: x.Name, kind: protocol.Package, start: int(x.Pos) + 1, + length: utf8.RuneCount([]byte(x.Name))}) + nxt(x.Pipe) + case *parse.TextNode: + if len(x.Text) == 1 && x.Text[0] == '\n' { + break + } + // nothing to report, but build one for hover + sz := utf8.RuneCount([]byte(x.Text)) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.Constant}) + case *parse.VariableNode: + p.symbols = append(p.symbols, p.fields(x.Ident, x)...) + case *parse.WithNode: + nxt(&x.BranchNode) + + } + pop() +} + +// DocumentSymbols returns a hierarchy of the symbols defined in a template file. +// (The hierarchy is flat. SymbolInformation might be better.) +func DocumentSymbols(snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentSymbol, error) { + buf, err := fh.Read() + if err != nil { + return nil, err + } + p := parseBuffer(buf) + if p.ParseErr != nil { + return nil, p.ParseErr + } + var ans []protocol.DocumentSymbol + for _, s := range p.symbols { + if s.kind == protocol.Constant { + continue + } + d := kindStr(s.kind) + if d == "Namespace" { + d = "Template" + } + if s.vardef { + d += "(def)" + } else { + d += "(use)" + } + r := p.Range(s.start, s.length) + y := protocol.DocumentSymbol{ + Name: s.name, + Detail: d, + Kind: s.kind, + Range: r, + SelectionRange: r, // or should this be the entire {{...}}? + } + ans = append(ans, y) + } + return ans, nil +} |