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