Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts
4779 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancelablePromise, createCancelablePromise, timeout } from '../../../../base/common/async.js';
7
import { onUnexpectedError } from '../../../../base/common/errors.js';
8
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
9
import { CodeEditorStateFlag, EditorState } from '../../editorState/browser/editorState.js';
10
import { ICodeEditor } from '../../../browser/editorBrowser.js';
11
import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from '../../../browser/editorExtensions.js';
12
import { Range } from '../../../common/core/range.js';
13
import { Selection } from '../../../common/core/selection.js';
14
import { IEditorContribution, IEditorDecorationsCollection } from '../../../common/editorCommon.js';
15
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
16
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
17
import { IInplaceReplaceSupportResult } from '../../../common/languages.js';
18
import { IEditorWorkerService } from '../../../common/services/editorWorker.js';
19
import * as nls from '../../../../nls.js';
20
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
21
import { InPlaceReplaceCommand } from './inPlaceReplaceCommand.js';
22
import './inPlaceReplace.css';
23
24
class InPlaceReplaceController implements IEditorContribution {
25
26
public static readonly ID = 'editor.contrib.inPlaceReplaceController';
27
28
static get(editor: ICodeEditor): InPlaceReplaceController | null {
29
return editor.getContribution<InPlaceReplaceController>(InPlaceReplaceController.ID);
30
}
31
32
private static readonly DECORATION = ModelDecorationOptions.register({
33
description: 'in-place-replace',
34
className: 'valueSetReplacement'
35
});
36
37
private readonly editor: ICodeEditor;
38
private readonly editorWorkerService: IEditorWorkerService;
39
private readonly decorations: IEditorDecorationsCollection;
40
private currentRequest?: CancelablePromise<IInplaceReplaceSupportResult | null>;
41
private decorationRemover?: CancelablePromise<void>;
42
43
constructor(
44
editor: ICodeEditor,
45
@IEditorWorkerService editorWorkerService: IEditorWorkerService
46
) {
47
this.editor = editor;
48
this.editorWorkerService = editorWorkerService;
49
this.decorations = this.editor.createDecorationsCollection();
50
}
51
52
public dispose(): void {
53
}
54
55
public run(source: string, up: boolean): Promise<void> | undefined {
56
57
// cancel any pending request
58
this.currentRequest?.cancel();
59
60
const editorSelection = this.editor.getSelection();
61
const model = this.editor.getModel();
62
if (!model || !editorSelection) {
63
return undefined;
64
}
65
let selection = editorSelection;
66
if (selection.startLineNumber !== selection.endLineNumber) {
67
// Can't accept multiline selection
68
return undefined;
69
}
70
71
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
72
const modelURI = model.uri;
73
if (!this.editorWorkerService.canNavigateValueSet(modelURI)) {
74
return Promise.resolve(undefined);
75
}
76
77
this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection, up));
78
79
return this.currentRequest.then(result => {
80
81
if (!result || !result.range || !result.value) {
82
// No proper result
83
return;
84
}
85
86
if (!state.validate(this.editor)) {
87
// state has changed
88
return;
89
}
90
91
// Selection
92
const editRange = Range.lift(result.range);
93
let highlightRange = result.range;
94
const diff = result.value.length - (selection.endColumn - selection.startColumn);
95
96
// highlight
97
highlightRange = {
98
startLineNumber: highlightRange.startLineNumber,
99
startColumn: highlightRange.startColumn,
100
endLineNumber: highlightRange.endLineNumber,
101
endColumn: highlightRange.startColumn + result.value.length
102
};
103
if (diff > 1) {
104
selection = new Selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn + diff - 1);
105
}
106
107
// Insert new text
108
const command = new InPlaceReplaceCommand(editRange, selection, result.value);
109
110
this.editor.pushUndoStop();
111
this.editor.executeCommand(source, command);
112
this.editor.pushUndoStop();
113
114
// add decoration
115
this.decorations.set([{
116
range: highlightRange,
117
options: InPlaceReplaceController.DECORATION
118
}]);
119
120
// remove decoration after delay
121
this.decorationRemover?.cancel();
122
this.decorationRemover = timeout(350);
123
this.decorationRemover.then(() => this.decorations.clear()).catch(onUnexpectedError);
124
125
}).catch(onUnexpectedError);
126
}
127
}
128
129
class InPlaceReplaceUp extends EditorAction {
130
131
constructor() {
132
super({
133
id: 'editor.action.inPlaceReplace.up',
134
label: nls.localize2('InPlaceReplaceAction.previous.label', "Replace with Previous Value"),
135
precondition: EditorContextKeys.writable,
136
kbOpts: {
137
kbExpr: EditorContextKeys.editorTextFocus,
138
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Comma,
139
weight: KeybindingWeight.EditorContrib
140
}
141
});
142
}
143
144
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
145
const controller = InPlaceReplaceController.get(editor);
146
if (!controller) {
147
return Promise.resolve(undefined);
148
}
149
return controller.run(this.id, false);
150
}
151
}
152
153
class InPlaceReplaceDown extends EditorAction {
154
155
constructor() {
156
super({
157
id: 'editor.action.inPlaceReplace.down',
158
label: nls.localize2('InPlaceReplaceAction.next.label', "Replace with Next Value"),
159
precondition: EditorContextKeys.writable,
160
kbOpts: {
161
kbExpr: EditorContextKeys.editorTextFocus,
162
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Period,
163
weight: KeybindingWeight.EditorContrib
164
}
165
});
166
}
167
168
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
169
const controller = InPlaceReplaceController.get(editor);
170
if (!controller) {
171
return Promise.resolve(undefined);
172
}
173
return controller.run(this.id, true);
174
}
175
}
176
177
registerEditorContribution(InPlaceReplaceController.ID, InPlaceReplaceController, EditorContributionInstantiation.Lazy);
178
registerEditorAction(InPlaceReplaceUp);
179
registerEditorAction(InPlaceReplaceDown);
180
181