github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/template/implementations.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 "strconv" 12 "time" 13 14 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 15 "github.com/jhump/golang-x-tools/internal/lsp/source" 16 "github.com/jhump/golang-x-tools/internal/span" 17 ) 18 19 // line number (1-based) and message 20 var errRe = regexp.MustCompile(`template.*:(\d+): (.*)`) 21 22 // Diagnose returns parse errors. There is only one. 23 // The errors are not always helpful. For instance { {end}} 24 // will likely point to the end of the file. 25 func Diagnose(f source.VersionedFileHandle) []*source.Diagnostic { 26 // no need for skipTemplate check, as Diagnose is called on the 27 // snapshot's template files 28 buf, err := f.Read() 29 if err != nil { 30 // Is a Diagnostic with no Range useful? event.Error also? 31 msg := fmt.Sprintf("failed to read %s (%v)", f.URI().Filename(), err) 32 d := source.Diagnostic{Message: msg, Severity: protocol.SeverityError, URI: f.URI(), 33 Source: source.TemplateError} 34 return []*source.Diagnostic{&d} 35 } 36 p := parseBuffer(buf) 37 if p.ParseErr == nil { 38 return nil 39 } 40 unknownError := func(msg string) []*source.Diagnostic { 41 s := fmt.Sprintf("malformed template error %q: %s", p.ParseErr.Error(), msg) 42 d := source.Diagnostic{ 43 Message: s, Severity: protocol.SeverityError, Range: p.Range(p.nls[0], 1), 44 URI: f.URI(), Source: source.TemplateError} 45 return []*source.Diagnostic{&d} 46 } 47 // errors look like `template: :40: unexpected "}" in operand` 48 // so the string needs to be parsed 49 matches := errRe.FindStringSubmatch(p.ParseErr.Error()) 50 if len(matches) != 3 { 51 msg := fmt.Sprintf("expected 3 matches, got %d (%v)", len(matches), matches) 52 return unknownError(msg) 53 } 54 lineno, err := strconv.Atoi(matches[1]) 55 if err != nil { 56 msg := fmt.Sprintf("couldn't convert %q to int, %v", matches[1], err) 57 return unknownError(msg) 58 } 59 msg := matches[2] 60 d := source.Diagnostic{Message: msg, Severity: protocol.SeverityError, 61 Source: source.TemplateError} 62 start := p.nls[lineno-1] 63 if lineno < len(p.nls) { 64 size := p.nls[lineno] - start 65 d.Range = p.Range(start, size) 66 } else { 67 d.Range = p.Range(start, 1) 68 } 69 return []*source.Diagnostic{&d} 70 } 71 72 // Definition finds the definitions of the symbol at loc. It 73 // does not understand scoping (if any) in templates. This code is 74 // for defintions, type definitions, and implementations. 75 // Results only for variables and templates. 76 func Definition(snapshot source.Snapshot, fh source.VersionedFileHandle, loc protocol.Position) ([]protocol.Location, error) { 77 x, _, err := symAtPosition(fh, loc) 78 if err != nil { 79 return nil, err 80 } 81 sym := x.name 82 ans := []protocol.Location{} 83 // PJW: this is probably a pattern to abstract 84 a := New(snapshot.Templates()) 85 for k, p := range a.files { 86 for _, s := range p.symbols { 87 if !s.vardef || s.name != sym { 88 continue 89 } 90 ans = append(ans, protocol.Location{URI: protocol.DocumentURI(k), Range: p.Range(s.start, s.length)}) 91 } 92 } 93 return ans, nil 94 } 95 96 func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) { 97 sym, p, err := symAtPosition(fh, position) 98 if sym == nil || err != nil { 99 return nil, err 100 } 101 ans := protocol.Hover{Range: p.Range(sym.start, sym.length), Contents: protocol.MarkupContent{Kind: protocol.Markdown}} 102 switch sym.kind { 103 case protocol.Function: 104 ans.Contents.Value = fmt.Sprintf("function: %s", sym.name) 105 case protocol.Variable: 106 ans.Contents.Value = fmt.Sprintf("variable: %s", sym.name) 107 case protocol.Constant: 108 ans.Contents.Value = fmt.Sprintf("constant %s", sym.name) 109 case protocol.Method: // field or method 110 ans.Contents.Value = fmt.Sprintf("%s: field or method", sym.name) 111 case protocol.Package: // template use, template def (PJW: do we want two?) 112 ans.Contents.Value = fmt.Sprintf("template %s\n(add definition)", sym.name) 113 case protocol.Namespace: 114 ans.Contents.Value = fmt.Sprintf("template %s defined", sym.name) 115 case protocol.Number: 116 ans.Contents.Value = "number" 117 case protocol.String: 118 ans.Contents.Value = "string" 119 case protocol.Boolean: 120 ans.Contents.Value = "boolean" 121 default: 122 ans.Contents.Value = fmt.Sprintf("oops, sym=%#v", sym) 123 } 124 return &ans, nil 125 } 126 127 func References(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, params *protocol.ReferenceParams) ([]protocol.Location, error) { 128 sym, _, err := symAtPosition(fh, params.Position) 129 if sym == nil || err != nil || sym.name == "" { 130 return nil, err 131 } 132 ans := []protocol.Location{} 133 134 a := New(snapshot.Templates()) 135 for k, p := range a.files { 136 for _, s := range p.symbols { 137 if s.name != sym.name { 138 continue 139 } 140 if s.vardef && !params.Context.IncludeDeclaration { 141 continue 142 } 143 ans = append(ans, protocol.Location{URI: protocol.DocumentURI(k), Range: p.Range(s.start, s.length)}) 144 } 145 } 146 // do these need to be sorted? (a.files is a map) 147 return ans, nil 148 } 149 150 func SemanticTokens(ctx context.Context, snapshot source.Snapshot, spn span.URI, add func(line, start, len uint32), d func() []uint32) (*protocol.SemanticTokens, error) { 151 fh, err := snapshot.GetFile(ctx, spn) 152 if err != nil { 153 return nil, err 154 } 155 buf, err := fh.Read() 156 if err != nil { 157 return nil, err 158 } 159 p := parseBuffer(buf) 160 161 for _, t := range p.Tokens() { 162 if t.Multiline { 163 la, ca := p.LineCol(t.Start) 164 lb, cb := p.LineCol(t.End) 165 add(la, ca, p.RuneCount(la, ca, 0)) 166 for l := la + 1; l < lb; l++ { 167 add(l, 0, p.RuneCount(l, 0, 0)) 168 } 169 add(lb, 0, p.RuneCount(lb, 0, cb)) 170 continue 171 } 172 sz, err := p.TokenSize(t) 173 if err != nil { 174 return nil, err 175 } 176 line, col := p.LineCol(t.Start) 177 add(line, col, uint32(sz)) 178 } 179 data := d() 180 ans := &protocol.SemanticTokens{ 181 Data: data, 182 // for small cache, some day. for now, the LSP client ignores this 183 // (that is, when the LSP client starts returning these, we can cache) 184 ResultID: fmt.Sprintf("%v", time.Now()), 185 } 186 return ans, nil 187 } 188 189 // still need to do rename, etc