| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package template |
| |
| import ( |
| "context" |
| "fmt" |
| "regexp" |
| |
| "golang.org/x/tools/internal/lsp/protocol" |
| "golang.org/x/tools/internal/lsp/source" |
| ) |
| |
| func Highlight(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, loc protocol.Position) ([]protocol.DocumentHighlight, error) { |
| if skipTemplates(snapshot) { |
| return nil, nil |
| } |
| buf, err := fh.Read() |
| if err != nil { |
| return nil, err |
| } |
| p := parseBuffer(buf) |
| pos := p.FromPosition(loc) |
| var ans []protocol.DocumentHighlight |
| if p.ParseErr == nil { |
| for _, s := range p.symbols { |
| if s.start <= pos && pos < s.start+s.length { |
| return markSymbols(p, s) |
| } |
| } |
| } |
| // these tokens exist whether or not there was a parse error |
| // (symbols require a successful parse) |
| for _, tok := range p.tokens { |
| if tok.Start <= pos && pos < tok.End { |
| wordAt := findWordAt(p, pos) |
| if len(wordAt) > 0 { |
| return markWordInToken(p, wordAt) |
| } |
| } |
| } |
| // find the 'word' at pos, etc: someday |
| // until then we get the default action, which doesn't respect word boundaries |
| return ans, nil |
| } |
| |
| func markSymbols(p *Parsed, sym symbol) ([]protocol.DocumentHighlight, error) { |
| var ans []protocol.DocumentHighlight |
| for _, s := range p.symbols { |
| if s.name == sym.name { |
| kind := protocol.Read |
| if s.vardef { |
| kind = protocol.Write |
| } |
| ans = append(ans, protocol.DocumentHighlight{ |
| Range: p.Range(s.start, s.length), |
| Kind: kind, |
| }) |
| } |
| } |
| return ans, nil |
| } |
| |
| // A token is {{...}}, and this marks words in the token that equal the give word |
| func markWordInToken(p *Parsed, wordAt string) ([]protocol.DocumentHighlight, error) { |
| var ans []protocol.DocumentHighlight |
| pat, err := regexp.Compile(fmt.Sprintf(`\b%s\b`, wordAt)) |
| if err != nil { |
| return nil, fmt.Errorf("%q: unmatchable word (%v)", wordAt, err) |
| } |
| for _, tok := range p.tokens { |
| got := pat.FindAllIndex(p.buf[tok.Start:tok.End], -1) |
| for i := 0; i < len(got); i++ { |
| ans = append(ans, protocol.DocumentHighlight{ |
| Range: p.Range(got[i][0], got[i][1]-got[i][0]), |
| Kind: protocol.Text, |
| }) |
| } |
| } |
| return ans, nil |
| } |
| |
| var wordRe = regexp.MustCompile(`[$]?\w+$`) |
| var moreRe = regexp.MustCompile(`^[$]?\w+`) |
| |
| // findWordAt finds the word the cursor is in (meaning in or just before) |
| func findWordAt(p *Parsed, pos int) string { |
| if pos >= len(p.buf) { |
| return "" // can't happen, as we are called with pos < tok.End |
| } |
| after := moreRe.Find(p.buf[pos:]) |
| if len(after) == 0 { |
| return "" // end of the word |
| } |
| got := wordRe.Find(p.buf[:pos+len(after)]) |
| return string(got) |
| } |