Skip to content

Commit

Permalink
Hook up very basic undo/redo for webview editors
Browse files Browse the repository at this point in the history
For #77131
  • Loading branch information
mjbvz committed Nov 16, 2019
1 parent ba19fe0 commit 27a33ee
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 48 deletions.
4 changes: 2 additions & 2 deletions extensions/image-preview/src/preview.ts
Expand Up @@ -260,10 +260,10 @@ class Preview extends Disposable implements vscode.WebviewEditorEditingCapabilit

async hotExit() { }

async applyEdits(_edits: any[]) { }

async undoEdits(edits: any[]) { console.log('undo', edits); }

async applyEdits(edits: any[]) { console.log('apply', edits); }

//#endregion

public test_makeEdit() {
Expand Down
24 changes: 12 additions & 12 deletions src/vs/workbench/api/browser/mainThreadWebview.ts
Expand Up @@ -12,7 +12,6 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
Expand All @@ -21,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr
import { IEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput';
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel';
import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
Expand Down Expand Up @@ -96,14 +95,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
private readonly _webviewInputs = new WebviewInputStore();
private readonly _revivers = new Map<string, IDisposable>();
private readonly _editorProviders = new Map<string, IDisposable>();
private readonly _models = new Map<string, CustomEditorModel>();

constructor(
context: extHostProtocol.IExtHostContext,
@IExtensionService extensionService: IExtensionService,
@ICustomEditorService private readonly _customEditorService: ICustomEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IOpenerService private readonly _openerService: IOpenerService,
@IProductService private readonly _productService: IProductService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
Expand Down Expand Up @@ -273,18 +271,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webviewInput.webview.options = options;
webviewInput.webview.extension = extension;

const model = this._instantiationService.createInstance(CustomEditorModel, webviewInput.getResource());
webviewInput.setModel(model);
this._models.set(handle, model);

webviewInput.onDispose(() => {
this._models.delete(handle);
});
const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType);

model.onUndo(edit => {
this._proxy.$undoEdits(handle, [edit]);
});

model.onRedo(edit => {
this._proxy.$redoEdits(handle, [edit]);
});

webviewInput.onDispose(() => {
this._customEditorService.models.disposeModel(model);
});

try {
await this._proxy.$resolveWebviewEditor(
webviewInput.getResource(),
Expand Down Expand Up @@ -318,7 +318,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
throw new Error('Webview is not a webview editor');
}

const model = this._models.get(handle);
const model = this._customEditorService.models.get(webview.getResource(), webview.viewType);
if (!model) {
throw new Error('Could not find model for webview editor');
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -590,6 +590,7 @@ export interface ExtHostWebviewsShape {
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void;
$redoEdits(handle: WebviewPanelHandle, edits: string[]): void;
}

export interface MainThreadUrlsShape extends IDisposable {
Expand Down
12 changes: 12 additions & 0 deletions src/vs/workbench/api/common/extHostWebview.ts
Expand Up @@ -252,6 +252,10 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits);
}

_redoEdits(edits: string[]): void {
assertIsDefined(this._capabilities).editingCapability?.applyEdits(edits);
}

private assertNotDisposed() {
if (this._isDisposed) {
throw new Error('Webview is disposed');
Expand Down Expand Up @@ -447,6 +451,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
panel._undoEdits(edits);
}

$redoEdits(handle: WebviewPanelHandle, edits: string[]): void {
const panel = this.getWebviewPanel(handle);
if (!panel) {
return;
}
panel._redoEdits(edits);
}

private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined {
return this._webviewPanels.get(handle);
}
Expand Down
30 changes: 25 additions & 5 deletions src/vs/workbench/contrib/customEditor/browser/commands.ts
Expand Up @@ -120,12 +120,20 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
});
}

public runCommand(accessor: ServicesAccessor, _args: any): void {
public runCommand(accessor: ServicesAccessor): void {
const customEditorService = accessor.get<ICustomEditorService>(ICustomEditorService);
if (!customEditorService.activeCustomEditor) {

const activeCustomEditor = customEditorService.activeCustomEditor;
if (!activeCustomEditor) {
return;
}

const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType);
if (!model) {
return;
}
console.log(customEditorService.activeCustomEditor);

model.undo();
}
}).register();

Expand All @@ -147,7 +155,19 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
});
}

