将webview展示到vscode的视图容器中
大家已经注意到上边的webview是显示在编辑器区域的,而cline的对话界面是作为一个视图显示在单独的视图容器里边的,下边我们将进行改造,将我们的react webview显示在单独的视图容器中。
下边我们将参考 Weather Webview Sample Extension, 将webview显示在视图中。
定义视图容器和视图
在 package.json 文件中的 contributes 中定义 viewsContainers 和 views , 文件内容看起来如下
...
"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.svg 到 assets/icons/ 目录下,再次启动插件,侧边栏将出现新的minicline的视图容器,点击图标将显示minicline视图,当然,现在我们还没有实现新的webview,所以显示不了任何内容,如下图所示。
实现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内容了。现在启动插件,应该看到如下图所示。
下边我们把 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。
启动插件,现在视图看起来如下所示。
接下来我们需要把 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
总结
本章我们新建了视图容器,并且将webview移到了视图容器中的一个视图中。我们添加了新的vscode webview-ui-toolkit的一些react组件。并且实现了webview和vscode extension host之间的双向消息发送和监听。