golang.org/x/tools/gopls@v0.15.3/internal/template/symbols.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package template 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "text/template/parse" 12 "unicode/utf8" 13 14 "golang.org/x/tools/gopls/internal/cache" 15 "golang.org/x/tools/gopls/internal/file" 16 "golang.org/x/tools/gopls/internal/protocol" 17 "golang.org/x/tools/internal/event" 18 ) 19 20 // in local coordinates, to be translated to protocol.DocumentSymbol 21 type symbol struct { 22 start int // for sorting 23 length int // in runes (unicode code points) 24 name string 25 kind protocol.SymbolKind 26 vardef bool // is this a variable definition? 27 // do we care about selection range, or children? 28 // no children yet, and selection range is the same as range 29 } 30 31 func (s symbol) String() string { 32 return fmt.Sprintf("{%d,%d,%s,%s,%v}", s.start, s.length, s.name, s.kind, s.vardef) 33 } 34 35 // for FieldNode or VariableNode (or ChainNode?) 36 func (p *Parsed) fields(flds []string, x parse.Node) []symbol { 37 ans := []symbol{} 38 // guessing that there are no embedded blanks allowed. The doc is unclear 39 lookfor := "" 40 switch x.(type) { 41 case *parse.FieldNode: 42 for _, f := range flds { 43 lookfor += "." + f // quadratic, but probably ok 44 } 45 case *parse.VariableNode: 46 lookfor = flds[0] 47 for i := 1; i < len(flds); i++ { 48 lookfor += "." + flds[i] 49 } 50 case *parse.ChainNode: // PJW, what are these? 51 for _, f := range flds { 52 lookfor += "." + f // quadratic, but probably ok 53 } 54 default: 55 // If these happen they will happen even if gopls is restarted 56 // and the users does the same thing, so it is better not to panic. 57 // context.Background() is used because we don't have access 58 // to any other context. [we could, but it would be complicated] 59 event.Log(context.Background(), fmt.Sprintf("%T unexpected in fields()", x)) 60 return nil 61 } 62 if len(lookfor) == 0 { 63 event.Log(context.Background(), fmt.Sprintf("no strings in fields() %#v", x)) 64 return nil 65 } 66 startsAt := int(x.Position()) 67 ix := bytes.Index(p.buf[startsAt:], []byte(lookfor)) // HasPrefix? PJW? 68 if ix < 0 || ix > len(lookfor) { // lookfor expected to be at start (or so) 69 // probably golang.go/#43388, so back up 70 startsAt -= len(flds[0]) + 1 71 ix = bytes.Index(p.buf[startsAt:], []byte(lookfor)) // ix might be 1? PJW 72 if ix < 0 { 73 return ans 74 } 75 } 76 at := ix + startsAt 77 for _, f := range flds { 78 at += 1 // . 79 kind := protocol.Method 80 if f[0] == '$' { 81 kind = protocol.Variable 82 } 83 sym := symbol{name: f, kind: kind, start: at, length: utf8.RuneCount([]byte(f))} 84 if kind == protocol.Variable && len(p.stack) > 1 { 85 if pipe, ok := p.stack[len(p.stack)-2].(*parse.PipeNode); ok { 86 for _, y := range pipe.Decl { 87 if x == y { 88 sym.vardef = true 89 } 90 } 91 } 92 } 93 ans = append(ans, sym) 94 at += len(f) 95 } 96 return ans 97 } 98 99 func (p *Parsed) findSymbols() { 100 if len(p.stack) == 0 { 101 return 102 } 103 n := p.stack[len(p.stack)-1] 104 pop := func() { 105 p.stack = p.stack[:len(p.stack)-1] 106 } 107 if n == nil { // allowing nil simplifies the code 108 pop() 109 return 110 } 111 nxt := func(nd parse.Node) { 112 p.stack = append(p.stack, nd) 113 p.findSymbols() 114 } 115 switch x := n.(type) { 116 case *parse.ActionNode: 117 nxt(x.Pipe) 118 case *parse.BoolNode: 119 // need to compute the length from the value 120 msg := fmt.Sprintf("%v", x.True) 121 p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(msg), kind: protocol.Boolean}) 122 case *parse.BranchNode: 123 nxt(x.Pipe) 124 nxt(x.List) 125 nxt(x.ElseList) 126 case *parse.ChainNode: 127 p.symbols = append(p.symbols, p.fields(x.Field, x)...) 128 nxt(x.Node) 129 case *parse.CommandNode: 130 for _, a := range x.Args { 131 nxt(a) 132 } 133 //case *parse.CommentNode: // go 1.16 134 // log.Printf("implement %d", x.Type()) 135 case *parse.DotNode: 136 sym := symbol{name: "dot", kind: protocol.Variable, start: int(x.Pos), length: 1} 137 p.symbols = append(p.symbols, sym) 138 case *parse.FieldNode: 139 p.symbols = append(p.symbols, p.fields(x.Ident, x)...) 140 case *parse.IdentifierNode: 141 sym := symbol{name: x.Ident, kind: protocol.Function, start: int(x.Pos), 142 length: utf8.RuneCount([]byte(x.Ident))} 143 p.symbols = append(p.symbols, sym) 144 case *parse.IfNode: 145 nxt(&x.BranchNode) 146 case *parse.ListNode: 147 if x != nil { // wretched typed nils. Node should have an IfNil 148 for _, nd := range x.Nodes { 149 nxt(nd) 150 } 151 } 152 case *parse.NilNode: 153 sym := symbol{name: "nil", kind: protocol.Constant, start: int(x.Pos), length: 3} 154 p.symbols = append(p.symbols, sym) 155 case *parse.NumberNode: 156 // no name; ascii 157 p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(x.Text), kind: protocol.Number}) 158 case *parse.PipeNode: 159 if x == nil { // {{template "foo"}} 160 return 161 } 162 for _, d := range x.Decl { 163 nxt(d) 164 } 165 for _, c := range x.Cmds { 166 nxt(c) 167 } 168 case *parse.RangeNode: 169 nxt(&x.BranchNode) 170 case *parse.StringNode: 171 // no name 172 sz := utf8.RuneCount([]byte(x.Text)) 173 p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.String}) 174 case *parse.TemplateNode: // invoking a template 175 // x.Pos points to the quote before the name 176 p.symbols = append(p.symbols, symbol{name: x.Name, kind: protocol.Package, start: int(x.Pos) + 1, 177 length: utf8.RuneCount([]byte(x.Name))}) 178 nxt(x.Pipe) 179 case *parse.TextNode: 180 if len(x.Text) == 1 && x.Text[0] == '\n' { 181 break 182 } 183 // nothing to report, but build one for hover 184 sz := utf8.RuneCount(x.Text) 185 p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.Constant}) 186 case *parse.VariableNode: 187 p.symbols = append(p.symbols, p.fields(x.Ident, x)...) 188 case *parse.WithNode: 189 nxt(&x.BranchNode) 190 191 } 192 pop() 193 } 194 195 // DocumentSymbols returns a hierarchy of the symbols defined in a template file. 196 // (The hierarchy is flat. SymbolInformation might be better.) 197 func DocumentSymbols(snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentSymbol, error) { 198 buf, err := fh.Content() 199 if err != nil { 200 return nil, err 201 } 202 p := parseBuffer(buf) 203 if p.ParseErr != nil { 204 return nil, p.ParseErr 205 } 206 var ans []protocol.DocumentSymbol 207 for _, s := range p.symbols { 208 if s.kind == protocol.Constant { 209 continue 210 } 211 d := kindStr(s.kind) 212 if d == "Namespace" { 213 d = "Template" 214 } 215 if s.vardef { 216 d += "(def)" 217 } else { 218 d += "(use)" 219 } 220 r := p.Range(s.start, s.length) 221 y := protocol.DocumentSymbol{ 222 Name: s.name, 223 Detail: d, 224 Kind: s.kind, 225 Range: r, 226 SelectionRange: r, // or should this be the entire {{...}}? 227 } 228 ans = append(ans, y) 229 } 230 return ans, nil 231 }