Skip to content

Commit ff9fd2f

Browse files
committed
Custom Editors: pass original edit objects back to extensions
For microsoft#88719 With this change, instead of passing custom editor edit json back and forth with the extension host, we keep the original edit objects on the extension host. This means that we can pass extensions back the exact same edit object they first hand to us. It also means that edits no longer need to be json serializable.
1 parent 74cc2f3 commit ff9fd2f

9 files changed

Lines changed: 152 additions & 75 deletions

File tree

src/vs/vscode.proposed.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ declare module 'vscode' {
11811181
* Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard
11821182
* editor events such as `undo` or `save`.
11831183
*
1184-
* @param EditType Type of edits. Edit objects must be json serializable.
1184+
* @param EditType Type of edits.
11851185
*/
11861186
interface WebviewCustomEditorEditingDelegate<EditType> {
11871187
/**

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
306306

307307
provider.dispose();
308308
this._editorProviders.delete(viewType);
309+
310+
this._customEditorService.models.disposeAllModelsForView(viewType);
309311
}
310312

311313
private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) {
@@ -323,14 +325,17 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
323325
const capabilitiesSet = new Set(capabilities);
324326
const isEditable = capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable);
325327
if (isEditable) {
326-
model.onUndo(edits => {
327-
this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data));
328+
model.onUndo(e => {
329+
this._proxy.$undoEdits(resource, viewType, e.edits);
330+
});
331+
332+
model.onDisposeEdits(e => {
333+
this._proxy.$disposeEdits(e.edits);
328334
});
329335

330-
model.onApplyEdit(edits => {
331-
const editsToApply = edits.filter(x => x.source !== model).map(x => x.data);
332-
if (editsToApply.length) {
333-
this._proxy.$applyEdits(resource, viewType, editsToApply);
336+
model.onApplyEdit(e => {
337+
if (e.trigger !== model) {
338+
this._proxy.$applyEdits(resource, viewType, e.edits);
334339
}
335340
});
336341

@@ -369,13 +374,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
369374
}
370375
}
371376

372-
public $onEdit(resource: UriComponents, viewType: string, editData: any): void {
377+
public $onEdit(resource: UriComponents, viewType: string, editId: number): void {
373378
const model = this._customEditorService.models.get(URI.revive(resource), viewType);
374379
if (!model) {
375380
throw new Error('Could not find model for webview editor');
376381
}
377382

378-
model.pushEdit({ source: model, data: editData });
383+
model.pushEdit(editId, model);
379384
}
380385

381386
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
export class Cache<T> {
7+
8+
private static readonly enableDebugLogging = false;
9+
10+
private readonly _data = new Map<number, readonly T[]>();
11+
private _idPool = 1;
12+
13+
constructor(
14+
private readonly id: string
15+
) { }
16+
17+
add(item: readonly T[]): number {
18+
const id = this._idPool++;
19+
this._data.set(id, item);
20+
this.logDebugInfo();
21+
return id;
22+
}
23+
24+
get(pid: number, id: number): T | undefined {
25+
return this._data.has(pid) ? this._data.get(pid)![id] : undefined;
26+
}
27+
28+
delete(id: number) {
29+
this._data.delete(id);
30+
this.logDebugInfo();
31+
}
32+
33+
private logDebugInfo() {
34+
if (!Cache.enableDebugLogging) {
35+
return;
36+
}
37+
console.log(`${this.id} cache size — ${this._data.size}`);
38+
}
39+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ export interface MainThreadWebviewsShape extends IDisposable {
584584
$registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): void;
585585
$unregisterEditorProvider(viewType: string): void;
586586

587-
$onEdit(resource: UriComponents, viewType: string, editJson: any): void;
587+
$onEdit(resource: UriComponents, viewType: string, editId: number): void;
588588
}
589589

590590
export interface WebviewPanelViewStateData {
@@ -604,8 +604,9 @@ export interface ExtHostWebviewsShape {
604604
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
605605
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
606606

607-
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
608-
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
607+
$undoEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void;
608+
$applyEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void;
609+
$disposeEdits(editIds: readonly number[]): void;
609610

610611
$onSave(resource: UriComponents, viewType: string): Promise<void>;
611612
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;

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

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
3030
import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens';
3131
import { IdGenerator } from 'vs/base/common/idGenerator';
3232
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
33+
import { Cache } from './cache';
3334

3435
// --- adapter
3536

@@ -1064,40 +1065,6 @@ class SignatureHelpAdapter {
10641065
}
10651066
}
10661067

1067-
class Cache<T> {
1068-
private static readonly enableDebugLogging = false;
1069-
1070-
private readonly _data = new Map<number, readonly T[]>();
1071-
private _idPool = 1;
1072-
1073-
constructor(
1074-
private readonly id: string
1075-
) { }
1076-
1077-
add(item: readonly T[]): number {
1078-
const id = this._idPool++;
1079-
this._data.set(id, item);
1080-
this.logDebugInfo();
1081-
return id;
1082-
}
1083-
1084-
get(pid: number, id: number): T | undefined {
1085-
return this._data.has(pid) ? this._data.get(pid)![id] : undefined;
1086-
}
1087-
1088-
delete(id: number) {
1089-
this._data.delete(id);
1090-
this.logDebugInfo();
1091-
}
1092-
1093-
private logDebugInfo() {
1094-
if (!Cache.enableDebugLogging) {
1095-
return;
1096-
}
1097-
console.log(`${this.id} cache size — ${this._data.size}`);
1098-
}
1099-
}
1100-
11011068
class LinkProviderAdapter {
11021069

11031070
private _cache = new Cache<vscode.DocumentLink>('DocumentLink');

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

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
1515
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
1616
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
1717
import type * as vscode from 'vscode';
18+
import { Cache } from './cache';
1819
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol';
1920
import { Disposable as VSCodeDisposable } from './extHostTypes';
2021

@@ -251,8 +252,18 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
251252

252253
private readonly _proxy: MainThreadWebviewsShape;
253254
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewEditor>();
254-
private readonly _serializers = new Map<string, { readonly serializer: vscode.WebviewPanelSerializer, readonly extension: IExtensionDescription }>();
255-
private readonly _editorProviders = new Map<string, { readonly provider: vscode.WebviewCustomEditorProvider, readonly extension: IExtensionDescription }>();
255+
256+
private readonly _serializers = new Map<string, {
257+
readonly serializer: vscode.WebviewPanelSerializer;
258+
readonly extension: IExtensionDescription;
259+
}>();
260+
261+
private readonly _editorProviders = new Map<string, {
262+
readonly provider: vscode.WebviewCustomEditorProvider;
263+
readonly extension: IExtensionDescription;
264+
}>();
265+
266+
private readonly _edits = new Cache<unknown>('edits');
256267

257268
constructor(
258269
mainContext: IMainContext,
@@ -312,11 +323,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
312323
if (this._editorProviders.has(viewType)) {
313324
throw new Error(`Editor provider for '${viewType}' already registered`);
314325
}
315-
316326
this._editorProviders.set(viewType, { extension, provider, });
327+
317328
this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider));
329+
330+
// Hook up events
318331
provider?.editingDelegate?.onEdit(({ edit, resource }) => {
319-
this._proxy.$onEdit(resource, viewType, edit);
332+
const id = this._edits.add([edit]);
333+
this._proxy.$onEdit(resource, viewType, id);
320334
});
321335

322336
return new VSCodeDisposable(() => {
@@ -426,14 +440,32 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
426440
await provider.resolveWebviewEditor(revivedResource, revivedPanel);
427441
}
428442

429-
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
443+
$undoEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void {
430444
const provider = this.getEditorProvider(viewType);
431-
provider?.editingDelegate?.undoEdits(URI.revive(resource), edits);
445+
if (!provider?.editingDelegate) {
446+
return;
447+
}
448+
449+
const resource = URI.revive(resourceComponents);
450+
const edits = editIds.map(id => this._edits.get(id, 0));
451+
provider.editingDelegate.undoEdits(resource, edits);
432452
}
433453

434-
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
454+
$applyEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void {
435455
const provider = this.getEditorProvider(viewType);
436-
provider?.editingDelegate?.applyEdits(URI.revive(resource), edits);
456+
if (!provider?.editingDelegate) {
457+
return;
458+
}
459+
460+
const resource = URI.revive(resourceComponents);
461+
const edits = editIds.map(id => this._edits.get(id, 0));
462+
provider.editingDelegate.applyEdits(resource, edits);
463+
}
464+
465+
$disposeEdits(editIds: readonly number[]): void {
466+
for (const edit of editIds) {
467+
this._edits.delete(edit);
468+
}
437469
}
438470

439471
async $onSave(resource: UriComponents, viewType: string): Promise<void> {

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,16 @@ export interface ICustomEditorService {
4242
promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditor | undefined>;
4343
}
4444

45-
export type CustomEditorEdit = { source?: any, data: any };
45+
export type CustomEditorEdit = number;
4646

4747
export interface ICustomEditorModelManager {
4848
get(resource: URI, viewType: string): ICustomEditorModel | undefined;
4949

5050
resolve(resource: URI, viewType: string): Promise<ICustomEditorModel>;
5151

5252
disposeModel(model: ICustomEditorModel): void;
53+
54+
disposeAllModelsForView(viewType: string): void;
5355
}
5456

5557
export interface CustomEditorSaveEvent {
@@ -64,21 +66,23 @@ export interface CustomEditorSaveAsEvent {
6466
}
6567

6668
export interface ICustomEditorModel extends IWorkingCopy {
67-
readonly onUndo: Event<readonly CustomEditorEdit[]>;
68-
readonly onApplyEdit: Event<readonly CustomEditorEdit[]>;
69+
readonly viewType: string;
70+
71+
readonly onUndo: Event<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>;
72+
readonly onApplyEdit: Event<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>;
73+
readonly onDisposeEdits: Event<{ edits: readonly CustomEditorEdit[] }>;
74+
6975
readonly onWillSave: Event<CustomEditorSaveEvent>;
7076
readonly onWillSaveAs: Event<CustomEditorSaveAsEvent>;
7177

72-
readonly currentEdits: readonly CustomEditorEdit[];
73-
7478
undo(): void;
7579
redo(): void;
7680
revert(options?: IRevertOptions): Promise<boolean>;
7781

7882
save(options?: ISaveOptions): Promise<boolean>;
7983
saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise<boolean>;
8084

81-
pushEdit(edit: CustomEditorEdit): void;
85+
pushEdit(edit: CustomEditorEdit, trigger: any): void;
8286
}
8387

8488
export const enum CustomEditorPriority {

0 commit comments

Comments
 (0)