UNPKG

monaco-editor-core

Version:

A browser based code editor

360 lines (359 loc) • 18.9 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); } }; var CodeActionController_1; import { getDomNodePagePosition } from '../../../../base/browser/dom.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Position } from '../../../common/core/position.js'; import { ModelDecorationOptions } from '../../../common/model/textModel.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { ApplyCodeActionReason, applyCodeAction } from './codeAction.js'; import { CodeActionKeybindingResolver } from './codeActionKeybindingResolver.js'; import { toMenuItems } from './codeActionMenu.js'; import { LightBulbWidget } from './lightBulbWidget.js'; import { MessageController } from '../../message/browser/messageController.js'; import { localize } from '../../../../nls.js'; import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IMarkerService } from '../../../../platform/markers/common/markers.js'; import { IEditorProgressService } from '../../../../platform/progress/common/progress.js'; import { editorFindMatchHighlight, editorFindMatchHighlightBorder } from '../../../../platform/theme/common/colorRegistry.js'; import { isHighContrast } from '../../../../platform/theme/common/theme.js'; import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js'; import { CodeActionKind, CodeActionTriggerSource } from '../common/types.js'; import { CodeActionModel } from './codeActionModel.js'; import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; const DECORATION_CLASS_NAME = 'quickfix-edit-highlight'; let CodeActionController = class CodeActionController extends Disposable { static { CodeActionController_1 = this; } static { this.ID = 'editor.contrib.codeActionController'; } static get(editor) { return editor.getContribution(CodeActionController_1.ID); } constructor(editor, markerService, contextKeyService, instantiationService, languageFeaturesService, progressService, _commandService, _configurationService, _actionWidgetService, _instantiationService, _telemetryService) { super(); this._commandService = _commandService; this._configurationService = _configurationService; this._actionWidgetService = _actionWidgetService; this._instantiationService = _instantiationService; this._telemetryService = _telemetryService; this._activeCodeActions = this._register(new MutableDisposable()); this._showDisabled = false; this._disposed = false; this._editor = editor; this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService, _configurationService, this._telemetryService)); this._register(this._model.onDidChangeState(newState => this.update(newState))); this._lightBulbWidget = new Lazy(() => { const widget = this._editor.getContribution(LightBulbWidget.ID); if (widget) { this._register(widget.onClick(e => this.showCodeActionsFromLightbulb(e.actions, e))); } return widget; }); this._resolver = instantiationService.createInstance(CodeActionKeybindingResolver); this._register(this._editor.onDidLayoutChange(() => this._actionWidgetService.hide())); } dispose() { this._disposed = true; super.dispose(); } async showCodeActionsFromLightbulb(actions, at) { if (actions.allAIFixes && actions.validActions.length === 1) { const actionItem = actions.validActions[0]; const command = actionItem.action.command; if (command && command.id === 'inlineChat.start') { if (command.arguments && command.arguments.length >= 1) { command.arguments[0] = { ...command.arguments[0], autoSend: false }; } } await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); return; } await this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: true }); } showCodeActions(_trigger, actions, at) { return this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: false }); } manualTriggerAtCurrentPosition(notAvailableMessage, triggerAction, filter, autoApply) { if (!this._editor.hasModel()) { return; } MessageController.get(this._editor)?.closeMessage(); const triggerPosition = this._editor.getPosition(); this._trigger({ type: 1 /* CodeActionTriggerType.Invoke */, triggerAction, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); } _trigger(trigger) { return this._model.trigger(trigger); } async _applyCodeAction(action, retrigger, preview, actionReason) { try { await this._instantiationService.invokeFunction(applyCodeAction, action, actionReason, { preview, editor: this._editor }); } finally { if (retrigger) { this._trigger({ type: 2 /* CodeActionTriggerType.Auto */, triggerAction: CodeActionTriggerSource.QuickFix, filter: {} }); } } } hideLightBulbWidget() { this._lightBulbWidget.rawValue?.hide(); this._lightBulbWidget.rawValue?.gutterHide(); } async update(newState) { if (newState.type !== 1 /* CodeActionsState.Type.Triggered */) { this.hideLightBulbWidget(); return; } let actions; try { actions = await newState.actions; } catch (e) { onUnexpectedError(e); return; } if (this._disposed) { return; } const selection = this._editor.getSelection(); if (selection?.startLineNumber !== newState.position.lineNumber) { return; } this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position); if (newState.trigger.type === 1 /* CodeActionTriggerType.Invoke */) { if (newState.trigger.filter?.include) { // Triggered for specific scope // Check to see if we want to auto apply. const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions); if (validActionToApply) { try { this.hideLightBulbWidget(); await this._applyCodeAction(validActionToApply, false, false, ApplyCodeActionReason.FromCodeActions); } finally { actions.dispose(); } return; } // Check to see if there is an action that we would have applied were it not invalid if (newState.trigger.context) { const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions); if (invalidAction && invalidAction.action.disabled) { MessageController.get(this._editor)?.showMessage(invalidAction.action.disabled, newState.trigger.context.position); actions.dispose(); return; } } } const includeDisabledActions = !!newState.trigger.filter?.include; if (newState.trigger.context) { if (!actions.allActions.length || !includeDisabledActions && !actions.validActions.length) { MessageController.get(this._editor)?.showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); this._activeCodeActions.value = actions; actions.dispose(); return; } } this._activeCodeActions.value = actions; this.showCodeActionList(actions, this.toCoords(newState.position), { includeDisabledActions, fromLightbulb: false }); } else { // auto magically triggered if (this._actionWidgetService.isVisible) { // TODO: Figure out if we should update the showing menu? actions.dispose(); } else { this._activeCodeActions.value = actions; } } } getInvalidActionThatWouldHaveBeenApplied(trigger, actions) { if (!actions.allActions.length) { return undefined; } if ((trigger.autoApply === "first" /* CodeActionAutoApply.First */ && actions.validActions.length === 0) || (trigger.autoApply === "ifSingle" /* CodeActionAutoApply.IfSingle */ && actions.allActions.length === 1)) { return actions.allActions.find(({ action }) => action.disabled); } return undefined; } tryGetValidActionToApply(trigger, actions) { if (!actions.validActions.length) { return undefined; } if ((trigger.autoApply === "first" /* CodeActionAutoApply.First */ && actions.validActions.length > 0) || (trigger.autoApply === "ifSingle" /* CodeActionAutoApply.IfSingle */ && actions.validActions.length === 1)) { return actions.validActions[0]; } return undefined; } static { this.DECORATION = ModelDecorationOptions.register({ description: 'quickfix-highlight', className: DECORATION_CLASS_NAME }); } async showCodeActionList(actions, at, options) { const currentDecorations = this._editor.createDecorationsCollection(); const editorDom = this._editor.getDomNode(); if (!editorDom) { return; } const actionsToShow = options.includeDisabledActions && (this._showDisabled || actions.validActions.length === 0) ? actions.allActions : actions.validActions; if (!actionsToShow.length) { return; } const anchor = Position.isIPosition(at) ? this.toCoords(at) : at; const delegate = { onSelect: async (action, preview) => { this._applyCodeAction(action, /* retrigger */ true, !!preview, options.fromLightbulb ? ApplyCodeActionReason.FromAILightbulb : ApplyCodeActionReason.FromCodeActions); this._actionWidgetService.hide(false); currentDecorations.clear(); }, onHide: (didCancel) => { this._editor?.focus(); currentDecorations.clear(); }, onHover: async (action, token) => { if (token.isCancellationRequested) { return; } let canPreview = false; const actionKind = action.action.kind; if (actionKind) { const hierarchicalKind = new HierarchicalKind(actionKind); const refactorKinds = [ CodeActionKind.RefactorExtract, CodeActionKind.RefactorInline, CodeActionKind.RefactorRewrite, CodeActionKind.RefactorMove, CodeActionKind.Source ]; canPreview = refactorKinds.some(refactorKind => refactorKind.contains(hierarchicalKind)); } return { canPreview: canPreview || !!action.action.edit?.edits.length }; }, onFocus: (action) => { if (action && action.action) { const ranges = action.action.ranges; const diagnostics = action.action.diagnostics; currentDecorations.clear(); if (ranges && ranges.length > 0) { // Handles case for `fix all` where there are multiple diagnostics. const decorations = (diagnostics && diagnostics?.length > 1) ? diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController_1.DECORATION })) : ranges.map(range => ({ range, options: CodeActionController_1.DECORATION })); currentDecorations.set(decorations); } else if (diagnostics && diagnostics.length > 0) { const decorations = diagnostics.map(diagnostic => ({ range: diagnostic, options: CodeActionController_1.DECORATION })); currentDecorations.set(decorations); const diagnostic = diagnostics[0]; if (diagnostic.startLineNumber && diagnostic.startColumn) { const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); } } } else { currentDecorations.clear(); } } }; this._actionWidgetService.show('codeActionWidget', true, toMenuItems(actionsToShow, this._shouldShowHeaders(), this._resolver.getResolver()), delegate, anchor, editorDom, this._getActionBarActions(actions, at, options)); } toCoords(position) { if (!this._editor.hasModel()) { return { x: 0, y: 0 }; } this._editor.revealPosition(position, 1 /* ScrollType.Immediate */); this._editor.render(); // Translate to absolute editor position const cursorCoords = this._editor.getScrolledVisiblePosition(position); const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); const x = editorCoords.left + cursorCoords.left; const y = editorCoords.top + cursorCoords.top + cursorCoords.height; return { x, y }; } _shouldShowHeaders() { const model = this._editor?.getModel(); return this._configurationService.getValue('editor.codeActionWidget.showHeaders', { resource: model?.uri }); } _getActionBarActions(actions, at, options) { if (options.fromLightbulb) { return []; } const resultActions = actions.documentation.map((command) => ({ id: command.id, label: command.title, tooltip: command.tooltip ?? '', class: undefined, enabled: true, run: () => this._commandService.executeCommand(command.id, ...(command.arguments ?? [])), })); if (options.includeDisabledActions && actions.validActions.length > 0 && actions.allActions.length !== actions.validActions.length) { resultActions.push(this._showDisabled ? { id: 'hideMoreActions', label: localize('hideMoreActions', 'Hide Disabled'), enabled: true, tooltip: '', class: undefined, run: () => { this._showDisabled = false; return this.showCodeActionList(actions, at, options); } } : { id: 'showMoreActions', label: localize('showMoreActions', 'Show Disabled'), enabled: true, tooltip: '', class: undefined, run: () => { this._showDisabled = true; return this.showCodeActionList(actions, at, options); } }); } return resultActions; } }; CodeActionController = CodeActionController_1 = __decorate([ __param(1, IMarkerService), __param(2, IContextKeyService), __param(3, IInstantiationService), __param(4, ILanguageFeaturesService), __param(5, IEditorProgressService), __param(6, ICommandService), __param(7, IConfigurationService), __param(8, IActionWidgetService), __param(9, IInstantiationService), __param(10, ITelemetryService) ], CodeActionController); export { CodeActionController }; registerThemingParticipant((theme, collector) => { const addBackgroundColorRule = (selector, color) => { if (color) { collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`); } }; addBackgroundColorRule('.quickfix-edit-highlight', theme.getColor(editorFindMatchHighlight)); const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); if (findMatchHighlightBorder) { collector.addRule(`.monaco-editor .quickfix-edit-highlight { border: 1px ${isHighContrast(theme.type) ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); } });