将webview展示到vscode的视图容器中

38 阅读3分钟

将webview展示到vscode的视图容器中

大家已经注意到上边的webview是显示在编辑器区域的,而cline的对话界面是作为一个视图显示在单独的视图容器里边的,下边我们将进行改造,将我们的react webview显示在单独的视图容器中。

下边我们将参考 Weather Webview Sample Extension, 将webview显示在视图中。

定义视图容器和视图

package.json 文件中的 contributes 中定义 viewsContainersviews , 文件内容看起来如下

  ...
  "main": "./out/extension.js",
  "contributes": {
    "viewsContainers": {
      "activitybar": [
        {
          "id": "minicline-ActivityBar",
          "title": "MiniCline",
          "icon": "assets/icons/icon.svg"
        }
      ]
    },
    "views": {
      "minicline-ActivityBar": [
        {
          "type": "webview",
          "id": "minicline.SidebarProvider",
          "name": "MiniCline"
        }
      ]
    },
    "commands": [
      {
        "command": "hello-world.showHelloWorld",
        "title": "Hello World (React + Vite): Show"
      }
    ]
  },
  ...

从cline项目中拷贝 icon.svgassets/icons/ 目录下,再次启动插件,侧边栏将出现新的minicline的视图容器,点击图标将显示minicline视图,当然,现在我们还没有实现新的webview,所以显示不了任何内容,如下图所示。

chapter-2-1.png

实现WebviewViewProvider

因为现在我们的webview实现为视图容器里边的视图,所以我们需要实现WebviewViewProvider,并且在插件启动时注册该WebviewViewProvider。 我们将 weather-webview 项目中的 src/providers/WeatherViewProvider.ts 拷贝到我们 minicline 对应目录下,并且在修改插件入口函数调用它。

src/extension.ts

  const provider = new WeatherViewProvider(context.extensionUri);

  const weatherViewDisposable = window.registerWebviewViewProvider(
    WeatherViewProvider.viewType,
    provider
  );

  context.subscriptions.push(weatherViewDisposable);

然后我们把原先src/panels/HelloWorldPanel.ts中对应的函数 _getWebviewContent_setWebviewMessageListener 的内容替换 src/providers/WeatherViewProvider.ts 对应函数内容。这样我们的视图就会显示我们react编译的webview内容了。现在启动插件,应该看到如下图所示。

chapter-2-2.png

下边我们把 weather-webview 里一些有用的组件添加到我们的react webview中,比如vscode-text-field,vscode-dropdown等。后边我们的chat box会用到。

将原 src/providers/WeatherViewProvider.ts 中函数 _getWebviewContent 的如下内容拷贝到 webview-ui/src/App.tsx 中,需要注意的是,在 @vscode/webview-ui-toolkit/react 中对应的react组件名字不是 vscode-button 而是 VSCodeButton,其他组件名字也需要对应改动,具体可以到 @vscode/webview-ui-toolkit/react中查看。然后在react中class需要替换成className。

src/providers/WeatherViewProvider.ts

<h1>Weather Checker</h1>
<section id="search-container">
  <vscode-text-field
    id="location"
    placeholder="Location"
    value="Seattle, WA">
  </vscode-text-field>
  <vscode-dropdown id="unit">
    <vscode-option value="F">Fahrenheit</vscode-option>
    <vscode-option value="C">Celsius</vscode-option>
  </vscode-dropdown>
</section>
<vscode-button id="check-weather-button">Check</vscode-button>
<h2>Current Weather</h2>
<section id="results-container">
  <vscode-progress-ring id="loading" class="hidden"></vscode-progress-ring>
  <p id="icon"></p>
  <p id="summary"></p>
</section>

webview-ui/src/App.tsx

function App() {
  ...
  return (
  <main>
    <h1>Weather Checker</h1>
    <section id="search-container">
      <VSCodeTextField
        id="location"
        placeholder="Location"
        value="Seattle, WA">
      </VSCodeTextField>
      <VSCodeDropdown id="unit">
        <VSCodeOption value="F">Fahrenheit</VSCodeOption>
        <VSCodeOption value="C">Celsius</VSCodeOption>
      </VSCodeDropdown>
    </section>
    <VSCodeButton id="check-weather-button">Check</VSCodeButton>
    <h2>Current Weather</h2>
    <section id="results-container">
      <VSCodeProgressRing id="loading" className="hidden"></VSCodeProgressRing>
      <p id="icon"></p>
      <p id="summary"></p>
    </section>
  </main>
  ); 
}

拷贝对应的 weather-webview/src/webview/styles.css 的内容到 webview-ui/src/App.css 中。

别忘了在启动插件前重新运行 npm run build:webview 编译我们的react webview。

启动插件,现在视图看起来如下所示。

chapter-2-3.png

接下来我们需要把 weather-webview/src/webview/main.ts 中的代码迁移到react webview中。

关键代码如下

在webview端,当点击 Check 按钮时会向vscode extension host发送当前位置信息,请求天气情况信息。并且使用react的useEffect函数在App启动时注册监听器,监听vscode extension host端发送的天气消息。

webview-ui/src/App.tsx

function App() {
  ...
  useEffect(() => {

    window.addEventListener("message", (event) => {
      const command = event.data.command;
      switch (command) {
        case "weather":
          const weatherData = JSON.parse(event.data.payload);
          displayWeatherData(weatherData);
          break;
          ...
      }
    });
  }, []);
  ...
  function checkWeather() {
    const location = document.getElementById("location") as TextField;
    const unit = document.getElementById("unit") as Dropdown;
    vscode.postMessage({
      command: "weather",
      location: location.value,
      unit: unit.value,
    });
    displayLoadingState();
  }
  ...
  return (
    <main>
      <h1>Weather Checker</h1>
      ...
      <VSCodeButton id="check-weather-button" onClick={checkWeather}>Check</VSCodeButton>
      ...
    </main>
  );
}

在vscode extension host端,修改WeatherViewProvider监听的消息类型,当消息类型为天气情况时,调用weather库获取实际天气信息发送给webview端。

src/providers/WeatherViewProvider.ts

private _setWebviewMessageListener(webviewView: WebviewView) {
  webviewView.webview.onDidReceiveMessage(
    (message: any) => {
      ...
      switch (command) {
        case "weather":
          weather.find({ search: location, degreeType: unit }, (err: any, result: any) => {
            ...
            const weatherForecast = result[0];
            // Pass the weather forecast object to the webview
            webviewView.webview.postMessage({
              command: "weather",
              payload: JSON.stringify(weatherForecast),
            });
          });
          break;
      }
    },
    ...
  );
}

现在我们启动插件项目,点击 Check 按钮后会更新天气情况,如下图所示,别忘了启动前运行 npm run build:webview

chapter-2-4.png

总结

本章我们新建了视图容器,并且将webview移到了视图容器中的一个视图中。我们添加了新的vscode webview-ui-toolkit的一些react组件。并且实现了webview和vscode extension host之间的双向消息发送和监听。