From 4862602c4c3a14de76e24898c4d94a79210f7356 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 24 Apr 2020 14:47:00 -0700 Subject: [PATCH] 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 folder --- extensions/image-preview/src/preview.ts | 2 +- src/vs/vscode.proposed.d.ts | 238 +++++++++--------- .../workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostWebview.ts | 103 +++++--- 4 files changed, 188 insertions(+), 157 deletions(-) diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index ebe5653513a..25d3ac86c22 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -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'; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b70937e6e4b..5fe1106d050 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -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 | Event; - - /** - * 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; - - /** - * 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; - - /** - * 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; - - /** - * 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; + readonly destination: Uri; } /** @@ -1434,7 +1343,7 @@ declare module 'vscode' { * * @param T Type of the custom document returned by this provider. */ - export interface CustomEditorProvider { + export interface CustomReadonlyEditorProvider { /** * Create a new document for a given resource. @@ -1470,17 +1379,118 @@ declare module 'vscode' { resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; } + export interface CustomEditorProvider extends CustomReadonlyEditorProvider { + /** + * 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 | Event; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + } + 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. * diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index abdf14aee5d..ec91092cbb6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -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); }, diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 2bf7e9a54d1..ee06aec4d66 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -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('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 { @@ -680,29 +689,33 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { 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 { 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 { - 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 { 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) + ''; +}