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