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  }