зеркало из https://github.com/golang/tools.git
232 строки
6.4 KiB
Go
232 строки
6.4 KiB
Go
// 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
|
|
}
|