mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Add proposed webview view API (#104601)
Add proposed webview view API For #46585 This adds a new `WebviewView` proposed api to VS Code that lets webview be used inside views. Webview views can be contributed using a contribution point such as : ```json "views": { "explorer": [ { "type": "webview", "id": "cats.cat", "name": "Cats", "visibility": "visible" } ] }, ``` * Use proper activation event * Transparent background * Fix resize observer * Adding documentation * Move webview view to new directory under workbench * Remove resolver By moving the webviews view into their own fodler, I was able to avoid the cycle the resolver was originally introduced for * Use enum in more places * Hook up title and visible properties for webview views * Remove test view * Prefer Thenable * Add unknown view type error to collector
This commit is contained in:
parent
038e1a93b9
commit
61f799f53b
|
@ -4,9 +4,9 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
|
||||||
import { PreviewManager } from './preview';
|
import { PreviewManager } from './preview';
|
||||||
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
|
import { SizeStatusBarEntry } from './sizeStatusBarEntry';
|
||||||
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
|
|
||||||
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
|
import { ZoomStatusBarEntry } from './zoomStatusBarEntry';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
|
137
src/vs/vscode.proposed.d.ts
vendored
137
src/vs/vscode.proposed.d.ts
vendored
|
@ -1971,4 +1971,141 @@ declare module 'vscode' {
|
||||||
notebook: NotebookDocument | undefined;
|
notebook: NotebookDocument | undefined;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
||||||
|
//#region https://github.com/microsoft/vscode/issues/46585
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A webview based view.
|
||||||
|
*/
|
||||||
|
export interface WebviewView {
|
||||||
|
/**
|
||||||
|
* Identifies the type of the webview view, such as `'hexEditor.dataView'`.
|
||||||
|
*/
|
||||||
|
readonly viewType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying webview for the view.
|
||||||
|
*/
|
||||||
|
readonly webview: Webview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View title displayed in the UI.
|
||||||
|
*
|
||||||
|
* The view title is initially taken from the extension `package.json` contribution.
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when the view is disposed.
|
||||||
|
*
|
||||||
|
* Views are disposed of in a few cases:
|
||||||
|
*
|
||||||
|
* - When a view is collapsed and `retainContextWhenHidden` has not been set.
|
||||||
|
* - When a view is hidden by a user.
|
||||||
|
*
|
||||||
|
* Trying to use the view after it has been disposed throws an exception.
|
||||||
|
*/
|
||||||
|
readonly onDidDispose: Event<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks if the webview is currently visible.
|
||||||
|
*
|
||||||
|
* Views are visible when they are on the screen and expanded.
|
||||||
|
*/
|
||||||
|
readonly visible: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when the visibility of the view changes
|
||||||
|
*/
|
||||||
|
readonly onDidChangeVisibility: Event<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebviewViewResolveContext<T = unknown> {
|
||||||
|
/**
|
||||||
|
* Persisted state from the webview content.
|
||||||
|
*
|
||||||
|
* To save resources, VS Code normally deallocates webview views that are not visible. For example, if the user
|
||||||
|
* collapse a view or switching to another top level activity, the underlying webview document is deallocates.
|
||||||
|
*
|
||||||
|
* You can prevent this behavior by setting `retainContextWhenHidden` in the `WebviewOptions`. However this
|
||||||
|
* increases resource usage and should be avoided wherever possible. Instead, you can use persisted state to
|
||||||
|
* save off a webview's state so that it can be quickly recreated as needed.
|
||||||
|
*
|
||||||
|
* To save off a persisted state, inside the webview call `acquireVsCodeApi().setState()` with
|
||||||
|
* any json serializable object. To restore the state again, call `getState()`. For example:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // Within the webview
|
||||||
|
* const vscode = acquireVsCodeApi();
|
||||||
|
*
|
||||||
|
* // Get existing state
|
||||||
|
* const oldState = vscode.getState() || { value: 0 };
|
||||||
|
*
|
||||||
|
* // Update state
|
||||||
|
* setState({ value: oldState.value + 1 })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* VS Code ensures that the persisted state is saved correctly when a webview is hidden and across
|
||||||
|
* editor restarts.
|
||||||
|
*/
|
||||||
|
readonly state: T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for creating `WebviewView` elements.
|
||||||
|
*/
|
||||||
|
export interface WebviewViewProvider {
|
||||||
|
/**
|
||||||
|
* Revolves a webview view.
|
||||||
|
*
|
||||||
|
* `resolveWebviewView` is called when a view first becomes visible. This may happen when the view is
|
||||||
|
* first loaded or when the user hides and then shows a view again.
|
||||||
|
*
|
||||||
|
* @param webviewView Webview panel to restore. The serializer should take ownership of this panel. The
|
||||||
|
* provider must set the webview's `.html` and hook up all webview events it is interested in.
|
||||||
|
* @param context Additional metadata about the view being resolved.
|
||||||
|
* @param token Cancellation token indicating that the view being provided is no longer needed.
|
||||||
|
*
|
||||||
|
* @return Optional thenable indicating that the view has been fully resolved.
|
||||||
|
*/
|
||||||
|
resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Thenable<void> | void;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace window {
|
||||||
|
/**
|
||||||
|
* Register a new provider for webview views.
|
||||||
|
*
|
||||||
|
* @param viewId Unique id of the view. This should match the `id` from the
|
||||||
|
* `views` contribution in the package.json.
|
||||||
|
* @param provider Provider for the webview views.
|
||||||
|
*
|
||||||
|
* @return Disposable that unregisters the provider.
|
||||||
|
*/
|
||||||
|
export function registerWebviewViewProvider(viewId: string, provider: WebviewViewProvider, options?: {
|
||||||
|
/**
|
||||||
|
* Content settings for the webview created for this view.
|
||||||
|
*/
|
||||||
|
readonly webviewOptions?: {
|
||||||
|
/**
|
||||||
|
* Controls if the webview panel's content (iframe) is kept around even when the panel
|
||||||
|
* is no longer visible.
|
||||||
|
*
|
||||||
|
* Normally the webview's html context is created when the panel becomes visible
|
||||||
|
* and destroyed when it is hidden. Extensions that have complex state
|
||||||
|
* or UI can set the `retainContextWhenHidden` to make VS Code keep the webview
|
||||||
|
* context around, even when the webview moves to a background tab. When a webview using
|
||||||
|
* `retainContextWhenHidden` becomes hidden, its scripts and other dynamic content are suspended.
|
||||||
|
* When the panel becomes visible again, the context is automatically restored
|
||||||
|
* in the exact same state it was in originally. You cannot send messages to a
|
||||||
|
* hidden webview, even with `retainContextWhenHidden` enabled.
|
||||||
|
*
|
||||||
|
* `retainContextWhenHidden` has a high memory overhead and should only be used if
|
||||||
|
* your panel's context cannot be quickly saved and restored.
|
||||||
|
*/
|
||||||
|
readonly retainContextWhenHidden?: boolean;
|
||||||
|
};
|
||||||
|
}): Disposable;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,10 @@ import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/cus
|
||||||
import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory';
|
import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory';
|
||||||
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
||||||
import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel';
|
import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel';
|
||||||
import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview';
|
import { Webview, WebviewExtensionDescription, WebviewIcons, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||||
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
|
import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
|
||||||
|
import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
|
||||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
@ -119,6 +120,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape;
|
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape;
|
||||||
private readonly _webviewInputs = new WebviewInputStore();
|
private readonly _webviewInputs = new WebviewInputStore();
|
||||||
private readonly _revivers = new Map<string, IDisposable>();
|
private readonly _revivers = new Map<string, IDisposable>();
|
||||||
|
|
||||||
|
private readonly _webviewViewProviders = new Map<string, IDisposable>();
|
||||||
|
private readonly _webviewViews = new Map<string, WebviewView>();
|
||||||
|
|
||||||
private readonly _editorProviders = new Map<string, IDisposable>();
|
private readonly _editorProviders = new Map<string, IDisposable>();
|
||||||
private readonly _webviewFromDiffEditorHandles = new Set<string>();
|
private readonly _webviewFromDiffEditorHandles = new Set<string>();
|
||||||
|
|
||||||
|
@ -136,6 +141,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
|
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
|
||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
@IBackupFileService private readonly _backupService: IBackupFileService,
|
@IBackupFileService private readonly _backupService: IBackupFileService,
|
||||||
|
@IWebviewViewService private readonly _webviewViewService: IWebviewViewService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -212,7 +218,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
|
|
||||||
const extension = reviveWebviewExtension(extensionData);
|
const extension = reviveWebviewExtension(extensionData);
|
||||||
const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension);
|
const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension);
|
||||||
this.hookupWebviewEventDelegate(handle, webview);
|
this.hookupWebviewEventDelegate(handle, webview.webview);
|
||||||
|
|
||||||
this._webviewInputs.add(handle, webview);
|
this._webviewInputs.add(handle, webview);
|
||||||
|
|
||||||
|
@ -234,14 +240,22 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
webview.setName(value);
|
webview.setName(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $setWebviewViewTitle(handle: extHostProtocol.WebviewPanelHandle, value: string | undefined): void {
|
||||||
|
const webviewView = this._webviewViews.get(handle);
|
||||||
|
if (!webviewView) {
|
||||||
|
throw new Error('unknown webview view');
|
||||||
|
}
|
||||||
|
webviewView.title = value;
|
||||||
|
}
|
||||||
|
|
||||||
public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
|
public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
|
||||||
const webview = this.getWebviewInput(handle);
|
const webview = this.getWebviewInput(handle);
|
||||||
webview.iconPath = reviveWebviewIcon(value);
|
webview.iconPath = reviveWebviewIcon(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $setHtml(handle: extHostProtocol.WebviewPanelHandle, value: string): void {
|
public $setHtml(handle: extHostProtocol.WebviewPanelHandle, value: string): void {
|
||||||
const webview = this.getWebviewInput(handle);
|
const webview = this.getWebview(handle);
|
||||||
webview.webview.html = value;
|
webview.html = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public $setOptions(handle: extHostProtocol.WebviewPanelHandle, options: modes.IWebviewOptions): void {
|
public $setOptions(handle: extHostProtocol.WebviewPanelHandle, options: modes.IWebviewOptions): void {
|
||||||
|
@ -285,7 +299,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
|
|
||||||
const handle = webviewInput.id;
|
const handle = webviewInput.id;
|
||||||
this._webviewInputs.add(handle, webviewInput);
|
this._webviewInputs.add(handle, webviewInput);
|
||||||
this.hookupWebviewEventDelegate(handle, webviewInput);
|
this.hookupWebviewEventDelegate(handle, webviewInput.webview);
|
||||||
|
|
||||||
let state = undefined;
|
let state = undefined;
|
||||||
if (webviewInput.webview.state) {
|
if (webviewInput.webview.state) {
|
||||||
|
@ -316,6 +330,49 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
this._revivers.delete(viewType);
|
this._revivers.delete(viewType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void {
|
||||||
|
if (this._webviewViewProviders.has(viewType)) {
|
||||||
|
throw new Error(`View provider for ${viewType} already registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._webviewViewService.register(viewType, {
|
||||||
|
resolve: async (webviewView: WebviewView, cancellation: CancellationToken) => {
|
||||||
|
this._webviewViews.set(viewType, webviewView);
|
||||||
|
|
||||||
|
const handle = viewType;
|
||||||
|
this.hookupWebviewEventDelegate(handle, webviewView.webview);
|
||||||
|
|
||||||
|
let state = undefined;
|
||||||
|
if (webviewView.webview.state) {
|
||||||
|
try {
|
||||||
|
state = JSON.parse(webviewView.webview.state);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Could not load webview state', e, webviewView.webview.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
webviewView.webview.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
webviewView.onDidChangeVisibility(visible => {
|
||||||
|
this._proxy.$onDidChangeWebviewViewVisibility(handle, visible);
|
||||||
|
});
|
||||||
|
|
||||||
|
webviewView.onDispose(() => {
|
||||||
|
this._proxy.$disposeWebviewView(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._proxy.$resolveWebviewView(handle, viewType, state, cancellation);
|
||||||
|
} catch (error) {
|
||||||
|
onUnexpectedError(error);
|
||||||
|
webviewView.webview.html = MainThreadWebviews.getWebviewResolvedFailedContent(viewType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
|
public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void {
|
||||||
this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities, true);
|
this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities, true);
|
||||||
}
|
}
|
||||||
|
@ -353,7 +410,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
const resource = webviewInput.resource;
|
const resource = webviewInput.resource;
|
||||||
|
|
||||||
this._webviewInputs.add(handle, webviewInput);
|
this._webviewInputs.add(handle, webviewInput);
|
||||||
this.hookupWebviewEventDelegate(handle, webviewInput);
|
this.hookupWebviewEventDelegate(handle, webviewInput.webview);
|
||||||
webviewInput.webview.options = options;
|
webviewInput.webview.options = options;
|
||||||
webviewInput.webview.extension = extension;
|
webviewInput.webview.extension = extension;
|
||||||
|
|
||||||
|
@ -460,14 +517,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
model.changeContent();
|
model.changeContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
|
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, webview: WebviewOverlay) {
|
||||||
const disposables = new DisposableStore();
|
const disposables = new DisposableStore();
|
||||||
|
|
||||||
disposables.add(input.webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
|
disposables.add(webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
|
||||||
disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
|
disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
|
||||||
disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
|
disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
|
||||||
|
|
||||||
disposables.add(input.webview.onDispose(() => {
|
disposables.add(webview.onDispose(() => {
|
||||||
disposables.dispose();
|
disposables.dispose();
|
||||||
|
|
||||||
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
|
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
|
||||||
|
@ -554,6 +611,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
|
||||||
return !!webview.webview.contentOptions.enableCommandUris && link.scheme === Schemas.command;
|
return !!webview.webview.contentOptions.enableCommandUris && link.scheme === Schemas.command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getWebview(handle: extHostProtocol.WebviewPanelHandle): Webview {
|
||||||
|
const webview = this.tryGetWebviewInput(handle)?.webview ?? this._webviewViews.get(handle)?.webview;
|
||||||
|
if (!webview) {
|
||||||
|
throw new Error(`Unknown webview handle:${handle}`);
|
||||||
|
}
|
||||||
|
return webview;
|
||||||
|
}
|
||||||
|
|
||||||
private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput {
|
private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput {
|
||||||
const webview = this.tryGetWebviewInput(handle);
|
const webview = this.tryGetWebviewInput(handle);
|
||||||
if (!webview) {
|
if (!webview) {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
|
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
|
||||||
|
import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane';
|
||||||
|
|
||||||
export interface IUserFriendlyViewsContainerDescriptor {
|
export interface IUserFriendlyViewsContainerDescriptor {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -76,7 +77,15 @@ export const viewsContainersContribution: IJSONSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ViewType {
|
||||||
|
Tree = 'tree',
|
||||||
|
Webview = 'webview'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface IUserFriendlyViewDescriptor {
|
interface IUserFriendlyViewDescriptor {
|
||||||
|
type?: ViewType;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
when?: string;
|
when?: string;
|
||||||
|
@ -208,11 +217,18 @@ const viewsContribution: IJSONSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ICustomViewDescriptor extends ITreeViewDescriptor {
|
export interface ICustomTreeViewDescriptor extends ITreeViewDescriptor {
|
||||||
readonly extensionId: ExtensionIdentifier;
|
readonly extensionId: ExtensionIdentifier;
|
||||||
readonly originalContainerId: string;
|
readonly originalContainerId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICustomWebviewViewDescriptor extends IViewDescriptor {
|
||||||
|
readonly extensionId: ExtensionIdentifier;
|
||||||
|
readonly originalContainerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ICustomViewDescriptor = ICustomTreeViewDescriptor | ICustomWebviewViewDescriptor;
|
||||||
|
|
||||||
type ViewContainerExtensionPointType = { [loc: string]: IUserFriendlyViewsContainerDescriptor[] };
|
type ViewContainerExtensionPointType = { [loc: string]: IUserFriendlyViewsContainerDescriptor[] };
|
||||||
const viewsContainersExtensionPoint: IExtensionPoint<ViewContainerExtensionPointType> = ExtensionsRegistry.registerExtensionPoint<ViewContainerExtensionPointType>({
|
const viewsContainersExtensionPoint: IExtensionPoint<ViewContainerExtensionPointType> = ExtensionsRegistry.registerExtensionPoint<ViewContainerExtensionPointType>({
|
||||||
extensionPoint: 'viewsContainers',
|
extensionPoint: 'viewsContainers',
|
||||||
|
@ -442,16 +458,24 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||||
|
|
||||||
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
|
const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined;
|
||||||
const initialVisibility = this.convertInitialVisibility(item.visibility);
|
const initialVisibility = this.convertInitialVisibility(item.visibility);
|
||||||
const viewDescriptor = <ICustomViewDescriptor>{
|
|
||||||
|
const type = this.getViewType(item.type);
|
||||||
|
if (!type) {
|
||||||
|
collector.error(localize('unknownViewType', "Unknown view type `{0}`.", item.type));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewDescriptor = <ICustomTreeViewDescriptor>{
|
||||||
|
type: type,
|
||||||
|
ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane),
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
|
||||||
when: ContextKeyExpr.deserialize(item.when),
|
when: ContextKeyExpr.deserialize(item.when),
|
||||||
containerIcon: icon || viewContainer?.icon,
|
containerIcon: icon || viewContainer?.icon,
|
||||||
containerTitle: item.contextualTitle || viewContainer?.name,
|
containerTitle: item.contextualTitle || viewContainer?.name,
|
||||||
canToggleVisibility: true,
|
canToggleVisibility: true,
|
||||||
canMoveView: true,
|
canMoveView: true,
|
||||||
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name),
|
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name) : undefined,
|
||||||
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
|
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
|
||||||
order: order,
|
order: order,
|
||||||
extensionId: extension.description.identifier,
|
extensionId: extension.description.identifier,
|
||||||
|
@ -461,6 +485,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||||
hideByDefault: initialVisibility === InitialVisibility.Hidden
|
hideByDefault: initialVisibility === InitialVisibility.Hidden
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
viewIds.add(viewDescriptor.id);
|
viewIds.add(viewDescriptor.id);
|
||||||
return viewDescriptor;
|
return viewDescriptor;
|
||||||
}));
|
}));
|
||||||
|
@ -473,6 +498,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||||
this.viewsRegistry.registerViews2(allViewDescriptors);
|
this.viewsRegistry.registerViews2(allViewDescriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getViewType(type: string | undefined): ViewType | undefined {
|
||||||
|
if (type === ViewType.Webview) {
|
||||||
|
return ViewType.Webview;
|
||||||
|
}
|
||||||
|
if (!type || type === ViewType.Tree) {
|
||||||
|
return ViewType.Tree;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private getDefaultViewContainer(): ViewContainer {
|
private getDefaultViewContainer(): ViewContainer {
|
||||||
return this.viewContainersRegistry.get(EXPLORER)!;
|
return this.viewContainersRegistry.get(EXPLORER)!;
|
||||||
}
|
}
|
||||||
|
|
|
@ -612,6 +612,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||||
},
|
},
|
||||||
onDidChangeActiveColorTheme(listener, thisArg?, disposables?) {
|
onDidChangeActiveColorTheme(listener, thisArg?, disposables?) {
|
||||||
return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables);
|
return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables);
|
||||||
|
},
|
||||||
|
registerWebviewViewProvider(viewId: string, provider: vscode.WebviewViewProvider, options?: {
|
||||||
|
webviewOptions?: {
|
||||||
|
retainContextWhenHidden?: boolean
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
checkProposedApiEnabled(extension);
|
||||||
|
return extHostWebviews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -629,6 +629,10 @@ export interface MainThreadWebviewsShape extends IDisposable {
|
||||||
|
|
||||||
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
|
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
|
||||||
$onContentChange(resource: UriComponents, viewType: string): void;
|
$onContentChange(resource: UriComponents, viewType: string): void;
|
||||||
|
|
||||||
|
$registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void;
|
||||||
|
|
||||||
|
$setWebviewViewTitle(handle: WebviewPanelHandle, value: string | undefined): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebviewPanelViewStateData {
|
export interface WebviewPanelViewStateData {
|
||||||
|
@ -662,6 +666,10 @@ export interface ExtHostWebviewsShape {
|
||||||
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string>;
|
$backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<string>;
|
||||||
|
|
||||||
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
|
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
|
||||||
|
|
||||||
|
$resolveWebviewView(webviewHandle: WebviewPanelHandle, viewType: string, state: any, cancellation: CancellationToken): Promise<void>;
|
||||||
|
$onDidChangeWebviewViewVisibility(webviewHandle: WebviewPanelHandle, visible: boolean): void;
|
||||||
|
$disposeWebviewView(webviewHandle: WebviewPanelHandle): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CellKind {
|
export enum CellKind {
|
||||||
|
|
|
@ -267,6 +267,92 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ExtHostWebviewView extends Disposable implements vscode.WebviewView {
|
||||||
|
|
||||||
|
readonly #handle: extHostProtocol.WebviewPanelHandle;
|
||||||
|
readonly #proxy: extHostProtocol.MainThreadWebviewsShape;
|
||||||
|
|
||||||
|
readonly #viewType: string;
|
||||||
|
readonly #webview: ExtHostWebview;
|
||||||
|
|
||||||
|
#isDisposed = false;
|
||||||
|
#isVisible: boolean;
|
||||||
|
#title: string | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
handle: extHostProtocol.WebviewPanelHandle,
|
||||||
|
proxy: extHostProtocol.MainThreadWebviewsShape,
|
||||||
|
viewType: string,
|
||||||
|
webview: ExtHostWebview,
|
||||||
|
isVisible: boolean,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#viewType = viewType;
|
||||||
|
this.#handle = handle;
|
||||||
|
this.#proxy = proxy;
|
||||||
|
this.#webview = webview;
|
||||||
|
this.#isVisible = isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
if (this.#isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isDisposed = true;
|
||||||
|
this.#onDidDispose.fire();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #onDidChangeVisibility = this._register(new Emitter<void>());
|
||||||
|
public readonly onDidChangeVisibility = this.#onDidChangeVisibility.event;
|
||||||
|
|
||||||
|
readonly #onDidDispose = this._register(new Emitter<void>());
|
||||||
|
public readonly onDidDispose = this.#onDidDispose.event;
|
||||||
|
|
||||||
|
get title(): string | undefined {
|
||||||
|
this.assertNotDisposed();
|
||||||
|
return this.#title;
|
||||||
|
}
|
||||||
|
|
||||||
|
set title(value: string | undefined) {
|
||||||
|
this.assertNotDisposed();
|
||||||
|
if (this.#title !== value) {
|
||||||
|
this.#title = value;
|
||||||
|
this.#proxy.$setWebviewViewTitle(this.#handle, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get visible() {
|
||||||
|
return this.#isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
get webview() {
|
||||||
|
return this.#webview;
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewType(): string {
|
||||||
|
return this.#viewType;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setVisible(visible: boolean) {
|
||||||
|
if (visible === this.#isVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isVisible = visible;
|
||||||
|
this.#onDidChangeVisibility.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertNotDisposed() {
|
||||||
|
if (this.#isDisposed) {
|
||||||
|
throw new Error('Webview is disposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CustomDocumentStoreEntry {
|
class CustomDocumentStoreEntry {
|
||||||
|
|
||||||
private _backupCounter = 1;
|
private _backupCounter = 1;
|
||||||
|
@ -412,7 +498,13 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||||
readonly extension: IExtensionDescription;
|
readonly extension: IExtensionDescription;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
private readonly _viewProviders = new Map<string, {
|
||||||
|
readonly provider: vscode.WebviewViewProvider;
|
||||||
|
readonly extension: IExtensionDescription;
|
||||||
|
}>();
|
||||||
|
|
||||||
private readonly _editorProviders = new EditorProviderStore();
|
private readonly _editorProviders = new EditorProviderStore();
|
||||||
|
private readonly _webviewViews = new Map<extHostProtocol.WebviewPanelHandle, ExtHostWebviewView>();
|
||||||
|
|
||||||
private readonly _documents = new CustomDocumentStore();
|
private readonly _documents = new CustomDocumentStore();
|
||||||
|
|
||||||
|
@ -468,6 +560,27 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerWebviewViewProvider(
|
||||||
|
extension: IExtensionDescription,
|
||||||
|
viewType: string,
|
||||||
|
provider: vscode.WebviewViewProvider,
|
||||||
|
webviewOptions?: {
|
||||||
|
retainContextWhenHidden?: boolean
|
||||||
|
},
|
||||||
|
): vscode.Disposable {
|
||||||
|
if (this._viewProviders.has(viewType)) {
|
||||||
|
throw new Error(`View provider for '${viewType}' already registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._viewProviders.set(viewType, { provider, extension });
|
||||||
|
this._proxy.$registerWebviewViewProvider(viewType, webviewOptions);
|
||||||
|
|
||||||
|
return new extHostTypes.Disposable(() => {
|
||||||
|
this._viewProviders.delete(viewType);
|
||||||
|
this._proxy.$unregisterSerializer(viewType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public registerCustomEditorProvider(
|
public registerCustomEditorProvider(
|
||||||
extension: IExtensionDescription,
|
extension: IExtensionDescription,
|
||||||
viewType: string,
|
viewType: string,
|
||||||
|
@ -583,6 +696,41 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||||
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
await serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async $resolveWebviewView(
|
||||||
|
webviewHandle: string,
|
||||||
|
viewType: string,
|
||||||
|
state: any,
|
||||||
|
cancellation: CancellationToken,
|
||||||
|
): Promise<void> {
|
||||||
|
const entry = this._viewProviders.get(viewType);
|
||||||
|
if (!entry) {
|
||||||
|
throw new Error(`No view provider found for '${viewType}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { provider, extension } = entry;
|
||||||
|
|
||||||
|
const webview = new ExtHostWebview(webviewHandle, this._proxy, reviveOptions({ /* todo */ }), this.initData, this.workspace, extension, this._deprecationService);
|
||||||
|
const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, webview, true);
|
||||||
|
|
||||||
|
this._webviewViews.set(webviewHandle, revivedView);
|
||||||
|
|
||||||
|
await provider.resolveWebviewView(revivedView, { state }, cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $onDidChangeWebviewViewVisibility(
|
||||||
|
webviewHandle: string,
|
||||||
|
visible: boolean
|
||||||
|
) {
|
||||||
|
const webviewView = this.getWebviewView(webviewHandle);
|
||||||
|
webviewView._setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $disposeWebviewView(webviewHandle: string) {
|
||||||
|
const webviewView = this.getWebviewView(webviewHandle);
|
||||||
|
this._webviewViews.delete(webviewHandle);
|
||||||
|
webviewView.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
async $createCustomDocument(resource: UriComponents, viewType: string, backupId: string | undefined, cancellation: CancellationToken) {
|
||||||
const entry = this._editorProviders.get(viewType);
|
const entry = this._editorProviders.get(viewType);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
@ -746,6 +894,14 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getWebviewView(handle: string): ExtHostWebviewView {
|
||||||
|
const entry = this._webviewViews.get(handle);
|
||||||
|
if (!entry) {
|
||||||
|
throw new Error('Custom document is not editable');
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
private supportEditing(
|
private supportEditing(
|
||||||
provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider
|
provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider
|
||||||
): provider is vscode.CustomEditorProvider {
|
): provider is vscode.CustomEditorProvider {
|
||||||
|
|
|
@ -196,6 +196,8 @@ Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl()
|
||||||
|
|
||||||
export interface IViewDescriptor {
|
export interface IViewDescriptor {
|
||||||
|
|
||||||
|
readonly type?: string;
|
||||||
|
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
|
import { IWebviewViewService, WebviewViewService } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
|
||||||
|
|
||||||
|
registerSingleton(IWebviewViewService, WebviewViewService, true);
|
153
src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts
Normal file
153
src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||||
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { setImmediate } from 'vs/base/common/platform';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
|
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||||
|
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||||
|
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||||
|
import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
|
import { IWebviewViewService } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
|
||||||
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
|
||||||
|
|
||||||
|
declare const ResizeObserver: any;
|
||||||
|
|
||||||
|
export class WebviewViewPane extends ViewPane {
|
||||||
|
|
||||||
|
private _webview?: WebviewOverlay;
|
||||||
|
private _activated = false;
|
||||||
|
|
||||||
|
private _container?: HTMLElement;
|
||||||
|
private _resizeObserver?: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: IViewletViewOptions,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
@IOpenerService openerService: IOpenerService,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
|
@IExtensionService private readonly extensionService: IExtensionService,
|
||||||
|
@IProgressService private readonly progressService: IProgressService,
|
||||||
|
@IWebviewService private readonly webviewService: IWebviewService,
|
||||||
|
@IWebviewViewService private readonly webviewViewService: IWebviewViewService,
|
||||||
|
) {
|
||||||
|
super({ ...options, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||||
|
|
||||||
|
this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
|
||||||
|
this.updateTreeVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||||
|
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||||
|
|
||||||
|
private readonly _onDispose = this._register(new Emitter<void>());
|
||||||
|
readonly onDispose = this._onDispose.event;
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._onDispose.fire();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
focus(): void {
|
||||||
|
super.focus();
|
||||||
|
this._webview?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody(container: HTMLElement): void {
|
||||||
|
super.renderBody(container);
|
||||||
|
|
||||||
|
this._container = container;
|
||||||
|
|
||||||
|
if (!this._resizeObserver) {
|
||||||
|
this._resizeObserver = new ResizeObserver(() => {
|
||||||
|
setImmediate(() => {
|
||||||
|
if (this._container) {
|
||||||
|
this._webview?.layoutWebviewOverElement(this._container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._register(toDisposable(() => {
|
||||||
|
this._resizeObserver.disconnect();
|
||||||
|
}));
|
||||||
|
this._resizeObserver.observe(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layoutBody(height: number, width: number): void {
|
||||||
|
super.layoutBody(height, width);
|
||||||
|
|
||||||
|
if (!this._webview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._container) {
|
||||||
|
this._webview.layoutWebviewOverElement(this._container, { width, height });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTreeVisibility() {
|
||||||
|
if (this.isBodyVisible()) {
|
||||||
|
this.activate();
|
||||||
|
this._webview?.claim(this);
|
||||||
|
} else {
|
||||||
|
this._webview?.release(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private activate() {
|
||||||
|
if (!this._activated) {
|
||||||
|
this._activated = true;
|
||||||
|
|
||||||
|
const webview = this.webviewService.createWebviewOverlay(generateUuid(), {}, {}, undefined);
|
||||||
|
this._webview = webview;
|
||||||
|
|
||||||
|
this._register(toDisposable(() => {
|
||||||
|
this._webview?.release(this);
|
||||||
|
}));
|
||||||
|
|
||||||
|
const source = this._register(new CancellationTokenSource());
|
||||||
|
|
||||||
|
this.withProgress(async () => {
|
||||||
|
await this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
await this.webviewViewService.resolve(this.id, {
|
||||||
|
webview,
|
||||||
|
onDidChangeVisibility: this.onDidChangeBodyVisibility,
|
||||||
|
onDispose: this.onDispose,
|
||||||
|
get title() { return self.title; },
|
||||||
|
set title(value: string) { self.updateTitle(value); }
|
||||||
|
}, source.token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async withProgress(task: () => Promise<void>): Promise<void> {
|
||||||
|
return this.progressService.withProgress({ location: this.id, delay: 500 }, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
|
|
||||||
|
export const IWebviewViewService = createDecorator<IWebviewViewService>('webviewViewService');
|
||||||
|
|
||||||
|
export interface WebviewView {
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
readonly webview: WebviewOverlay;
|
||||||
|
|
||||||
|
readonly onDidChangeVisibility: Event<boolean>;
|
||||||
|
readonly onDispose: Event<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWebviewViewResolver {
|
||||||
|
resolve(webviewView: WebviewView, cancellation: CancellationToken): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWebviewViewService {
|
||||||
|
|
||||||
|
readonly _serviceBrand: undefined;
|
||||||
|
|
||||||
|
register(type: string, resolver: IWebviewViewResolver): IDisposable;
|
||||||
|
|
||||||
|
resolve(viewType: string, webview: WebviewView, cancellation: CancellationToken): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebviewViewService extends Disposable implements IWebviewViewService {
|
||||||
|
|
||||||
|
readonly _serviceBrand: undefined;
|
||||||
|
|
||||||
|
private readonly _views = new Map<string, IWebviewViewResolver>();
|
||||||
|
|
||||||
|
private readonly _awaitingRevival = new Map<string, { webview: WebviewView, resolve: () => void }>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
register(viewType: string, resolver: IWebviewViewResolver): IDisposable {
|
||||||
|
if (this._views.has(viewType)) {
|
||||||
|
throw new Error(`View resolver already registered for ${viewType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._views.set(viewType, resolver);
|
||||||
|
|
||||||
|
const pending = this._awaitingRevival.get(viewType);
|
||||||
|
if (pending) {
|
||||||
|
resolver.resolve(pending.webview, CancellationToken.None).then(() => {
|
||||||
|
this._awaitingRevival.delete(viewType);
|
||||||
|
pending.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDisposable(() => {
|
||||||
|
this._views.delete(viewType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(viewType: string, webview: WebviewView, cancellation: CancellationToken): Promise<void> {
|
||||||
|
const resolver = this._views.get(viewType);
|
||||||
|
if (!resolver) {
|
||||||
|
if (this._awaitingRevival.has(viewType)) {
|
||||||
|
throw new Error('View already awaiting revival');
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolve: () => void;
|
||||||
|
const p = new Promise<void>(r => resolve = r);
|
||||||
|
this._awaitingRevival.set(viewType, { webview, resolve: resolve! });
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver.resolve(webview, cancellation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -199,6 +199,7 @@ import 'vs/workbench/contrib/url/browser/url.contribution';
|
||||||
|
|
||||||
// Webview
|
// Webview
|
||||||
import 'vs/workbench/contrib/webview/browser/webview.contribution';
|
import 'vs/workbench/contrib/webview/browser/webview.contribution';
|
||||||
|
import 'vs/workbench/contrib/webviewView/browser/webviewView.contribution';
|
||||||
import 'vs/workbench/contrib/customEditor/browser/customEditor.contribution';
|
import 'vs/workbench/contrib/customEditor/browser/customEditor.contribution';
|
||||||
|
|
||||||
// Extensions Management
|
// Extensions Management
|
||||||
|
|
Loading…
Reference in a new issue