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  }