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(),
 				},
 			},
 		})