.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / cmd / semantictokens.go
1 // Copyright 2020 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 cmd
6
7 import (
8         "bytes"
9         "context"
10         "flag"
11         "fmt"
12         "go/parser"
13         "go/token"
14         "io/ioutil"
15         "log"
16         "os"
17         "runtime"
18         "unicode/utf8"
19
20         "golang.org/x/tools/internal/lsp"
21         "golang.org/x/tools/internal/lsp/protocol"
22         "golang.org/x/tools/internal/lsp/source"
23         "golang.org/x/tools/internal/span"
24 )
25
26 // generate semantic tokens and interpolate them in the file
27
28 // The output is the input file decorated with comments showing the
29 // syntactic tokens. The comments are stylized:
30 //   /*<arrow><length>,<token type>,[<modifiers]*/
31 // For most occurrences, the comment comes just before the token it
32 // describes, and arrow is a right arrow. If the token is inside a string
33 // the comment comes just after the string, and the arrow is a left arrow.
34 // <length> is the length of the token in runes, <token type> is one
35 // of the supported semantic token types, and <modifiers. is a
36 // (possibly empty) list of token type modifiers.
37
38 // There are 3 coordinate systems for lines and character offsets in lines
39 // LSP (what's returned from semanticTokens()):
40 //    0-based: the first line is line 0, the first character of a line
41 //      is character 0, and characters are counted as UTF-16 code points
42 // gopls (and Go error messages):
43 //    1-based: the first line is line1, the first chararcter of a line
44 //      is character 0, and characters are counted as bytes
45 // internal (as used in marks, and lines:=bytes.Split(buf, '\n'))
46 //    0-based: lines and character positions are 1 less than in
47 //      the gopls coordinate system
48
49 type semtok struct {
50         app *Application
51 }
52
53 var colmap *protocol.ColumnMapper
54
55 func (c *semtok) Name() string      { return "semtok" }
56 func (c *semtok) Usage() string     { return "<filename>" }
57 func (c *semtok) ShortHelp() string { return "show semantic tokens for the specified file" }
58 func (c *semtok) DetailedHelp(f *flag.FlagSet) {
59         for i := 1; ; i++ {
60                 _, f, l, ok := runtime.Caller(i)
61                 if !ok {
62                         break
63                 }
64                 log.Printf("%d: %s:%d", i, f, l)
65         }
66         fmt.Fprint(f.Output(), `
67 Example: show the semantic tokens for this file:
68
69   $ gopls semtok internal/lsp/cmd/semtok.go
70 `)
71         f.PrintDefaults()
72 }
73
74 // Run performs the semtok on the files specified by args and prints the
75 // results to stdout in the format described above.
76 func (c *semtok) Run(ctx context.Context, args ...string) error {
77         if len(args) != 1 {
78                 return fmt.Errorf("expected one file name, got %d", len(args))
79         }
80         // perhaps simpler if app had just had a FlagSet member
81         origOptions := c.app.options
82         c.app.options = func(opts *source.Options) {
83                 origOptions(opts)
84                 opts.SemanticTokens = true
85         }
86         conn, err := c.app.connect(ctx)
87         if err != nil {
88                 return err
89         }
90         defer conn.terminate(ctx)
91         uri := span.URIFromPath(args[0])
92         file := conn.AddFile(ctx, uri)
93         if file.err != nil {
94                 return file.err
95         }
96
97         resp, err := conn.semanticTokens(ctx, uri)
98         if err != nil {
99                 return err
100         }
101         buf, err := ioutil.ReadFile(args[0])
102         if err != nil {
103                 log.Fatal(err)
104         }
105         fset := token.NewFileSet()
106         f, err := parser.ParseFile(fset, args[0], buf, 0)
107         if err != nil {
108                 log.Printf("parsing %s failed %v", args[0], err)
109                 return err
110         }
111         tok := fset.File(f.Pos())
112         if tok == nil {
113                 // can't happen; just parsed this file
114                 return fmt.Errorf("can't find %s in fset", args[0])
115         }
116         tc := span.NewContentConverter(args[0], buf)
117         colmap = &protocol.ColumnMapper{
118                 URI:       span.URI(args[0]),
119                 Content:   buf,
120                 Converter: tc,
121         }
122         err = decorate(file.uri.Filename(), resp.Data)
123         if err != nil {
124                 return err
125         }
126         return nil
127 }
128
129 type mark struct {
130         line, offset int // 1-based, from RangeSpan
131         len          int // bytes, not runes
132         typ          string
133         mods         []string
134 }
135
136 // prefixes for semantic token comments
137 const (
138         SemanticLeft  = "/*⇐"
139         SemanticRight = "/*⇒"
140 )
141
142 func markLine(m mark, lines [][]byte) {
143         l := lines[m.line-1] // mx is 1-based
144         length := utf8.RuneCount(l[m.offset-1 : m.offset-1+m.len])
145         splitAt := m.offset - 1
146         insert := ""
147         if m.typ == "namespace" && m.offset-1+m.len < len(l) && l[m.offset-1+m.len] == '"' {
148                 // it is the last component of an import spec
149                 // cannot put a comment inside a string
150                 insert = fmt.Sprintf("%s%d,namespace,[]*/", SemanticLeft, length)
151                 splitAt = m.offset + m.len
152         } else {
153                 // be careful not to generate //*
154                 spacer := ""
155                 if splitAt-1 >= 0 && l[splitAt-1] == '/' {
156                         spacer = " "
157                 }
158                 insert = fmt.Sprintf("%s%s%d,%s,%v*/", spacer, SemanticRight, length, m.typ, m.mods)
159         }
160         x := append([]byte(insert), l[splitAt:]...)
161         l = append(l[:splitAt], x...)
162         lines[m.line-1] = l
163 }
164
165 func decorate(file string, result []uint32) error {
166         buf, err := ioutil.ReadFile(file)
167         if err != nil {
168                 return err
169         }
170         marks := newMarks(result)
171         if len(marks) == 0 {
172                 return nil
173         }
174         lines := bytes.Split(buf, []byte{'\n'})
175         for i := len(marks) - 1; i >= 0; i-- {
176                 mx := marks[i]
177                 markLine(mx, lines)
178         }
179         os.Stdout.Write(bytes.Join(lines, []byte{'\n'}))
180         return nil
181 }
182
183 func newMarks(d []uint32) []mark {
184         ans := []mark{}
185         // the following two loops could be merged, at the cost
186         // of making the logic slightly more complicated to understand
187         // first, convert from deltas to absolute, in LSP coordinates
188         lspLine := make([]uint32, len(d)/5)
189         lspChar := make([]uint32, len(d)/5)
190         var line, char uint32
191         for i := 0; 5*i < len(d); i++ {
192                 lspLine[i] = line + d[5*i+0]
193                 if d[5*i+0] > 0 {
194                         char = 0
195                 }
196                 lspChar[i] = char + d[5*i+1]
197                 char = lspChar[i]
198                 line = lspLine[i]
199         }
200         // second, convert to gopls coordinates
201         for i := 0; 5*i < len(d); i++ {
202                 pr := protocol.Range{
203                         Start: protocol.Position{
204                                 Line:      lspLine[i],
205                                 Character: lspChar[i],
206                         },
207                         End: protocol.Position{
208                                 Line:      lspLine[i],
209                                 Character: lspChar[i] + d[5*i+2],
210                         },
211                 }
212                 spn, err := colmap.RangeSpan(pr)
213                 if err != nil {
214                         log.Fatal(err)
215                 }
216                 m := mark{
217                         line:   spn.Start().Line(),
218                         offset: spn.Start().Column(),
219                         len:    spn.End().Column() - spn.Start().Column(),
220                         typ:    lsp.SemType(int(d[5*i+3])),
221                         mods:   lsp.SemMods(int(d[5*i+4])),
222                 }
223                 ans = append(ans, m)
224         }
225         return ans
226 }