github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/template/implementations.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  	"context"
     9  	"fmt"
    10  	"regexp"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    15  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    16  	"github.com/jhump/golang-x-tools/internal/span"
    17  )
    18  
    19  // line number (1-based) and message
    20  var errRe = regexp.MustCompile(`template.*:(\d+): (.*)`)
    21  
    22  // Diagnose returns parse errors. There is only one.
    23  // The errors are not always helpful. For instance { {end}}
    24  // will likely point to the end of the file.
    25  func Diagnose(f source.VersionedFileHandle) []*source.Diagnostic {
    26  	// no need for skipTemplate check, as Diagnose is called on the
    27  	// snapshot's template files
    28  	buf, err := f.Read()
    29  	if err != nil {
    30  		// Is a Diagnostic with no Range useful? event.Error also?
    31  		msg := fmt.Sprintf("failed to read %s (%v)", f.URI().Filename(), err)
    32  		d := source.Diagnostic{Message: msg, Severity: protocol.SeverityError, URI: f.URI(),
    33  			Source: source.TemplateError}
    34  		return []*source.Diagnostic{&d}
    35  	}
    36  	p := parseBuffer(buf)
    37  	if p.ParseErr == nil {
    38  		return nil
    39  	}
    40  	unknownError := func(msg string) []*source.Diagnostic {
    41  		s := fmt.Sprintf("malformed template error %q: %s", p.ParseErr.Error(), msg)
    42  		d := source.Diagnostic{
    43  			Message: s, Severity: protocol.SeverityError, Range: p.Range(p.nls[0], 1),
    44  			URI: f.URI(), Source: source.TemplateError}
    45  		return []*source.Diagnostic{&d}
    46  	}
    47  	// errors look like `template: :40: unexpected "}" in operand`
    48  	// so the string needs to be parsed
    49  	matches := errRe.FindStringSubmatch(p.ParseErr.Error())
    50  	if len(matches) != 3 {
    51  		msg := fmt.Sprintf("expected 3 matches, got %d (%v)", len(matches), matches)
    52  		return unknownError(msg)
    53  	}
    54  	lineno, err := strconv.Atoi(matches[1])
    55  	if err != nil {
    56  		msg := fmt.Sprintf("couldn't convert %q to int, %v", matches[1], err)
    57  		return unknownError(msg)
    58  	}
    59  	msg := matches[2]
    60  	d := source.Diagnostic{Message: msg, Severity: protocol.SeverityError,
    61  		Source: source.TemplateError}
    62  	start := p.nls[lineno-1]
    63  	if lineno < len(p.nls) {
    64  		size := p.nls[lineno] - start
    65  		d.Range = p.Range(start, size)
    66  	} else {
    67  		d.Range = p.Range(start, 1)
    68  	}
    69  	return []*source.Diagnostic{&d}
    70  }
    71  
    72  // Definition finds the definitions of the symbol at loc. It
    73  // does not understand scoping (if any) in templates. This code is
    74  // for defintions, type definitions, and implementations.
    75  // Results only for variables and templates.
    76  func Definition(snapshot source.Snapshot, fh source.VersionedFileHandle, loc protocol.Position) ([]protocol.Location, error) {
    77  	x, _, err := symAtPosition(fh, loc)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	sym := x.name
    82  	ans := []protocol.Location{}
    83  	// PJW: this is probably a pattern to abstract
    84  	a := New(snapshot.Templates())
    85  	for k, p := range a.files {
    86  		for _, s := range p.symbols {
    87  			if !s.vardef || s.name != sym {
    88  				continue
    89  			}
    90  			ans = append(ans, protocol.Location{URI: protocol.DocumentURI(k), Range: p.Range(s.start, s.length)})
    91  		}
    92  	}
    93  	return ans, nil
    94  }
    95  
    96  func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
    97  	sym, p, err := symAtPosition(fh, position)
    98  	if sym == nil || err != nil {
    99  		return nil, err
   100  	}
   101  	ans := protocol.Hover{Range: p.Range(sym.start, sym.length), Contents: protocol.MarkupContent{Kind: protocol.Markdown}}
   102  	switch sym.kind {
   103  	case protocol.Function:
   104  		ans.Contents.Value = fmt.Sprintf("function: %s", sym.name)
   105  	case protocol.Variable:
   106  		ans.Contents.Value = fmt.Sprintf("variable: %s", sym.name)
   107  	case protocol.Constant:
   108  		ans.Contents.Value = fmt.Sprintf("constant %s", sym.name)
   109  	case protocol.Method: // field or method
   110  		ans.Contents.Value = fmt.Sprintf("%s: field or method", sym.name)
   111  	case protocol.Package: // template use, template def (PJW: do we want two?)
   112  		ans.Contents.Value = fmt.Sprintf("template %s\n(add definition)", sym.name)
   113  	case protocol.Namespace:
   114  		ans.Contents.Value = fmt.Sprintf("template %s defined", sym.name)
   115  	case protocol.Number:
   116  		ans.Contents.Value = "number"
   117  	case protocol.String:
   118  		ans.Contents.Value = "string"
   119  	case protocol.Boolean:
   120  		ans.Contents.Value = "boolean"
   121  	default:
   122  		ans.Contents.Value = fmt.Sprintf("oops, sym=%#v", sym)
   123  	}
   124  	return &ans, nil
   125  }
   126  
   127  func References(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, params *protocol.ReferenceParams) ([]protocol.Location, error) {
   128  	sym, _, err := symAtPosition(fh, params.Position)
   129  	if sym == nil || err != nil || sym.name == "" {
   130  		return nil, err
   131  	}
   132  	ans := []protocol.Location{}
   133  
   134  	a := New(snapshot.Templates())
   135  	for k, p := range a.files {
   136  		for _, s := range p.symbols {
   137  			if s.name != sym.name {
   138  				continue
   139  			}
   140  			if s.vardef && !params.Context.IncludeDeclaration {
   141  				continue
   142  			}
   143  			ans = append(ans, protocol.Location{URI: protocol.DocumentURI(k), Range: p.Range(s.start, s.length)})
   144  		}
   145  	}
   146  	// do these need to be sorted? (a.files is a map)
   147  	return ans, nil
   148  }
   149  
   150  func SemanticTokens(ctx context.Context, snapshot source.Snapshot, spn span.URI, add func(line, start, len uint32), d func() []uint32) (*protocol.SemanticTokens, error) {
   151  	fh, err := snapshot.GetFile(ctx, spn)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	buf, err := fh.Read()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	p := parseBuffer(buf)
   160  
   161  	for _, t := range p.Tokens() {
   162  		if t.Multiline {
   163  			la, ca := p.LineCol(t.Start)
   164  			lb, cb := p.LineCol(t.End)
   165  			add(la, ca, p.RuneCount(la, ca, 0))
   166  			for l := la + 1; l < lb; l++ {
   167  				add(l, 0, p.RuneCount(l, 0, 0))
   168  			}
   169  			add(lb, 0, p.RuneCount(lb, 0, cb))
   170  			continue
   171  		}
   172  		sz, err := p.TokenSize(t)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		line, col := p.LineCol(t.Start)
   177  		add(line, col, uint32(sz))
   178  	}
   179  	data := d()
   180  	ans := &protocol.SemanticTokens{
   181  		Data: data,
   182  		// for small cache, some day. for now, the LSP client ignores this
   183  		// (that is, when the LSP client starts returning these, we can cache)
   184  		ResultID: fmt.Sprintf("%v", time.Now()),
   185  	}
   186  	return ans, nil
   187  }
   188  
   189  // still need to do rename, etc