如何将来自VSCODE扩展的消息发布到使用WebViewProvider创建的自定义WebView?

发布于 2025-02-09 15:17:56 字数 2054 浏览 3 评论 0 原文

我正在构建一个Vscode扩展程序,其中使用WebView提供商在面板中创建一个自定义选项卡。我想将Extension命令的输出引导到WebView并在HTML脚本中渲染。替代方法是在WebView的HTML脚本内执行VSCODE扩展命令。但是我找不到使用WebView提供商的示例,而是 currentpanel.webview.postmessage({命令:'recactor'}); 在我的情况下,这是不确定的,因为我没有创建面板。

Extension.js

let disposable = vscode.commands.registerCommand(
"this is where I want to send data to webview"
...
);

var thisProvider={
    resolveWebviewView:function(thisWebview, thisWebviewContext, thisToke){
        thisWebview.webview.options={enableScripts:true}
        thisWebview.webview.html=`<!DOCTYPE html>
        <html>
        <body>
            <div id="results" style="white-space: pre;" />
            <script>
            const resultsEl = document.getElementById("results");
            window.addEventListener('message', event => {

                const message = event.data; // The JSON data our extension sent
    
                switch (message.command) {
                    case 'results':
                        console.log(results);
                        break;
                }
            });
            </script>
        </body>
        </html>`;
    }
}

context.subscriptions.push(
    vscode.window.registerWebviewViewProvider("monitor.output", thisProvider)
  );

package.json:

"contributes": {
    "commands": [
    {
        "command": "monitor.listen",
        "title": "Connect"
    }
    ],
    "menus": {
        "view/title": [
            {
                "command": "monitor.listen",
                "group": "navigation",
                "when": "view == monitor.output"
            }
        ]
    },
    "viewsContainers": {
      "panel": [
        {
          "id": "monitor",
          "title": "Monitor",
          "icon": "resources/monitor.jpeg"
        }
      ]
    },
    "views": {
      "monitor": [
        {
          "type": "webview",
          "id": "monitor.output",
          "name": "Monitor"
        }
      ]
    }
  }

I am building a VSCode extension where I create a custom tab in the panel with a Webview Provider. I want to direct the output of an extension command to Webview and render in html script. The alternative is to execute vscode extension command inside html script of the webview. However I could not find an example that uses Webview Provider, instead they all have
currentPanel.webview.postMessage({ command: 'refactor' });
which is undefined in my case because I do not create a panel.

extension.js

let disposable = vscode.commands.registerCommand(
"this is where I want to send data to webview"
...
);

var thisProvider={
    resolveWebviewView:function(thisWebview, thisWebviewContext, thisToke){
        thisWebview.webview.options={enableScripts:true}
        thisWebview.webview.html=`<!DOCTYPE html>
        <html>
        <body>
            <div id="results" style="white-space: pre;" />
            <script>
            const resultsEl = document.getElementById("results");
            window.addEventListener('message', event => {

                const message = event.data; // The JSON data our extension sent
    
                switch (message.command) {
                    case 'results':
                        console.log(results);
                        break;
                }
            });
            </script>
        </body>
        </html>`;
    }
}

context.subscriptions.push(
    vscode.window.registerWebviewViewProvider("monitor.output", thisProvider)
  );

package.json:

