Skip to content

Commit 9dae408

Browse files
committed
Switch webview api back to use delegate model
For microsoft#77131 Going back the the delegate based model for a few reasons: - It gives us a better approach to add additional API hooks in the future (such as for rename) - In practive, the capabilities were almost always the same as the `userData` on the document. It is rather confusing to have both `userData` and the capabilities 'on' the document
1 parent c65ea43 commit 9dae408

6 files changed

Lines changed: 92 additions & 70 deletions

File tree

extensions/image-preview/src/preview.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export class PreviewManager implements vscode.CustomEditorProvider {
2727
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
2828
) { }
2929

30-
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<vscode.CustomEditorCapabilities> {
31-
return {};
30+
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<void> {
31+
// noop
3232
}
3333

3434
public async resolveCustomEditor(

extensions/markdown-language-features/src/features/previewManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
148148
this.registerDynamicPreview(preview);
149149
}
150150

151-
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<vscode.CustomEditorCapabilities> {
152-
return {};
151+
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<void> {
152+
// noop
153153
}
154154

155155
public async resolveCustomTextEditor(

src/vs/vscode.proposed.d.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,78 +1222,88 @@ declare module 'vscode' {
12221222
// - Should we expose edits?
12231223
// - More properties from `TextDocument`?
12241224

1225-
/**
1226-
* Defines the capabilities of a custom webview editor.
1227-
*/
1228-
interface CustomEditorCapabilities {
1229-
/**
1230-
* Defines the editing capability of a custom webview document.
1231-
*
1232-
* When not provided, the document is considered readonly.
1233-
*/
1234-
readonly editing?: CustomEditorEditingCapability;
1235-
}
1236-
12371225
/**
12381226
* Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard
12391227
* editor events such as `undo` or `save`.
12401228
*
12411229
* @param EditType Type of edits.
12421230
*/
1243-
interface CustomEditorEditingCapability<EditType = unknown> {
1231+
interface CustomEditorEditingDelegate<EditType = unknown> {
12441232
/**
12451233
* Save the resource.
12461234
*
1235+
* @param document Document to save.
12471236
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
12481237
*
12491238
* @return Thenable signaling that the save has completed.
12501239
*/
1251-
save(cancellation: CancellationToken): Thenable<void>;
1240+
save(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
12521241

12531242
/**
12541243
* Save the existing resource at a new path.
12551244
*
1245+
* @param document Document to save.
12561246
* @param targetResource Location to save to.
12571247
*
12581248
* @return Thenable signaling that the save has completed.
12591249
*/
1260-
saveAs(targetResource: Uri): Thenable<void>;
1250+
saveAs(document: CustomDocument, targetResource: Uri): Thenable<void>;
12611251

12621252
/**
12631253
* Event triggered by extensions to signal to VS Code that an edit has occurred.
12641254
*/
1265-
readonly onDidEdit: Event<EditType>;
1255+
readonly onDidEdit: Event<{
1256+
/**
1257+
* Document the edit is for.
1258+
*/
1259+
readonly document: CustomDocument;
1260+
1261+
/**
1262+
* Object that describes the edit.
1263+
*
1264+
* Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`.
1265+
*/
1266+
readonly edit: EditType;
1267+
1268+
/**
1269+
* Display name describing the edit.
1270+
*/
1271+
readonly label?: string;
1272+
}>;
12661273

12671274
/**
12681275
* Apply a set of edits.
12691276
*
12701277
* Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit.
12711278
*
1279+
* @param document Document to apply edits to.
12721280
* @param edit Array of edits. Sorted from oldest to most recent.
12731281
*
12741282
* @return Thenable signaling that the change has completed.
12751283
*/
1276-
applyEdits(edits: readonly EditType[]): Thenable<void>;
1284+
applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
12771285

12781286
/**
12791287
* Undo a set of edits.
12801288
*
12811289
* This is triggered when a user undoes an edit.
12821290
*
1291+
* @param document Document to undo edits from.
12831292
* @param edit Array of edits. Sorted from most recent to oldest.
12841293
*
12851294
* @return Thenable signaling that the change has completed.
12861295
*/
1287-
undoEdits(edits: readonly EditType[]): Thenable<void>;
1296+
undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
12881297

12891298
/**
12901299
* Revert the file to its last saved state.
12911300
*
1301+
* @param document Document to revert.
12921302
* @param change Added or applied edits.
12931303
*
12941304
* @return Thenable signaling that the change has completed.
12951305
*/
1296-
revert(change: {
1306+
revert(document: CustomDocument, change: {
12971307
readonly undoneEdits: readonly EditType[];
12981308
readonly appliedEdits: readonly EditType[];
12991309
}): Thenable<void>;
@@ -1311,12 +1321,13 @@ declare module 'vscode' {
13111321
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
13121322
* `auto save` is enabled (since auto save already persists resource ).
13131323
*
1324+
* @param document Document to revert.
13141325
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
13151326
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
13161327
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
13171328
* than cancelling it to ensure that VS Code has some valid backup.
13181329
*/
1319-
backup(cancellation: CancellationToken): Thenable<void>;
1330+
backup(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
13201331
}
13211332

13221333
/**
@@ -1375,7 +1386,7 @@ declare module 'vscode' {
13751386
*
13761387
* @return The capabilities of the resolved document.
13771388
*/
1378-
resolveCustomDocument(document: CustomDocument): Thenable<CustomEditorCapabilities>;
1389+
resolveCustomDocument(document: CustomDocument): Thenable<void>;
13791390

13801391
/**
13811392
* Resolve a webview editor for a given resource.
@@ -1393,6 +1404,13 @@ declare module 'vscode' {
13931404
* @return Thenable indicating that the webview editor has been resolved.
13941405
*/
13951406
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable<void>;
1407+
1408+
/**
1409+
* Defines the editing capability of a custom webview document.
1410+
*
1411+
* When not provided, the document is considered readonly.
1412+
*/
1413+
readonly editingDelegate?: CustomEditorEditingDelegate;
13961414
}
13971415

13981416
/**

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
366366
return this._customEditorService.models.add(resource, viewType, model);
367367
}
368368

369-
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number): Promise<void> {
369+
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise<void> {
370370
const resource = URI.revive(resourceComponents);
371371
const model = await this._customEditorService.models.get(resource, viewType);
372372
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
373373
throw new Error('Could not find model for webview editor');
374374
}
375375

376-
model.pushEdit(editId);
376+
model.pushEdit(editId, label);
377377
}
378378

379379
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
@@ -604,7 +604,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
604604
return this._viewType;
605605
}
606606

607-
public pushEdit(editId: number) {
607+
public pushEdit(editId: number, label: string | undefined) {
608608
if (!this._editable) {
609609
throw new Error('Document is not editable');
610610
}
@@ -617,7 +617,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
617617
this._undoService.pushElement({
618618
type: UndoRedoElementType.Resource,
619619
resource: this.resource,
620-
label: 'Edit', // TODO: get this from extensions?
620+
label: label ?? 'Edit',
621621
undo: async () => {
622622
if (!this._editable) {
623623
return;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ export interface MainThreadWebviewsShape extends IDisposable {
592592
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
593593
$unregisterEditorProvider(viewType: string): void;
594594

595-
$onDidEdit(resource: UriComponents, viewType: string, editId: number): void;
595+
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
596596
}
597597

598598
export interface WebviewPanelViewStateData {

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

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { CancellationToken } from 'vs/base/common/cancellation';
77
import { Emitter, Event } from 'vs/base/common/event';
8-
import { Disposable } from 'vs/base/common/lifecycle';
8+
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
99
import { URI, UriComponents } from 'vs/base/common/uri';
1010
import { generateUuid } from 'vs/base/common/uuid';
1111
import * as modes from 'vs/editor/common/modes';
@@ -248,25 +248,31 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
248248

249249
class CustomDocument extends Disposable implements vscode.CustomDocument {
250250

251-
public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
252-
return Object.seal(new CustomDocument(proxy, viewType, uri));
251+
public static create(
252+
viewType: string,
253+
uri: vscode.Uri,
254+
editingDelegate: vscode.CustomEditorEditingDelegate | undefined
255+
) {
256+
return Object.seal(new CustomDocument(viewType, uri, editingDelegate));
253257
}
254258

255259
// Explicitly initialize all properties as we seal the object after creation!
256260

257261
readonly #_edits = new Cache<unknown>('edits');
258262

259-
readonly #proxy: MainThreadWebviewsShape;
260263
readonly #viewType: string;
261264
readonly #uri: vscode.Uri;
265+
readonly #editingDelegate: vscode.CustomEditorEditingDelegate | undefined;
262266

263-
#capabilities: vscode.CustomEditorCapabilities | undefined = undefined;
264-
265-
private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
267+
private constructor(
268+
viewType: string,
269+
uri: vscode.Uri,
270+
editingDelegate: vscode.CustomEditorEditingDelegate | undefined,
271+
) {
266272
super();
267-
this.#proxy = proxy;
268273
this.#viewType = viewType;
269274
this.#uri = uri;
275+
this.#editingDelegate = editingDelegate;
270276
}
271277

272278
dispose() {
@@ -289,47 +295,35 @@ class CustomDocument extends Disposable implements vscode.CustomDocument {
289295

290296
//#region Internal
291297

292-
/** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) {
293-
if (this.#capabilities) {
294-
throw new Error('Capabilities already provided');
295-
}
296-
297-
this.#capabilities = capabilities;
298-
capabilities.editing?.onDidEdit(edit => {
299-
const id = this.#_edits.add([edit]);
300-
this.#proxy.$onDidEdit(this.uri, this.viewType, id);
301-
});
302-
}
303-
304298
/** @internal*/ async _revert(changes: { undoneEdits: number[], redoneEdits: number[] }) {
305-
const editing = this.getEditingCapability();
299+
const editing = this.getEditingDelegate();
306300
const undoneEdits = changes.undoneEdits.map(id => this.#_edits.get(id, 0));
307301
const appliedEdits = changes.redoneEdits.map(id => this.#_edits.get(id, 0));
308-
return editing.revert({ undoneEdits, appliedEdits });
302+
return editing.revert(this, { undoneEdits, appliedEdits });
309303
}
310304

311305
/** @internal*/ _undo(editId: number) {
312-
const editing = this.getEditingCapability();
306+
const editing = this.getEditingDelegate();
313307
const edit = this.#_edits.get(editId, 0);
314-
return editing.undoEdits([edit]);
308+
return editing.undoEdits(this, [edit]);
315309
}
316310

317311
/** @internal*/ _redo(editId: number) {
318-
const editing = this.getEditingCapability();
312+
const editing = this.getEditingDelegate();
319313
const edit = this.#_edits.get(editId, 0);
320-
return editing.applyEdits([edit]);
314+
return editing.applyEdits(this, [edit]);
321315
}
322316

323317
/** @internal*/ _save(cancellation: CancellationToken) {
324-
return this.getEditingCapability().save(cancellation);
318+
return this.getEditingDelegate().save(this, cancellation);
325319
}
326320

327321
/** @internal*/ _saveAs(target: vscode.Uri) {
328-
return this.getEditingCapability().saveAs(target);
322+
return this.getEditingDelegate().saveAs(this, target);
329323
}
330324

331325
/** @internal*/ _backup(cancellation: CancellationToken) {
332-
return this.getEditingCapability().backup(cancellation);
326+
return this.getEditingDelegate().backup(this, cancellation);
333327
}
334328

335329
/** @internal*/ _disposeEdits(editIds: number[]) {
@@ -338,13 +332,17 @@ class CustomDocument extends Disposable implements vscode.CustomDocument {
338332
}
339333
}
340334

335+
/** @internal*/ _pushEdit(edit: unknown): number {
336+
return this.#_edits.add([edit]);
337+
}
338+
341339
//#endregion
342340

343-
private getEditingCapability(): vscode.CustomEditorEditingCapability {
344-
if (!this.#capabilities?.editing) {
341+
private getEditingDelegate(): vscode.CustomEditorEditingDelegate {
342+
if (!this.#editingDelegate) {
345343
throw new Error('Document is not editable');
346344
}
347-
return this.#capabilities.editing;
345+
return this.#editingDelegate;
348346
}
349347
}
350348

@@ -487,17 +485,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
487485
provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider,
488486
options: vscode.WebviewPanelOptions | undefined = {}
489487
): vscode.Disposable {
490-
let disposable: vscode.Disposable;
488+
const disposables = new DisposableStore();
491489
if ('resolveCustomTextEditor' in provider) {
492-
disposable = this._editorProviders.addTextProvider(viewType, extension, provider);
490+
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
493491
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options);
494492
} else {
495-
disposable = this._editorProviders.addCustomProvider(viewType, extension, provider);
493+
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
496494
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options);
495+
if (provider.editingDelegate) {
496+
disposables.add(provider.editingDelegate.onDidEdit(e => {
497+
const document = e.document;
498+
const editId = (document as CustomDocument)._pushEdit(e.edit);
499+
this._proxy.$onDidEdit(document.uri, document.viewType, editId, e.label);
500+
}));
501+
}
497502
}
498503

499504
return VSCodeDisposable.from(
500-
disposable,
505+
disposables,
501506
new VSCodeDisposable(() => {
502507
this._proxy.$unregisterEditorProvider(viewType);
503508
}));
@@ -592,12 +597,11 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
592597
}
593598

594599
const revivedResource = URI.revive(resource);
595-
const document = CustomDocument.create(this._proxy, viewType, revivedResource);
596-
const capabilities = await entry.provider.resolveCustomDocument(document);
597-
document._setCapabilities(capabilities);
600+
const document = CustomDocument.create(viewType, revivedResource, entry.provider.editingDelegate);
601+
await entry.provider.resolveCustomDocument(document);
598602
this._documents.add(document);
599603
return {
600-
editable: !!capabilities.editing
604+
editable: !!entry.provider.editingDelegate,
601605
};
602606
}
603607

0 commit comments

Comments
 (0)