From 13b3b307426dd0181419b5eab2302e95473dcf5b Mon Sep 17 00:00:00 2001 From: Peter Weinbergr Date: Wed, 21 Oct 2020 21:38:00 -0400 Subject: [PATCH] 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 gopls-CI: kokoro TryBot-Result: Go Bot Reviewed-by: Rebecca Stambler Trust: Peter Weinberger --- internal/lsp/cmd/semantictokens.go | 10 ++-- internal/lsp/general.go | 5 +- internal/lsp/lsp_test.go | 2 - internal/lsp/semantic.go | 81 +++++++++++++----------------- internal/lsp/source/options.go | 9 ++++ internal/lsp/workspace.go | 4 +- 6 files changed, 51 insertions(+), 60 deletions(-) diff --git a/internal/lsp/cmd/semantictokens.go b/internal/lsp/cmd/semantictokens.go index 0bcb48773d..f3c4b124a0 100644 --- a/internal/lsp/cmd/semantictokens.go +++ b/internal/lsp/cmd/semantictokens.go @@ -74,9 +74,8 @@ Example: show the semantic tokens for this file: } // 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 @@ func (c *semtok) Run(ctx context.Context, args ...string) error { Content: buf, Converter: tc, } - memo = lsp.SemanticMemo err = decorate(file.uri.Filename(), resp.Data) if err != nil { return err @@ -130,8 +128,6 @@ func (c *semtok) Run(ctx context.Context, args ...string) error { 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 @@ func newMarks(d []float64) []mark { 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 a6de385162..0dd0ad274a 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -82,10 +82,6 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ } } - 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 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ 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 d94b8f08f9..e031d6d003 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -391,8 +391,6 @@ func (r *runner) Format(t *testing.T, spn span.Span) { } 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 b0f42d6df2..b669fabaff 100644 --- a/internal/lsp/semantic.go +++ b/internal/lsp/semantic.go @@ -69,11 +69,13 @@ func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocu 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 @@ type encoded struct { // 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 @@ func (e *encoded) Data() ([]float64, error) { } 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 @@ func (e *encoded) Data() ([]float64, error) { 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 @@ func (e *encoded) unexpected(msg string) { 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 900d17a1cb..d764a90ab5 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -162,6 +162,8 @@ type ClientOptions struct { 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 @@ func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { 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 beced5a8a2..0d0e8a3994 100644 --- a/internal/lsp/workspace.go +++ b/internal/lsp/workspace.go @@ -105,8 +105,8 @@ func semanticTokenRegistrations() []protocol.Registration { 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(), }, }, })