Skip to content

Commit d256d56

Browse files
committed
replace first cut
1 parent c41d631 commit d256d56

8 files changed

Lines changed: 184 additions & 21 deletions

File tree

src/vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
212212
label: NLS_REPLACE_BTN_LABEL,
213213
className: 'codicon codicon-replace',
214214
onTrigger: () => {
215-
// this._controller.replace();
215+
this.replaceOne();
216216
}
217217
}));
218218

@@ -221,7 +221,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
221221
label: NLS_REPLACE_ALL_BTN_LABEL,
222222
className: 'codicon codicon-replace-all',
223223
onTrigger: () => {
224-
// this._controller.replaceAll();
224+
this.replaceAll();
225225
}
226226
}));
227227

@@ -234,6 +234,8 @@ export abstract class SimpleFindReplaceWidget extends Widget {
234234
protected abstract onInputChanged(): boolean;
235235
protected abstract find(previous: boolean): void;
236236
protected abstract findFirst(): void;
237+
protected abstract replaceOne(): void;
238+
protected abstract replaceAll(): void;
237239
protected abstract onFocusTrackerFocus(): void;
238240
protected abstract onFocusTrackerBlur(): void;
239241
protected abstract onFindInputFocusTrackerFocus(): void;
@@ -245,6 +247,10 @@ export abstract class SimpleFindReplaceWidget extends Widget {
245247
return this._findInput.getValue();
246248
}
247249

250+
protected get replaceValue() {
251+
return this._replaceInput.getValue();
252+
}
253+
248254
public get focusTracker(): dom.IFocusTracker {
249255
return this._focusTracker;
250256
}

src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,39 @@ registerAction2(class extends Action2 {
858858
}
859859
});
860860

861+
registerAction2(class extends Action2 {
862+
constructor() {
863+
super({
864+
id: 'workbench.action.notebook.undo',
865+
title: 'Notebook Undo',
866+
keybinding: {
867+
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
868+
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z,
869+
weight: KeybindingWeight.WorkbenchContrib
870+
}
871+
});
872+
}
873+
874+
async run(accessor: ServicesAccessor): Promise<void> {
875+
const editorService = accessor.get(IEditorService);
876+
877+
const editor = getActiveNotebookEditor(editorService);
878+
if (!editor) {
879+
return;
880+
}
881+
882+
const viewModel = editor.viewModel;
883+
884+
if (!viewModel) {
885+
return;
886+
}
887+
888+
if (viewModel.canUndo()) {
889+
viewModel.undo();
890+
}
891+
}
892+
});
893+
861894
registerAction2(class extends Action2 {
862895
constructor() {
863896
super({

src/vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget {
8282
this.revealCellRange(nextIndex.index, nextIndex.remainder);
8383
}
8484

85+
protected replaceOne() {
86+
if (!this._findMatches.length) {
87+
return;
88+
}
89+
90+
if (!this._findMatchesStarts) {
91+
this.set(this._findMatches);
92+
}
93+
94+
const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch);
95+
const cell = this._findMatches[nextIndex.index].cell;
96+
const match = this._findMatches[nextIndex.index].matches[nextIndex.remainder];
97+
98+
return this._notebookEditor.viewModel!.replaceOne(cell, match.range, this.replaceValue);
99+
100+
}
101+
102+
protected replaceAll() {
103+
return this._notebookEditor.viewModel!.replaceAll(this._findMatches, this.replaceValue);
104+
}
105+
85106
private revealCellRange(cellIndex: number, matchIndex: number) {
86107
this._findMatches[cellIndex].cell.state = CellState.Editing;
87108
this._notebookEditor.selectElement(this._findMatches[cellIndex].cell);

src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ export class StatefullMarkdownCell extends Disposable {
185185
const clientHeight = this.cellContainer.clientHeight;
186186
notebookEditor.layoutNotebookCell(viewCell, clientHeight);
187187
}));
188+
189+
this.localDisposables.add(viewCell.onDidChangeContent(() => {
190+
this.cellContainer.innerHTML = '';
191+
let renderedHTML = viewCell.getHTML();
192+
if (renderedHTML) {
193+
this.cellContainer.appendChild(renderedHTML);
194+
}
195+
}));
188196
}
189197
}
190198
};

