Skip to content

Commit 27a33ee

Browse files
committed
Hook up very basic undo/redo for webview editors
For microsoft#77131
1 parent ba19fe0 commit 27a33ee

11 files changed

Lines changed: 191 additions & 48 deletions

File tree

extensions/image-preview/src/preview.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,10 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit
260260

261261
async hotExit() { }
262262

263-
async applyEdits(_edits: any[]) { }
264-
265263
async undoEdits(edits: any[]) { console.log('undo', edits); }
266264

265+
async applyEdits(edits: any[]) { console.log('apply', edits); }
266+
267267
//#endregion
268268

269269
public test_makeEdit() {

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { URI, UriComponents } from 'vs/base/common/uri';
1212
import * as modes from 'vs/editor/common/modes';
1313
import { localize } from 'vs/nls';
1414
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
15-
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1615
import { IOpenerService } from 'vs/platform/opener/common/opener';
1716
import { IProductService } from 'vs/platform/product/common/productService';
1817
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -21,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr
2120
import { IEditorInput } from 'vs/workbench/common/editor';
2221
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
2322
import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
24-
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel';
23+
import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
2524
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
2625
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
2726
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
@@ -96,14 +95,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
9695
private readonly _webviewInputs = new WebviewInputStore();
9796
private readonly _revivers = new Map<string, IDisposable>();
9897
private readonly _editorProviders = new Map<string, IDisposable>();
99-
private readonly _models = new Map<string, CustomEditorModel>();
10098

10199
constructor(
102100
context: extHostProtocol.IExtHostContext,
103101
@IExtensionService extensionService: IExtensionService,
102+
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
104103
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
105104
@IEditorService private readonly _editorService: IEditorService,
106-
@IInstantiationService private readonly _instantiationService: IInstantiationService,
107105
@IOpenerService private readonly _openerService: IOpenerService,
108106
@IProductService private readonly _productService: IProductService,
109107
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@@ -273,18 +271,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
273271
webviewInput.webview.options = options;
274272
webviewInput.webview.extension = extension;
275273

276-
const model = this._instantiationService.createInstance(CustomEditorModel, webviewInput.getResource());
277-
webviewInput.setModel(model);
278-
this._models.set(handle, model);
279-
280-
webviewInput.onDispose(() => {
281-
this._models.delete(handle);
282-
});
274+
const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType);
283275

284276
model.onUndo(edit => {
285277
this._proxy.$undoEdits(handle, [edit]);
286278
});
287279

280+
model.onRedo(edit => {
281+
this._proxy.$redoEdits(handle, [edit]);
282+
});
283+
284+
webviewInput.onDispose(() => {
285+
this._customEditorService.models.disposeModel(model);
286+
});
287+
288288
try {
289289
await this._proxy.$resolveWebviewEditor(
290290
webviewInput.getResource(),
@@ -318,7 +318,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
318318
throw new Error('Webview is not a webview editor');
319319
}
320320

321-
const model = this._models.get(handle);
321+
const model = this._customEditorService.models.get(webview.getResource(), webview.viewType);
322322
if (!model) {
323323
throw new Error('Could not find model for webview editor');
324324
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ export interface ExtHostWebviewsShape {
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>;
592592
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void;
593+
$redoEdits(handle: WebviewPanelHandle, edits: string[]): void;
593594
}
594595

595596
export interface MainThreadUrlsShape extends IDisposable {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
252252
assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits);
253253
}
254254

255+
_redoEdits(edits: string[]): void {
256+
assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits);
257+
}
258+
255259
private assertNotDisposed() {
256260
if (this._isDisposed) {
257261
throw new Error('Webview is disposed');
@@ -447,6 +451,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
447451
panel._undoEdits(edits);
448452
}
449453

454+
$redoEdits(handle: WebviewPanelHandle, edits: string[]): void {
455+
const panel = this.getWebviewPanel(handle);
456+
if (!panel) {
457+
return;
458+
}
459+
panel._redoEdits(edits);
460+
}
461+
450462
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
451463
return this._webviewPanels.get(handle);
452464
}

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,20 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
120120
});
121121
}
122122

123-
public runCommand(accessor: ServicesAccessor, _args: any): void {
123+
public runCommand(accessor: ServicesAccessor): void {
124124
const customEditorService = accessor.get<ICustomEditorService>(ICustomEditorService);
125-
if (!customEditorService.activeCustomEditor) {
125+
126+
const activeCustomEditor = customEditorService.activeCustomEditor;
127+
if (!activeCustomEditor) {
128+
return;
129+
}
130+
131+
const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType);
132+
if (!model) {
126133
return;
127134
}
128-
console.log(customEditorService.activeCustomEditor);
135+
136+
model.undo();
129137
}
130138
}).register();
131139

@@ -147,7 +155,19 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
147155
});
148156
}
149157

