Skip to content

Commit 278b4f8

Browse files
committed
Hookup experimental undo for customEditors
This currently is not connected to any actual actions in the editor
1 parent b5dbace commit 278b4f8

7 files changed

Lines changed: 125 additions & 22 deletions

File tree

extensions/image-preview/src/extension.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ export function activate(context: vscode.ExtensionContext) {
2323
PreviewManager.viewType,
2424
{
2525
async resolveWebviewEditor({ resource }, editor: vscode.WebviewPanel): Promise<vscode.WebviewEditorCapabilities> {
26-
previewManager.resolve(resource, editor);
27-
return {};
26+
return previewManager.resolve(resource, editor);
2827
}
2928
}));
3029

extensions/image-preview/src/preview.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class PreviewManager {
2828
public resolve(
2929
resource: vscode.Uri,
3030
webviewEditor: vscode.WebviewPanel,
31-
) {
31+
): vscode.WebviewEditorCapabilities {
3232
const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.zoomStatusBarEntry);
3333
this._previews.add(preview);
3434
this.setActivePreview(preview);
@@ -42,6 +42,17 @@ export class PreviewManager {
4242
this.setActivePreview(undefined);
4343
}
4444
});
45+
46+
const onEdit = new vscode.EventEmitter<{ now: number }>();
47+
return {
48+
editingCapability: {
49+
onEdit: onEdit.event,
50+
save: async () => { },
51+
hotExit: async () => { },
52+
applyEdits: async () => { },
53+
undoEdits: async (edits) => { console.log('undo', edits); },
54+
}
55+
};
4556
}
4657

4758
public get activePreview() { return this._activePreview; }

src/vs/workbench/api/browser/mainThreadWebview.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor
2727
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
2828
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
2929
import { extHostNamedCustomer } from '../common/extHostCustomers';
30+
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/browser/customEditorModel';
3031

