UNPKG

monaco-editor-core

Version:

A browser based code editor

467 lines (466 loc) • 22.7 kB
/*--------------------------------------------------------------------------------------------- * 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 { createCancelablePromise, disposableTimeout, RunOnceScheduler } from '../../../../base/common/async.js'; import { onUnexpectedError, onUnexpectedExternalError } from '../../../../base/common/errors.js'; import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js'; import { EditorAction, registerEditorAction, registerEditorContribution } from '../../../browser/editorExtensions.js'; import { EDITOR_FONT_DEFAULTS } from '../../../common/config/editorOptions.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { getCodeLensModel } from './codelens.js'; import { ICodeLensCache } from './codeLensCache.js'; import { CodeLensHelper, CodeLensWidget } from './codelensWidget.js'; import { localize } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; import { ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; let CodeLensContribution = class CodeLensContribution { static { this.ID = 'css.editor.codeLens'; } constructor(_editor, _languageFeaturesService, debounceService, _commandService, _notificationService, _codeLensCache) { this._editor = _editor; this._languageFeaturesService = _languageFeaturesService; this._commandService = _commandService; this._notificationService = _notificationService; this._codeLensCache = _codeLensCache; this._disposables = new DisposableStore(); this._localToDispose = new DisposableStore(); this._lenses = []; this._oldCodeLensModels = new DisposableStore(); this._provideCodeLensDebounce = debounceService.for(_languageFeaturesService.codeLensProvider, 'CodeLensProvide', { min: 250 }); this._resolveCodeLensesDebounce = debounceService.for(_languageFeaturesService.codeLensProvider, 'CodeLensResolve', { min: 250, salt: 'resolve' }); this._resolveCodeLensesScheduler = new RunOnceScheduler(() => this._resolveCodeLensesInViewport(), this._resolveCodeLensesDebounce.default()); this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange())); this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); this._disposables.add(this._editor.onDidChangeConfiguration((e) => { if (e.hasChanged(50 /* EditorOption.fontInfo */) || e.hasChanged(19 /* EditorOption.codeLensFontSize */) || e.hasChanged(18 /* EditorOption.codeLensFontFamily */)) { this._updateLensStyle(); } if (e.hasChanged(17 /* EditorOption.codeLens */)) { this._onModelChange(); } })); this._disposables.add(_languageFeaturesService.codeLensProvider.onDidChange(this._onModelChange, this)); this._onModelChange(); this._updateLensStyle(); } dispose() { this._localDispose(); this._disposables.dispose(); this._oldCodeLensModels.dispose(); this._currentCodeLensModel?.dispose(); } _getLayoutInfo() { const lineHeightFactor = Math.max(1.3, this._editor.getOption(67 /* EditorOption.lineHeight */) / this._editor.getOption(52 /* EditorOption.fontSize */)); let fontSize = this._editor.getOption(19 /* EditorOption.codeLensFontSize */); if (!fontSize || fontSize < 5) { fontSize = (this._editor.getOption(52 /* EditorOption.fontSize */) * .9) | 0; } return { fontSize, codeLensHeight: (fontSize * lineHeightFactor) | 0, }; } _updateLensStyle() { const { codeLensHeight, fontSize } = this._getLayoutInfo(); const fontFamily = this._editor.getOption(18 /* EditorOption.codeLensFontFamily */); const editorFontInfo = this._editor.getOption(50 /* EditorOption.fontInfo */); const { style } = this._editor.getContainerDomNode(); style.setProperty('--vscode-editorCodeLens-lineHeight', `${codeLensHeight}px`); style.setProperty('--vscode-editorCodeLens-fontSize', `${fontSize}px`); style.setProperty('--vscode-editorCodeLens-fontFeatureSettings', editorFontInfo.fontFeatureSettings); if (fontFamily) { style.setProperty('--vscode-editorCodeLens-fontFamily', fontFamily); style.setProperty('--vscode-editorCodeLens-fontFamilyDefault', EDITOR_FONT_DEFAULTS.fontFamily); } // this._editor.changeViewZones(accessor => { for (const lens of this._lenses) { lens.updateHeight(codeLensHeight, accessor); } }); } _localDispose() { this._getCodeLensModelPromise?.cancel(); this._getCodeLensModelPromise = undefined; this._resolveCodeLensesPromise?.cancel(); this._resolveCodeLensesPromise = undefined; this._localToDispose.clear(); this._oldCodeLensModels.clear(); this._currentCodeLensModel?.dispose(); } _onModelChange() { this._localDispose(); const model = this._editor.getModel(); if (!model) { return; } if (!this._editor.getOption(17 /* EditorOption.codeLens */) || model.isTooLargeForTokenization()) { return; } const cachedLenses = this._codeLensCache.get(model); if (cachedLenses) { this._renderCodeLensSymbols(cachedLenses); } if (!this._languageFeaturesService.codeLensProvider.has(model)) { // no provider -> return but check with // cached lenses. they expire after 30 seconds if (cachedLenses) { disposableTimeout(() => { const cachedLensesNow = this._codeLensCache.get(model); if (cachedLenses === cachedLensesNow) { this._codeLensCache.delete(model); this._onModelChange(); } }, 30 * 1000, this._localToDispose); } return; } for (const provider of this._languageFeaturesService.codeLensProvider.all(model)) { if (typeof provider.onDidChange === 'function') { const registration = provider.onDidChange(() => scheduler.schedule()); this._localToDispose.add(registration); } } const scheduler = new RunOnceScheduler(() => { const t1 = Date.now(); this._getCodeLensModelPromise?.cancel(); this._getCodeLensModelPromise = createCancelablePromise(token => getCodeLensModel(this._languageFeaturesService.codeLensProvider, model, token)); this._getCodeLensModelPromise.then(result => { if (this._currentCodeLensModel) { this._oldCodeLensModels.add(this._currentCodeLensModel); } this._currentCodeLensModel = result; // cache model to reduce flicker this._codeLensCache.put(model, result); // update moving average const newDelay = this._provideCodeLensDebounce.update(model, Date.now() - t1); scheduler.delay = newDelay; // render lenses this._renderCodeLensSymbols(result); // dom.scheduleAtNextAnimationFrame(() => this._resolveCodeLensesInViewport()); this._resolveCodeLensesInViewportSoon(); }, onUnexpectedError); }, this._provideCodeLensDebounce.get(model)); this._localToDispose.add(scheduler); this._localToDispose.add(toDisposable(() => this._resolveCodeLensesScheduler.cancel())); this._localToDispose.add(this._editor.onDidChangeModelContent(() => { this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZonesAccessor => { const toDispose = []; let lastLensLineNumber = -1; this._lenses.forEach((lens) => { if (!lens.isValid() || lastLensLineNumber === lens.getLineNumber()) { // invalid -> lens collapsed, attach range doesn't exist anymore // line_number -> lenses should never be on the same line toDispose.push(lens); } else { lens.update(viewZonesAccessor); lastLensLineNumber = lens.getLineNumber(); } }); const helper = new CodeLensHelper(); toDispose.forEach((l) => { l.dispose(helper, viewZonesAccessor); this._lenses.splice(this._lenses.indexOf(l), 1); }); helper.commit(decorationsAccessor); }); }); // Ask for all references again scheduler.schedule(); // Cancel pending and active resolve requests this._resolveCodeLensesScheduler.cancel(); this._resolveCodeLensesPromise?.cancel(); this._resolveCodeLensesPromise = undefined; })); this._localToDispose.add(this._editor.onDidFocusEditorText(() => { scheduler.schedule(); })); this._localToDispose.add(this._editor.onDidBlurEditorText(() => { scheduler.cancel(); })); this._localToDispose.add(this._editor.onDidScrollChange(e => { if (e.scrollTopChanged && this._lenses.length > 0) { this._resolveCodeLensesInViewportSoon(); } })); this._localToDispose.add(this._editor.onDidLayoutChange(() => { this._resolveCodeLensesInViewportSoon(); })); this._localToDispose.add(toDisposable(() => { if (this._editor.getModel()) { const scrollState = StableEditorScrollState.capture(this._editor); this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZonesAccessor => { this._disposeAllLenses(decorationsAccessor, viewZonesAccessor); }); }); scrollState.restore(this._editor); } else { // No accessors available this._disposeAllLenses(undefined, undefined); } })); this._localToDispose.add(this._editor.onMouseDown(e => { if (e.target.type !== 9 /* MouseTargetType.CONTENT_WIDGET */) { return; } let target = e.target.element; if (target?.tagName === 'SPAN') { target = target.parentElement; } if (target?.tagName === 'A') { for (const lens of this._lenses) { const command = lens.getCommand(target); if (command) { this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err)); break; } } } })); scheduler.schedule(); } _disposeAllLenses(decChangeAccessor, viewZoneChangeAccessor) { const helper = new CodeLensHelper(); for (const lens of this._lenses) { lens.dispose(helper, viewZoneChangeAccessor); } if (decChangeAccessor) { helper.commit(decChangeAccessor); } this._lenses.length = 0; } _renderCodeLensSymbols(symbols) { if (!this._editor.hasModel()) { return; } const maxLineNumber = this._editor.getModel().getLineCount(); const groups = []; let lastGroup; for (const symbol of symbols.lenses) { const line = symbol.symbol.range.startLineNumber; if (line < 1 || line > maxLineNumber) { // invalid code lens continue; } else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) { // on same line as previous lastGroup.push(symbol); } else { // on later line as previous lastGroup = [symbol]; groups.push(lastGroup); } } if (!groups.length && !this._lenses.length) { // Nothing to change return; } const scrollState = StableEditorScrollState.capture(this._editor); const layoutInfo = this._getLayoutInfo(); this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZoneAccessor => { const helper = new CodeLensHelper(); let codeLensIndex = 0; let groupsIndex = 0; while (groupsIndex < groups.length && codeLensIndex < this._lenses.length) { const symbolsLineNumber = groups[groupsIndex][0].symbol.range.startLineNumber; const codeLensLineNumber = this._lenses[codeLensIndex].getLineNumber(); if (codeLensLineNumber < symbolsLineNumber) { this._lenses[codeLensIndex].dispose(helper, viewZoneAccessor); this._lenses.splice(codeLensIndex, 1); } else if (codeLensLineNumber === symbolsLineNumber) { this._lenses[codeLensIndex].updateCodeLensSymbols(groups[groupsIndex], helper); groupsIndex++; codeLensIndex++; } else { this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon())); codeLensIndex++; groupsIndex++; } } // Delete extra code lenses while (codeLensIndex < this._lenses.length) { this._lenses[codeLensIndex].dispose(helper, viewZoneAccessor); this._lenses.splice(codeLensIndex, 1); } // Create extra symbols while (groupsIndex < groups.length) { this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon())); groupsIndex++; } helper.commit(decorationsAccessor); }); }); scrollState.restore(this._editor); } _resolveCodeLensesInViewportSoon() { const model = this._editor.getModel(); if (model) { this._resolveCodeLensesScheduler.schedule(); } } _resolveCodeLensesInViewport() { this._resolveCodeLensesPromise?.cancel(); this._resolveCodeLensesPromise = undefined; const model = this._editor.getModel(); if (!model) { return; } const toResolve = []; const lenses = []; this._lenses.forEach((lens) => { const request = lens.computeIfNecessary(model); if (request) { toResolve.push(request); lenses.push(lens); } }); if (toResolve.length === 0) { return; } const t1 = Date.now(); const resolvePromise = createCancelablePromise(token => { const promises = toResolve.map((request, i) => { const resolvedSymbols = new Array(request.length); const promises = request.map((request, i) => { if (!request.symbol.command && typeof request.provider.resolveCodeLens === 'function') { return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => { resolvedSymbols[i] = symbol; }, onUnexpectedExternalError); } else { resolvedSymbols[i] = request.symbol; return Promise.resolve(undefined); } }); return Promise.all(promises).then(() => { if (!token.isCancellationRequested && !lenses[i].isDisposed()) { lenses[i].updateCommands(resolvedSymbols); } }); }); return Promise.all(promises); }); this._resolveCodeLensesPromise = resolvePromise; this._resolveCodeLensesPromise.then(() => { // update moving average const newDelay = this._resolveCodeLensesDebounce.update(model, Date.now() - t1); this._resolveCodeLensesScheduler.delay = newDelay; if (this._currentCodeLensModel) { // update the cached state with new resolved items this._codeLensCache.put(model, this._currentCodeLensModel); } this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model if (resolvePromise === this._resolveCodeLensesPromise) { this._resolveCodeLensesPromise = undefined; } }, err => { onUnexpectedError(err); // can also be cancellation! if (resolvePromise === this._resolveCodeLensesPromise) { this._resolveCodeLensesPromise = undefined; } }); } async getModel() { await this._getCodeLensModelPromise; await this._resolveCodeLensesPromise; return !this._currentCodeLensModel?.isDisposed ? this._currentCodeLensModel : undefined; } }; CodeLensContribution = __decorate([ __param(1, ILanguageFeaturesService), __param(2, ILanguageFeatureDebounceService), __param(3, ICommandService), __param(4, INotificationService), __param(5, ICodeLensCache) ], CodeLensContribution); export { CodeLensContribution }; registerEditorContribution(CodeLensContribution.ID, CodeLensContribution, 1 /* EditorContributionInstantiation.AfterFirstRender */); registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { constructor() { super({ id: 'codelens.showLensesInCurrentLine', precondition: EditorContextKeys.hasCodeLensProvider, label: localize('showLensOnLine', "Show CodeLens Commands For Current Line"), alias: 'Show CodeLens Commands For Current Line', }); } async run(accessor, editor) { if (!editor.hasModel()) { return; } const quickInputService = accessor.get(IQuickInputService); const commandService = accessor.get(ICommandService); const notificationService = accessor.get(INotificationService); const lineNumber = editor.getSelection().positionLineNumber; const codelensController = editor.getContribution(CodeLensContribution.ID); if (!codelensController) { return; } const model = await codelensController.getModel(); if (!model) { // nothing return; } const items = []; for (const lens of model.lenses) { if (lens.symbol.command && lens.symbol.range.startLineNumber === lineNumber) { items.push({ label: lens.symbol.command.title, command: lens.symbol.command }); } } if (items.length === 0) { // We dont want an empty picker return; } const item = await quickInputService.pick(items, { canPickMany: false, placeHolder: localize('placeHolder', "Select a command") }); if (!item) { // Nothing picked return; } let command = item.command; if (model.isDisposed) { // try to find the same command again in-case the model has been re-created in the meantime // this is a best attempt approach which shouldn't be needed because eager model re-creates // shouldn't happen due to focus in/out anymore const newModel = await codelensController.getModel(); const newLens = newModel?.lenses.find(lens => lens.symbol.range.startLineNumber === lineNumber && lens.symbol.command?.title === command.title); if (!newLens || !newLens.symbol.command) { return; } command = newLens.symbol.command; } try { await commandService.executeCommand(command.id, ...(command.arguments || [])); } catch (err) { notificationService.error(err); } } });