public runCommand(_accessor: ServicesAccessor, args: any): void {
console.log('redo', args);
public runCommand(accessor: ServicesAccessor): void {
const customEditorService = accessor.get<ICustomEditorService>(ICustomEditorService);

const activeCustomEditor = customEditorService.activeCustomEditor;
if (!activeCustomEditor) {
return;
}

const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType);
if (!model) {
return;
}

model.redo();
}
}).register();
32 changes: 21 additions & 11 deletions src/vs/workbench/contrib/customEditor/browser/customEditors.ts
Expand Up @@ -3,15 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { coalesce, distinct, mergeSort, find } from 'vs/base/common/arrays';
import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays';
import * as glob from 'vs/base/common/glob';
import { UnownedDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Lazy } from 'vs/base/common/lazy';
import { Disposable, UnownedDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename, DataUri, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
Expand All @@ -21,14 +23,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint';
import { CustomEditorPriority, CustomEditorInfo, CustomEditorSelector, ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, ICustomEditor } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { CustomFileEditorInput } from './customEditorInput';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Lazy } from 'vs/base/common/lazy';

const defaultEditorId = 'default';

Expand Down Expand Up @@ -73,11 +75,15 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ

private readonly _editorInfoStore = new CustomEditorInfoStore();

private readonly _models: CustomEditorModelManager;

private readonly _hasCustomEditor: IContextKey<boolean>;
private readonly _focusedCustomEditorIsEditable: IContextKey<boolean>;
private readonly _webviewHasOwnEditFunctions: IContextKey<boolean>;

constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
Expand All @@ -86,6 +92,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
) {
super();

this._models = new CustomEditorModelManager(workingCopyService);

webviewEditorsExtensionPoint.setHandler(extensions => {
this._editorInfoStore.clear();

Expand All @@ -104,21 +112,21 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ

this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService);
this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService);
this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService);

this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts()));
this.updateContexts();
}

public get models() { return this._models; }

public get activeCustomEditor(): ICustomEditor | undefined {
const activeInput = this.editorService.activeControl?.input;
if (!(activeInput instanceof CustomFileEditorInput)) {
return undefined;
}

return {
resource: activeInput.getResource(),
model: undefined,
};
const resource = activeInput.getResource();
return { resource, viewType: activeInput.viewType };
}

public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] {
Expand Down Expand Up @@ -236,6 +244,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
if (!resource) {
this._hasCustomEditor.reset();
this._focusedCustomEditorIsEditable.reset();
this._webviewHasOwnEditFunctions.reset();
return;
}

Expand All @@ -245,6 +254,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
];
this._hasCustomEditor.set(possibleEditors.length > 0);
this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput);
this._webviewHasOwnEditFunctions.set(true);
}
}

Expand Down
27 changes: 25 additions & 2 deletions src/vs/workbench/contrib/customEditor/common/customEditor.ts
Expand Up @@ -3,13 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from 'vs/base/common/event';
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 { EditorInput, IEditor } from 'vs/workbench/common/editor';
import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';

export const ICustomEditorService = createDecorator<ICustomEditorService>('customEditorService');

Expand All @@ -18,12 +19,14 @@ export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey<boole

export interface ICustomEditor {
readonly resource: URI;
readonly model: CustomEditorModel | undefined;
readonly viewType: string;
}

export interface ICustomEditorService {
_serviceBrand: any;

readonly models: ICustomEditorModelManager;

readonly activeCustomEditor: ICustomEditor | undefined;

getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[];
Expand All @@ -35,6 +38,26 @@ export interface ICustomEditorService {
promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditor | undefined>;
}

export type CustomEditorEdit = string;

export interface ICustomEditorModelManager {
get(resource: URI, viewType: string): ICustomEditorModel | undefined;

loadOrCreate(resource: URI, viewType: string): Promise<ICustomEditorModel>;

disposeModel(model: ICustomEditorModel): void;
}

export interface ICustomEditorModel extends IWorkingCopy {
readonly onUndo: Event<CustomEditorEdit>;
readonly onRedo: Event<CustomEditorEdit>;

undo(): void;
redo(): void;

makeEdit(data: string): void;
}

export const enum CustomEditorPriority {
default = 'default',
builtin = 'builtin',
Expand Down

0 comments on commit 27a33ee

Please sign in to comment.