- 'use strict';
- import { TextDecoder } from 'util';
- import {
- commands,
- ConfigurationChangeEvent,
- ConfigurationTarget,
- Disposable,
- Uri,
- ViewColumn,
- Webview,
- WebviewPanel,
- WebviewPanelOnDidChangeViewStateEvent,
- window,
- workspace,
- } from 'vscode';
- import { configuration } from '../configuration';
- import { Container } from '../container';
- import { Logger } from '../logger';
- import {
- DidChangeConfigurationNotificationType,
- IpcMessage,
- IpcNotificationParamsOf,
- IpcNotificationType,
- onIpcCommand,
- UpdateConfigurationCommandType,
- } from './protocol';
- import { Commands } from '../commands';
-
- let ipcSequence = 0;
- function nextIpcId() {
- if (ipcSequence === Number.MAX_SAFE_INTEGER) {
- ipcSequence = 1;
- } else {
- ipcSequence++;
- }
-
- return `host:${ipcSequence}`;
- }
-
- const emptyCommands: Disposable[] = [
- {
- dispose: function () {
- /* noop */
- },
- },
- ];
-
- export abstract class WebviewBase implements Disposable {
- protected disposable: Disposable;
- private _disposablePanel: Disposable | undefined;
- private _panel: WebviewPanel | undefined;
-
- constructor(showCommand: Commands, private readonly _column?: ViewColumn) {
- this.disposable = Disposable.from(
- configuration.onDidChange(this.onConfigurationChanged, this),
- commands.registerCommand(showCommand, this.onShowCommand, this),
- );
- }
-
- abstract get filename(): string;
- abstract get id(): string;
- abstract get title(): string;
-
- registerCommands(): Disposable[] {
- return emptyCommands;
- }
-
- renderHead?(): string | Promise<string>;
- renderBody?(): string | Promise<string>;
- renderEndOfBody?(): string | Promise<string>;
-
- dispose() {
- this.disposable.dispose();
- this._disposablePanel?.dispose();
- }
-
- protected onShowCommand() {
- void this.show(this._column);
- }
-
- private onConfigurationChanged(_e: ConfigurationChangeEvent) {
- void this.notifyDidChangeConfiguration();
- }
-
- private onPanelDisposed() {
- this._disposablePanel?.dispose();
- this._panel = undefined;
- }
-
- private onViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent) {
- Logger.log(
- `Webview(${this.id}).onViewStateChanged`,
- `active=${e.webviewPanel.active}, visible=${e.webviewPanel.visible}`,
- );
-
- // Anytime the webview becomes active, make sure it has the most up-to-date config
- if (e.webviewPanel.active) {
- void this.notifyDidChangeConfiguration();
- }
- }
-
- protected onMessageReceived(e: IpcMessage) {
- switch (e.method) {
- case UpdateConfigurationCommandType.method:
- onIpcCommand(UpdateConfigurationCommandType, e, async params => {
- const target =
- params.scope === 'workspace' ? ConfigurationTarget.Workspace : ConfigurationTarget.Global;
-
- for (const key in params.changes) {
- const inspect = configuration.inspect(key as any)!;
-
- let value = params.changes[key];
- if (value != null) {
- if (params.scope === 'workspace') {
- if (value === inspect.workspaceValue) continue;
- } else {
- if (value === inspect.globalValue && value !== inspect.defaultValue) continue;
-
- if (value === inspect.defaultValue) {
- value = undefined;
- }
- }
- }
-
- void (await configuration.update(key as any, value, target));
- }
-
- for (const key of params.removes) {
- void (await configuration.update(key as any, undefined, target));
- }
- });
-
- break;
- default:
- break;
- }
- }
-
- private onMessageReceivedCore(e: IpcMessage) {
- if (e == null) return;
-
- Logger.log(`Webview(${this.id}).onMessageReceived: method=${e.method}, data=${JSON.stringify(e)}`);
-
- this.onMessageReceived(e);
- }
-
- get visible() {
- return this._panel?.visible ?? false;
- }
-
- hide() {
- this._panel?.dispose();
- }
-
- setTitle(title: string) {
- if (this._panel == null) return;
-
- this._panel.title = title;
- }
-
- async show(column: ViewColumn = ViewColumn.Beside): Promise<void> {
- if (this._panel == null) {
- this._panel = window.createWebviewPanel(
- this.id,
- this.title,
- { viewColumn: column, preserveFocus: false },
- {
- retainContextWhenHidden: true,
- enableFindWidget: true,
- enableCommandUris: true,
- enableScripts: true,
- },
- );
-
- this._panel.iconPath = Uri.file(Container.context.asAbsolutePath('images/gitlens-icon.png'));
- this._disposablePanel = Disposable.from(
- this._panel,
- this._panel.onDidDispose(this.onPanelDisposed, this),
- this._panel.onDidChangeViewState(this.onViewStateChanged, this),
- this._panel.webview.onDidReceiveMessage(this.onMessageReceivedCore, this),
- ...this.registerCommands(),
- );
-
- this._panel.webview.html = await this.getHtml(this._panel.webview);
- } else {
- const html = await this.getHtml(this._panel.webview);
-
- // Reset the html to get the webview to reload
- this._panel.webview.html = '';
- this._panel.webview.html = html;
-
- this._panel.reveal(this._panel.viewColumn ?? ViewColumn.Active, false);
- }
- }
-
- private async getHtml(webview: Webview): Promise<string> {
- const uri = Uri.joinPath(Container.context.extensionUri, 'dist', 'webviews', this.filename);
- const content = new TextDecoder('utf8').decode(await workspace.fs.readFile(uri));
-
- let html = content
- .replace(/#{cspSource}/g, webview.cspSource)
- .replace(/#{root}/g, webview.asWebviewUri(Container.context.extensionUri).toString());
-
- if (this.renderHead != null) {
- html = html.replace(/#{head}/i, await this.renderHead());
- }
-
- if (this.renderBody != null) {
- html = html.replace(/#{body}/i, await this.renderBody());
- }
-
- if (this.renderEndOfBody != null) {
- html = html.replace(/#{endOfBody}/i, await this.renderEndOfBody());
- }
-
- return html;
- }
-
- protected notify<NT extends IpcNotificationType>(type: NT, params: IpcNotificationParamsOf<NT>): Thenable<boolean> {
- return this.postMessage({ id: nextIpcId(), method: type.method, params: params });
- }
-
- private notifyDidChangeConfiguration() {
- // Make sure to get the raw config, not from the container which has the modes mixed in
- return this.notify(DidChangeConfigurationNotificationType, { config: configuration.get() });
- }
-
- private postMessage(message: IpcMessage) {
- if (this._panel == null) return Promise.resolve(false);
-
- return this._panel.webview.postMessage(message);
- }
- }
|