internal/lsp/semantic.go: add the skeleton for supporting semantic tokens

This Cl adds all the support for producing semantic tokens other than
generating them. That includes new stubs for LSP, a new option for
the user to choose semantic tokens, and the skeleton for producing
semantic tokens, except that 0 semantic tokens are produced.

vscode 1.49.1 (the current version) does not ask for semantic tokens;
vscode-insiders does.

Change-Id: Iceb8fff974deb9283281319bb5878271c3d3170d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/257578
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/gopls/doc/settings.md b/gopls/doc/settings.md
index 78a3ccb..c180f70 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -218,6 +218,12 @@
 
 
 Default: `false`.
+### **semanticTokens** *bool*
+semanticTokens controls whether the LSP server will send
+semantic tokens to the client.
+
+
+Default: `false`.
 ### **expandWorkspaceToModule** *bool*
 expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the
 modules containing the workspace folders. Set this to false to avoid loading
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 0ca97c9..27df063 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -86,7 +86,7 @@
 	goplsVer := &bytes.Buffer{}
 	debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText)
 
-	return &protocol.InitializeResult{
+	ans := &protocol.InitializeResult{
 		Capabilities: protocol.ServerCapabilities{
 			CallHierarchyProvider: true,
 			CodeActionProvider:    codeActionProvider,
@@ -132,7 +132,25 @@
 			Name:    "gopls",
 			Version: goplsVer.String(),
 		},
-	}, nil
+	}
+
+	st := params.Capabilities.TextDocument.SemanticTokens
+	if st != nil {
+		tokTypes, tokModifiers := rememberToks(st.TokenTypes, st.TokenModifiers)
+		// check that st.TokenFormat is "relative"
+		v := &protocol.SemanticTokensOptions{
+			Legend: protocol.SemanticTokensLegend{
+				// TODO(pjw): trim these to what we use (and an unused one
+				// at position 0 of TokTypes, to catch typos)
+				TokenTypes:     tokTypes,
+				TokenModifiers: tokModifiers,
+			},
+			Range: true,
+			Full:  true,
+		}
+		ans.Capabilities.SemanticTokensProvider = v
+	}
+	return ans, nil
 }
 
 func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
index 5d1c0a0..c3532b5 100644
--- a/internal/lsp/protocol/tsprotocol.go
+++ b/internal/lsp/protocol/tsprotocol.go
@@ -3298,6 +3298,9 @@
 	 * @since 3.16.0
 	 */
 	CallHierarchy CallHierarchyClientCapabilities `json:"callHierarchy,omitempty"`
+
+	// missing in source, generated
+	SemanticTokens *SemanticTokensClientCapabilities `json:"semanticTokens,omitempty"`
 }
 
 /**
@@ -3861,6 +3864,20 @@
 	PartialResultParams
 }
 
+// generated
+type SemanticTokensClientCapabilities struct {
+	TokenModifiers []string
+	Formats        []string
+	Requests       struct {
+		Range bool
+		Full  struct {
+			Delta bool
+		}
+	}
+	DynamicRegistration bool
+	TokenTypes          []string
+}
+
 const (
 	/**
 	 * Empty kind.
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
new file mode 100644
index 0000000..71dd320
--- /dev/null
+++ b/internal/lsp/semantic.go
@@ -0,0 +1,308 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lsp
+
+import (
+	"context"
+	"fmt"
+	"go/ast"
+	"go/types"
+	"log"
+	"sort"
+	"strings"
+	"time"
+
+	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	errors "golang.org/x/xerrors"
+)
+
+func (s *Server) semanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
+	now := time.Now()
+	ret, err := s.computeSemanticTokens(ctx, p.TextDocument, nil)
+	if err == nil {
+		event.Log(ctx, fmt.Sprintf("Full(%v): %d items for %s in %s",
+			s.session.Options().SemanticTokens, len(ret.Data)/5, p.TextDocument.URI.SpanURI().Filename(), time.Since(now)))
+	} else {
+		event.Error(ctx, fmt.Sprintf("Full failed for %s in %s", p.TextDocument.URI.SpanURI().Filename(), time.Since(now)), err)
+	}
+	return ret, err
+}
+
+func (s *Server) semanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
+	return nil, errors.Errorf("implement SemanticTokensFullDelta")
+}
+
+func (s *Server) semanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
+	now := time.Now()
+	ret, err := s.computeSemanticTokens(ctx, p.TextDocument, &p.Range)
+	if err == nil {
+		event.Log(ctx, fmt.Sprintf("Range(%v): %d items for %s %s in %s",
+			s.session.Options().SemanticTokens, len(ret.Data)/5, p.TextDocument.URI.SpanURI().Filename(),
+			p.Range, time.Since(now)))
+	} else {
+		event.Error(ctx, "semanticTokensRange failed", err)
+	}
+	return ret, err
+}
+
+func (s *Server) semanticTokensRefresh(ctx context.Context) error {
+	// in the code, but not in the protocol spec
+	return errors.Errorf("implement SemanticTokensRefresh")
+}
+
+func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocumentIdentifier, rng *protocol.Range) (*protocol.SemanticTokens, error) {
+	ans := protocol.SemanticTokens{
+		Data: []float64{},
+	}
+	snapshot, _, ok, release, err := s.beginFileRequest(ctx, td.URI, source.Go)
+	defer release()
+	if !ok {
+		return nil, err
+	}
+	vv, err := s.session.ViewOf(td.URI.SpanURI())
+	if err != nil {
+		return nil, err
+	}
+	if !vv.Options().SemanticTokens {
+		// return an error, so if the option changes
+		// the client won't remember the wrong answer
+		return nil, errors.Errorf("semantictokens are disabled")
+	}
+	pkg, err := snapshot.PackageForFile(ctx, td.URI.SpanURI(), source.TypecheckFull, source.WidestPackage)
+	if err != nil {
+		return nil, err
+	}
+	info := pkg.GetTypesInfo()
+	pgf, err := pkg.File(td.URI.SpanURI())
+	if err != nil {
+		return nil, err
+	}
+	e := &encoded{
+		pgf: pgf,
+		rng: rng,
+		ti:  info,
+	}
+	if err := e.init(); err != nil {
+		return nil, err
+	}
+	e.semantics()
+	ans.Data, err = e.Data()
+	if err != nil {
+		// this is an internal error, likely caused by a typo
+		// for a token or modifier
+		return nil, err
+	}
+	// for small cache, some day. for now, the client ignores this
+	ans.ResultID = fmt.Sprintf("%v", time.Now())
+	return &ans, nil
+}
+
+func (e *encoded) semantics() {
+	f := e.pgf.File
+	inspect := func(n ast.Node) bool {
+		return e.inspector(n)
+	}
+	for _, d := range f.Decls {
+		// only look at the decls that overlap the range
+		start, end := d.Pos(), d.End()
+		if int(end) <= e.start || int(start) >= e.end {
+			continue
+		}
+		ast.Inspect(d, inspect)
+	}
+}
+
+// semItem represents a token found walking the parse tree
+type semItem struct {
+	line, start, len int
+	typeStr          string
+	mods             []string
+}
+
+type encoded struct {
+	// the generated data
+	items []semItem
+
+	pgf *source.ParsedGoFile
+	rng *protocol.Range
+	ti  *types.Info
+	// allowed starting and ending token.Pos, set by init
+	// used to avoid looking at declarations not in range
+	start, end int
+	// path from the root of the parse tree, used for debugging
+	stack []ast.Node
+}
+
+// convert the stack to a string, for debugging
+func (e *encoded) strStack() string {
+	msg := []string{"["}
+	for _, s := range e.stack {
+		msg = append(msg, fmt.Sprintf("%T", s)[5:])
+	}
+	if len(e.stack) > 0 {
+		loc := e.stack[len(e.stack)-1].Pos()
+		add := e.pgf.Tok.PositionFor(loc, false)
+		msg = append(msg, fmt.Sprintf("(%d:%d)", add.Line, add.Column))
+	}
+	msg = append(msg, "]")
+	return strings.Join(msg, " ")
+}
+
+func (e *encoded) inspector(n ast.Node) bool {
+	// this will be filled in, in the next CL
+	pop := func() {
+		e.stack = e.stack[:len(e.stack)-1]
+	}
+	if n == nil {
+		pop()
+		return true
+	}
+	e.stack = append(e.stack, n)
+	switch x := n.(type) {
+	case *ast.ArrayType:
+	case *ast.AssignStmt:
+	case *ast.BasicLit:
+	case *ast.BinaryExpr:
+	case *ast.BlockStmt:
+	case *ast.BranchStmt:
+	case *ast.CallExpr:
+	case *ast.CaseClause:
+	case *ast.ChanType:
+	case *ast.CommClause:
+	case *ast.CompositeLit:
+	case *ast.DeclStmt:
+	case *ast.DeferStmt:
+	case *ast.Ellipsis:
+	case *ast.EmptyStmt:
+	case *ast.ExprStmt:
+	case *ast.Field:
+	case *ast.FieldList:
+	case *ast.ForStmt:
+	case *ast.FuncDecl:
+	case *ast.FuncLit:
+	case *ast.FuncType:
+	case *ast.GenDecl:
+	case *ast.GoStmt:
+	case *ast.Ident:
+	case *ast.IfStmt:
+	case *ast.ImportSpec:
+		pop()
+		return false
+	case *ast.IncDecStmt:
+	case *ast.IndexExpr:
+	case *ast.InterfaceType:
+	case *ast.KeyValueExpr:
+	case *ast.LabeledStmt:
+	case *ast.MapType:
+	case *ast.ParenExpr:
+	case *ast.RangeStmt:
+	case *ast.ReturnStmt:
+	case *ast.SelectStmt:
+	case *ast.SelectorExpr:
+	case *ast.SendStmt:
+	case *ast.SliceExpr:
+	case *ast.StarExpr:
+	case *ast.StructType:
+	case *ast.SwitchStmt:
+	case *ast.TypeAssertExpr:
+	case *ast.TypeSpec:
+	case *ast.TypeSwitchStmt:
+	case *ast.UnaryExpr:
+	case *ast.ValueSpec:
+	// things we won't see
+	case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt,
+		*ast.File, *ast.Package:
+		log.Printf("implement %T %s", x, e.pgf.Tok.PositionFor(x.Pos(), false))
+	// things we knowingly ignore
+	case *ast.Comment, *ast.CommentGroup:
+		pop()
+		return false
+	default: // just to be super safe.
+		panic(fmt.Sprintf("failed to implement %T", x))
+	}
+	return true
+}
+
+func (e *encoded) init() error {
+	e.start = e.pgf.Tok.Base()
+	e.end = e.start + e.pgf.Tok.Size()
+	if e.rng == nil {
+		return nil
+	}
+	span, err := e.pgf.Mapper.RangeSpan(*e.rng)
+	if err != nil {
+		return errors.Errorf("range span error for %s", e.pgf.File.Name)
+	}
+	e.end = e.start + span.End().Offset()
+	e.start += span.Start().Offset()
+	return nil
+}
+
+func (e *encoded) Data() ([]float64, error) {
+	// binary operators, at least, will be out of order
+	sort.Slice(e.items, func(i, j int) bool {
+		if e.items[i].line != e.items[j].line {
+			return e.items[i].line < e.items[j].line
+		}
+		return e.items[i].start < e.items[j].start
+	})
+	// each semantic token needs five values
+	// (see Integer Encoding for Tokens in the LSP spec)
+	x := make([]int, 5*len(e.items))
+	for i := 0; i < len(e.items); i++ {
+		j := 5 * i
+		if i == 0 {
+			x[0] = e.items[0].line
+		} else {
+			x[j] = e.items[i].line - e.items[i-1].line
+		}
+		x[j+1] = e.items[i].start
+		if i > 0 && e.items[i].line == e.items[i-1].line {
+			x[j+1] = e.items[i].start - e.items[i-1].start
+		}
+		x[j+2] = e.items[i].len
+		x[j+3] = SemanticMemo.typeMap[e.items[i].typeStr]
+		for _, s := range e.items[i].mods {
+			x[j+4] |= SemanticMemo.modMap[s]
+		}
+	}
+	// As the client never sends these, we could fix the generated
+	// type to want []int rather than []float64.
+	ans := make([]float64, len(x))
+	for i, y := range x {
+		ans[i] = float64(y)
+	}
+	return ans, nil
+}
+
+// SemMemo supports semantic token translations between numbers and strings
+type SemMemo struct {
+	tokTypes, tokMods []string
+	typeMap           map[string]int
+	modMap            map[string]int
+}
+
+var SemanticMemo *SemMemo
+
+// save what the client sent
+func rememberToks(toks []string, mods []string) ([]string, []string) {
+	SemanticMemo = &SemMemo{
+		tokTypes: toks,
+		tokMods:  mods,
+		typeMap:  make(map[string]int),
+		modMap:   make(map[string]int),
+	}
+	for i, t := range toks {
+		SemanticMemo.typeMap[t] = i
+	}
+	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 SemanticMemo.tokTypes, SemanticMemo.tokMods
+}
diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go
index d22a5eb..99fc6b0 100644
--- a/internal/lsp/server_gen.go
+++ b/internal/lsp/server_gen.go
@@ -164,20 +164,20 @@
 	return nil, notImplemented("SelectionRange")
 }
 
-func (s *Server) SemanticTokensFull(context.Context, *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
-	return nil, notImplemented("SemanticTokensFull")
+func (s *Server) SemanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
+	return s.semanticTokensFull(ctx, p)
 }
 
-func (s *Server) SemanticTokensFullDelta(context.Context, *protocol.SemanticTokensDeltaParams) (interface{}, error) {
-	return nil, notImplemented("SemanticTokensFullDelta")
+func (s *Server) SemanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
+	return s.semanticTokensFullDelta(ctx, p)
 }
 
-func (s *Server) SemanticTokensRange(context.Context, *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
-	return nil, notImplemented("SemanticTokensRange")
+func (s *Server) SemanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
+	return s.semanticTokensRange(ctx, p)
 }
 
-func (s *Server) SemanticTokensRefresh(context.Context) error {
-	return notImplemented("SemanticTokensRefresh")
+func (s *Server) SemanticTokensRefresh(ctx context.Context) error {
+	return s.semanticTokensRefresh(ctx)
 }
 
 func (s *Server) SetTrace(context.Context, *protocol.SetTraceParams) error {
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index df859c5..7295d64 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -2,4 +2,4 @@
 
 package source
 
-const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle specifies what style of symbols to return in symbol requests.\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"fill_struct\",\"Title\":\"fill_struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"undeclared_name\",\"Title\":\"undeclared_name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
+const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle specifies what style of symbols to return in symbol requests.\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"semanticTokens\",\"Type\":\"bool\",\"Doc\":\"semanticTokens controls whether the LSP server will send\\nsemantic tokens to the client.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"fill_struct\",\"Title\":\"fill_struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"undeclared_name\",\"Title\":\"undeclared_name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 9140f4e..0ce5fb7 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -313,6 +313,10 @@
 	// progress reports for all work done outside the scope of an RPC.
 	VerboseWorkDoneProgress bool
 
+	// SemanticTokens controls whether the LSP server will send
+	// semantic tokens to the client.
+	SemanticTokens bool
+
 	// ExpandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the
 	// modules containing the workspace folders. Set this to false to avoid loading
 	// your entire module. This is particularly useful for those working in a monorepo.
@@ -680,6 +684,9 @@
 	case "gofumpt":
 		result.setBool(&o.Gofumpt)
 
+	case "semantictokens":
+		result.setBool(&o.SemanticTokens)
+
 	case "expandWorkspaceToModule":
 		result.setBool(&o.ExpandWorkspaceToModule)