150-
public runCommand(_accessor: ServicesAccessor, args: any): void {
151-
console.log('redo', args);
158+
public runCommand(accessor: ServicesAccessor): void {
159+
const customEditorService = accessor.get<ICustomEditorService>(ICustomEditorService);
160+
161+
const activeCustomEditor = customEditorService.activeCustomEditor;
162+
if (!activeCustomEditor) {
163+
return;
164+
}
165+
166+
const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType);
167+
if (!model) {
168+
return;
169+
}
170+
171+
model.redo();
152172
}
153173
}).register();

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { coalesce, distinct, mergeSort, find } from 'vs/base/common/arrays';
6+
import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays';
77
import * as glob from 'vs/base/common/glob';
8-
import { UnownedDisposable, Disposable } from 'vs/base/common/lifecycle';
8+
import { Lazy } from 'vs/base/common/lazy';
9+
import { Disposable, UnownedDisposable } from 'vs/base/common/lifecycle';
910
import { Schemas } from 'vs/base/common/network';
1011
import { basename, DataUri, isEqual } from 'vs/base/common/resources';
1112
import { URI } from 'vs/base/common/uri';
1213
import { generateUuid } from 'vs/base/common/uuid';
1314
import * as nls from 'vs/nls';
1415
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
16+
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1517
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
1618
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1719
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
@@ -21,14 +23,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
2123
import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor';
2224
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
2325
import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint';
24-
import { CustomEditorPriority, CustomEditorInfo, CustomEditorSelector, ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, ICustomEditor } from 'vs/workbench/contrib/customEditor/common/customEditor';
26+
import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
27+
import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager';
2528
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
26-
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
29+
import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview';
2730
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
2831
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
32+
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
2933
import { CustomFileEditorInput } from './customEditorInput';
30-
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
31-
import { Lazy } from 'vs/base/common/lazy';
3234

3335
const defaultEditorId = 'default';
3436

@@ -73,11 +75,15 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
7375

7476
private readonly _editorInfoStore = new CustomEditorInfoStore();
7577

78+
private readonly _models: CustomEditorModelManager;
79+
7680
private readonly _hasCustomEditor: IContextKey<boolean>;
7781
private readonly _focusedCustomEditorIsEditable: IContextKey<boolean>;
82+
private readonly _webviewHasOwnEditFunctions: IContextKey<boolean>;
7883

7984
constructor(
8085
@IContextKeyService contextKeyService: IContextKeyService,
86+
@IWorkingCopyService workingCopyService: IWorkingCopyService,
8187
@IConfigurationService private readonly configurationService: IConfigurationService,
8288
@IEditorService private readonly editorService: IEditorService,
8389
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -86,6 +92,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
8692
) {
8793
super();
8894

95+
this._models = new CustomEditorModelManager(workingCopyService);
96+
8997
webviewEditorsExtensionPoint.setHandler(extensions => {
9098
this._editorInfoStore.clear();
9199

@@ -104,21 +112,21 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
104112

105113
this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService);
106114
this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService);
115+
this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService);
107116

108117
this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts()));
109118
this.updateContexts();
110119
}
111120

121+
public get models() { return this._models; }
122+
112123
public get activeCustomEditor(): ICustomEditor | undefined {
113124
const activeInput = this.editorService.activeControl?.input;
114125
if (!(activeInput instanceof CustomFileEditorInput)) {
115126
return undefined;
116127
}
117-
118-
return {
119-
resource: activeInput.getResource(),
120-
model: undefined,
121-
};
128+
const resource = activeInput.getResource();
129+
return { resource, viewType: activeInput.viewType };
122130
}
123131

124132
public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] {
@@ -236,6 +244,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
236244
if (!resource) {
237245
this._hasCustomEditor.reset();
238246
this._focusedCustomEditorIsEditable.reset();
247+
this._webviewHasOwnEditFunctions.reset();
239248
return;
240249
}
241250

@@ -245,6 +254,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
245254
];
246255
this._hasCustomEditor.set(possibleEditors.length > 0);
247256
this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput);
257+
this._webviewHasOwnEditFunctions.set(true);
248258
}
249259
}
250260

src/vs/workbench/contrib/customEditor/common/customEditor.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Event } from 'vs/base/common/event';
67
import { URI } from 'vs/base/common/uri';
78
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
89
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
910
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1011
import { EditorInput, IEditor } from 'vs/workbench/common/editor';
11-
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel';
1212
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
13+
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
1314

1415
export const ICustomEditorService = createDecorator<ICustomEditorService>('customEditorService');
1516

@@ -18,12 +19,14 @@ export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey<boole
1819

1920
export interface ICustomEditor {
2021
readonly resource: URI;
21-
readonly model: CustomEditorModel | undefined;
22+
readonly viewType: string;
2223
}
2324

2425
export interface ICustomEditorService {
2526
_serviceBrand: any;
2627

28+
readonly models: ICustomEditorModelManager;
29+
2730
readonly activeCustomEditor: ICustomEditor | undefined;
2831

2932
getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[];
@@ -35,6 +38,26 @@ export interface ICustomEditorService {
3538
promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditor | undefined>;
3639
}
3740

41+
export type CustomEditorEdit = string;
42+
43+
export interface ICustomEditorModelManager {
44+
get(resource: URI, viewType: string): ICustomEditorModel | undefined;
45+
46+
loadOrCreate(resource: URI, viewType: string): Promise<ICustomEditorModel>;
47+
48+
disposeModel(model: ICustomEditorModel): void;
49+
}
50+
51+
export interface ICustomEditorModel extends IWorkingCopy {
52+
readonly onUndo: Event<CustomEditorEdit>;
53+
readonly onRedo: Event<CustomEditorEdit>;
54+
55+
undo(): void;
56+
redo(): void;
57+
58+
makeEdit(data: string): void;
59+
}
60+
3861
export const enum CustomEditorPriority {
3962
default = 'default',
4063
builtin = 'builtin',

0 commit comments

Comments
 (0)