Align custom editor API proposal with notebook API
Fixes #95854 Fixes #95849 For #77131 - Move all editing functionality back onto the provider. This better matches the notebook API. - Rename `CustomEditorProvider` to `CustomReadonlyEditorProvider`. `CustomEditorProvider` is now how editable custom editors are implemented - Give extension a full suggested backup path instead of just a folderpull/96108/head
parent
37944c88ac
commit
4862602c4c
|
@ -13,7 +13,7 @@ import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
|
|||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class PreviewManager implements vscode.CustomEditorProvider {
|
||||
export class PreviewManager implements vscode.CustomReadonlyEditorProvider {
|
||||
|
||||
public static readonly viewType = 'imagePreview.previewEditor';
|
||||
|
||||
|
|
|
@ -1239,11 +1239,17 @@ declare module 'vscode' {
|
|||
}
|
||||
|
||||
/**
|
||||
* Event triggered by extensions to signal to VS Code that an edit has occurred on an [`CustomEditableDocument`](#CustomEditableDocument).
|
||||
* Event triggered by extensions to signal to VS Code that an edit has occurred on an [`CustomDocument`](#CustomDocument).
|
||||
*
|
||||
* @see [`CustomEditableDocument.onDidChange`](#CustomEditableDocument.onDidChange).
|
||||
* @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument).
|
||||
*/
|
||||
interface CustomDocumentEditEvent {
|
||||
|
||||
/**
|
||||
* The document that the edit is for.
|
||||
*/
|
||||
readonly document: CustomDocument;
|
||||
|
||||
/**
|
||||
* Undo the edit operation.
|
||||
*
|
||||
|
@ -1267,17 +1273,20 @@ declare module 'vscode' {
|
|||
}
|
||||
|
||||
/**
|
||||
* Event triggered by extensions to signal to VS Code that the content of a [`CustomEditableDocument`](#CustomEditableDocument)
|
||||
* Event triggered by extensions to signal to VS Code that the content of a [`CustomDocument`](#CustomDocument)
|
||||
* has changed.
|
||||
*
|
||||
* @see [`CustomEditableDocument.onDidChange`](#CustomEditableDocument.onDidChange).
|
||||
* @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument).
|
||||
*/
|
||||
interface CustomDocumentContentChangeEvent {
|
||||
// marker interface
|
||||
/**
|
||||
* The document that the change is for.
|
||||
*/
|
||||
readonly document: CustomDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* A backup for an [`CustomEditableDocument`](#CustomEditableDocument).
|
||||
* A backup for an [`CustomDocument`](#CustomDocument).
|
||||
*/
|
||||
interface CustomDocumentBackup {
|
||||
/**
|
||||
|
@ -1285,7 +1294,7 @@ declare module 'vscode' {
|
|||
*
|
||||
* This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup.
|
||||
*/
|
||||
readonly backupId: string;
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Delete the current backup.
|
||||
|
@ -1301,113 +1310,13 @@ declare module 'vscode' {
|
|||
*/
|
||||
interface CustomDocumentBackupContext {
|
||||
/**
|
||||
* Uri of a workspace specific directory in which the extension can store backup data.
|
||||
* Suggested file location to write the new backup.
|
||||
*
|
||||
* The directory might not exist on disk and creation is up to the extension.
|
||||
* Note that your extension is free to ignore this and use its own strategy for backup.
|
||||
*
|
||||
* For editors for workspace resource, this destination will be in the workspace storage. The path may not
|
||||
*/
|
||||
readonly workspaceStorageUri: Uri | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an editable custom document used by a [`CustomEditorProvider`](#CustomEditorProvider).
|
||||
*
|
||||
* `CustomEditableDocument` is how custom editors hook into standard VS Code operations such as save and undo. The
|
||||
* document is also how custom editors notify VS Code that an edit has taken place.
|
||||
*/
|
||||
interface CustomEditableDocument extends CustomDocument {
|
||||
|
||||
/**
|
||||
* Signal that an edit has occurred inside a custom editor.
|
||||
*
|
||||
* This event must be fired by your extension whenever an edit happens in a custom editor. An edit can be
|
||||
* anything from changing some text, to cropping an image, to reordering a list. Your extension is free to
|
||||
* define what an edit is and what data is stored on each edit.
|
||||
*
|
||||
* Firing `onDidChange` causes VS Code to mark the editors as being dirty. This is cleared when the user either
|
||||
* saves or reverts the file.
|
||||
*
|
||||
* Editors that support undo/redo must fire a `CustomDocumentEditEvent` whenever an edit happens. This allows
|
||||
* users to undo and redo the edit using VS Code's standard VS Code keyboard shortcuts. VS Code will also mark
|
||||
* the editor as no longer being dirty if the user undoes all edits to the last saved state.
|
||||
*
|
||||
* Editors that support editing but cannot use VS Code's standard undo/redo mechanism must fire a `CustomDocumentContentChangeEvent`.
|
||||
* The only way for a user to clear the dirty state of an editor that does not support undo/redo is to either
|
||||
* `save` or `revert` the file.
|
||||
*
|
||||
* An editor should only ever fire `CustomDocumentEditEvent` events, or only ever fire `CustomDocumentContentChangeEvent` events.
|
||||
*/
|
||||
readonly onDidChange: Event<CustomDocumentEditEvent> | Event<CustomDocumentContentChangeEvent>;
|
||||
|
||||
/**
|
||||
* Save the resource for a custom editor.
|
||||
*
|
||||
* This method is invoked by VS Code when the user saves a custom editor. This can happen when the user
|
||||
* triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled.
|
||||
*
|
||||
* To implement `save`, the implementer must persist the custom editor. This usually means writing the
|
||||
* file data for the custom document to disk. After `save` completes, any associated editor instances will
|
||||
* no longer be marked as dirty.
|
||||
*
|
||||
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
|
||||
*
|
||||
* @return Thenable signaling that saving has completed.
|
||||
*/
|
||||
save(cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Save the resource for a custom editor to a different location.
|
||||
*
|
||||
* This method is invoked by VS Code when the user triggers save as on a custom editor. The implementer must
|
||||
* persist the custom editor to `targetResource`.
|
||||
*
|
||||
* When the user accepts save as, the current editor is be replaced by an editor for the newly saved file.
|
||||
*
|
||||
* @param uri Location to save to.
|
||||
* @param cancellation Token that signals the save is no longer required.
|
||||
*
|
||||
* @return Thenable signaling that saving has completed.
|
||||
*/
|
||||
saveAs(uri: Uri, cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Revert a custom editor to its last saved state.
|
||||
*
|
||||
* This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that
|
||||
* this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file).
|
||||
*
|
||||
* To implement `revert`, the implementer must make sure all editor instances (webviews) for `document`
|
||||
* are displaying the document in the same state is saved in. This usually means reloading the file from the
|
||||
* workspace.
|
||||
*
|
||||
* During `revert`, your extension should also clear any backups for the custom editor. Backups are only needed
|
||||
* when there is a difference between an editor's state in VS Code and its save state on disk.
|
||||
*
|
||||
* @param cancellation Token that signals the revert is no longer required.
|
||||
*
|
||||
* @return Thenable signaling that the change has completed.
|
||||
*/
|
||||
revert(cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Back up the resource in its current state.
|
||||
*
|
||||
* Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in
|
||||
* its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in
|
||||
* the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource,
|
||||
* your extension should first check to see if any backups exist for the resource. If there is a backup, your
|
||||
* extension should load the file contents from there instead of from the resource in the workspace.
|
||||
*
|
||||
* `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are
|
||||
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
|
||||
* `auto save` is enabled (since auto save already persists resource ).
|
||||
*
|
||||
* @param context Information that can be used to backup the document.
|
||||
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
|
||||
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
|
||||
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
|
||||
* than cancelling it to ensure that VS Code has some valid backup.
|
||||
*/
|
||||
backup(context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable<CustomDocumentBackup>;
|
||||
readonly destination: Uri;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1434,7 +1343,7 @@ declare module 'vscode' {
|
|||
*
|
||||
* @param T Type of the custom document returned by this provider.
|
||||
*/
|
||||
export interface CustomEditorProvider<T extends CustomDocument = CustomDocument> {
|
||||
export interface CustomReadonlyEditorProvider<T extends CustomDocument = CustomDocument> {
|
||||
|
||||
/**
|
||||
* Create a new document for a given resource.
|
||||
|
@ -1470,17 +1379,118 @@ declare module 'vscode' {
|
|||
resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable<void> | void;
|
||||
}
|
||||
|
||||
export interface CustomEditorProvider<T extends CustomDocument = CustomDocument> extends CustomReadonlyEditorProvider<T> {
|
||||
/**
|
||||
* Signal that an edit has occurred inside a custom editor.
|
||||
*
|
||||
* This event must be fired by your extension whenever an edit happens in a custom editor. An edit can be
|
||||
* anything from changing some text, to cropping an image, to reordering a list. Your extension is free to
|
||||
* define what an edit is and what data is stored on each edit.
|
||||
*
|
||||
* Firing `onDidChange` causes VS Code to mark the editors as being dirty. This is cleared when the user either
|
||||
* saves or reverts the file.
|
||||
*
|
||||
* Editors that support undo/redo must fire a `CustomDocumentEditEvent` whenever an edit happens. This allows
|
||||
* users to undo and redo the edit using VS Code's standard VS Code keyboard shortcuts. VS Code will also mark
|
||||
* the editor as no longer being dirty if the user undoes all edits to the last saved state.
|
||||
*
|
||||
* Editors that support editing but cannot use VS Code's standard undo/redo mechanism must fire a `CustomDocumentContentChangeEvent`.
|
||||
* The only way for a user to clear the dirty state of an editor that does not support undo/redo is to either
|
||||
* `save` or `revert` the file.
|
||||
*
|
||||
* An editor should only ever fire `CustomDocumentEditEvent` events, or only ever fire `CustomDocumentContentChangeEvent` events.
|
||||
*/
|
||||
readonly onDidChangeCustomDocument: Event<CustomDocumentEditEvent> | Event<CustomDocumentContentChangeEvent>;
|
||||
|
||||
/**
|
||||
* Save a custom document.
|
||||
*
|
||||
* This method is invoked by VS Code when the user saves a custom editor. This can happen when the user
|
||||
* triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled.
|
||||
*
|
||||
* To implement `save`, the implementer must persist the custom editor. This usually means writing the
|
||||
* file data for the custom document to disk. After `save` completes, any associated editor instances will
|
||||
* no longer be marked as dirty.
|
||||
*
|
||||
* @param document Document to save.
|
||||
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
|
||||
*
|
||||
* @return Thenable signaling that saving has completed.
|
||||
*/
|
||||
saveCustomDocument(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Save a custom document to a different location.
|
||||
*
|
||||
* This method is invoked by VS Code when the user triggers 'save as' on a custom editor. The implementer must
|
||||
* persist the custom editor to `destination`.
|
||||
*
|
||||
* When the user accepts save as, the current editor is be replaced by an editor for the newly saved file.
|
||||
*
|
||||
* @param document Document to save.
|
||||
* @param destination Location to save to.
|
||||
* @param cancellation Token that signals the save is no longer required.
|
||||
*
|
||||
* @return Thenable signaling that saving has completed.
|
||||
*/
|
||||
saveCustomDocumentAs(document: CustomDocument, destination: Uri, cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Revert a custom document to its last saved state.
|
||||
*
|
||||
* This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that
|
||||
* this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file).
|
||||
*
|
||||
* To implement `revert`, the implementer must make sure all editor instances (webviews) for `document`
|
||||
* are displaying the document in the same state is saved in. This usually means reloading the file from the
|
||||
* workspace.
|
||||
*
|
||||
* During `revert`, your extension should also clear any backups for the custom editor. Backups are only needed
|
||||
* when there is a difference between an editor's state in VS Code and its save state on disk.
|
||||
*
|
||||
* @param document Document to revert.
|
||||
* @param cancellation Token that signals the revert is no longer required.
|
||||
*
|
||||
* @return Thenable signaling that the change has completed.
|
||||
*/
|
||||
revertCustomDocument(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Back up a dirty custom document.
|
||||
*
|
||||
* Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in
|
||||
* its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in
|
||||
* the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource,
|
||||
* your extension should first check to see if any backups exist for the resource. If there is a backup, your
|
||||
* extension should load the file contents from there instead of from the resource in the workspace.
|
||||
*
|
||||
* `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are
|
||||
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
|
||||
* `auto save` is enabled (since auto save already persists resource ).
|
||||
*
|
||||
* @param document Document to backup.
|
||||
* @param context Information that can be used to backup the document.
|
||||
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
|
||||
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
|
||||
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
|
||||
* than cancelling it to ensure that VS Code has some valid backup.
|
||||
*/
|
||||
backupCustomDocument(document: CustomDocument, context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable<CustomDocumentBackup>;
|
||||
}
|
||||
|
||||
namespace window {
|
||||
/**
|
||||
* Temporary overload for `registerCustomEditorProvider` that takes a `CustomEditorProvider`.
|
||||
*/
|
||||
export function registerCustomEditorProvider2(
|
||||
viewType: string,
|
||||
provider: CustomEditorProvider,
|
||||
provider: CustomReadonlyEditorProvider | CustomEditorProvider,
|
||||
options?: {
|
||||
readonly webviewOptions?: WebviewPanelOptions;
|
||||
|
||||
/**
|
||||
* Only applies to `CustomReadonlyEditorProvider | CustomEditorProvider`.
|
||||
*
|
||||
* Indicates that the provider allows multiple editor instances to be open at the same time for
|
||||
* the same resource.
|
||||
*
|
||||
|
|
|
@ -588,7 +588,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions } = {}) => {
|
||||
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
|
||||
},
|
||||
registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => {
|
||||
registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options);
|
||||
},
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
|
@ -13,6 +16,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
|
@ -21,7 +25,6 @@ import type * as vscode from 'vscode';
|
|||
import { Cache } from './cache';
|
||||
import * as extHostProtocol from './extHost.protocol';
|
||||
import * as extHostTypes from './extHostTypes';
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
|
||||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
|
@ -266,16 +269,17 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
|
|||
|
||||
class CustomDocumentStoreEntry {
|
||||
|
||||
private _backupCounter = 1;
|
||||
|
||||
constructor(
|
||||
public readonly document: vscode.CustomDocument,
|
||||
public readonly backupContext: vscode.CustomDocumentBackupContext,
|
||||
private readonly _storagePath: string,
|
||||
) { }
|
||||
|
||||
private readonly _edits = new Cache<vscode.CustomDocumentEditEvent>('custom documents');
|
||||
|
||||
private _backup?: vscode.CustomDocumentBackup;
|
||||
|
||||
|
||||
addEdit(item: vscode.CustomDocumentEditEvent): number {
|
||||
return this._edits.add([item]);
|
||||
}
|
||||
|
@ -300,6 +304,10 @@ class CustomDocumentStoreEntry {
|
|||
}
|
||||
}
|
||||
|
||||
getNewBackupUri(): URI {
|
||||
return joinPath(URI.file(this._storagePath), hashPath(this.document.uri) + (this._backupCounter++));
|
||||
}
|
||||
|
||||
updateBackup(backup: vscode.CustomDocumentBackup): void {
|
||||
this._backup?.delete();
|
||||
this._backup = backup;
|
||||
|
@ -326,12 +334,12 @@ class CustomDocumentStore {
|
|||
return this._documents.get(this.key(viewType, resource));
|
||||
}
|
||||
|
||||
public add(viewType: string, document: vscode.CustomDocument, backupContext: vscode.CustomDocumentBackupContext): CustomDocumentStoreEntry {
|
||||
public add(viewType: string, document: vscode.CustomDocument, storagePath: string): 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}`);
|
||||
}
|
||||
const entry = new CustomDocumentStoreEntry(document, backupContext);
|
||||
const entry = new CustomDocumentStoreEntry(document, storagePath);
|
||||
this._documents.set(key, entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -359,7 +367,7 @@ type ProviderEntry = {
|
|||
} | {
|
||||
readonly extension: IExtensionDescription;
|
||||
readonly type: WebviewEditorType.Custom;
|
||||
readonly provider: vscode.CustomEditorProvider;
|
||||
readonly provider: vscode.CustomReadonlyEditorProvider;
|
||||
};
|
||||
|
||||
class EditorProviderStore {
|
||||
|
@ -369,7 +377,7 @@ class EditorProviderStore {
|
|||
return this.add(WebviewEditorType.Text, viewType, extension, provider);
|
||||
}
|
||||
|
||||
public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomEditorProvider): vscode.Disposable {
|
||||
public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomReadonlyEditorProvider): vscode.Disposable {
|
||||
return this.add(WebviewEditorType.Custom, viewType, extension, provider);
|
||||
}
|
||||
|
||||
|
@ -377,7 +385,7 @@ class EditorProviderStore {
|
|||
return this._providers.get(viewType);
|
||||
}
|
||||
|
||||
private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider): vscode.Disposable {
|
||||
private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider): vscode.Disposable {
|
||||
if (this._providers.has(viewType)) {
|
||||
throw new Error(`Provider for viewType:${viewType} already registered`);
|
||||
}
|
||||
|
@ -459,7 +467,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
|||
public registerCustomEditorProvider(
|
||||
extension: IExtensionDescription,
|
||||
viewType: string,
|
||||
provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider,
|
||||
provider: vscode.CustomReadonlyEditorProvider | vscode.CustomTextEditorProvider,
|
||||
options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean },
|
||||
): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
@ -470,6 +478,19 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
|||
});
|
||||
} else {
|
||||
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
|
||||
|
||||
if (this.supportEditing(provider)) {
|
||||
disposables.add(provider.onDidChangeCustomDocument(e => {
|
||||
const entry = this.getCustomDocumentEntry(viewType, e.document.uri);
|
||||
if (isEditEvent(e)) {
|
||||
const editId = entry.addEdit(e);
|
||||
this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label);
|
||||
} else {
|
||||
this._proxy.$onContentChange(e.document.uri, viewType);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument);
|
||||
}
|
||||
|
||||
|
@ -570,23 +591,11 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
|||
|
||||
const revivedResource = URI.revive(resource);
|
||||
const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation);
|
||||
const workspaceStoragePath = this._extensionStoragePaths?.workspaceValue(entry.extension);
|
||||
const documentEntry = this._documents.add(viewType, document, {
|
||||
workspaceStorageUri: workspaceStoragePath ? URI.file(workspaceStoragePath) : undefined,
|
||||
});
|
||||
|
||||
if (this.isEditable(document)) {
|
||||
document.onDidChange(e => {
|
||||
if (isEditEvent(e)) {
|
||||
const editId = documentEntry.addEdit(e);
|
||||
this._proxy.$onDidEdit(document.uri, viewType, editId, e.label);
|
||||
} else {
|
||||
this._proxy.$onContentChange(document.uri, viewType);
|
||||
}
|
||||
});
|
||||
}
|
||||
const storageRoot = this._extensionStoragePaths?.workspaceValue(entry.extension) ?? this._extensionStoragePaths?.globalValue(entry.extension);
|
||||
this._documents.add(viewType, document, storageRoot!);
|
||||
|
||||
return { editable: this.isEditable(document) };
|
||||
return { editable: this.supportEditing(entry.provider) };
|
||||
}
|
||||
|
||||
async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise<void> {
|
||||
|
@ -680,29 +689,33 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
|||
|
||||
async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getCustomEditableDocument(viewType, resourceComponents);
|
||||
await document.revert(cancellation);
|
||||
const provider = this.getCustomEditorProvider(viewType);
|
||||
await provider.revertCustomDocument(entry.document, cancellation);
|
||||
entry.disposeBackup();
|
||||
}
|
||||
|
||||
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getCustomEditableDocument(viewType, resourceComponents);
|
||||
await document.save(cancellation);
|
||||
const provider = this.getCustomEditorProvider(viewType);
|
||||
await provider.saveCustomDocument(entry.document, cancellation);
|
||||
entry.disposeBackup();
|
||||
}
|
||||
|
||||
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise<void> {
|
||||
const document = this.getCustomEditableDocument(viewType, resourceComponents);
|
||||
return document.saveAs(URI.revive(targetResource), cancellation);
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const provider = this.getCustomEditorProvider(viewType);
|
||||
return provider.saveCustomDocumentAs(entry.document, URI.revive(targetResource), cancellation);
|
||||
}
|
||||
|
||||
async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string> {
|
||||
const entry = this.getCustomDocumentEntry(viewType, resourceComponents);
|
||||
const document = this.getCustomEditableDocument(viewType, resourceComponents);
|
||||
const backup = await document.backup(entry.backupContext, cancellation);
|
||||
const provider = this.getCustomEditorProvider(viewType);
|
||||
|
||||
const backup = await provider.backupCustomDocument(entry.document, {
|
||||
destination: entry.getNewBackupUri(),
|
||||
}, cancellation);
|
||||
entry.updateBackup(backup);
|
||||
return backup.backupId;
|
||||
return backup.id;
|
||||
}
|
||||
|
||||
private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined {
|
||||
|
@ -717,16 +730,19 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
|||
return entry;
|
||||
}
|
||||
|
||||
private isEditable(document: vscode.CustomDocument): document is vscode.CustomEditableDocument {
|
||||
return !!(document as vscode.CustomEditableDocument).onDidChange;
|
||||
}
|
||||
|
||||
private getCustomEditableDocument(viewType: string, resource: UriComponents): vscode.CustomEditableDocument {
|
||||
const { document } = this.getCustomDocumentEntry(viewType, resource);
|
||||
if (!this.isEditable(document)) {
|
||||
private getCustomEditorProvider(viewType: string): vscode.CustomEditorProvider {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
const provider = entry?.provider;
|
||||
if (!provider || !this.supportEditing(provider)) {
|
||||
throw new Error('Custom document is not editable');
|
||||
}
|
||||
return document;
|
||||
return provider;
|
||||
}
|
||||
|
||||
private supportEditing(
|
||||
provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider
|
||||
): provider is vscode.CustomEditorProvider {
|
||||
return !!(provider as vscode.CustomEditorProvider).onDidChangeCustomDocument;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -759,3 +775,8 @@ function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomD
|
|||
return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function'
|
||||
&& typeof (e as vscode.CustomDocumentEditEvent).redo === 'function';
|
||||
}
|
||||
|
||||
function hashPath(resource: URI): string {
|
||||
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
|
||||
return hash(str) + '';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue