Skip to content

Commit 4403100

Browse files
h9jianggopherbot
authored andcommitted
gopls/internal/golang: customize semantic token types and modifiers
We agreed to return full set of token types and modifiers from gopls by default (full means token types and modifiers that gopls understand) and provide configuraton options for users to disable some of them. - Two fields of type map[string]bool are introduced to gopls UIOptions (workspace/configuration) to customize semantic token types and modifiers. For now, only value of "false" is effective. Choose type of map over array to keep future compatibility in case we want to introduce enable capabilities. - VSCode-Go populate these options from user settings to gopls. - Gopls "initialize" protocol returns a pre-defined fixed legend including subset of standard legend defined LSP that gopls understand with additional customize modifiers gopls recoganized. - Gopls "textDocument/semanticTokens" protocol returns token types and modifiers based on configuration defined in workspace/configuration. Tested with vscode-go changes CL 642416, screenshot is at golang/vscode-go#3632 (comment) For golang/vscode-go#3632 Change-Id: Ie8220e12a4c8d6c84c54992d84277767e61ec023 Reviewed-on: https://go-review.googlesource.com/c/tools/+/642077 Auto-Submit: Hongxiang Jiang <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent c9ef861 commit 4403100

File tree

13 files changed

+313
-171
lines changed

13 files changed

+313
-171
lines changed

gopls/doc/settings.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,32 @@ Default: `false`.
215215

216216
**This setting is experimental and may be deleted.**
217217

218-
noSemanticNumber turns off the sending of the semantic token 'number'
218+
noSemanticNumber turns off the sending of the semantic token 'number'
219219

220220
Default: `false`.
221221

222+
<a id='semanticTokenTypes'></a>
223+
### `semanticTokenTypes map[string]bool`
224+
225+
**This setting is experimental and may be deleted.**
226+
227+
semanticTokenTypes configures the semantic token types. It allows
228+
disabling types by setting each value to false.
229+
By default, all types are enabled.
230+
231+
Default: `{}`.
232+
233+
<a id='semanticTokenModifiers'></a>
234+
### `semanticTokenModifiers map[string]bool`
235+
236+
**This setting is experimental and may be deleted.**
237+
238+
semanticTokenModifiers configures the semantic token modifiers. It allows
239+
disabling modifiers by setting each value to false.
240+
By default, all modifiers are enabled.
241+
242+
Default: `{}`.
243+
222244
<a id='completion'></a>
223245
## Completion
224246

