github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/template/highlight.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  
    12  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    13  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    14  )
    15  
    16  func Highlight(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, loc protocol.Position) ([]protocol.DocumentHighlight, error) {
    17  	buf, err := fh.Read()
    18  	if err != nil {
    19  		return nil, err
    20  	}
    21  	p := parseBuffer(buf)
    22  	pos := p.FromPosition(loc)
    23  	var ans []protocol.DocumentHighlight
    24  	if p.ParseErr == nil {
    25  		for _, s := range p.symbols {
    26  			if s.start <= pos && pos < s.start+s.length {
    27  				return markSymbols(p, s)
    28  			}
    29  		}
    30  	}
    31  	// these tokens exist whether or not there was a parse error
    32  	// (symbols require a successful parse)
    33  	for _, tok := range p.tokens {
    34  		if tok.Start <= pos && pos < tok.End {
    35  			wordAt := findWordAt(p, pos)
    36  			if len(wordAt) > 0 {
    37  				return markWordInToken(p, wordAt)
    38  			}
    39  		}
    40  	}
    41  	// find the 'word' at pos, etc: someday
    42  	// until then we get the default action, which doesn't respect word boundaries
    43  	return ans, nil
    44  }
    45  
    46  func markSymbols(p *Parsed, sym symbol) ([]protocol.DocumentHighlight, error) {
    47  	var ans []protocol.DocumentHighlight
    48  	for _, s := range p.symbols {
    49  		if s.name == sym.name {
    50  			kind := protocol.Read
    51  			if s.vardef {
    52  				kind = protocol.Write
    53  			}
    54  			ans = append(ans, protocol.DocumentHighlight{
    55  				Range: p.Range(s.start, s.length),
    56  				Kind:  kind,
    57  			})
    58  		}
    59  	}
    60  	return ans, nil
    61  }
    62  
    63  // A token is {{...}}, and this marks words in the token that equal the give word
    64  func markWordInToken(p *Parsed, wordAt string) ([]protocol.DocumentHighlight, error) {
    65  	var ans []protocol.DocumentHighlight
    66  	pat, err := regexp.Compile(fmt.Sprintf(`\b%s\b`, wordAt))
    67  	if err != nil {
    68  		return nil, fmt.Errorf("%q: unmatchable word (%v)", wordAt, err)
    69  	}
    70  	for _, tok := range p.tokens {
    71  		got := pat.FindAllIndex(p.buf[tok.Start:tok.End], -1)
    72  		for i := 0; i < len(got); i++ {
    73  			ans = append(ans, protocol.DocumentHighlight{
    74  				Range: p.Range(got[i][0], got[i][1]-got[i][0]),
    75  				Kind:  protocol.Text,
    76  			})
    77  		}
    78  	}
    79  	return ans, nil
    80  }
    81  
    82  var wordRe = regexp.MustCompile(`[$]?\w+$`)
    83  var moreRe = regexp.MustCompile(`^[$]?\w+`)
    84  
    85  // findWordAt finds the word the cursor is in (meaning in or just before)
    86  func findWordAt(p *Parsed, pos int) string {
    87  	if pos >= len(p.buf) {
    88  		return "" // can't happen, as we are called with pos < tok.End
    89  	}
    90  	after := moreRe.Find(p.buf[pos:])
    91  	if len(after) == 0 {
    92  		return "" // end of the word
    93  	}
    94  	got := wordRe.Find(p.buf[:pos+len(after)])
    95  	return string(got)
    96  }