|  | // 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/gopls/internal/cache" | 
|  | "golang.org/x/tools/gopls/internal/file" | 
|  | "golang.org/x/tools/gopls/internal/protocol" | 
|  | "golang.org/x/tools/internal/event" | 
|  | ) | 
|  |  | 
|  | // 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(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 *cache.Snapshot, fh file.Handle) ([]protocol.DocumentSymbol, error) { | 
|  | buf, err := fh.Content() | 
|  | 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 | 
|  | } |