mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 16:49:06 +01:00
New custom editor API proposal
For #77131 Fixes #93963 Fixes #94515 Fixes #94517 Fixes #94527 Fixes #94509 Fixes #94514 Fixes #93996 Fixes #93913 This removes explicit edits from the API and reshapes the API to more closely match VS Code's internal API. The change also tries to better express the lifecycle of backups
This commit is contained in:
@@ -18,6 +18,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
import type * as vscode from 'vscode';
|
||||
import { Cache } from './cache';
|
||||
import * as extHostProtocol from './extHost.protocol';
|
||||
import * as extHostTypes from './extHostTypes';
|
||||
|
||||
@@ -262,22 +263,78 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
|
||||
}
|
||||
}
|
||||
|
||||
class WebviewDocumentStore {
|
||||
private readonly _documents = new Map<string, extHostTypes.CustomDocument>();
|
||||
class CustomDocumentStoreEntry {
|
||||
|
||||
public get(viewType: string, resource: vscode.Uri): extHostTypes.CustomDocument | undefined {
|
||||
constructor(
|
||||
public readonly document: vscode.CustomDocument,
|
||||
) { }
|
||||
|
||||
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
|
||||
|
||||
private _backup?: vscode.CustomDocumentBackup;
|
||||
|
||||
|
||||
addEdit(item: vscode.CustomDocumentEditEvent): number {
|
||||
return this._edits.add([item]);
|
||||
}
|
||||
|
||||
async undo(editId: number, isDirty: boolean): Promise<void> {
|
||||
await this.getEdit(editId).undo();
|
||||
if (!isDirty) {
|
||||
this.disposeBackup();
|
||||
}
|
||||
}
|
||||
|
||||
async redo(editId: number, isDirty: boolean): Promise<void> {
|
||||
await this.getEdit(editId).redo();
|
||||
if (!isDirty) {
|
||||
this.disposeBackup();
|
||||
}
|
||||
}
|
||||
|
||||
disposeEdits(editIds: number[]): void {
|
||||
for (const id of editIds) {
|
||||
this._edits.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
updateBackup(backup: vscode.CustomDocumentBackup): void {
|
||||
this._backup?.dispose();
|
||||
this._backup = backup;
|
||||
}
|
||||
|
||||
disposeBackup(): void {
|
||||
this._backup?.dispose();
|
||||
this._backup = undefined;
|
||||
}
|
||||
|
||||
private getEdit(editId: number): vscode.CustomDocumentEditEvent {
|
||||
const edit = this._edits.get(editId, 0);
|
||||
if (!edit) {
|
||||
throw new Error('No edit found');
|
||||
}
|
||||
return edit;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDocumentStore {
|
||||
private readonly _documents = new Map<string, CustomDocumentStoreEntry>();
|
||||
|
||||
public get(viewType: string, resource: vscode.Uri): CustomDocumentStoreEntry | undefined {
|
||||
return this._documents.get(this.key(viewType, resource));
|
||||
}
|
||||
|
||||
public add(viewType: string, document: extHostTypes.CustomDocument) {
|
||||
public add(viewType: string, document: vscode.CustomDocument): CustomDocumentStoreEntry {
|
||||
const key = this.key(viewType, document.uri);
|
||||
if (this._documents.has(key)) {
|
||||
throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`);
|
||||
}
|
||||
this._documents.set(key, document);
|
||||
const entry = new CustomDocumentStoreEntry(document);
|
||||
this._documents.set(key, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public delete(viewType: string, document: extHostTypes.CustomDocument) {
|
||||
public delete(viewType: string, document: vscode.CustomDocument) {
|
||||
const key = this.key(viewType, document.uri);
|
||||
this._documents.delete(key);
|
||||
}
|
||||
@@ -285,6 +342,7 @@ class WebviewDocumentStore {
|
||||
private key(viewType: string, resource: vscode.Uri): string {
|
||||
return `${viewType}@@@${resource}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const enum WebviewEditorType {
|
||||
@@ -342,7 +400,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
|
||||
private readonly _editorProviders = new EditorProviderStore();
|
||||
|
||||
private readonly _documents = new WebviewDocumentStore();
|
||||
private readonly _documents = new CustomDocumentStore();
|
||||
|
||||
constructor(
|
||||
mainContext: extHostProtocol.IMainContext,
|
||||
@@ -410,13 +468,6 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
} else {
|
||||
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
|
||||
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options);
|
||||
if (provider.editingDelegate) {
|
||||
disposables.add(provider.editingDelegate.onDidEdit(e => {
|
||||
const document = e.document;
|
||||
const editId = (document as extHostTypes.CustomDocument)._addEdit(e.edit);
|
||||
this._proxy.$onDidEdit(document.uri, viewType, editId, e.label);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return extHostTypes.Disposable.from(
|
||||
@@ -504,7 +555,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
}
|
||||
|
||||
async $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string, cancellation: CancellationToken) {
|
||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
@@ -515,14 +566,20 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
}
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
const document = await entry.provider.openCustomDocument(revivedResource, cancellation);
|
||||
this._documents.add(viewType, document as extHostTypes.CustomDocument);
|
||||
return {
|
||||
editable: !!entry.provider.editingDelegate,
|
||||
};
|
||||
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
|
||||
const documentEntry = this._documents.add(viewType, document);
|
||||
|
||||
if (this.isEditable(document)) {
|
||||
document.onDidEdit(e => {
|
||||
const editId = documentEntry.addEdit(e);
|
||||
this._proxy.$onDidEdit(document.uri, viewType, editId, e.label);
|
||||
});
|
||||
}
|
||||
|
||||
return { editable: this.isEditable(document) };
|
||||
}
|
||||
|
||||
async $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<void> {
|
||||
async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void> {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
@@ -533,9 +590,9 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
}
|
||||
|
||||
const revivedResource = URI.revive(resource);
|
||||
const document = this.getCustomDocument(viewType, revivedResource);
|
||||
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
|
||||
this._documents.delete(viewType, document);
|
||||
document._dispose();
|
||||
document.dispose();
|
||||
}
|
||||
|
||||
async $resolveWebviewEditor(
|
||||
@@ -561,7 +618,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
switch (entry.type) {
|
||||
case WebviewEditorType.Custom:
|
||||
{
|
||||
const document = this.getCustomDocument(viewType, revivedResource);
|
||||
const { document } = this.getCustomDocumentEntry(viewType, revivedResource);
|
||||
return entry.provider.resolveCustomEditor(document, revivedPanel, cancellation);
|
||||
}
|
||||
case WebviewEditorType.Text:
|
||||
@@ -577,8 +634,8 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
}
|
||||
|
||||
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void {
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
document._disposeEdits(editIds);
|
||||
const document = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
document.disposeEdits(editIds);
|
||||
}
|
||||
|
||||
async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
@@ -601,69 +658,65 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||
await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview, CancellationToken.None);
|
||||
}
|
||||
|
||||
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
document._updateEditState(state);
|
||||
return delegate.undoEdits(document, [document._getEdit(editId)]);
|
||||
async $undo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
return entry.undo(editId, isDirty);
|
||||
}
|
||||
|
||||
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
document._updateEditState(state);
|
||||
return delegate.applyEdits(document, [document._getEdit(editId)]);
|
||||
async $redo(resourceComponents: UriComponents, viewType: string, editId: number, isDirty: boolean): Promise<void> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
return entry.redo(editId, isDirty);
|
||||
}
|
||||
|
||||
async $revert(resourceComponents: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }, state: extHostProtocol.CustomDocumentEditState): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
const undoneEdits = changes.undoneEdits.map(id => document._getEdit(id));
|
||||
const appliedEdits = changes.redoneEdits.map(id => document._getEdit(id));
|
||||
document._updateEditState(state);
|
||||
return delegate.revert(document, { undoneEdits, appliedEdits });
|
||||
async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getEditableCustomDocument(viewType, resourceComponents);
|
||||
await document.revert(cancellation);
|
||||
entry.disposeBackup();
|
||||
}
|
||||
|
||||
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
return delegate.save(document, cancellation);
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getEditableCustomDocument(viewType, resourceComponents);
|
||||
await document.save(cancellation);
|
||||
entry.disposeBackup();
|
||||
}
|
||||
|
||||
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
return delegate.saveAs(document, URI.revive(targetResource), cancellation);
|
||||
const document = this.getEditableCustomDocument(viewType, resourceComponents);
|
||||
return document.saveAs(URI.revive(targetResource), cancellation);
|
||||
}
|
||||
|
||||
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const delegate = this.getEditingDelegate(viewType);
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
return delegate.backup(document, cancellation);
|
||||
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getEditableCustomDocument(viewType, resourceComponents);
|
||||
const backup = await document.backup(cancellation);
|
||||
entry.updateBackup(backup);
|
||||
return backup.backupId;
|
||||
}
|
||||
|
||||
private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined {
|
||||
return this._webviewPanels.get(handle);
|
||||
}
|
||||
|
||||
private getCustomDocument(viewType: string, resource: UriComponents): extHostTypes.CustomDocument {
|
||||
const document = this._documents.get(viewType, URI.revive(resource));
|
||||
if (!document) {
|
||||
throw new Error('No webview editor custom document found');
|
||||
private getCustomDocumentEntry(viewType: string, resource: UriComponents): CustomDocumentStoreEntry {
|
||||
const entry = this._documents.get(viewType, URI.revive(resource));
|
||||
if (!entry) {
|
||||
throw new Error('No custom document found');
|
||||
}
|
||||
return document;
|
||||
return entry;
|
||||
}
|
||||
|
||||
private getEditingDelegate(viewType: string): vscode.CustomEditorEditingDelegate {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
private isEditable(document: vscode.CustomDocument): document is vscode.EditableCustomDocument {
|
||||
return !!(document as vscode.EditableCustomDocument).onDidEdit;
|
||||
}
|
||||
|
||||
private getEditableCustomDocument(viewType: string, resource: UriComponents): vscode.EditableCustomDocument {
|
||||
const { document } = this.getCustomDocumentEntry(viewType, resource);
|
||||
if (!this.isEditable(document)) {
|
||||
throw new Error('Custom document is not editable');
|
||||
}
|
||||
const delegate = (entry.provider as vscode.CustomEditorProvider).editingDelegate;
|
||||
if (!delegate) {
|
||||
throw new Error(`Provider for ${viewType}' does not support editing`);
|
||||
}
|
||||
return delegate;
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user