Path: blob/main/src/vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens.ts
5270 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from '../../../../base/common/async.js';6import { Disposable, dispose, IDisposable } from '../../../../base/common/lifecycle.js';7import { ICodeEditor } from '../../../browser/editorBrowser.js';8import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';9import { Range } from '../../../common/core/range.js';10import { IEditorContribution } from '../../../common/editorCommon.js';11import { ITextModel } from '../../../common/model.js';12import { getDocumentRangeSemanticTokens, hasDocumentRangeSemanticTokensProvider } from '../common/getSemanticTokens.js';13import { isSemanticColoringEnabled, SEMANTIC_HIGHLIGHTING_SETTING_ID } from '../common/semanticTokensConfig.js';14import { toMultilineTokens2 } from '../../../common/services/semanticTokensProviderStyling.js';15import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';16import { IThemeService } from '../../../../platform/theme/common/themeService.js';17import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';18import { StopWatch } from '../../../../base/common/stopwatch.js';19import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';20import { DocumentRangeSemanticTokensProvider } from '../../../common/languages.js';21import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';22import { ISemanticTokensStylingService } from '../../../common/services/semanticTokensStyling.js';2324export class ViewportSemanticTokensContribution extends Disposable implements IEditorContribution {2526public static readonly ID = 'editor.contrib.viewportSemanticTokens';2728public static get(editor: ICodeEditor): ViewportSemanticTokensContribution | null {29return editor.getContribution<ViewportSemanticTokensContribution>(ViewportSemanticTokensContribution.ID);30}3132private readonly _editor: ICodeEditor;33private readonly _provider: LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>;34private readonly _debounceInformation: IFeatureDebounceInformation;35private readonly _tokenizeViewport: RunOnceScheduler;36private _outstandingRequests: CancelablePromise<unknown>[];37private _rangeProvidersChangeListeners: IDisposable[];3839constructor(40editor: ICodeEditor,41@ISemanticTokensStylingService private readonly _semanticTokensStylingService: ISemanticTokensStylingService,42@IThemeService private readonly _themeService: IThemeService,43@IConfigurationService private readonly _configurationService: IConfigurationService,44@ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService,45@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,46) {47super();48this._editor = editor;49this._provider = languageFeaturesService.documentRangeSemanticTokensProvider;50this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentRangeSemanticTokens', { min: 100, max: 500 });51this._tokenizeViewport = this._register(new RunOnceScheduler(() => this._tokenizeViewportNow(), 100));52this._outstandingRequests = [];53this._rangeProvidersChangeListeners = [];54const scheduleTokenizeViewport = () => {55if (this._editor.hasModel()) {56this._tokenizeViewport.schedule(this._debounceInformation.get(this._editor.getModel()));57}58};59const bindRangeProvidersChangeListeners = () => {60this._cleanupProviderListeners();61if (this._editor.hasModel()) {62const model = this._editor.getModel();63for (const provider of this._provider.all(model)) {64const disposable = provider.onDidChange?.(() => {65this._cancelAll();66scheduleTokenizeViewport();67});68if (disposable) {69this._rangeProvidersChangeListeners.push(disposable);70}71}72}73};7475this._register(this._editor.onDidScrollChange(() => {76scheduleTokenizeViewport();77}));78this._register(this._editor.onDidChangeModel(() => {79bindRangeProvidersChangeListeners();80this._cancelAll();81scheduleTokenizeViewport();82}));83this._register(this._editor.onDidChangeModelLanguage(() => {84// The cleanup of the model's semantic tokens happens in the DocumentSemanticTokensFeature85bindRangeProvidersChangeListeners();86this._cancelAll();87scheduleTokenizeViewport();88}));89this._register(this._editor.onDidChangeModelContent((e) => {90this._cancelAll();91scheduleTokenizeViewport();92}));9394bindRangeProvidersChangeListeners();95this._register(this._provider.onDidChange(() => {96bindRangeProvidersChangeListeners();97this._cancelAll();98scheduleTokenizeViewport();99}));100this._register(this._configurationService.onDidChangeConfiguration(e => {101if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) {102this._cancelAll();103scheduleTokenizeViewport();104}105}));106this._register(this._themeService.onDidColorThemeChange(() => {107this._cancelAll();108scheduleTokenizeViewport();109}));110scheduleTokenizeViewport();111}112113public override dispose(): void {114this._cleanupProviderListeners();115super.dispose();116}117118private _cleanupProviderListeners(): void {119dispose(this._rangeProvidersChangeListeners);120this._rangeProvidersChangeListeners = [];121}122123private _cancelAll(): void {124for (const request of this._outstandingRequests) {125request.cancel();126}127this._outstandingRequests = [];128}129130private _removeOutstandingRequest(req: CancelablePromise<unknown>): void {131for (let i = 0, len = this._outstandingRequests.length; i < len; i++) {132if (this._outstandingRequests[i] === req) {133this._outstandingRequests.splice(i, 1);134return;135}136}137}138139private _tokenizeViewportNow(): void {140if (!this._editor.hasModel()) {141return;142}143const model = this._editor.getModel();144if (model.tokenization.hasCompleteSemanticTokens()) {145return;146}147if (!isSemanticColoringEnabled(model, this._themeService, this._configurationService)) {148if (model.tokenization.hasSomeSemanticTokens()) {149model.tokenization.setSemanticTokens(null, false);150}151return;152}153if (!hasDocumentRangeSemanticTokensProvider(this._provider, model)) {154if (model.tokenization.hasSomeSemanticTokens()) {155model.tokenization.setSemanticTokens(null, false);156}157return;158}159const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow();160161this._outstandingRequests = this._outstandingRequests.concat(visibleRanges.map(range => this._requestRange(model, range)));162}163164private _requestRange(model: ITextModel, range: Range): CancelablePromise<unknown> {165const requestVersionId = model.getVersionId();166const request = createCancelablePromise(token => Promise.resolve(getDocumentRangeSemanticTokens(this._provider, model, range, token)));167const sw = new StopWatch(false);168request.then((r) => {169this._debounceInformation.update(model, sw.elapsed());170if (!r || !r.tokens || model.isDisposed() || model.getVersionId() !== requestVersionId) {171return;172}173const { provider, tokens: result } = r;174const styling = this._semanticTokensStylingService.getStyling(provider);175model.tokenization.setPartialSemanticTokens(range, toMultilineTokens2(result, styling, model.getLanguageId()));176}).then(() => this._removeOutstandingRequest(request), () => this._removeOutstandingRequest(request));177return request;178}179}180181registerEditorContribution(ViewportSemanticTokensContribution.ID, ViewportSemanticTokensContribution, EditorContributionInstantiation.AfterFirstRender);182183184