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>
This commit is contained in:
Peter Weinbergr 2020-10-21 21:38:00 -04:00 committed by Peter Weinberger
parent 9cf592e881
commit 13b3b30742
6 changed files with 51 additions and 60 deletions

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

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