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 }