mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-23 01:58:53 +01:00
Prototyping custom editors (#77789)
* Custom Editor exploration For #77131 Adds a prototype of custom editors contributed by extensions. This change does the following: - Introduces a new contribution point for the declarative parts of a custom editor - Adds API for registering a webview editor provider. This lets VS Code decided when to create a webview editor - Adds an `openWith` command that lets you select which editor to use to open a resource from the file explorer - Adds a setting that lets you say that you always want to use a custom editor for a given file extension - Hooks up auto opening of a custom editor when opening a file from quick open or explorer - Adds a new extension that contributes a custom image preview for png and jpg files Still needs a lot of UX work and testing. We are also going to explore a more generic "open handler" based approach for supporting custom editors Revert * Re-use existing custom editor if one is already open * Don't re-create custom editor webview when clicking on already visible custom editor * Move customEditorInput to own file * First draft of serializing custom editor inputs * Use glob patterns instead of simple file extensions for matching custom resoruces for custom editors * Add descriptions * Try opening standard editor while prompting for custom editor * Make sure we hide image status on dispose * Make sure we restore editor group too * Use glob patterns for workbench.editor.custom * Allow users to configure custom editors for additional file types * Use filename glob instead of glob on full resource path * Adding placeholder for prompt open with * Add enableByDefault setting for editor contributions * Enable custom editors by default and add `discretion` enum Changes `enableByDefault` boolean to a `discretion` enum. This should give more flexibility if we want other options (such as forcing a given custom editor to always be used even if there are other default ones) * Allow custom editors to specify both a scheme and filenamePattern they are active for * Rework custom editor setting * Don't allow custom editors to be enabled for all resources by a config mistake * Replace built-in image editor with one from extension * Adding reopen with command * Improve comment * Remove commented code * Localize package.json and remove image * Remove extra lib setting from tsconfig
This commit is contained in:
@@ -16,7 +16,6 @@ import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions, WebviewPanelViewStateData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
|
||||
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
@@ -78,6 +77,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
private readonly _proxy: ExtHostWebviewsShape;
|
||||
private readonly _webviewEditorInputs = new WebviewHandleStore();
|
||||
private readonly _revivers = new Map<string, IDisposable>();
|
||||
private readonly _editorProviders = new Map<string, IDisposable>();
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@@ -95,11 +95,11 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this));
|
||||
this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this));
|
||||
|
||||
// This reviver's only job is to activate webview extensions
|
||||
// This reviver's only job is to activate webview panel extensions
|
||||
// This should trigger the real reviver to be registered from the extension host side.
|
||||
this._register(_webviewEditorService.registerReviver({
|
||||
canRevive: (webview: WebviewEditorInput) => {
|
||||
if (!webview.webview.state) {
|
||||
this._register(_webviewEditorService.registerResolver({
|
||||
canResolve: (webview: WebviewEditorInput) => {
|
||||
if (!webview.webview.state && !webview.editorResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
}
|
||||
return false;
|
||||
},
|
||||
reviveWebview: () => { throw new Error('not implemented'); }
|
||||
resolveWebview: () => { throw new Error('not implemented'); }
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -160,13 +160,13 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
}
|
||||
|
||||
public $setHtml(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.html = value;
|
||||
const webview = this.getWebviewEditorInput(handle);
|
||||
webview.webview.html = value;
|
||||
}
|
||||
|
||||
public $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */);
|
||||
const webview = this.getWebviewEditorInput(handle);
|
||||
webview.webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */);
|
||||
}
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
@@ -182,8 +182,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
}
|
||||
|
||||
public async $postMessage(handle: WebviewPanelHandle, message: any): Promise<boolean> {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.sendMessage(message);
|
||||
const webview = this.getWebviewEditorInput(handle);
|
||||
webview.webview.sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,11 +192,11 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._revivers.set(viewType, this._webviewEditorService.registerReviver({
|
||||
canRevive: (webviewEditorInput) => {
|
||||
this._revivers.set(viewType, this._webviewEditorService.registerResolver({
|
||||
canResolve: (webviewEditorInput) => {
|
||||
return !!webviewEditorInput.webview.state && webviewEditorInput.viewType === this.getInternalWebviewViewType(viewType);
|
||||
},
|
||||
reviveWebview: async (webviewEditorInput): Promise<void> => {
|
||||
resolveWebview: async (webviewEditorInput): Promise<void> => {
|
||||
const viewType = this.fromInternalWebviewViewType(webviewEditorInput.viewType);
|
||||
if (!viewType) {
|
||||
webviewEditorInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(webviewEditorInput.viewType);
|
||||
@@ -245,6 +245,48 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
this._revivers.delete(viewType);
|
||||
}
|
||||
|
||||
public $registerEditorProvider(viewType: string): void {
|
||||
if (this._editorProviders.has(viewType)) {
|
||||
throw new Error(`Provider for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._editorProviders.set(viewType, this._webviewEditorService.registerResolver({
|
||||
canResolve: (webviewEditorInput) => {
|
||||
return !!webviewEditorInput.editorResource && webviewEditorInput.viewType === viewType;
|
||||
},
|
||||
resolveWebview: async (webview: WebviewEditorInput) => {
|
||||
const handle = `resolved-${MainThreadWebviews.revivalPool++}`;
|
||||
this._webviewEditorInputs.add(handle, webview);
|
||||
this.hookupWebviewEventDelegate(handle, webview);
|
||||
|
||||
try {
|
||||
await this._proxy.$resolveWebviewEditor(
|
||||
webview.editorResource,
|
||||
handle,
|
||||
viewType,
|
||||
webview.getTitle(),
|
||||
webview.webview.state,
|
||||
editorGroupToViewColumn(this._editorGroupService, webview.group || 0),
|
||||
webview.webview.options
|
||||
);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
webview.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public $unregisterEditorProvider(viewType: string): void {
|
||||
const provider = this._editorProviders.get(viewType);
|
||||
if (!provider) {
|
||||
throw new Error(`No provider for ${viewType} registered`);
|
||||
}
|
||||
|
||||
provider.dispose();
|
||||
this._editorProviders.delete(viewType);
|
||||
}
|
||||
|
||||
private getInternalWebviewViewType(viewType: string): string {
|
||||
return `mainThreadWebview-${viewType}`;
|
||||
}
|
||||
@@ -334,10 +376,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
return this._webviewEditorInputs.getInputForHandle(handle);
|
||||
}
|
||||
|
||||
private getWebview(handle: WebviewPanelHandle): Webview {
|
||||
return this.getWebviewEditorInput(handle).webview;
|
||||
}
|
||||
|
||||
private static getDeserializationFailedContents(viewType: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
Reference in New Issue
Block a user