3132
/**
3233
* Bi-directional map between webview handles and inputs.
@@ -94,6 +95,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
9495
private readonly _webviewInputs = new WebviewInputStore();
9596
private readonly _revivers = new Map<string, IDisposable>();
9697
private readonly _editorProviders = new Map<string, IDisposable>();
98+
private readonly _models = new Map<string, CustomEditorModel>();
9799

98100
constructor(
99101
context: extHostProtocol.IExtHostContext,
@@ -261,14 +263,26 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
261263
canResolve: (webviewInput) => {
262264
return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType;
263265
},
264-
resolveWebview: async (webviewInput) => {
266+
resolveWebview: async (webviewInput: CustomFileEditorInput) => {
265267
const handle = webviewInput.id;
266268
this._webviewInputs.add(handle, webviewInput);
267269
this.hookupWebviewEventDelegate(handle, webviewInput);
268270

269271
webviewInput.webview.options = options;
270272
webviewInput.webview.extension = extension;
271273

274+
const model = new CustomEditorModel();
275+
webviewInput.setModel(model);
276+
this._models.set(handle, model);
277+
278+
webviewInput.onDispose(() => {
279+
this._models.delete(handle);
280+
});
281+
282+
model.onUndo(edit => {
283+
this._proxy.$undoEdits(handle, [edit]);
284+
});
285+
272286
try {
273287
await this._proxy.$resolveWebviewEditor(
274288
webviewInput.getResource(),
@@ -296,11 +310,18 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
296310
this._editorProviders.delete(viewType);
297311
}
298312

299-
public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editJson: string): void {
313+
public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: string): void {
300314
const webview = this.getWebviewInput(handle);
301315
if (!(webview instanceof CustomFileEditorInput)) {
302-
throw new Error(`Webview is not a webview editor`);
316+
throw new Error('Webview is not a webview editor');
303317
}
318+
319+
const model = this._models.get(handle);
320+
if (!model) {
321+
throw new Error('Could not find model for webview editor');
322+
}
323+
324+
model.makeEdit(editData);
304325
}
305326

306327
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ export interface ExtHostWebviewsShape {
589589
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void>;
590590
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
591591
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
592+
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void;
592593
}
593594

594595
export interface MainThreadUrlsShape extends IDisposable {

src/vs/workbench/api/common/extHostWebview.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Emitter, Event } from 'vs/base/common/event';
7-
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
7+
import { Disposable } from 'vs/base/common/lifecycle';
88
import { URI, UriComponents } from 'vs/base/common/uri';
99
import { generateUuid } from 'vs/base/common/uuid';
1010
import * as modes from 'vs/editor/common/modes';
@@ -93,6 +93,7 @@ export class ExtHostWebview implements vscode.Webview {
9393
}
9494

9595
export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel {
96+
9697
private readonly _handle: WebviewPanelHandle;
9798
private readonly _proxy: MainThreadWebviewsShape;
9899
private readonly _viewType: string;
@@ -112,6 +113,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
112113

113114
readonly _onDidChangeViewStateEmitter = this._register(new Emitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
114115
public readonly onDidChangeViewState: Event<vscode.WebviewPanelOnDidChangeViewStateEvent> = this._onDidChangeViewStateEmitter.event;
116+
_capabilities: vscode.WebviewEditorCapabilities;
115117

116118
constructor(
117119
handle: WebviewPanelHandle,
@@ -233,8 +235,17 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
233235
});
234236
}
235237

236-
_addDisposable(disposable: IDisposable) {
237-
this._register(disposable);
238+
_setCapabilities(capabilities: vscode.WebviewEditorCapabilities) {
239+
this._capabilities = capabilities;
240+
if (capabilities.editingCapability) {
241+
this._register(capabilities.editingCapability.onEdit(edit => {
242+
this._proxy.$onEdit(this._handle, JSON.stringify(edit));
243+
}));
244+
}
245+
}
246+
247+
_undoEdits(edits: string[]): void {
248+
this._capabilities.editingCapability?.undoEdits(edits);
238249
}
239250

240251
private assertNotDisposed() {
@@ -402,10 +413,6 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
402413
await serializer.deserializeWebviewPanel(revivedPanel, state);
403414
}
404415

405-
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
406-
return this._webviewPanels.get(handle);
407-
}
408-
409416
async $resolveWebviewEditor(
410417
resource: UriComponents,
411418
handle: WebviewPanelHandle,
@@ -424,19 +431,19 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
424431
const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
425432
this._webviewPanels.set(handle, revivedPanel);
426433
const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(resource) }, revivedPanel);
427-
revivedPanel._addDisposable(this.hookupCapabilities(handle, capabilities));
434+
revivedPanel._setCapabilities(capabilities);
428435
}
429436

430-
private hookupCapabilities(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities): IDisposable {
431-
const disposables = new DisposableStore();
432-
433-
if (capabilities.editingCapability) {
434-
disposables.add(capabilities.editingCapability.onEdit(edit => {
435-
this._proxy.$onEdit(handle, JSON.stringify(edit));
436-
}));
437+
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void {
438+
const panel = this.getWebviewPanel(handle);
439+
if (!panel) {
440+
return;
437441
}
442+
panel._undoEdits(edits);
443+
}
438444

439-
return disposables;
445+
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
446+
return this._webviewPanels.get(handle);
440447
}
441448
}
442449

src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
1515
import { IEditorInput, Verbosity } from 'vs/workbench/common/editor';
1616
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview';
1717
import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
18+
import { CustomEditorModel } from './customEditorModel';
1819

1920
export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput {
2021

2122
public static typeId = 'workbench.editors.webviewEditor';
2223

2324
private readonly _editorResource: URI;
25+
private _model?: CustomEditorModel;
2426

2527
constructor(
2628
resource: URI,
@@ -105,4 +107,16 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput {
105107
return this.longTitle;
106108
}
107109
}
110+
111+
public setModel(model: CustomEditorModel) {
112+
if (this._model) {
113+
throw new Error('Model is already set');
114+
}
115+
this._model = model;
116+
this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
117+
}
118+
119+
public isDirty(): boolean {
120+
return this._model ? this._model.isDirty() : false;
121+
}
108122
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 { Emitter, Event } from 'vs/base/common/event';
7+
import { Disposable } from 'vs/base/common/lifecycle';
8+
9+
type Edit = string;
10+
11+
export class CustomEditorModel extends Disposable {
12+
13+
private _currentEditIndex: number = 0;
14+
private _savePoint: number = -1;
15+
private _edits: Array<Edit> = [];
16+
17+
protected readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
18+
readonly onDidChangeDirty: Event<void> = this._onDidChangeDirty.event;
19+
20+
protected readonly _onUndo: Emitter<Edit> = this._register(new Emitter<Edit>());
21+
readonly onUndo: Event<Edit> = this._onUndo.event;
22+
23+
public makeEdit(data: string): void {
24+
this._edits.splice(this._currentEditIndex, this._edits.length - this._currentEditIndex, data);
25+
this._currentEditIndex = this._edits.length - 1;
26+
this.updateDirty();
27+
}
28+
29+
public isDirty(): boolean {
30+
return this._edits.length > 0 && this._savePoint !== this._edits.length;
31+
}
32+
33+
private updateDirty() {
34+
this._onDidChangeDirty.fire();
35+
}
36+
37+
public save() {
38+
this._savePoint = this._edits.length;
39+
this.updateDirty();
40+
}
41+
42+
public undo() {
43+
if (this._currentEditIndex >= 0) {
44+
const undoneEdit = this._edits[this._currentEditIndex];
45+
--this._currentEditIndex;
46+
this._onUndo.fire(undoneEdit);
47+
}
48+
this.updateDirty();
49+
}
50+
}

0 commit comments

Comments
 (0)