monaco-editor-core
Version:
A browser based code editor
361 lines (360 loc) • 18.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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); }
};
var ModelSemanticColoring_1;
import { Disposable, dispose } from '../../../../base/common/lifecycle.js';
import * as errors from '../../../../base/common/errors.js';
import { IModelService } from '../../../common/services/model.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 { toMultilineTokens2 } from '../../../common/services/semanticTokensProviderStyling.js';
import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from '../common/getSemanticTokens.js';
import { ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';
import { StopWatch } from '../../../../base/common/stopwatch.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { ISemanticTokensStylingService } from '../../../common/services/semanticTokensStyling.js';
import { registerEditorFeature } from '../../../common/editorFeatures.js';
import { SEMANTIC_HIGHLIGHTING_SETTING_ID, isSemanticColoringEnabled } from '../common/semanticTokensConfig.js';
let DocumentSemanticTokensFeature = class DocumentSemanticTokensFeature extends Disposable {
constructor(semanticTokensStylingService, modelService, themeService, configurationService, languageFeatureDebounceService, languageFeaturesService) {
super();
this._watchers = Object.create(null);
const register = (model) => {
this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService);
};
const deregister = (model, modelSemanticColoring) => {
modelSemanticColoring.dispose();
delete this._watchers[model.uri.toString()];
};
const handleSettingOrThemeChange = () => {
for (const 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);
}
}
}
};
modelService.getModels().forEach(model => {
if (isSemanticColoringEnabled(model, themeService, configurationService)) {
register(model);
}
});
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));
}
dispose() {
// Dispose all watchers
for (const watcher of Object.values(this._watchers)) {
watcher.dispose();
}
super.dispose();
}
};
DocumentSemanticTokensFeature = __decorate([
__param(0, ISemanticTokensStylingService),
__param(1, IModelService),
__param(2, IThemeService),
__param(3, IConfigurationService),
__param(4, ILanguageFeatureDebounceService),
__param(5, ILanguageFeaturesService)
], DocumentSemanticTokensFeature);
export { DocumentSemanticTokensFeature };
let ModelSemanticColoring = class ModelSemanticColoring extends Disposable {
static { ModelSemanticColoring_1 = this; }
static { this.REQUEST_MIN_DELAY = 300; }
static { this.REQUEST_MAX_DELAY = 2000; }
constructor(model, _semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService) {
super();
this._semanticTokensStylingService = _semanticTokensStylingService;
this._isDisposed = false;
this._model = model;
this._provider = languageFeaturesService.documentSemanticTokensProvider;
this._debounceInformation = languageFeatureDebounceService.for(this._provider, 'DocumentSemanticTokens', { min: ModelSemanticColoring_1.REQUEST_MIN_DELAY, max: ModelSemanticColoring_1.REQUEST_MAX_DELAY });
this._fetchDocumentSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchDocumentSemanticTokensNow(), ModelSemanticColoring_1.REQUEST_MIN_DELAY));
this._currentDocumentResponse = null;
this._currentDocumentRequestCancellationTokenSource = null;
this._documentProvidersChangeListeners = [];
this._providersChangedDuringRequest = false;
this._register(this._model.onDidChangeContent(() => {
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}
}));
this._register(this._model.onDidChangeAttached(() => {
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}
}));
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 this._provider.all(model)) {
if (typeof provider.onDidChange === 'function') {
this._documentProvidersChangeListeners.push(provider.onDidChange(() => {
if (this._currentDocumentRequestCancellationTokenSource) {
// there is already a request running,
this._providersChangedDuringRequest = true;
return;
}
this._fetchDocumentSemanticTokens.schedule(0);
}));
}
}
};
bindDocumentChangeListeners();
this._register(this._provider.onDidChange(() => {
bindDocumentChangeListeners();
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}));
this._register(themeService.onDidColorThemeChange(_ => {
// clear out existing tokens
this._setDocumentSemanticTokens(null, null, null, []);
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}));
this._fetchDocumentSemanticTokens.schedule(0);
}
dispose() {
if (this._currentDocumentResponse) {
this._currentDocumentResponse.dispose();
this._currentDocumentResponse = null;
}
if (this._currentDocumentRequestCancellationTokenSource) {
this._currentDocumentRequestCancellationTokenSource.cancel();
this._currentDocumentRequestCancellationTokenSource = null;
}
dispose(this._documentProvidersChangeListeners);
this._documentProvidersChangeListeners = [];
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._provider, this._model)) {
// there is no provider
if (this._currentDocumentResponse) {
// there are semantic tokens set
this._model.tokenization.setSemanticTokens(null, false);
}
return;
}
if (!this._model.isAttachedToEditor()) {
// this document is not visible, there is no need to fetch semantic tokens for it
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._provider, this._model, lastProvider, lastResultId, cancellationTokenSource.token);
this._currentDocumentRequestCancellationTokenSource = cancellationTokenSource;
this._providersChangedDuringRequest = false;
const pendingChanges = [];
const contentChangeListener = this._model.onDidChangeContent((e) => {
pendingChanges.push(e);
});
const sw = new StopWatch(false);
request.then((res) => {
this._debounceInformation.update(this._model, sw.elapsed());
this._currentDocumentRequestCancellationTokenSource = null;
contentChangeListener.dispose();
if (!res) {
this._setDocumentSemanticTokens(null, null, null, pendingChanges);
}
else {
const { provider, tokens } = res;
const styling = this._semanticTokensStylingService.getStyling(provider);
this._setDocumentSemanticTokens(provider, tokens || null, styling, pendingChanges);
}
}, (err) => {
const isExpectedError = err && (errors.isCancellationError(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 || this._providersChangedDuringRequest) {
// More changes occurred while the request was running
if (!this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}
}
});
}
static _copy(src, srcOffset, dest, destOffset, length) {
// protect against overflows
length = Math.min(length, dest.length - destOffset, src.length - srcOffset);
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._providersChangedDuringRequest) && !this._fetchDocumentSemanticTokens.isScheduled()) {
this._fetchDocumentSemanticTokens.schedule(this._debounceInformation.get(this._model));
}
};
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.tokenization.setSemanticTokens(null, false);
return;
}
if (!tokens) {
this._model.tokenization.setSemanticTokens(null, true);
rescheduleIfNeeded();
return;
}
if (isSemanticTokensEdits(tokens)) {
if (!currentResponse) {
// not possible!
this._model.tokenization.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];
if (edit.start > srcData.length) {
styling.warnInvalidEditStart(currentResponse.resultId, tokens.resultId, i, edit.start, srcData.length);
// The edits are invalid and there's no way to recover
this._model.tokenization.setSemanticTokens(null, true);
return;
}
const copyCount = srcLastStart - (edit.start + edit.deleteCount);
if (copyCount > 0) {
ModelSemanticColoring_1._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount);
destLastStart -= copyCount;
}
if (edit.data) {
ModelSemanticColoring_1._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length);
destLastStart -= edit.data.length;
}
srcLastStart = edit.start;
}
if (srcLastStart > 0) {
ModelSemanticColoring_1._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.tokenization.setSemanticTokens(result, true);
}
else {
this._model.tokenization.setSemanticTokens(null, true);
}
rescheduleIfNeeded();
}
};
ModelSemanticColoring = ModelSemanticColoring_1 = __decorate([
__param(1, ISemanticTokensStylingService),
__param(2, IThemeService),
__param(3, ILanguageFeatureDebounceService),
__param(4, ILanguageFeaturesService)
], ModelSemanticColoring);
class SemanticTokensResponse {
constructor(provider, resultId, data) {
this.provider = provider;
this.resultId = resultId;
this.data = data;
}
dispose() {
this.provider.releaseDocumentSemanticTokens(this.resultId);
}
}
registerEditorFeature(DocumentSemanticTokensFeature);