src/vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ export class CellViewModel extends Disposable implements ICellViewModel {
110110
private _editorViewStates: editorCommon.ICodeEditorViewState | null;
111111
private _lastDecorationId: number = 0;
112112
private _resolvedDecorations = new Map<string, { id?: string, options: model.IModelDeltaDecoration }>();
113+
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
114+
public readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
113115
private readonly _onDidChangeCursorSelection: Emitter<void> = this._register(new Emitter<void>());
114116
public readonly onDidChangeCursorSelection: Event<void> = this._onDidChangeCursorSelection.event;
115117

@@ -275,6 +277,8 @@ export class CellViewModel extends Disposable implements ICellViewModel {
275277
this._register(ref);
276278
this._register(this._textModel.onDidChangeContent(() => {
277279
this.cell.isDirty = true;
280+
this._html = null;
281+
this._onDidChangeContent.fire();
278282
}));
279283
}
280284
return this._textModel;

src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import { NotebookCellsSplice, ICell } from 'vs/workbench/contrib/notebook/common
1313
import { IModelDeltaDecoration } from 'vs/editor/common/model';
1414
import { onUnexpectedError } from 'vs/base/common/errors';
1515
import { CellFindMatch, CellState, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
16+
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
17+
import { Range } from 'vs/editor/common/core/range';
18+
import { WorkspaceTextEdit } from 'vs/editor/common/modes';
19+
import { URI } from 'vs/base/common/uri';
20+
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
1621

1722
export interface INotebookEditorViewState {
1823
editingCells: { [key: number]: boolean };
@@ -67,10 +72,21 @@ export class NotebookViewModel extends Disposable {
6772
private readonly _onDidChangeCells = new Emitter<NotebookCellsSplice[]>();
6873
get onDidChangeCells(): Event<NotebookCellsSplice[]> { return this._onDidChangeCells.event; }
6974

75+
private _lastNotebookEditResource: URI[] = [];
76+
77+
get lastNotebookEditResource(): URI | null {
78+
if (this._lastNotebookEditResource.length) {
79+
return this._lastNotebookEditResource[this._lastNotebookEditResource.length - 1];
80+
}
81+
return null;
82+
}
83+
7084
constructor(
7185
public viewType: string,
7286
private _model: NotebookEditorModel,
7387
@IInstantiationService private readonly instantiationService: IInstantiationService,
88+
@IBulkEditService private readonly bulkEditService: IBulkEditService,
89+
@IUndoRedoService private readonly undoService: IUndoRedoService
7490
) {
7591
super();
7692

@@ -98,22 +114,6 @@ export class NotebookViewModel extends Disposable {
98114
return this._viewCells.indexOf(cell as CellViewModel);
99115
}
100116

101-
/**
102-
* Search in notebook text model
103-
* @param value
104-
*/
105-
find(value: string): CellFindMatch[] {
106-
const matches: CellFindMatch[] = [];
107-
this._viewCells.forEach(cell => {
108-
const cellMatches = cell.startFind(value);
109-
if (cellMatches) {
110-
matches.push(cellMatches);
111-
}
112-
});
113-
114-
return matches;
115-
}
116-
117117
insertCell(index: number, cell: ICell): CellViewModel {
118118
const newCell = this.instantiationService.createInstance(CellViewModel, this.viewType, this.handle, cell);
119119
this._viewCells!.splice(index, 0, newCell);
@@ -245,6 +245,87 @@ export class NotebookViewModel extends Disposable {
245245
return ret;
246246
}
247247

248+
249+
/**
250+
* Search in notebook text model
251+
* @param value
252+
*/
253+
find(value: string): CellFindMatch[] {
254+
const matches: CellFindMatch[] = [];
255+
this._viewCells.forEach(cell => {
256+
const cellMatches = cell.startFind(value);
257+
if (cellMatches) {
258+
matches.push(cellMatches);
259+
}
260+
});
261+
262+
return matches;
263+
}
264+
265+
replaceOne(cell: ICellViewModel, range: Range, text: string): Promise<void> {
266+
const viewCell = cell as CellViewModel;
267+
this._lastNotebookEditResource.push(viewCell.uri);
268+
return viewCell.resolveTextModel().then(() => {
269+
this.bulkEditService.apply({ edits: [{ edit: { range: range, text: text }, resource: cell.uri }] }, { quotableLabel: 'Notebook Replace' });
270+
});
271+
}
272+
273+
async replaceAll(matches: CellFindMatch[], text: string): Promise<void> {
274+
if (!matches.length) {
275+
return;
276+
}
277+
278+
let textEdits: WorkspaceTextEdit[] = [];
279+
this._lastNotebookEditResource.push(matches[0].cell.uri);
280+
281+
matches.forEach(match => {
282+
match.matches.forEach(singleMatch => {
283+
textEdits.push({
284+
edit: { range: singleMatch.range, text: text },
285+
resource: match.cell.uri
286+
});
287+
});
288+
});
289+
290+
return Promise.all(matches.map(match => {
291+
return match.cell.resolveTextModel();
292+
})).then(async () => {
293+
this.bulkEditService.apply({ edits: textEdits }, { quotableLabel: 'Notebook Replace All' });
294+
return;
295+
});
296+
}
297+
298+
canUndo(): boolean {
299+
const lastResource = this.lastNotebookEditResource;
300+
301+
if (!lastResource) {
302+
return false;
303+
}
304+
305+
const lastElement = this.undoService.getLastElement(lastResource);
306+
307+
if (lastElement?.label === 'Notebook Replace' || lastElement?.label === 'Notebook Replace All') {
308+
return true;
309+
}
310+
311+
return false;
312+
}
313+
314+
undo() {
315+
const lastResource = this.lastNotebookEditResource;
316+
317+
if (!lastResource) {
318+
return;
319+
}
320+
321+
const lastElement = this.undoService.getLastElement(lastResource);
322+
323+
if (lastElement?.label === 'Notebook Replace' || lastElement?.label === 'Notebook Replace All') {
324+
this.undoService.undo(lastResource);
325+
this._lastNotebookEditResource.pop();
326+
}
327+
}
328+
248329
equal(model: NotebookEditorModel) {
249330
return this._model === model;
250331
}

src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,26 @@ import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/noteb
1010
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
1111
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1212
import { TestNotebook, withTestNotebook, TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
13+
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
14+
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
1315

1416
suite('NotebookViewModel', () => {
1517
const instantiationService = new TestInstantiationService();
18+
const blukEditService = instantiationService.get(IBulkEditService);
19+
const undoRedoService = instantiationService.get(IUndoRedoService);
1620

1721
test('ctor', function () {
1822
const notebook = new TestNotebook(0, 'notebook', URI.parse('test'));
1923
const model = new NotebookEditorModel(notebook);
20-
const viewModel = new NotebookViewModel('notebook', model, instantiationService);
24+
const viewModel = new NotebookViewModel('notebook', model, instantiationService, blukEditService, undoRedoService);
2125
assert.equal(viewModel.viewType, 'notebook');
2226
});
2327

2428
test('insert/delete', function () {
2529
withTestNotebook(
2630
instantiationService,
31+
blukEditService,
32+
undoRedoService,
2733
[
2834
[['var a = 1;'], 'javascript', CellKind.Code, []],
2935
[['var b = 2;'], 'javascript', CellKind.Code, []]
@@ -45,6 +51,8 @@ suite('NotebookViewModel', () => {
4551
test('index', function () {
4652
withTestNotebook(
4753
instantiationService,
54+
blukEditService,
55+
undoRedoService,
4856
[
4957
[['var a = 1;'], 'javascript', CellKind.Code, []],
5058
[['var b = 2;'], 'javascript', CellKind.Code, []]

src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
1717
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
1818
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
1919
import { Range } from 'vs/editor/common/core/range';
20+
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
21+
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
2022

2123
export class TestCell implements ICell {
2224
uri: URI;
@@ -200,15 +202,15 @@ export function createTestCellViewModel(instantiationService: IInstantiationServ
200202
return instantiationService.createInstance(CellViewModel, viewType, notebookHandle, mockCell);
201203
}
202204

203-
export function withTestNotebook(instantiationService: IInstantiationService, cells: [string[], string, CellKind, IOutput[]][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel) => void) {
205+
export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[]][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel) => void) {
204206
const viewType = 'notebook';
205207
const editor = new TestNotebookEditor();
206208
const notebook = new TestNotebook(0, viewType, URI.parse('test'));
207209
notebook.cells = cells.map((cell, index) => {
208210
return new TestCell(viewType, index, cell[0], cell[1], cell[2], cell[3]);
209211
});
210212
const model = new NotebookEditorModel(notebook);
211-
const viewModel = new NotebookViewModel(viewType, model, instantiationService);
213+
const viewModel = new NotebookViewModel(viewType, model, instantiationService, blukEditService, undoRedoService);
212214

213215
callback(editor, viewModel);
214216

0 commit comments

Comments
 (0)