mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-12 22:08:04 -05:00
Add experimental moveCustomTextEditor hook
For #77131 Adds a hook that lets extensions preserve the webview for a custom editor across a rename
This commit is contained in:
15
src/vs/vscode.proposed.d.ts
vendored
15
src/vs/vscode.proposed.d.ts
vendored
@@ -1473,6 +1473,21 @@ declare module 'vscode' {
|
||||
* @return Thenable indicating that the webview editor has been resolved.
|
||||
*/
|
||||
resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable<void>;
|
||||
|
||||
/**
|
||||
* TODO: discuss this at api sync.
|
||||
*
|
||||
* Handle when the underlying resource for a custom editor is renamed.
|
||||
*
|
||||
* This allows the webview for the editor be preserved throughout the rename. If this method is not implemented,
|
||||
* VS Code will destory the previous custom editor and create a replacement one.
|
||||
*
|
||||
* @param newDocument New text document to use for the custom editor.
|
||||
* @param existingWebviewPanel Webview panel for the custom editor.
|
||||
*
|
||||
* @return Thenable indicating that the webview editor has been moved.
|
||||
*/
|
||||
moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel): Thenable<void>;
|
||||
}
|
||||
|
||||
namespace window {
|
||||
|
||||
@@ -576,6 +576,10 @@ export interface WebviewExtensionDescription {
|
||||
readonly location: UriComponents;
|
||||
}
|
||||
|
||||
export interface CustomTextEditorCapabilities {
|
||||
readonly supportsMove?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void;
|
||||
$disposeWebview(handle: WebviewPanelHandle): void;
|
||||
@@ -591,7 +595,7 @@ export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$registerSerializer(viewType: string): void;
|
||||
$unregisterSerializer(viewType: string): void;
|
||||
|
||||
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
|
||||
$registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void;
|
||||
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
|
||||
$unregisterEditorProvider(viewType: string): void;
|
||||
|
||||
@@ -627,6 +631,8 @@ export interface ExtHostWebviewsShape {
|
||||
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;
|
||||
|
||||
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
|
||||
|
||||
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUrlsShape extends IDisposable {
|
||||
|
||||
@@ -488,7 +488,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
const disposables = new DisposableStore();
|
||||
if ('resolveCustomTextEditor' in provider) {
|
||||
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
|
||||
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options);
|
||||
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, {
|
||||
supportsMove: !!provider.moveCustomTextEditor,
|
||||
});
|
||||
} else {
|
||||
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
|
||||
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options);
|
||||
@@ -663,6 +665,26 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
document._disposeEdits(editIds);
|
||||
}
|
||||
|
||||
async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise<void> {
|
||||
const entry = this._editorProviders.get(viewType);
|
||||
if (!entry) {
|
||||
throw new Error(`No provider found for '${viewType}'`);
|
||||
}
|
||||
|
||||
if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) {
|
||||
throw new Error(`Provider does not implement move '${viewType}'`);
|
||||
}
|
||||
|
||||
const webview = this.getWebviewPanel(handle);
|
||||
if (!webview) {
|
||||
throw new Error(`No webview found`);
|
||||
}
|
||||
|
||||
const resource = URI.revive(newResourceComponents);
|
||||
const document = this._extHostDocuments.getDocument(resource);
|
||||
await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview);
|
||||
}
|
||||
|
||||
async $undo(resourceComponents: UriComponents, viewType: string, editId: number): Promise<void> {
|
||||
const document = this.getCustomDocument(viewType, resourceComponents);
|
||||
return document._undo(editId);
|
||||
|
||||
@@ -186,7 +186,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
}
|
||||
}
|
||||
|
||||
const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup);
|
||||
const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup.id);
|
||||
|
||||
editorService.replaceEditors([{
|
||||
editor: activeEditor,
|
||||
|
||||
@@ -161,6 +161,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
}
|
||||
|
||||
move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
|
||||
if (!this._moveHandler) {
|
||||
return {
|
||||
editor: this.customEditorService.createInput(newResource, this.viewType, group)
|
||||
};
|
||||
}
|
||||
this._moveHandler(newResource);
|
||||
const newEditor = this.tryMoveWebview(group, newResource);
|
||||
if (!newEditor) {
|
||||
return;
|
||||
@@ -171,12 +177,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
private tryMoveWebview(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined {
|
||||
const editorInfo = this.customEditorService.getCustomEditor(this.viewType);
|
||||
if (editorInfo?.matches(uri)) {
|
||||
const webview = assertIsDefined(this.takeOwnershipOfWebview());
|
||||
const newInput = this.instantiationService.createInstance(CustomEditorInput,
|
||||
uri,
|
||||
this.viewType,
|
||||
this.id,
|
||||
new Lazy(() => webview));
|
||||
new Lazy(() => undefined!)); // this webview is replaced in the transfer call
|
||||
this.transfer(newInput);
|
||||
newInput.updateGroup(groupId);
|
||||
return newInput;
|
||||
}
|
||||
@@ -193,4 +199,21 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
|
||||
assertIsDefined(this._modelRef);
|
||||
this.undoRedoService.redo(this.resource);
|
||||
}
|
||||
|
||||
private _moveHandler?: (newResource: URI) => void;
|
||||
|
||||
public onMove(handler: (newResource: URI) => void): void {
|
||||
// TODO: Move this to the service
|
||||
this._moveHandler = handler;
|
||||
}
|
||||
|
||||
protected transfer(other: CustomEditorInput): CustomEditorInput | undefined {
|
||||
if (!super.transfer(other)) {
|
||||
return;
|
||||
}
|
||||
|
||||
other._moveHandler = this._moveHandler;
|
||||
this._moveHandler = undefined;
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { EditorInput, EditorOptions, IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions, IEditorInput, IEditorPane, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint';
|
||||
import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
||||
@@ -238,14 +238,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
return this.promptOpenWith(resource, options, group);
|
||||
}
|
||||
|
||||
const input = this.createInput(resource, viewType, group);
|
||||
const input = this.createInput(resource, viewType, group?.id);
|
||||
return this.openEditorForResource(resource, input, options, group);
|
||||
}
|
||||
|
||||
public createInput(
|
||||
resource: URI,
|
||||
viewType: string,
|
||||
group: IEditorGroup | undefined,
|
||||
group: GroupIdentifier | undefined,
|
||||
options?: { readonly customClasses: string; },
|
||||
): IEditorInput {
|
||||
if (viewType === defaultEditorId) {
|
||||
@@ -257,8 +257,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {});
|
||||
});
|
||||
const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview);
|
||||
if (group) {
|
||||
input.updateGroup(group.id);
|
||||
if (typeof group !== 'undefined') {
|
||||
input.updateGroup(group);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
@@ -327,7 +327,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
}
|
||||
|
||||
const moveResult = editor.move(group.id, newResource);
|
||||
const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group);
|
||||
const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group.id);
|
||||
|
||||
this.editorService.replaceEditors([{
|
||||
editor: editor,
|
||||
@@ -492,7 +492,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses });
|
||||
const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group.id, { customClasses });
|
||||
if (input instanceof EditorInput) {
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
import { distinct, mergeSort } from 'vs/base/common/arrays';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorPane, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const ICustomEditorService = createDecorator<ICustomEditorService>('customEditorService');
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface ICustomEditorService {
|
||||
getContributedCustomEditors(resource: URI): CustomEditorInfoCollection;
|
||||
getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection;
|
||||
|
||||
createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput;
|
||||
createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput;
|
||||
|
||||
openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
|
||||
promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
|
||||
|
||||
@@ -109,7 +109,8 @@ export class WebviewEditor extends BaseEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.webview) {
|
||||
const alreadyOwnsWebview = input instanceof WebviewInput && input.webview === this.webview;
|
||||
if (this.webview && !alreadyOwnsWebview) {
|
||||
this.webview.release(this);
|
||||
}
|
||||
|
||||
@@ -125,7 +126,9 @@ export class WebviewEditor extends BaseEditor {
|
||||
input.updateGroup(this.group.id);
|
||||
}
|
||||
|
||||
this.claimWebview(input);
|
||||
if (!alreadyOwnsWebview) {
|
||||
this.claimWebview(input);
|
||||
}
|
||||
if (this._dimension) {
|
||||
this.layout(this._dimension);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ export class WebviewInput extends EditorInput {
|
||||
private _iconPath?: WebviewIcons;
|
||||
private _group?: GroupIdentifier;
|
||||
|
||||
private readonly _webview: Lazy<WebviewOverlay>;
|
||||
private _didSomeoneTakeMyWebview = false;
|
||||
private _webview: Lazy<WebviewOverlay>;
|
||||
|
||||
private _hasTransfered = false;
|
||||
|
||||
get resource() {
|
||||
return URI.from({
|
||||
@@ -42,8 +43,8 @@ export class WebviewInput extends EditorInput {
|
||||
|
||||
dispose() {
|
||||
if (!this.isDisposed()) {
|
||||
if (!this._didSomeoneTakeMyWebview) {
|
||||
this._webview?.rawValue?.dispose();
|
||||
if (!this._hasTransfered) {
|
||||
this._webview.rawValue?.dispose();
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
@@ -107,11 +108,12 @@ export class WebviewInput extends EditorInput {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected takeOwnershipOfWebview(): WebviewOverlay | undefined {
|
||||
if (this._didSomeoneTakeMyWebview) {
|
||||
protected transfer(other: WebviewInput): WebviewInput | undefined {
|
||||
if (this._hasTransfered) {
|
||||
return undefined;
|
||||
}
|
||||
this._didSomeoneTakeMyWebview = true;
|
||||
return this.webview;
|
||||
this._hasTransfered = true;
|
||||
other._webview = this._webview;
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,11 +113,25 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput {
|
||||
super(id, viewType, name, webview, webviewService);
|
||||
}
|
||||
|
||||
#resolved = false;
|
||||
|
||||
@memoize
|
||||
public async resolve() {
|
||||
await this._webviewWorkbenchService.resolveWebview(this);
|
||||
if (!this.#resolved) {
|
||||
this.#resolved = true;
|
||||
await this._webviewWorkbenchService.resolveWebview(this);
|
||||
}
|
||||
return super.resolve();
|
||||
}
|
||||
|
||||
protected transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined {
|
||||
if (!super.transfer(other)) {
|
||||
return;
|
||||
}
|
||||
|
||||
other.#resolved = this.#resolved;
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user