internal/lsp/semantic.go: remove global variable
instead copy the client's token types and modifiers into the options,
and reconstruct the inverse map once per LSP request.
Change-Id: I4ae5b56a31060882a06b1b6bbc65d26e4085e058
Reviewed-on: https://go-review.googlesource.com/c/tools/+/264320
Run-TryBot: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Peter Weinberger <pjw@google.com>
diff --git a/internal/lsp/cmd/semantictokens.go b/internal/lsp/cmd/semantictokens.go
index 0bcb487..f3c4b12 100644
--- a/internal/lsp/cmd/semantictokens.go
+++ b/internal/lsp/cmd/semantictokens.go
@@ -74,9 +74,8 @@
}
// Run performs the semtok on the files specified by args and prints the
-// results to stdout. PJW: fix this description
+// results to stdout in the format described above.
func (c *semtok) Run(ctx context.Context, args ...string) error {
- log.SetFlags(log.Lshortfile)
if len(args) != 1 {
return fmt.Errorf("expected one file name, got %d", len(args))
}
@@ -122,7 +121,6 @@
Content: buf,
Converter: tc,
}
- memo = lsp.SemanticMemo
err = decorate(file.uri.Filename(), resp.Data)
if err != nil {
return err
@@ -130,8 +128,6 @@
return nil
}
-var memo *lsp.SemMemo
-
type mark struct {
line, offset int // 1-based, from RangeSpan
len int // bytes, not runes
@@ -218,8 +214,8 @@
line: spn.Start().Line(),
offset: spn.Start().Column(),
len: spn.End().Column() - spn.Start().Column(),
- typ: memo.Type(int(d[5*i+3])),
- mods: memo.Mods(int(d[5*i+4])),
+ typ: lsp.SemType(int(d[5*i+3])),
+ mods: lsp.SemMods(int(d[5*i+4])),
}
ans = append(ans, m)
}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index a6de385..0dd0ad2 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -82,10 +82,6 @@
}
}
- if st := params.Capabilities.TextDocument.SemanticTokens; st != nil {
- rememberToks(st.TokenTypes, st.TokenModifiers)
- }
-
goplsVer := &bytes.Buffer{}
debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText)
@@ -136,6 +132,7 @@
Version: goplsVer.String(),
},
}, nil
+
}
func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index d94b8f0..e031d6d 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -391,8 +391,6 @@
}
func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
- // no client, so use default
- rememberToks(SemanticTypes(), SemanticModifiers())
uri := spn.URI()
filename := uri.Filename()
// this is called solely for coverage in semantic.go
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
index b0f42d6..b669fab 100644
--- a/internal/lsp/semantic.go
+++ b/internal/lsp/semantic.go
@@ -69,11 +69,13 @@
return nil, pgf.ParseErr
}
e := &encoded{
- ctx: ctx,
- pgf: pgf,
- rng: rng,
- ti: info,
- fset: snapshot.FileSet(),
+ ctx: ctx,
+ pgf: pgf,
+ rng: rng,
+ ti: info,
+ fset: snapshot.FileSet(),
+ tokTypes: s.session.Options().SemanticTypes,
+ tokMods: s.session.Options().SemanticMods,
}
if err := e.init(); err != nil {
return nil, err
@@ -174,11 +176,12 @@
// the generated data
items []semItem
- ctx context.Context
- pgf *source.ParsedGoFile
- rng *protocol.Range
- ti *types.Info
- fset *token.FileSet
+ ctx context.Context
+ tokTypes, tokMods []string
+ pgf *source.ParsedGoFile
+ rng *protocol.Range
+ ti *types.Info
+ fset *token.FileSet
// allowed starting and ending token.Pos, set by init
// used to avoid looking at declarations not in range
start, end token.Pos
@@ -509,6 +512,7 @@
}
return e.items[i].start < e.items[j].start
})
+ typeMap, modMap := e.maps()
// each semantic token needs five values
// (see Integer Encoding for Tokens in the LSP spec)
x := make([]float64, 5*len(e.items))
@@ -524,10 +528,10 @@
x[j+1] = e.items[i].start - e.items[i-1].start
}
x[j+2] = e.items[i].len
- x[j+3] = float64(SemanticMemo.TypeMap[e.items[i].typeStr])
+ x[j+3] = float64(typeMap[e.items[i].typeStr])
mask := 0
for _, s := range e.items[i].mods {
- mask |= SemanticMemo.ModMap[s]
+ mask |= modMap[s]
}
x[j+4] = float64(mask)
}
@@ -565,51 +569,38 @@
panic(msg)
}
-// SemMemo supports semantic token translations between numbers and strings
-type SemMemo struct {
- tokTypes, tokMods []string
- // these exported fields are used in the 'gopls semtok' command
- TypeMap map[tokenType]int
- ModMap map[string]int
-}
-
-var SemanticMemo *SemMemo
-
-// Type returns a string equivalent of the type, for gopls semtok
-func (m *SemMemo) Type(n int) string {
- if n >= 0 && n < len(m.tokTypes) {
- return m.tokTypes[n]
+// SemType returns a string equivalent of the type, for gopls semtok
+func SemType(n int) string {
+ tokTypes := SemanticTypes()
+ tokMods := SemanticModifiers()
+ if n >= 0 && n < len(tokTypes) {
+ return tokTypes[n]
}
- return fmt.Sprintf("?%d[%d,%d]?", n, len(m.tokTypes), len(m.tokMods))
+ return fmt.Sprintf("?%d[%d,%d]?", n, len(tokTypes), len(tokMods))
}
-// Mods returns the []string equivalent of the mods, for gopls semtok.
-func (m *SemMemo) Mods(n int) []string {
+// SemMods returns the []string equivalent of the mods, for gopls semtok.
+func SemMods(n int) []string {
+ tokMods := SemanticModifiers()
mods := []string{}
- for i := 0; i < len(m.tokMods); i++ {
+ for i := 0; i < len(tokMods); i++ {
if (n & (1 << uint(i))) != 0 {
- mods = append(mods, m.tokMods[i])
+ mods = append(mods, tokMods[i])
}
}
return mods
}
-// save what the client sent
-func rememberToks(toks []string, mods []string) {
- SemanticMemo = &SemMemo{
- tokTypes: toks,
- tokMods: mods,
- TypeMap: make(map[tokenType]int),
- ModMap: make(map[string]int),
+func (e *encoded) maps() (map[tokenType]int, map[string]int) {
+ tmap := make(map[tokenType]int)
+ mmap := make(map[string]int)
+ for i, t := range e.tokTypes {
+ tmap[tokenType(t)] = i
}
- for i, t := range toks {
- SemanticMemo.TypeMap[tokenType(t)] = i
+ for i, m := range e.tokMods {
+ mmap[m] = 1 << uint(i) // go 1.12 compatibility
}
- for i, m := range mods {
- SemanticMemo.ModMap[m] = 1 << uint(i)
- }
- // we could have pruned or rearranged them.
- // But then change the list in cmd.go too
+ return tmap, mmap
}
// SemanticTypes to use in case there is no client, as in the command line, or tests
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 900d17a..d764a90 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -162,6 +162,8 @@
PreferredContentFormat protocol.MarkupKind
LineFoldingOnly bool
HierarchicalDocumentSymbolSupport bool
+ SemanticTypes []string
+ SemanticMods []string
}
// ServerOptions holds LSP-specific configuration that is provided by the
@@ -535,6 +537,13 @@
o.LineFoldingOnly = fr.LineFoldingOnly
// Check if the client supports hierarchical document symbols.
o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
+ // Check if the client supports semantic tokens
+ if c := caps.TextDocument.SemanticTokens; c != nil {
+ o.SemanticTypes = c.TokenTypes
+ o.SemanticMods = c.TokenModifiers
+ // we don't need Requests, as we support full functionality
+ // we don't need Formats, as there is only one, for now
+ }
}
func (o *Options) Clone() *Options {
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index beced5a..0d0e8a3 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -105,8 +105,8 @@
Legend: protocol.SemanticTokensLegend{
// TODO(pjw): trim these to what we use (and an unused one
// at position 0 of TokTypes, to catch typos)
- TokenTypes: SemanticMemo.tokTypes,
- TokenModifiers: SemanticMemo.tokMods,
+ TokenTypes: SemanticTypes(),
+ TokenModifiers: SemanticModifiers(),
},
},
})