diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index c61f4c8e6c3..d892eee8f62 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1589,6 +1589,7 @@ export interface SemanticTokensEdits { } export interface DocumentSemanticTokensProvider { + onDidChange?: Event; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: model.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 20e32b9d2a7..67deb4ee702 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -724,6 +724,7 @@ class ModelSemanticColoring extends Disposable { private readonly _fetchSemanticTokens: RunOnceScheduler; private _currentResponse: SemanticTokensResponse | null; private _currentRequestCancellationTokenSource: CancellationTokenSource | null; + private _providersChangeListeners: IDisposable[]; constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { super(); @@ -734,13 +735,28 @@ class ModelSemanticColoring extends Disposable { this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300)); this._currentResponse = null; this._currentRequestCancellationTokenSource = null; + this._providersChangeListeners = []; this._register(this._model.onDidChangeContent(e => { if (!this._fetchSemanticTokens.isScheduled()) { this._fetchSemanticTokens.schedule(); } })); - this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); + const bindChangeListeners = () => { + dispose(this._providersChangeListeners); + this._providersChangeListeners = []; + for (const provider of DocumentSemanticTokensProviderRegistry.all(model)) { + if (typeof provider.onDidChange === 'function') { + this._providersChangeListeners.push(provider.onDidChange(() => this._fetchSemanticTokens.schedule(0))); + } + } + }; + bindChangeListeners(); + this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => { + bindChangeListeners(); + this._fetchSemanticTokens.schedule(); + })); + if (themeService) { // workaround for tests which use undefined... :/ this._register(themeService.onThemeChange(_ => { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e740dbc105e..63ee827cd7e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6183,6 +6183,7 @@ declare namespace monaco.languages { } export interface DocumentSemanticTokensProvider { + onDidChange?: IEvent; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: editor.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b880c7555d8..c5a0c707ce5 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -257,6 +257,11 @@ declare module 'vscode' { * semantic tokens. */ export interface DocumentSemanticTokensProvider { + /** + * An optional event to signal that the semantic tokens from this provider have changed. + */ + onDidChangeSemanticTokens?: Event; + /** * A file can contain many tokens, perhaps even hundreds of thousands of tokens. Therefore, to improve * the memory consumption around describing semantic tokens, we have decided to avoid allocating an object diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 4287e53a8c0..c079a43ef36 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { ITextModel, ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -367,8 +367,21 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- semantic tokens - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { - this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend))); + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void { + let event: Event | undefined = undefined; + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations.set(eventHandle, emitter); + event = emitter.event; + } + this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend, event))); + } + + $emitDocumentSemanticTokensEvent(eventHandle: number): void { + const obj = this._registrations.get(eventHandle); + if (obj instanceof Emitter) { + obj.fire(undefined); + } } $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { @@ -661,6 +674,7 @@ export class MainThreadDocumentSemanticTokensProvider implements modes.DocumentS private readonly _proxy: ExtHostLanguageFeaturesShape, private readonly _handle: number, private readonly _legend: modes.SemanticTokensLegend, + public readonly onDidChange: Event | undefined, ) { } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d9b76522d9a..9394de339c0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -367,7 +367,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void; + $emitDocumentSemanticTokensEvent(eventHandle: number): void; $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 479a5126b3c..a2d7ca79b98 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1702,9 +1702,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF //#region semantic coloring registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { - const handle = this._addNewAdapter(new DocumentSemanticTokensAdapter(this._documents, provider), extension); - this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); - return this._createDisposable(handle); + const handle = this._nextHandle(); + const eventHandle = (typeof provider.onDidChangeSemanticTokens === 'function' ? this._nextHandle() : undefined); + + this._adapter.set(handle, new AdapterData(new DocumentSemanticTokensAdapter(this._documents, provider), extension)); + this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend, eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle) { + const subscription = provider.onDidChangeSemanticTokens!(_ => this._proxy.$emitDocumentSemanticTokensEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + + return result; } $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise {