25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

233 satır
6.1 KiB

4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
4 yıl önce
  1. 'use strict';
  2. import { TextDecoder } from 'util';
  3. import {
  4. commands,
  5. ConfigurationChangeEvent,
  6. ConfigurationTarget,
  7. Disposable,
  8. Uri,
  9. ViewColumn,
  10. Webview,
  11. WebviewPanel,
  12. WebviewPanelOnDidChangeViewStateEvent,
  13. window,
  14. workspace,
  15. } from 'vscode';
  16. import { configuration } from '../configuration';
  17. import { Container } from '../container';
  18. import { Logger } from '../logger';
  19. import {
  20. DidChangeConfigurationNotificationType,
  21. IpcMessage,
  22. IpcNotificationParamsOf,
  23. IpcNotificationType,
  24. onIpcCommand,
  25. UpdateConfigurationCommandType,
  26. } from './protocol';
  27. import { Commands } from '../commands';
  28. let ipcSequence = 0;
  29. function nextIpcId() {
  30. if (ipcSequence === Number.MAX_SAFE_INTEGER) {
  31. ipcSequence = 1;
  32. } else {
  33. ipcSequence++;
  34. }
  35. return `host:${ipcSequence}`;
  36. }
  37. const emptyCommands: Disposable[] = [
  38. {
  39. dispose: function () {
  40. /* noop */
  41. },
  42. },
  43. ];
  44. export abstract class WebviewBase implements Disposable {
  45. protected disposable: Disposable;
  46. private _disposablePanel: Disposable | undefined;
  47. private _panel: WebviewPanel | undefined;
  48. constructor(showCommand: Commands, private readonly _column?: ViewColumn) {
  49. this.disposable = Disposable.from(
  50. configuration.onDidChange(this.onConfigurationChanged, this),
  51. commands.registerCommand(showCommand, this.onShowCommand, this),
  52. );
  53. }
  54. abstract get filename(): string;
  55. abstract get id(): string;
  56. abstract get title(): string;
  57. registerCommands(): Disposable[] {
  58. return emptyCommands;
  59. }
  60. renderHead?(): string | Promise<string>;
  61. renderBody?(): string | Promise<string>;
  62. renderEndOfBody?(): string | Promise<string>;
  63. dispose() {
  64. this.disposable.dispose();
  65. this._disposablePanel?.dispose();
  66. }
  67. protected onShowCommand() {
  68. void this.show(this._column);
  69. }
  70. private onConfigurationChanged(_e: ConfigurationChangeEvent) {
  71. void this.notifyDidChangeConfiguration();
  72. }
  73. private onPanelDisposed() {
  74. this._disposablePanel?.dispose();
  75. this._panel = undefined;
  76. }
  77. private onViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent) {
  78. Logger.log(
  79. `Webview(${this.id}).onViewStateChanged`,
  80. `active=${e.webviewPanel.active}, visible=${e.webviewPanel.visible}`,
  81. );
  82. // Anytime the webview becomes active, make sure it has the most up-to-date config
  83. if (e.webviewPanel.active) {
  84. void this.notifyDidChangeConfiguration();
  85. }
  86. }
  87. protected onMessageReceived(e: IpcMessage) {
  88. switch (e.method) {
  89. case UpdateConfigurationCommandType.method:
  90. onIpcCommand(UpdateConfigurationCommandType, e, async params => {
  91. const target =
  92. params.scope === 'workspace' ? ConfigurationTarget.Workspace : ConfigurationTarget.Global;
  93. for (const key in params.changes) {
  94. const inspect = configuration.inspect(key as any)!;
  95. let value = params.changes[key];
  96. if (value != null) {
  97. if (params.scope === 'workspace') {
  98. if (value === inspect.workspaceValue) continue;
  99. } else {
  100. if (value === inspect.globalValue && value !== inspect.defaultValue) continue;
  101. if (value === inspect.defaultValue) {
  102. value = undefined;
  103. }
  104. }
  105. }
  106. void (await configuration.update(key as any, value, target));
  107. }
  108. for (const key of params.removes) {
  109. void (await configuration.update(key as any, undefined, target));
  110. }
  111. });
  112. break;
  113. default:
  114. break;
  115. }
  116. }
  117. private onMessageReceivedCore(e: IpcMessage) {
  118. if (e == null) return;
  119. Logger.log(`Webview(${this.id}).onMessageReceived: method=${e.method}, data=${JSON.stringify(e)}`);
  120. this.onMessageReceived(e);
  121. }
  122. get visible() {
  123. return this._panel?.visible ?? false;
  124. }
  125. hide() {
  126. this._panel?.dispose();
  127. }
  128. setTitle(title: string) {
  129. if (this._panel == null) return;
  130. this._panel.title = title;
  131. }
  132. async show(column: ViewColumn = ViewColumn.Beside): Promise<void> {
  133. if (this._panel == null) {
  134. this._panel = window.createWebviewPanel(
  135. this.id,
  136. this.title,
  137. { viewColumn: column, preserveFocus: false },
  138. {
  139. retainContextWhenHidden: true,
  140. enableFindWidget: true,
  141. enableCommandUris: true,
  142. enableScripts: true,
  143. },
  144. );
  145. this._panel.iconPath = Uri.file(Container.context.asAbsolutePath('images/gitlens-icon.png'));
  146. this._disposablePanel = Disposable.from(
  147. this._panel,
  148. this._panel.onDidDispose(this.onPanelDisposed, this),
  149. this._panel.onDidChangeViewState(this.onViewStateChanged, this),
  150. this._panel.webview.onDidReceiveMessage(this.onMessageReceivedCore, this),
  151. ...this.registerCommands(),
  152. );
  153. this._panel.webview.html = await this.getHtml(this._panel.webview);
  154. } else {
  155. const html = await this.getHtml(this._panel.webview);
  156. // Reset the html to get the webview to reload
  157. this._panel.webview.html = '';
  158. this._panel.webview.html = html;
  159. this._panel.reveal(this._panel.viewColumn ?? ViewColumn.Active, false);
  160. }
  161. }
  162. private async getHtml(webview: Webview): Promise<string> {
  163. const uri = Uri.joinPath(Container.context.extensionUri, 'dist', 'webviews', this.filename);
  164. const content = new TextDecoder('utf8').decode(await workspace.fs.readFile(uri));
  165. let html = content
  166. .replace(/#{cspSource}/g, webview.cspSource)
  167. .replace(/#{root}/g, webview.asWebviewUri(Container.context.extensionUri).toString());
  168. if (this.renderHead != null) {
  169. html = html.replace(/#{head}/i, await this.renderHead());
  170. }
  171. if (this.renderBody != null) {
  172. html = html.replace(/#{body}/i, await this.renderBody());
  173. }
  174. if (this.renderEndOfBody != null) {
  175. html = html.replace(/#{endOfBody}/i, await this.renderEndOfBody());
  176. }
  177. return html;
  178. }
  179. protected notify<NT extends IpcNotificationType>(type: NT, params: IpcNotificationParamsOf<NT>): Thenable<boolean> {
  180. return this.postMessage({ id: nextIpcId(), method: type.method, params: params });
  181. }
  182. private notifyDidChangeConfiguration() {
  183. // Make sure to get the raw config, not from the container which has the modes mixed in
  184. return this.notify(DidChangeConfigurationNotificationType, { config: configuration.get() });
  185. }
  186. private postMessage(message: IpcMessage) {
  187. if (this._panel == null) return Promise.resolve(false);
  188. return this._panel.webview.postMessage(message);
  189. }
  190. }