gopls/internal/cmd/cmd.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import (
2727
"golang.org/x/tools/gopls/internal/lsprpc"
2828
"golang.org/x/tools/gopls/internal/protocol"
2929
"golang.org/x/tools/gopls/internal/protocol/command"
30+
"golang.org/x/tools/gopls/internal/protocol/semtok"
3031
"golang.org/x/tools/gopls/internal/server"
3132
"golang.org/x/tools/gopls/internal/settings"
3233
"golang.org/x/tools/gopls/internal/util/browser"
3334
bugpkg "golang.org/x/tools/gopls/internal/util/bug"
35+
"golang.org/x/tools/gopls/internal/util/moreslices"
3436
"golang.org/x/tools/internal/diff"
3537
"golang.org/x/tools/internal/jsonrpc2"
3638
"golang.org/x/tools/internal/tool"
@@ -299,7 +301,7 @@ func (app *Application) featureCommands() []tool.Application {
299301
&prepareRename{app: app},
300302
&references{app: app},
301303
&rename{app: app},
302-
&semtok{app: app},
304+
&semanticToken{app: app},
303305
&signature{app: app},
304306
&stats{app: app},
305307
&symbols{app: app},
@@ -322,7 +324,6 @@ func (app *Application) connect(ctx context.Context) (*connection, error) {
322324
options := settings.DefaultOptions(app.options)
323325
svr = server.New(cache.NewSession(ctx, cache.New(nil)), client, options)
324326
ctx = protocol.WithClient(ctx, client)
325-
326327
} else {
327328
// remote
328329
netConn, err := lsprpc.ConnectToRemote(ctx, app.Remote)
@@ -362,8 +363,8 @@ func (c *connection) initialize(ctx context.Context, options func(*settings.Opti
362363
params.Capabilities.TextDocument.SemanticTokens.Requests.Range = &protocol.Or_ClientSemanticTokensRequestOptions_range{Value: true}
363364
//params.Capabilities.TextDocument.SemanticTokens.Requests.Range.Value = true
364365
params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true}
365-
params.Capabilities.TextDocument.SemanticTokens.TokenTypes = protocol.SemanticTypes()
366-
params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = protocol.SemanticModifiers()
366+
params.Capabilities.TextDocument.SemanticTokens.TokenTypes = moreslices.ConvertStrings[string](semtok.TokenTypes)
367+
params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = moreslices.ConvertStrings[string](semtok.TokenModifiers)
367368
params.Capabilities.TextDocument.CodeAction = protocol.CodeActionClientCapabilities{
368369
CodeActionLiteralSupport: protocol.ClientCodeActionLiteralOptions{
369370
CodeActionKind: protocol.ClientCodeActionKindOptions{
@@ -376,7 +377,7 @@ func (c *connection) initialize(ctx context.Context, options func(*settings.Opti
376377
params.InitializationOptions = map[string]interface{}{
377378
"symbolMatcher": string(opts.SymbolMatcher),
378379
}
379-
if _, err := c.Server.Initialize(ctx, params); err != nil {
380+
if c.initializeResult, err = c.Server.Initialize(ctx, params); err != nil {
380381
return err
381382
}
382383
if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
@@ -388,6 +389,9 @@ func (c *connection) initialize(ctx context.Context, options func(*settings.Opti
388389
type connection struct {
389390
protocol.Server
390391
client *cmdClient
392+
// initializeResult keep the initialize protocol response from server
393+
// including server capabilities.
394+
initializeResult *protocol.InitializeResult
391395
}
392396

393397
// cmdClient defines the protocol.Client interface behavior of the gopls CLI tool.

gopls/internal/cmd/semantictokens.go

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"unicode/utf8"
1515

1616
"golang.org/x/tools/gopls/internal/protocol"
17+
"golang.org/x/tools/gopls/internal/protocol/semtok"
1718
"golang.org/x/tools/gopls/internal/settings"
1819
)
1920

@@ -40,15 +41,15 @@ import (
4041
// 0-based: lines and character positions are 1 less than in
4142
// the gopls coordinate system
4243

43-
type semtok struct {
44+
type semanticToken struct {
4445
app *Application
4546
}
4647

47-
func (c *semtok) Name() string { return "semtok" }
48-
func (c *semtok) Parent() string { return c.app.Name() }
49-
func (c *semtok) Usage() string { return "<filename>" }
50-
func (c *semtok) ShortHelp() string { return "show semantic tokens for the specified file" }
51-
func (c *semtok) DetailedHelp(f *flag.FlagSet) {
48+
func (c *semanticToken) Name() string { return "semtok" }
49+
func (c *semanticToken) Parent() string { return c.app.Name() }
50+
func (c *semanticToken) Usage() string { return "<filename>" }
51+
func (c *semanticToken) ShortHelp() string { return "show semantic tokens for the specified file" }
52+
func (c *semanticToken) DetailedHelp(f *flag.FlagSet) {
5253
fmt.Fprint(f.Output(), `
5354
Example: show the semantic tokens for this file:
5455
@@ -59,7 +60,7 @@ Example: show the semantic tokens for this file:
5960

6061
// Run performs the semtok on the files specified by args and prints the
6162
// results to stdout in the format described above.
62-
func (c *semtok) Run(ctx context.Context, args ...string) error {
63+
func (c *semanticToken) Run(ctx context.Context, args ...string) error {
6364
if len(args) != 1 {
6465
return fmt.Errorf("expected one file name, got %d", len(args))
6566
}
@@ -97,14 +98,16 @@ func (c *semtok) Run(ctx context.Context, args ...string) error {
9798
if err != nil {
9899
return err
99100
}
100-
return decorate(file, resp.Data)
101+
return decorate(conn.initializeResult.Capabilities.SemanticTokensProvider.(protocol.SemanticTokensOptions).Legend, file, resp.Data)
101102
}
102103

104+
// mark provides a human-readable representation of protocol.SemanticTokens.
105+
// It translates token types and modifiers to strings instead of uint32 values.
103106
type mark struct {
104107
line, offset int // 1-based, from RangeSpan
105108
len int // bytes, not runes
106-
typ string
107-
mods []string
109+
typ semtok.Type
110+
mods []semtok.Modifier
108111
}
109112

110113
// prefixes for semantic token comments
@@ -136,8 +139,10 @@ func markLine(m mark, lines [][]byte) {
136139
lines[m.line-1] = l
137140
}
138141

139-
func decorate(file *cmdFile, result []uint32) error {
140-
marks := newMarks(file, result)
142+
// decorate translates semantic token data (protocol.SemanticTokens) from its
143+
// raw []uint32 format into a human-readable representation and prints it to stdout.
144+
func decorate(legend protocol.SemanticTokensLegend, file *cmdFile, data []uint32) error {
145+
marks := newMarks(legend, file, data)
141146
if len(marks) == 0 {
142147
return nil
143148
}
@@ -150,45 +155,56 @@ func decorate(file *cmdFile, result []uint32) error {
150155
return nil
151156
}
152157

153-
func newMarks(file *cmdFile, d []uint32) []mark {
158+
func newMarks(legend protocol.SemanticTokensLegend, file *cmdFile, data []uint32) []mark {
154159
ans := []mark{}
155160
// the following two loops could be merged, at the cost
156161
// of making the logic slightly more complicated to understand
157162
// first, convert from deltas to absolute, in LSP coordinates
158-
lspLine := make([]uint32, len(d)/5)
159-
lspChar := make([]uint32, len(d)/5)
163+
lspLine := make([]uint32, len(data)/5)
164+
lspChar := make([]uint32, len(data)/5)
160165
var line, char uint32
161-
for i := 0; 5*i < len(d); i++ {
162-
lspLine[i] = line + d[5*i+0]
163-
if d[5*i+0] > 0 {
166+
for i := 0; 5*i < len(data); i++ {
167+
lspLine[i] = line + data[5*i+0]
168+
if data[5*i+0] > 0 {
164169
char = 0
165170
}
166-
lspChar[i] = char + d[5*i+1]
171+
lspChar[i] = char + data[5*i+1]
167172
char = lspChar[i]
168173
line = lspLine[i]
169174
}
170175
// second, convert to gopls coordinates
171-
for i := 0; 5*i < len(d); i++ {
176+
for i := 0; 5*i < len(data); i++ {
172177
pr := protocol.Range{
173178
Start: protocol.Position{
174179
Line: lspLine[i],
175180
Character: lspChar[i],
176181
},
177182
End: protocol.Position{
178183
Line: lspLine[i],
179-
Character: lspChar[i] + d[5*i+2],
184+
Character: lspChar[i] + data[5*i+2],
180185
},
181186
}
182187
spn, err := file.rangeSpan(pr)
183188
if err != nil {
184189
log.Fatal(err)
185190
}
191+
192+
var mods []semtok.Modifier
193+
{
194+
n := int(data[5*i+4])
195+
for i, mod := range legend.TokenModifiers {
196+
if (n & (1 << i)) != 0 {
197+
mods = append(mods, semtok.Modifier(mod))
198+
}
199+
}
200+
}
201+
186202
m := mark{
187203
line: spn.Start().Line(),
188204
offset: spn.Start().Column(),
189205
len: spn.End().Column() - spn.Start().Column(),
190-
typ: protocol.SemType(int(d[5*i+3])),
191-
mods: protocol.SemMods(int(d[5*i+4])),
206+
typ: semtok.Type(legend.TokenTypes[data[5*i+3]]),
207+
mods: mods,
192208
}
193209
ans = append(ans, m)
194210
}

gopls/internal/doc/api.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@
847847
{
848848
"Name": "noSemanticNumber",
849849
"Type": "bool",
850-
"Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n",
850+
"Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n",
851851
"EnumKeys": {
852852
"ValueType": "",
853853
"Keys": null
@@ -857,6 +857,32 @@
857857
"Status": "experimental",
858858
"Hierarchy": "ui"
859859
},
860+
{
861+
"Name": "semanticTokenTypes",
862+
"Type": "map[string]bool",
863+
"Doc": "semanticTokenTypes configures the semantic token types. It allows\ndisabling types by setting each value to false.\nBy default, all types are enabled.\n",
864+
"EnumKeys": {
865+
"ValueType": "",
866+
"Keys": null
867+
},
868+
"EnumValues": null,
869+
"Default": "{}",
870+
"Status": "experimental",
871+
"Hierarchy": "ui"
872+
},
873+
{
874+
"Name": "semanticTokenModifiers",
875+
"Type": "map[string]bool",
876+
"Doc": "semanticTokenModifiers configures the semantic token modifiers. It allows\ndisabling modifiers by setting each value to false.\nBy default, all modifiers are enabled.\n",
877+
"EnumKeys": {
878+
"ValueType": "",
879+
"Keys": null
880+
},
881+
"EnumValues": null,
882+
"Default": "{}",
883+
"Status": "experimental",
884+
"Hierarchy": "ui"
885+
},
860886
{
861887
"Name": "local",
862888
"Type": "string",

gopls/internal/golang/semtok.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,8 @@ func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handl
8282
return &protocol.SemanticTokens{
8383
Data: semtok.Encode(
8484
tv.tokens,
85-
snapshot.Options().NoSemanticString,
86-
snapshot.Options().NoSemanticNumber,
87-
snapshot.Options().SemanticTypes,
88-
snapshot.Options().SemanticMods),
85+
snapshot.Options().EnabledSemanticTokenTypes(),
86+
snapshot.Options().EnabledSemanticTokenModifiers()),
8987
ResultID: time.Now().String(), // for delta requests, but we've never seen any
9088
}, nil
9189
}
@@ -242,7 +240,7 @@ func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.P
242240
}
243241

244242
// token emits a token of the specified extent and semantics.
245-
func (tv *tokenVisitor) token(start token.Pos, length int, typ semtok.TokenType, modifiers ...semtok.Modifier) {
243+
func (tv *tokenVisitor) token(start token.Pos, length int, typ semtok.Type, modifiers ...semtok.Modifier) {
246244
if !start.IsValid() {
247245
return
248246
}
@@ -463,7 +461,7 @@ func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) {
463461
return true
464462
}
465463

466-
func (tv *tokenVisitor) appendObjectModifiers(mods []semtok.Modifier, obj types.Object) (semtok.TokenType, []semtok.Modifier) {
464+
func (tv *tokenVisitor) appendObjectModifiers(mods []semtok.Modifier, obj types.Object) (semtok.Type, []semtok.Modifier) {
467465
if obj.Pkg() == nil {
468466
mods = append(mods, semtok.ModDefaultLibrary)
469467
}
@@ -559,7 +557,7 @@ func appendTypeModifiers(mods []semtok.Modifier, t types.Type) []semtok.Modifier
559557

560558
func (tv *tokenVisitor) ident(id *ast.Ident) {
561559
var (
562-
tok semtok.TokenType
560+
tok semtok.Type
563561
mods []semtok.Modifier
564562
obj types.Object
565563
ok bool
@@ -623,7 +621,7 @@ func (tv *tokenVisitor) isParam(pos token.Pos) bool {
623621
// def), use the parse stack.
624622
// A lot of these only happen when the package doesn't compile,
625623
// but in that case it is all best-effort from the parse tree.
626-
func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.TokenType, []semtok.Modifier) {
624+
func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.Type, []semtok.Modifier) {
627625
def := []semtok.Modifier{semtok.ModDefinition}
628626
n := len(tv.stack) - 2 // parent of Ident; stack is [File ... Ident]
629627
if n < 0 {
@@ -746,7 +744,7 @@ func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.TokenType, []semtok.Modi
746744
}
747745

748746
// multiline emits a multiline token (`string` or /*comment*/).
749-
func (tv *tokenVisitor) multiline(start, end token.Pos, tok semtok.TokenType) {
747+
func (tv *tokenVisitor) multiline(start, end token.Pos, tok semtok.Type) {
750748
// TODO(adonovan): test with non-ASCII.
751749

752750
f := tv.fset.File(start)

gopls/internal/protocol/semantic.go

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)