"contributes": {
    "commands": [
    {
        "command": "monitor.listen",
        "title": "Connect"
    }
    ],
    "menus": {
        "view/title": [
            {
                "command": "monitor.listen",
                "group": "navigation",
                "when": "view == monitor.output"
            }
        ]
    },
    "viewsContainers": {
      "panel": [
        {
          "id": "monitor",
          "title": "Monitor",
          "icon": "resources/monitor.jpeg"
        }
      ]
    },
    "views": {
      "monitor": [
        {
          "type": "webview",
          "id": "monitor.output",
          "name": "Monitor"
        }
      ]
    }
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

油焖大侠 2025-02-16 15:17:56

我也一直在寻找这个,但是实际上没有例子说明如何添加它。

您可以在提供商中获取并保存对当前 WebView 内部 resolvewebview 函数。

将视图存储在私有实例变量 private _view?:vscode.webviewView; 中并在公共方法中使用它 public postmessagetowebview(message:nyy)

提供者代码:

import * as vscode from 'vscode';

function getNonce() {
  let text = "";
  const possible =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

export class WebViewProvider
  implements vscode.WebviewViewProvider
{
  public static readonly viewType = 'myExtension.controlsView';

  private _view?: vscode.WebviewView;

  constructor(private readonly _extensionUri: vscode.Uri) {
  }

  public postMessageToWebview(message: any) {
    this._view?.webview.postMessage(message);
  }

  public resolveWebviewView(
    webviewView: vscode.WebviewView,
    context: vscode.WebviewViewResolveContext,
    _token: vscode.CancellationToken,
  ) {
    this._view = webviewView; // needed so we can use it in postMessageToWebview later

    webviewView.webview.options = {
      // Allow scripts in the webview
      enableScripts: true,

      localResourceRoots: [this._extensionUri],
    };

    webviewView.webview.html = this._getHtmlForWebview(
      webviewView.webview,
    );

    webviewView.webview.onDidReceiveMessage((data) => {
      switch (data.type) {
        // other message types ...
        case 'onYourEvent': {
          console.log(data.value); // see below webview to extension communication snippet
          break;
        }
        case 'onInfo': {
          if (!data.value) {
            return;
          }
          vscode.window.showInformationMessage(data.value);
          break;
        }
        case 'onError': {
          if (!data.value) {
            return;
          }
          vscode.window.showErrorMessage(data.value);
          break;
        }
      }
    });
  }

  private _getHtmlForWebview(webview: vscode.Webview) {
    // // And the uri we use to load this script in the webview
    const scriptUri = webview.asWebviewUri(
      vscode.Uri.joinPath(
        this._extensionUri,
        'out',
        'svelte-app/bundle.js',
      ),
    );
    // const scriptUri = webview.asWebviewUri(
    //   vscode.Uri.joinPath(this._extensionUri, "media", "main.js")
    // );

    // Local path to css styles
    const styleResetPath = vscode.Uri.joinPath(
      this._extensionUri,
      'media',
      'reset.css',
    );
    const stylesPathMainPath = vscode.Uri.joinPath(
      this._extensionUri,
      'media',
      'vscode.css',
    );

    // Uri to load styles into webview
    const stylesResetUri = webview.asWebviewUri(styleResetPath);
    const stylesMainUri = webview.asWebviewUri(stylesPathMainPath);
    const cssUri = webview.asWebviewUri(
      vscode.Uri.joinPath(
        this._extensionUri,
        'out/svelte-app',
        'bundle.css',
      ),
      // vscode.Uri.joinPath(this._extensionUri, "media", "main.css")
    );

    // Use a nonce to only allow specific scripts to be run
    const nonce = getNonce();

    return `<!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <!--
                    Use a content security policy to only allow loading images from https or from our extension directory,
                    and only allow scripts that have a specific nonce.
        -->
        <meta http-equiv="Content-Security-Policy" content="img-src https: data:; style-src 'unsafe-inline' ${webview.cspSource}; script-src 'nonce-${nonce}';">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <link href="${stylesResetUri}" rel="stylesheet">
                <link href="${stylesMainUri}" rel="stylesheet">
        <link href="${cssUri}" rel="stylesheet">
        <script nonce="${nonce}">
            const tsvscode = acquireVsCodeApi();
        </script>
            </head>
      <body>
            </body>
                <script nonce="${nonce}" src="${scriptUri}"></script>
            </html>`;
  }
}

Note

  • 要开始删除Svelte Bundle的内容(这只是我的代码库的一部分),并在下面的WebView片段中添加脚本文件 MEDIA/MAIN.JS
  • CSS文件 reset.css vscode.css 可以从a vs代码示例
  • 查看您的 console.log 消息,您可以打开DEV。扩展主机与代码实例中的工具通过击中 ctrl+shif+shif+p 和type Open WebView dev - 也许您必须打开/关闭WebView才能生成新的控制台日志。

您可以在 extension.ts 中使用它:

import * as vscode from 'vscode';
import { WebViewProvider } from './WebViewProvider';

export function activate(context: vscode.ExtensionContext) {
  const provider = new WebViewProvider(context.extensionUri);

  context.subscriptions.push(
    vscode.commands.registerCommand('myExtension.sayHello', () => {
      // The code you place here will be executed every time your command is executed
      // Display a message box to the user
      // vscode.window.showInformationMessage(output);
      provider.postMessageToWebview({
        type: 'greeting',
        message: 'HelloWorld',
      });
    }),
  );

  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(
      WebViewProvider.viewType,
      provider,
    ),
  );
}

将其添加到 package.json 上,因此命令&amp;视图将可用:

{
  "other-config": "...",
  "activationEvents": [
    "onView:myExtension.controlsView",
    "onCommand:myExtension.sayHello"
  ],
  "contributes": {
    "views": {
      "explorer": [
        {
          "type": "webview",
          "id": "myExtension.controlsView",
          "name": "MyExtension"
        }
      ],
    },
    "commands": [
      {
        "command": "myExtension.sayHello",
        "category": "myExtension",
        "title": "SayHello"
      },
    ]
  }
}

可以在此代码的多个位置添加WebViews,它将添加到Explorer视图中。

使用WebView脚本中的以下代码从扩展程序中获取消息:

// main.js code
const handleExtensionMessages = (event) => {
    const { message, type }= event.data;
    switch (message.type) {
        case 'greeting':
            console.log("received", message);
            break;
    }
}

window.addEventListener("message", handleExtensionMessages);

对于另一个“方向” Web视图到扩展通信。

您可以在WebView Content脚本中使用:

tsvscode.postMessage({
    type: "onYourEvent",
    value: "anything you like to return",
  });

global tsvscode 变量是在 _gethtmlforwebview 中生成的, acepirevscodeapi()

要修复全局 tsvscode 的打字。 install @types/vscode-webview with npm as devDependentys ,然后将 global.d.t.ts 添加到您的WebView脚本中,并带有此内容:

import type WebViewApi from '@types/vscode-webview';

global {
  declare const tsvscode: WebViewApi<unknown>;
}

或者您不喜欢全局变量 tsvscode 和上面的键入。您还可以像以下内容一样创建VS代码API包装器WebView-ui/src/utilities/vscode.ts“ rel =“ nofollow noreferrer”>存储库。

只是要运行此代码,创建一个扩展名,如通过在终端中运行 yo Code ,使用Yeoman Generator。

您还可以在以下github

I was looking for this too, but there is really no example showing how this could be added.

You can get and save the reference to the current webView inside resolveWebView function in your provider.

Store the view in a private instance variable private _view?: vscode.WebviewView; and use it in a public method public postMessageToWebview(message: any)

The provider code:

import * as vscode from 'vscode';

function getNonce() {
  let text = "";
  const possible =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

export class WebViewProvider
  implements vscode.WebviewViewProvider
{
  public static readonly viewType = 'myExtension.controlsView';

  private _view?: vscode.WebviewView;

  constructor(private readonly _extensionUri: vscode.Uri) {
  }

  public postMessageToWebview(message: any) {
    this._view?.webview.postMessage(message);
  }

  public resolveWebviewView(
    webviewView: vscode.WebviewView,
    context: vscode.WebviewViewResolveContext,
    _token: vscode.CancellationToken,
  ) {
    this._view = webviewView; // needed so we can use it in postMessageToWebview later

    webviewView.webview.options = {
      // Allow scripts in the webview
      enableScripts: true,

      localResourceRoots: [this._extensionUri],
    };

    webviewView.webview.html = this._getHtmlForWebview(
      webviewView.webview,
    );

    webviewView.webview.onDidReceiveMessage((data) => {
      switch (data.type) {
        // other message types ...
        case 'onYourEvent': {
          console.log(data.value); // see below webview to extension communication snippet
          break;
        }
        case 'onInfo': {
          if (!data.value) {
            return;
          }
          vscode.window.showInformationMessage(data.value);
          break;
        }
        case 'onError': {
          if (!data.value) {
            return;
          }
          vscode.window.showErrorMessage(data.value);
          break;
        }
      }
    });
  }

  private _getHtmlForWebview(webview: vscode.Webview) {
    // // And the uri we use to load this script in the webview
    const scriptUri = webview.asWebviewUri(
      vscode.Uri.joinPath(
        this._extensionUri,
        'out',
        'svelte-app/bundle.js',
      ),
    );
    // const scriptUri = webview.asWebviewUri(
    //   vscode.Uri.joinPath(this._extensionUri, "media", "main.js")
    // );

    // Local path to css styles
    const styleResetPath = vscode.Uri.joinPath(
      this._extensionUri,
      'media',
      'reset.css',
    );
    const stylesPathMainPath = vscode.Uri.joinPath(
      this._extensionUri,
      'media',
      'vscode.css',
    );

    // Uri to load styles into webview
    const stylesResetUri = webview.asWebviewUri(styleResetPath);
    const stylesMainUri = webview.asWebviewUri(stylesPathMainPath);
    const cssUri = webview.asWebviewUri(
      vscode.Uri.joinPath(
        this._extensionUri,
        'out/svelte-app',
        'bundle.css',
      ),
      // vscode.Uri.joinPath(this._extensionUri, "media", "main.css")
    );

    // Use a nonce to only allow specific scripts to be run
    const nonce = getNonce();

    return `<!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <!--
                    Use a content security policy to only allow loading images from https or from our extension directory,
                    and only allow scripts that have a specific nonce.
        -->
        <meta http-equiv="Content-Security-Policy" content="img-src https: data:; style-src 'unsafe-inline' ${webview.cspSource}; script-src 'nonce-${nonce}';">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <link href="${stylesResetUri}" rel="stylesheet">
                <link href="${stylesMainUri}" rel="stylesheet">
        <link href="${cssUri}" rel="stylesheet">
        <script nonce="${nonce}">
            const tsvscode = acquireVsCodeApi();
        </script>
            </head>
      <body>
            </body>
                <script nonce="${nonce}" src="${scriptUri}"></script>
            </html>`;
  }
}

Note:

  • To get started remove the svelte bundle stuff (this was just part of my code base) and add a script file media/main.js with the code from the webview snippet below.
  • The CSS files reset.css and vscode.css can be downloaded from a VS Code example
  • To see your console.log messages you can open your dev. tools in your extension host VS code instance by hitting ctrl+shif+p and type Open webview dev - maybe you have to open/close your webview to generate new console logs.

You can use it in your extension.ts:

import * as vscode from 'vscode';
import { WebViewProvider } from './WebViewProvider';

export function activate(context: vscode.ExtensionContext) {
  const provider = new WebViewProvider(context.extensionUri);

  context.subscriptions.push(
    vscode.commands.registerCommand('myExtension.sayHello', () => {
      // The code you place here will be executed every time your command is executed
      // Display a message box to the user
      // vscode.window.showInformationMessage(output);
      provider.postMessageToWebview({
        type: 'greeting',
        message: 'HelloWorld',
      });
    }),
  );

  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(
      WebViewProvider.viewType,
      provider,
    ),
  );
}

Add it to the package.json so the command & view will be available:

{
  "other-config": "...",
  "activationEvents": [
    "onView:myExtension.controlsView",
    "onCommand:myExtension.sayHello"
  ],
  "contributes": {
    "views": {
      "explorer": [
        {
          "type": "webview",
          "id": "myExtension.controlsView",
          "name": "MyExtension"
        }
      ],
    },
    "commands": [
      {
        "command": "myExtension.sayHello",
        "category": "myExtension",
        "title": "SayHello"
      },
    ]
  }
}

Webviews can be added at multiple locations in this code it will be added to the explorer view.

Use the following code in your webview script to get the message from the extension:

// main.js code
const handleExtensionMessages = (event) => {
    const { message, type }= event.data;
    switch (message.type) {
        case 'greeting':
            console.log("received", message);
            break;
    }
}

window.addEventListener("message", handleExtensionMessages);

For the other "direction" web view to extension communication.

You can use in your webview content script:

tsvscode.postMessage({
    type: "onYourEvent",
    value: "anything you like to return",
  });

The global tsvscode variable is generated in _getHtmlForWebview with acquireVsCodeApi().

To fix the typing for the global tsvscode. Install @types/vscode-webview with npm as devDependency and add a global.d.ts to your webview script with this content:

import type WebViewApi from '@types/vscode-webview';

global {
  declare const tsvscode: WebViewApi<unknown>;
}

Or if you don't like the global variable tsvscode and the typing above. You could also create a VS Code API wrapper like in the following repository.

Just to get this code running, create an extension as mentioned in the Getting Started Guide with Yeoman generator by running yo code in your terminal.

You can also find the snippets in the following Github gist.

可遇━不可求 2025-02-16 15:17:56

我遇到了同样的问题。我想在WebView的顶部栏中注册操作按钮之一,该网络浏览量显示在“ Explore”选项卡中作为侧边栏显示,以刷新我的WebView中的内容。

上面的解决方案在一个警告中非常有效:我需要处理vscode-&gt; webview和webview- vscode互动。这意味着,当我单击刷新按钮时,将触发一个事件到包含一些数据的WebView,摘录下面显示:

  extensionContext.subscriptions.push(
    vscode.commands.registerCommand(RESET_COMMAND, () => {
      provider.postMessageToWebview({
        type: "reset"
      });
    })
  );

此消息,然后需要由WebView处理,因此,为此我必须在内部进行以下内容我的WebView提供商中的脚本:

    <script nonce=${nonce}>
      const tsvscode = acquireVsCodeApi();

      window.addEventListener("message", (event) => {
        const message = event.data;
        switch (message.type) {
            case 'reset':
                tsvscode.postMessage({ type:"reset" });
                break;
        }
      });
    </script>

> resolvewebviewView 函数中如下:

   webviewView.webview.onDidReceiveMessage((data) => {
      switch (data.type) {
        case "reset":
          webviewView.webview.html = this.getHtmlContent(
            webviewView.webview,
            "<div>reset page!</div>" // this is just rendering some new content in the page when it is reset
          );
          break;
        default: {
          break;
        }
      }
    });
  }

