Skip to content

Commit 84def5c

Browse files
authored
Fix #254: Force refresh semantic tokens after highlighting is generated (#271)
- Added `onDidChangeSemanticTokens` support to VS Code bindings. - Created a global event emitter for semantic tokens in `Main`. - Added `onUpdate` channel to `Tokens` that fires on highlighting generation. - Subscribed to `onUpdate` in `Main` to trigger the VS Code event. - Added regression test for `onUpdate` event emission.
1 parent d135c55 commit 84def5c

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

src/Editor.res

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,20 @@ module Provider = {
220220
}
221221

222222
module Mock = {
223+
// https://code.visualstudio.com/api/references/vscode-api#Event
224+
module Event = {
225+
type t<'a>
226+
}
227+
228+
// https://code.visualstudio.com/api/references/vscode-api#EventEmitter
229+
module EventEmitter = {
230+
type t<'a>
231+
@module("vscode") @new external make: unit => t<'a> = "EventEmitter"
232+
@get external event: t<'a> => Event.t<'a> = "event"
233+
@send external fire: (t<'a>, 'a) => unit = "fire"
234+
@send external dispose: t<'a> => unit = "dispose"
235+
}
236+
223237
// https://code.visualstudio.com/api/references/vscode-api#SemanticsTokens
224238
module SemanticsTokens = {
225239
type t
@@ -294,8 +308,6 @@ module Provider = {
294308

295309
// https://code.visualstudio.com/api/references/vscode-api#DocumentSemanticTokensProvider
296310
module DocumentSemanticTokensProvider = {
297-
// missing: onDidChangeSemanticTokens
298-
299311
type provideDocumentSemanticTokens = (
300312
TextDocument.t,
301313
CancellationToken.t,
@@ -314,15 +326,18 @@ module Provider = {
314326
>
315327

316328
type t = {
329+
onDidChangeSemanticTokens: option<Event.t<unit>>,
317330
provideDocumentSemanticTokens: option<provideDocumentSemanticTokens>,
318331
provideDocumentSemanticTokensEdits: option<provideDocumentSemanticTokensEdits>,
319332
}
320333

321334
let make = (
335+
~onDidChangeSemanticTokens: option<Event.t<unit>>=?,
322336
~provideDocumentSemanticTokens: option<provideDocumentSemanticTokens>=?,
323337
~provideDocumentSemanticTokensEdits: option<provideDocumentSemanticTokensEdits>=?,
324338
(),
325339
) => {
340+
onDidChangeSemanticTokens,
326341
provideDocumentSemanticTokens,
327342
provideDocumentSemanticTokensEdits,
328343
}
@@ -340,9 +355,11 @@ module Provider = {
340355

341356
let registerDocumentSemanticTokensProvider = (
342357
~provideDocumentSemanticTokens: Mock.DocumentSemanticTokensProvider.provideDocumentSemanticTokens,
358+
~onDidChangeSemanticTokens: option<Mock.Event.t<unit>>=?,
343359
(tokenTypes, tokenModifiers),
344360
) => {
345361
let documentSemanticTokensProvider = Mock.DocumentSemanticTokensProvider.make(
362+
~onDidChangeSemanticTokens?,
346363
~provideDocumentSemanticTokens,
347364
// =(textDocument, _cancel) => {
348365
// let builder = Mock.SemanticTokensBuilder.makeWithLegend(semanticTokensLegend)

src/Main/Main.res

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ let initialize = (
4949
memento,
5050
editor,
5151
semanticTokensRequest,
52+
semanticTokensEventEmitter,
5253
) => {
5354
let panel = Singleton.Panel.make(extensionUri)
5455
// if the panel is destroyed, destroy all every State in the Registry
@@ -80,6 +81,13 @@ let initialize = (
8081

8182
let subscribe = disposable => state.subscriptions->Array.push(disposable)->ignore
8283

84+
// trigger semantic tokens update when highlighting is generated
85+
semanticTokensEventEmitter->Option.forEach(emitter => {
86+
state.tokens->Tokens.onUpdate->Chan.on(() => {
87+
emitter->Editor.Provider.Mock.EventEmitter.fire()
88+
})->VSCode.Disposable.make->subscribe
89+
})
90+
8391
let getCurrentEditor = () =>
8492
switch VSCode.Window.activeTextEditor {
8593
| Some(editor) => Some(editor)
@@ -141,7 +149,7 @@ let initialize = (
141149
state
142150
}
143151

144-
let registerDocumentSemanticTokensProvider = () => {
152+
let registerDocumentSemanticTokensProvider = onDidChangeSemanticTokens => {
145153
// these two arrays are called "legends"
146154
let tokenTypes = Highlighting__SemanticToken.TokenType.enumurate
147155
let tokenModifiers = Highlighting__SemanticToken.TokenModifier.enumurate
@@ -175,6 +183,7 @@ let registerDocumentSemanticTokensProvider = () => {
175183

176184
Editor.Provider.registerDocumentSemanticTokensProvider(
177185
~provideDocumentSemanticTokens,
186+
~onDidChangeSemanticTokens?,
178187
(tokenTypes, tokenModifiers),
179188
)
180189
}
@@ -269,6 +278,11 @@ let activateWithoutContext = (
269278
let subscribe = x => subscriptions->Array.push(x)->ignore
270279
let subscribeMany = xs => subscriptions->Array.pushMany(xs)->ignore
271280

281+
// Semantic Tokens Event
282+
let semanticTokensEventEmitter = Editor.Provider.Mock.EventEmitter.make()
283+
subscribe(VSCode.Disposable.make(() => semanticTokensEventEmitter->Editor.Provider.Mock.EventEmitter.dispose))
284+
let onDidChangeSemanticTokens = semanticTokensEventEmitter->Editor.Provider.Mock.EventEmitter.event
285+
272286
// Channel for testing, emits events when something has been completed,
273287
// for example, when the input method has translated a key sequence into a symbol
274288
let channels = {
@@ -363,6 +377,7 @@ let activateWithoutContext = (
363377
memento,
364378
editor,
365379
None,
380+
Some(semanticTokensEventEmitter),
366381
)
367382
Registry.add(document, state)
368383
| Some(entry) =>
@@ -377,6 +392,7 @@ let activateWithoutContext = (
377392
memento,
378393
editor,
379394
Some(entry.semanticTokens),
395+
Some(semanticTokensEventEmitter),
380396
)
381397
Registry.add(document, state)
382398
| Some(_) => () // should not happen
@@ -394,7 +410,7 @@ let activateWithoutContext = (
394410
}
395411
})->subscribeMany
396412

397-
registerDocumentSemanticTokensProvider()->subscribe
413+
registerDocumentSemanticTokensProvider(Some(onDidChangeSemanticTokens))->subscribe
398414
registerInputMethodHintHoverProvider()->subscribe
399415

400416
// expose the channel for testing

src/State/State.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ let destroy = async (state, alsoRemoveFromRegistry) => {
125125
state.channels.log->Chan.emit(Others("State.destroy: Disposed subscriptions"))
126126
state.channels.log->Chan.emit(TokensReset("State.destroy"))
127127
state.tokens->Tokens.reset
128+
state.tokens->Tokens.destroyUpdateChannel
128129
state.channels.log->Chan.emit(Others("State.destroy: Tokens reset completed"))
129130
let result = await Connection.destroy(state.connection, state.channels.log)
130131
state.channels.log->Chan.emit(Others("State.destroy: Connection destroyed, destruction complete"))

src/Tokens.res

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module type Module = {
2323

2424
// Remove everything
2525
let reset: t => unit
26+
let destroyUpdateChannel: t => unit
2627

2728
// definition provider for go-to-definition
2829
let goToDefinition: (
@@ -41,6 +42,8 @@ module type Module = {
4142

4243
let toOriginalOffset: (t, int) => option<int>
4344

45+
let onUpdate: t => Chan.t<unit>
46+
4447
let getVSCodeTokens: t => Resource.t<array<Highlighting__SemanticToken.t>>
4548
let getHolePositionsFromLoad: t => Resource.t<Map.t<int, int>>
4649
let getHoles: t => Map.t<int, Token.t<vscodeOffset>>
@@ -118,6 +121,7 @@ module Module: Module = {
118121
// ranges of holes
119122
mutable holes: Map.t<int, Token.t<vscodeOffset>>,
120123
mutable holePositions: Resource.t<Map.t<int, int>>,
124+
onUpdate: Chan.t<unit>,
121125
}
122126

123127
let toString = self => {
@@ -153,6 +157,7 @@ module Module: Module = {
153157
decorations: Map.make(),
154158
holes: Map.make(),
155159
holePositions: Resource.make(),
160+
onUpdate: Chan.make(),
156161
}
157162

158163
let insertWithVSCodeOffsets = (self, token: Token.t<vscodeOffset>) => {
@@ -254,6 +259,8 @@ module Module: Module = {
254259
}
255260
}
256261

262+
let destroyUpdateChannel = self => self.onUpdate->Chan.destroy
263+
257264
let toTokenArray = self => self.agdaTokens->AVLTree.toArray->Array.map(snd)
258265
let toDecorations = self => self.decorations
259266

@@ -448,6 +455,8 @@ module Module: Module = {
448455
applyDecorations(self, editor)
449456
// set the holes positions
450457
self.holePositions->Resource.set(holePositions)
458+
459+
self.onUpdate->Chan.emit()
451460
}
452461

453462
// Update the deltas and generate new tokens
@@ -482,6 +491,8 @@ module Module: Module = {
482491
None,
483492
)
484493

494+
let onUpdate = self => self.onUpdate
495+
485496
let getVSCodeTokens = self => self.vscodeTokens
486497
let getHolePositionsFromLoad = self => self.holePositions
487498
let getHoles = self => {

test/tests/Test__Tokens.res

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ open Tokens
66
describe("Tokens", () => {
77
This.timeout(10000)
88
describe("Token generation", () => {
9+
Async.it(
10+
"should emit `onUpdate` event when highlighting is generated",
11+
async () => {
12+
let ctx = await AgdaMode.makeAndLoad("GotoDefinition.agda")
13+
let (promise, resolve, _) = Util.Promise_.pending()
14+
15+
let _disposable = ctx.state.tokens->Tokens.onUpdate->Chan.on(resolve)
16+
17+
ctx.state.tokens->Tokens.generateHighlighting(ctx.state.editor)
18+
19+
await promise
20+
Assert.ok(true)
21+
},
22+
)
23+
924
Async.it(
1025
"should produce 28 tokens",
1126
async () => {

0 commit comments

Comments
 (0)