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 }