现在,tsvscode可以通过发布消息来接触我的WebView事件侦听器,该消息将其处理在 resolve 页面!

我所描述的内容实现了通信vscode-&gt; webview和webview-&gt; vscode的两个方向。您可能想知道,为什么您不能只在网络浏览量上发布消息,然后在同一WebView上使用ondidrecieve处理?我想知道同一件事,这似乎没有正确的事件侦听器直接注册以直接工作,这就是为什么我们添加 addeventListener 的解决方法似乎可以使用。

我希望这会有所帮助!

I ran in to the same problem. I wanted to register one of the action buttons in the top bar of a webview that shows up as a sidebar in the explore tab to refresh the content in my webview.

The solution above worked very well with one caveat: I needed to both handle the vscode->webview and webview->vscode interaction. What this means is, when I clicked my refresh button, an event is triggered to the webview that contains some data, excerpt shown below:

  extensionContext.subscriptions.push(
    vscode.commands.registerCommand(RESET_COMMAND, () => {
      provider.postMessageToWebview({
        type: "reset"
      });
    })
  );

This message, then needed to be handled by the webview, so for that I had to do the following inside a script in my webview provider:

    <script nonce=${nonce}>
      const tsvscode = acquireVsCodeApi();

      window.addEventListener("message", (event) => {
        const message = event.data;
        switch (message.type) {
            case 'reset':
                tsvscode.postMessage({ type:"reset" });
                break;
        }
      });
    </script>

Now, tsvscode can reach out to my webview event listener by posting the message, which handles it inside the resolveWebviewView function as follows:

   webviewView.webview.onDidReceiveMessage((data) => {
      switch (data.type) {
        case "reset":
          webviewView.webview.html = this.getHtmlContent(
            webviewView.webview,
            "<div>reset page!</div>" // this is just rendering some new content in the page when it is reset
          );
          break;
        default: {
          break;
        }
      }
    });
  }

which finally resets the page!

What I have describes implements both directions of communication vscode->webview and webview->vscode. You might be wondering, why couldn't you just post the message on the webview and then handle it with the onDidRecieve on the same webview? I am wondering the same thing, that doesn't seem to have the proper event listener registered to work directly, which is why this workaround where we add addEventListener seems to work instead.

I hope this helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文