Path: blob/main/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts
4779 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, timeout } from '../../../../base/common/async.js';6import { onUnexpectedError } from '../../../../base/common/errors.js';7import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';8import { CodeEditorStateFlag, EditorState } from '../../editorState/browser/editorState.js';9import { ICodeEditor } from '../../../browser/editorBrowser.js';10import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js';11import { Range } from '../../../common/core/range.js';12import { Selection } from '../../../common/core/selection.js';13import { IEditorContribution, IEditorDecorationsCollection } from '../../../common/editorCommon.js';14import { EditorContextKeys } from '../../../common/editorContextKeys.js';15import { ModelDecorationOptions } from '../../../common/model/textModel.js';16import { IInplaceReplaceSupportResult } from '../../../common/languages.js';17import { IEditorWorkerService } from '../../../common/services/editorWorker.js';18import * as nls from '../../../../nls.js';19import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';20import { InPlaceReplaceCommand } from './inPlaceReplaceCommand.js';21import './inPlaceReplace.css';2223class InPlaceReplaceController implements IEditorContribution {2425public static readonly ID = 'editor.contrib.inPlaceReplaceController';2627static get(editor: ICodeEditor): InPlaceReplaceController | null {28return editor.getContribution<InPlaceReplaceController>(InPlaceReplaceController.ID);29}3031private static readonly DECORATION = ModelDecorationOptions.register({32description: 'in-place-replace',33className: 'valueSetReplacement'34});3536private readonly editor: ICodeEditor;37private readonly editorWorkerService: IEditorWorkerService;38private readonly decorations: IEditorDecorationsCollection;39private currentRequest?: CancelablePromise<IInplaceReplaceSupportResult | null>;40private decorationRemover?: CancelablePromise<void>;4142constructor(43editor: ICodeEditor,44@IEditorWorkerService editorWorkerService: IEditorWorkerService45) {46this.editor = editor;47this.editorWorkerService = editorWorkerService;48this.decorations = this.editor.createDecorationsCollection();49}5051public dispose(): void {52}5354public run(source: string, up: boolean): Promise<void> | undefined {5556// cancel any pending request57this.currentRequest?.cancel();5859const editorSelection = this.editor.getSelection();60const model = this.editor.getModel();61if (!model || !editorSelection) {62return undefined;63}64let selection = editorSelection;65if (selection.startLineNumber !== selection.endLineNumber) {66// Can't accept multiline selection67return undefined;68}6970const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);71const modelURI = model.uri;72if (!this.editorWorkerService.canNavigateValueSet(modelURI)) {73return Promise.resolve(undefined);74}7576this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection, up));7778return this.currentRequest.then(result => {7980if (!result || !result.range || !result.value) {81// No proper result82return;83}8485if (!state.validate(this.editor)) {86// state has changed87return;88}8990// Selection91const editRange = Range.lift(result.range);92let highlightRange = result.range;93const diff = result.value.length - (selection.endColumn - selection.startColumn);9495// highlight96highlightRange = {97startLineNumber: highlightRange.startLineNumber,98startColumn: highlightRange.startColumn,99endLineNumber: highlightRange.endLineNumber,100endColumn: highlightRange.startColumn + result.value.length101};102if (diff > 1) {103selection = new Selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn + diff - 1);104}105106// Insert new text107const command = new InPlaceReplaceCommand(editRange, selection, result.value);108109this.editor.pushUndoStop();110this.editor.executeCommand(source, command);111this.editor.pushUndoStop();112113// add decoration114this.decorations.set([{115range: highlightRange,116options: InPlaceReplaceController.DECORATION117}]);118119// remove decoration after delay120this.decorationRemover?.cancel();121this.decorationRemover = timeout(350);122this.decorationRemover.then(() => this.decorations.clear()).catch(onUnexpectedError);123124}).catch(onUnexpectedError);125}126}127128class InPlaceReplaceUp extends EditorAction {129130constructor() {131super({132id: 'editor.action.inPlaceReplace.up',133label: nls.localize2('InPlaceReplaceAction.previous.label', "Replace with Previous Value"),134precondition: EditorContextKeys.writable,135kbOpts: {136kbExpr: EditorContextKeys.editorTextFocus,137primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Comma,138weight: KeybindingWeight.EditorContrib139}140});141}142143public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {144const controller = InPlaceReplaceController.get(editor);145if (!controller) {146return Promise.resolve(undefined);147}148return controller.run(this.id, false);149}150}151152class InPlaceReplaceDown extends EditorAction {153154constructor() {155super({156id: 'editor.action.inPlaceReplace.down',157label: nls.localize2('InPlaceReplaceAction.next.label', "Replace with Next Value"),158precondition: EditorContextKeys.writable,159kbOpts: {160kbExpr: EditorContextKeys.editorTextFocus,161primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period,162weight: KeybindingWeight.EditorContrib163}164});165}166167public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {168const controller = InPlaceReplaceController.get(editor);169if (!controller) {170return Promise.resolve(undefined);171}172return controller.run(this.id, true);173}174}175176registerEditorContribution(InPlaceReplaceController.ID, InPlaceReplaceController, EditorContributionInstantiation.Lazy);177registerEditorAction(InPlaceReplaceUp);178registerEditorAction(InPlaceReplaceDown);179180181