/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { Emitter } from '../../../base/common/event.js'; import { Disposable, DisposableStore, dispose } from '../../../base/common/lifecycle.js'; import * as platform from '../../../base/common/platform.js'; import * as errors from '../../../base/common/errors.js'; import { EDITOR_MODEL_DEFAULTS } from '../config/editorOptions.js'; import { TextModel } from '../model/textModel.js'; import { DocumentSemanticTokensProviderRegistry } from '../modes.js'; import { PLAINTEXT_MODE_ID } from '../modes/modesRegistry.js'; import { IModeService } from './modeService.js'; import { ITextResourcePropertiesService } from './textResourceConfigurationService.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { RunOnceScheduler } from '../../../base/common/async.js'; import { CancellationTokenSource } from '../../../base/common/cancellation.js'; import { IThemeService } from '../../../platform/theme/common/themeService.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { IUndoRedoService } from '../../../platform/undoRedo/common/undoRedo.js'; import { StringSHA1 } from '../../../base/common/hash.js'; import { isEditStackElement } from '../model/editStack.js'; import { Schemas } from '../../../base/common/network.js'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from './semanticTokensProviderStyling.js'; import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from './getSemanticTokens.js'; import { equals } from '../../../base/common/objects.js'; import { ILanguageConfigurationService } from '../modes/languageConfigurationRegistry.js'; function MODEL_ID(resource) { return resource.toString(); } function computeModelSha1(model) { // compute the sha1 const shaComputer = new StringSHA1(); const snapshot = model.createSnapshot(); let text; while ((text = snapshot.read())) { shaComputer.update(text); } return shaComputer.digest(); } class ModelData { constructor(model, onWillDispose, onDidChangeLanguage) { this._modelEventListeners = new DisposableStore(); this.model = model; this._languageSelection = null; this._languageSelectionListener = null; this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model))); this._modelEventListeners.add(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e))); } _disposeLanguageSelection() { if (this._languageSelectionListener) { this._languageSelectionListener.dispose(); this._languageSelectionListener = null; } } dispose() { this._modelEventListeners.dispose(); this._disposeLanguageSelection(); } setLanguage(languageSelection) { this._disposeLanguageSelection(); this._languageSelection = languageSelection; this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageId)); this.model.setMode(languageSelection.languageId); } } const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? 1 /* LF */ : 2 /* CRLF */; class DisposedModelInfo { constructor(uri, initialUndoRedoSnapshot, time, sharesUndoRedoStack, heapSize, sha1, versionId, alternativeVersionId) { this.uri = uri; this.initialUndoRedoSnapshot = initialUndoRedoSnapshot; this.time = time; this.sharesUndoRedoStack = sharesUndoRedoStack; this.heapSize = heapSize; this.sha1 = sha1; this.versionId = versionId; this.alternativeVersionId = alternativeVersionId; } } let ModelServiceImpl = class ModelServiceImpl extends Disposable { constructor(_configurationService, _resourcePropertiesService, _themeService, _logService, _undoRedoService, _modeService, _languageConfigurationService) { super(); this._configurationService = _configurationService; this._resourcePropertiesService = _resourcePropertiesService; this._themeService = _themeService; this._logService = _logService; this._undoRedoService = _undoRedoService; this._modeService = _modeService; this._languageConfigurationService = _languageConfigurationService; this._onModelAdded = this._register(new Emitter()); this.onModelAdded = this._onModelAdded.event; this._onModelRemoved = this._register(new Emitter()); this.onModelRemoved = this._onModelRemoved.event; this._onModelModeChanged = this._register(new Emitter()); this.onModelModeChanged = this._onModelModeChanged.event; this._modelCreationOptionsByLanguageAndResource = Object.create(null); this._models = {}; this._disposedModels = new Map(); this._disposedModelsHeapSize = 0; this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._modeService, this._logService)); this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions())); this._updateModelOptions(); this._register(new SemanticColoringFeature(this, this._themeService, this._configurationService, this._semanticStyling)); } static _readModelOptions(config, isForSimpleWidget) { var _a; let tabSize = EDITOR_MODEL_DEFAULTS.tabSize; if (config.editor && typeof config.editor.tabSize !== 'undefined') { const parsedTabSize = parseInt(config.editor.tabSize, 10); if (!isNaN(parsedTabSize)) { tabSize = parsedTabSize; } if (tabSize < 1) { tabSize = 1; } } let indentSize = tabSize; if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') { const parsedIndentSize = parseInt(config.editor.indentSize, 10); if (!isNaN(parsedIndentSize)) { indentSize = parsedIndentSize; } if (indentSize < 1) { indentSize = 1; } } let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces; if (config.editor && typeof config.editor.insertSpaces !== 'undefined') { insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); } let newDefaultEOL = DEFAULT_EOL; const eol = config.eol; if (eol === '\r\n') { newDefaultEOL = 2 /* CRLF */; } else if (eol === '\n') { newDefaultEOL = 1 /* LF */; } let trimAutoWhitespace = EDITOR_MODEL_DEFAULTS.trimAutoWhitespace; if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') { trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace)); } let detectIndentation = EDITOR_MODEL_DEFAULTS.detectIndentation; if (config.editor && typeof config.editor.detectIndentation !== 'undefined') { detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation)); } let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations; if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') { largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations)); } let bracketPairColorizationOptions = EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions; if (((_a = config.editor) === null || _a === void 0 ? void 0 : _a.bracketPairColorization) && typeof config.editor.bracketPairColorization === 'object') { bracketPairColorizationOptions = { enabled: !!config.editor.bracketPairColorization.enabled }; } return { isForSimpleWidget: isForSimpleWidget, tabSize: tabSize, indentSize: indentSize, insertSpaces: insertSpaces, detectIndentation: detectIndentation, defaultEOL: newDefaultEOL, trimAutoWhitespace: trimAutoWhitespace, largeFileOptimizations: largeFileOptimizations, bracketPairColorizationOptions }; } _getEOL(resource, language) { if (resource) { return this._resourcePropertiesService.getEOL(resource, language); } const eol = this._configurationService.getValue('files.eol', { overrideIdentifier: language }); if (eol && typeof eol === 'string' && eol !== 'auto') { return eol; } return platform.OS === 3 /* Linux */ || platform.OS === 2 /* Macintosh */ ? '\n' : '\r\n'; } _shouldRestoreUndoStack() { const result = this._configurationService.getValue('files.restoreUndoStack'); if (typeof result === 'boolean') { return result; } return true; } getCreationOptions(language, resource, isForSimpleWidget) { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { const editor = this._configurationService.getValue('editor', { overrideIdentifier: language, resource }); const eol = this._getEOL(resource, language); creationOptions = ModelServiceImpl._readModelOptions({ editor, eol }, isForSimpleWidget); this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions; } return creationOptions; } _updateModelOptions() { const oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource; this._modelCreationOptionsByLanguageAndResource = Object.create(null); // Update options on all models const keys = Object.keys(this._models); for (let i = 0, len = keys.length; i < len; i++) { const modelId = keys[i]; const modelData = this._models[modelId]; const language = modelData.model.getLanguageId(); const uri = modelData.model.uri; const oldOptions = oldOptionsByLanguageAndResource[language + uri]; const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget); ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions); } } static _setModelOptionsForModel(model, newOptions, currentOptions) { if (currentOptions && currentOptions.defaultEOL !== newOptions.defaultEOL && model.getLineCount() === 1) { model.setEOL(newOptions.defaultEOL === 1 /* LF */ ? 0 /* LF */ : 1 /* CRLF */); } if (currentOptions && (currentOptions.detectIndentation === newOptions.detectIndentation) && (currentOptions.insertSpaces === newOptions.insertSpaces) && (currentOptions.tabSize === newOptions.tabSize) && (currentOptions.indentSize === newOptions.indentSize) && (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace) && equals(currentOptions.bracketPairColorizationOptions, newOptions.bracketPairColorizationOptions)) { // Same indent opts, no need to touch the model return; } if (newOptions.detectIndentation) { model.detectIndentation(newOptions.insertSpaces, newOptions.tabSize); model.updateOptions({ trimAutoWhitespace: newOptions.trimAutoWhitespace, bracketColorizationOptions: newOptions.bracketPairColorizationOptions }); } else { model.updateOptions({ insertSpaces: newOptions.insertSpaces, tabSize: newOptions.tabSize, indentSize: newOptions.indentSize, trimAutoWhitespace: newOptions.trimAutoWhitespace, bracketColorizationOptions: newOptions.bracketPairColorizationOptions }); } } // --- begin IModelService _insertDisposedModel(disposedModelData) { this._disposedModels.set(MODEL_ID(disposedModelData.uri), disposedModelData); this._disposedModelsHeapSize += disposedModelData.heapSize; } _removeDisposedModel(resource) { const disposedModelData = this._disposedModels.get(MODEL_ID(resource)); if (disposedModelData) { this._disposedModelsHeapSize -= disposedModelData.heapSize; } this._disposedModels.delete(MODEL_ID(resource)); return disposedModelData; } _ensureDisposedModelsHeapSize(maxModelsHeapSize) { if (this._disposedModelsHeapSize > maxModelsHeapSize) { // we must remove some old undo stack elements to free up some memory const disposedModels = []; this._disposedModels.forEach(entry => { if (!entry.sharesUndoRedoStack) { disposedModels.push(entry); } }); disposedModels.sort((a, b) => a.time - b.time); while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) { const disposedModel = disposedModels.shift(); this._removeDisposedModel(disposedModel.uri); if (disposedModel.initialUndoRedoSnapshot !== null) { this._undoRedoService.restoreSnapshot(disposedModel.initialUndoRedoSnapshot); } } } } _createModelData(value, languageId, resource, isForSimpleWidget) { // create & save the model const options = this.getCreationOptions(languageId, resource, isForSimpleWidget); const model = new TextModel(value, options, languageId, resource, this._undoRedoService, this._modeService, this._languageConfigurationService); if (resource && this._disposedModels.has(MODEL_ID(resource))) { const disposedModelData = this._removeDisposedModel(resource); const elements = this._undoRedoService.getElements(resource); const sha1IsEqual = (computeModelSha1(model) === disposedModelData.sha1); if (sha1IsEqual || disposedModelData.sharesUndoRedoStack) { for (const element of elements.past) { if (isEditStackElement(element) && element.matchesResource(resource)) { element.setModel(model); } } for (const element of elements.future) { if (isEditStackElement(element) && element.matchesResource(resource)) { element.setModel(model); } } this._undoRedoService.setElementsValidFlag(resource, true, (element) => (isEditStackElement(element) && element.matchesResource(resource))); if (sha1IsEqual) { model._overwriteVersionId(disposedModelData.versionId); model._overwriteAlternativeVersionId(disposedModelData.alternativeVersionId); model._overwriteInitialUndoRedoSnapshot(disposedModelData.initialUndoRedoSnapshot); } } else { if (disposedModelData.initialUndoRedoSnapshot !== null) { this._undoRedoService.restoreSnapshot(disposedModelData.initialUndoRedoSnapshot); } } } const modelId = MODEL_ID(model.uri); if (this._models[modelId]) { // There already exists a model with this id => this is a programmer error throw new Error('ModelService: Cannot add model because it already exists!'); } const modelData = new ModelData(model, (model) => this._onWillDispose(model), (model, e) => this._onDidChangeLanguage(model, e)); this._models[modelId] = modelData; return modelData; } createModel(value, languageSelection, resource, isForSimpleWidget = false) { let modelData; if (languageSelection) { modelData = this._createModelData(value, languageSelection.languageId, resource, isForSimpleWidget); this.setMode(modelData.model, languageSelection); } else { modelData = this._createModelData(value, PLAINTEXT_MODE_ID, resource, isForSimpleWidget); } this._onModelAdded.fire(modelData.model); return modelData.model; } setMode(model, languageSelection) { if (!languageSelection) { return; } const modelData = this._models[MODEL_ID(model.uri)]; if (!modelData) { return; } modelData.setLanguage(languageSelection); } getModels() { const ret = []; const keys = Object.keys(this._models); for (let i = 0, len = keys.length; i < len; i++) { const modelId = keys[i]; ret.push(this._models[modelId].model); } return ret; } getModel(resource) { const modelId = MODEL_ID(resource); const modelData = this._models[modelId]; if (!modelData) { return null; } return modelData.model; } getSemanticTokensProviderStyling(provider) { return this._semanticStyling.get(provider); } // --- end IModelService _schemaShouldMaintainUndoRedoElements(resource) { return (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData || resource.scheme === Schemas.vscodeNotebookCell || resource.scheme === 'fake-fs' // for tests ); } _onWillDispose(model) { const modelId = MODEL_ID(model.uri); const modelData = this._models[modelId]; const sharesUndoRedoStack = (this._undoRedoService.getUriComparisonKey(model.uri) !== model.uri.toString()); let maintainUndoRedoStack = false; let heapSize = 0; if (sharesUndoRedoStack || (this._shouldRestoreUndoStack() && this._schemaShouldMaintainUndoRedoElements(model.uri))) { const elements = this._undoRedoService.getElements(model.uri); if (elements.past.length > 0 || elements.future.length > 0) { for (const element of elements.past) { if (isEditStackElement(element) && element.matchesResource(model.uri)) { maintainUndoRedoStack = true; heapSize += element.heapSize(model.uri); element.setModel(model.uri); // remove reference from text buffer instance } } for (const element of elements.future) { if (isEditStackElement(element) && element.matchesResource(model.uri)) { maintainUndoRedoStack = true; heapSize += element.heapSize(model.uri); element.setModel(model.uri); // remove reference from text buffer instance } } } } const maxMemory = ModelServiceImpl.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK; if (!maintainUndoRedoStack) { if (!sharesUndoRedoStack) { const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); if (initialUndoRedoSnapshot !== null) { this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); } } } else if (!sharesUndoRedoStack && heapSize > maxMemory) { // the undo stack for this file would never fit in the configured memory, so don't bother with it. const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); if (initialUndoRedoSnapshot !== null) { this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); } } else { this._ensureDisposedModelsHeapSize(maxMemory - heapSize); // We only invalidate the elements, but they remain in the undo-redo service. this._undoRedoService.setElementsValidFlag(model.uri, false, (element) => (isEditStackElement(element) && element.matchesResource(model.uri))); this._insertDisposedModel(new DisposedModelInfo(model.uri, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); } delete this._models[modelId]; modelData.dispose(); // clean up cache delete this._modelCreationOptionsByLanguageAndResource[model.getLanguageId() + model.uri]; this._onModelRemoved.fire(model); } _onDidChangeLanguage(model, e) { const oldModeId = e.oldLanguage; const newModeId = model.getLanguageId(); const oldOptions = this.getCreationOptions(oldModeId, model.uri, model.isForSimpleWidget); const newOptions = this.getCreationOptions(newModeId, model.uri, model.isForSimpleWidget); ModelServiceImpl._setModelOptionsForModel(model, newOptions, oldOptions); this._onModelModeChanged.fire({ model, oldModeId }); } }; ModelServiceImpl.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK = 20 * 1024 * 1024; ModelServiceImpl = __decorate([ __param(0, IConfigurationService), __param(1, ITextResourcePropertiesService), __param(2, IThemeService), __param(3, ILogService), __param(4, IUndoRedoService), __param(5, IModeService), __param(6, ILanguageConfigurationService) ], ModelServiceImpl); export { ModelServiceImpl }; export const SEMANTIC_HIGHLIGHTING_SETTING_ID = 'editor.semanticHighlighting'; export function isSemanticColoringEnabled(model, themeService, configurationService) { var _a; const setting = (_a = configurationService.getValue(SEMANTIC_HIGHLIGHTING_SETTING_ID, { overrideIdentifier: model.getLanguageId(), resource: model.uri })) === null || _a === void 0 ? void 0 : _a.enabled; if (typeof setting === 'boolean') { return setting; } return themeService.getColorTheme().semanticHighlighting; } class SemanticColoringFeature extends Disposable { constructor(modelService, themeService, configurationService, semanticStyling) { super(); this._watchers = Object.create(null); this._semanticStyling = semanticStyling; const register = (model) => { this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); }; const deregister = (model, modelSemanticColoring) => { modelSemanticColoring.dispose(); delete this._watchers[model.uri.toString()]; }; const handleSettingOrThemeChange = () => { for (let model of modelService.getModels()) { const curr = this._watchers[model.uri.toString()]; if (isSemanticColoringEnabled(model, themeService, configurationService)) { if (!curr) { register(model); } } else { if (curr) { deregister(model, curr); } } } }; this._register(modelService.onModelAdded((model) => { if (isSemanticColoringEnabled(model, themeService, configurationService)) { register(model); } })); this._register(modelService.onModelRemoved((model) => { const curr = this._watchers[model.uri.toString()]; if (curr) { deregister(model, curr); } })); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SEMANTIC_HIGHLIGHTING_SETTING_ID)) { handleSettingOrThemeChange(); } })); this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); } } class SemanticStyling extends Disposable { constructor(_themeService, _modeService, _logService) { super(); this._themeService = _themeService; this._modeService = _modeService; this._logService = _logService; this._caches = new WeakMap(); this._register(this._themeService.onDidColorThemeChange(() => { this._caches = new WeakMap(); })); } get(provider) { if (!this._caches.has(provider)) { this._caches.set(provider, new SemanticTokensProviderStyling(provider.getLegend(), this._themeService, this._modeService, this._logService)); } return this._caches.get(provider); } } class SemanticTokensResponse { constructor(provider, resultId, data) { this.provider = provider; this.resultId = resultId; this.data = data; } dispose() { this.provider.releaseDocumentSemanticTokens(this.resultId); } } export class ModelSemanticColoring extends Disposable { constructor(model, themeService, stylingProvider) { super(); this._isDisposed = false; this._model = model; this._semanticStyling = stylingProvider; this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY)); this._currentDocumentResponse = null; this._currentDocumentRequestCancellationTokenSource = null; this._documentProvidersChangeListeners = []; this._register(this._model.onDidChangeContent(() => { if (!this._fetchDocumentSemanticTokens.isScheduled()) { this._fetchDocumentSemanticTokens.schedule(); } })); this._register(this._model.onDidChangeLanguage(() => { // clear any outstanding state if (this._currentDocumentResponse) { this._currentDocumentResponse.dispose(); this._currentDocumentResponse = null; } if (this._currentDocumentRequestCancellationTokenSource) { this._currentDocumentRequestCancellationTokenSource.cancel(); this._currentDocumentRequestCancellationTokenSource = null; } this._setDocumentSemanticTokens(null, null, null, []); this._fetchDocumentSemanticTokens.schedule(0); })); const bindDocumentChangeListeners = () => { dispose(this._documentProvidersChangeListeners); this._documentProvidersChangeListeners = []; for (const provider of DocumentSemanticTokensProviderRegistry.all(model)) { if (typeof provider.onDidChange === 'function') { this._documentProvidersChangeListeners.push(provider.onDidChange(() => this._fetchDocumentSemanticTokens.schedule(0))); } } }; bindDocumentChangeListeners(); this._register(DocumentSemanticTokensProviderRegistry.onDidChange(() => { bindDocumentChangeListeners(); this._fetchDocumentSemanticTokens.schedule(); })); this._register(themeService.onDidColorThemeChange(_ => { // clear out existing tokens this._setDocumentSemanticTokens(null, null, null, []); this._fetchDocumentSemanticTokens.schedule(); })); this._fetchDocumentSemanticTokens.schedule(0); } dispose() { if (this._currentDocumentResponse) { this._currentDocumentResponse.dispose(); this._currentDocumentResponse = null; } if (this._currentDocumentRequestCancellationTokenSource) { this._currentDocumentRequestCancellationTokenSource.cancel(); this._currentDocumentRequestCancellationTokenSource = null; } this._setDocumentSemanticTokens(null, null, null, []); this._isDisposed = true; super.dispose(); } _fetchDocumentSemanticTokensNow() { if (this._currentDocumentRequestCancellationTokenSource) { // there is already a request running, let it finish... return; } if (!hasDocumentSemanticTokensProvider(this._model)) { // there is no provider if (this._currentDocumentResponse) { // there are semantic tokens set this._model.setSemanticTokens(null, false); } return; } const cancellationTokenSource = new CancellationTokenSource(); const lastProvider = this._currentDocumentResponse ? this._currentDocumentResponse.provider : null; const lastResultId = this._currentDocumentResponse ? this._currentDocumentResponse.resultId || null : null; const request = getDocumentSemanticTokens(this._model, lastProvider, lastResultId, cancellationTokenSource.token); this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource; const pendingChanges = []; const contentChangeListener = this._model.onDidChangeContent((e) => { pendingChanges.push(e); }); request.then((res) => { this._currentDocumentRequestCancellationTokenSource = null; contentChangeListener.dispose(); if (!res) { this._setDocumentSemanticTokens(null, null, null, pendingChanges); } else { const { provider, tokens } = res; const styling = this._semanticStyling.get(provider); this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges); } }, (err) => { const isExpectedError = err && (errors.isPromiseCanceledError(err) || (typeof err.message === 'string' && err.message.indexOf('busy') !== -1)); if (!isExpectedError) { errors.onUnexpectedError(err); } // Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available // The API does not have a special error kind to express this... this._currentDocumentRequestCancellationTokenSource = null; contentChangeListener.dispose(); if (pendingChanges.length > 0) { // More changes occurred while the request was running if (!this._fetchDocumentSemanticTokens.isScheduled()) { this._fetchDocumentSemanticTokens.schedule(); } } }); } static _copy(src, srcOffset, dest, destOffset, length) { for (let i = 0; i < length; i++) { dest[destOffset + i] = src[srcOffset + i]; } } _setDocumentSemanticTokens(provider, tokens, styling, pendingChanges) { const currentResponse = this._currentDocumentResponse; const rescheduleIfNeeded = () => { if (pendingChanges.length > 0 && !this._fetchDocumentSemanticTokens.isScheduled()) { this._fetchDocumentSemanticTokens.schedule(); } }; if (this._currentDocumentResponse) { this._currentDocumentResponse.dispose(); this._currentDocumentResponse = null; } if (this._isDisposed) { // disposed! if (provider && tokens) { provider.releaseDocumentSemanticTokens(tokens.resultId); } return; } if (!provider || !styling) { this._model.setSemanticTokens(null, false); return; } if (!tokens) { this._model.setSemanticTokens(null, true); rescheduleIfNeeded(); return; } if (isSemanticTokensEdits(tokens)) { if (!currentResponse) { // not possible! this._model.setSemanticTokens(null, true); return; } if (tokens.edits.length === 0) { // nothing to do! tokens = { resultId: tokens.resultId, data: currentResponse.data }; } else { let deltaLength = 0; for (const edit of tokens.edits) { deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount; } const srcData = currentResponse.data; const destData = new Uint32Array(srcData.length + deltaLength); let srcLastStart = srcData.length; let destLastStart = destData.length; for (let i = tokens.edits.length - 1; i >= 0; i--) { const edit = tokens.edits[i]; const copyCount = srcLastStart - (edit.start + edit.deleteCount); if (copyCount > 0) { ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount); destLastStart -= copyCount; } if (edit.data) { ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length); destLastStart -= edit.data.length; } srcLastStart = edit.start; } if (srcLastStart > 0) { ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart); } tokens = { resultId: tokens.resultId, data: destData }; } } if (isSemanticTokens(tokens)) { this._currentDocumentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data); const result = toMultilineTokens2(tokens, styling, this._model.getLanguageId()); // Adjust incoming semantic tokens if (pendingChanges.length > 0) { // More changes occurred while the request was running // We need to: // 1. Adjust incoming semantic tokens // 2. Request them again for (const change of pendingChanges) { for (const area of result) { for (const singleChange of change.changes) { area.applyEdit(singleChange.range, singleChange.text); } } } } this._model.setSemanticTokens(result, true); } else { this._model.setSemanticTokens(null, true); } rescheduleIfNeeded(); } } ModelSemanticColoring.FETCH_DOCUMENT_SEMANTIC_TOKENS_DELAY = 300;