【vscode】vscode插件学习(四)
前言今天继续学习制作vscode插件,本篇基本把vscode插件开发套路给摸清了。官网与参考资料VSCode WebView说明 https://code.visualstudio.com/api/extension-guides/webviewVSCode 自身内置命令大全 https://code.visualstudio.com/docs/getstarted/keybindings小茗同学
·
前言
- 今天继续学习制作vscode插件,本篇基本把vscode插件开发套路给摸清了。
官网与参考资料
-
VSCode WebView说明 https://code.visualstudio.com/api/extension-guides/webview
-
VSCode 自身内置命令大全 https://code.visualstudio.com/docs/getstarted/keybindings
-
小茗同学的博客园 https://www.cnblogs.com/liuxianan/p/vscode-plugin-webview.html
-
VSCode插件开发入门 https://zhuanlan.zhihu.com/p/99198980
-
从零开始实现VS Code基金插件 https://juejin.cn/post/6864712731484749831
-
github小说插件原型 https://github.com/aooiuu/z-reader
前置理解
- vscode的插件开发主要是调api,那么如何找api和如何快速知道自己想要的是哪个api就比较重要。一般是需要查看ts声明或者直接查看文档。
- vscode的每个模块一般都是个类,自己去实现或者继承或者直接new出实例进行使用。反正最终都是要new出实例。此实例的生命周期以及一些方法都在类上。如果出了实例那就在实例上找,用ts很容易找到。
- 另外剩余一些跳出这个套路的查看文档或者别人写的示例即可。
插件制作
- 本次制作线上查找小说,并且可以在右下角显示读取进度,在每次tab页关闭后再次打开可以直接跳到进度处。
- 上一次制作的本地小说是TreeDataProvider,这次就直接在这个上加上线上功能。
- TreeDataProvider是可以注册多个的,注册多个的效果就类似于手风琴那种。我们要在这个Provider的右侧增加按钮,则需要在贡献点配上:
"menus": {
"view/title": [
{
"command": "searchOnlineNovel",
"when": "view == myread-list",
"group": "navigation"
}
]
}
"commands": [
{
"command": "storyvs.helloWorld",
"title": "Hello World"
},
{
"title": "线上搜索",
"command": "searchOnlineNovel"
}
],
- 这个when其实就是因为那个可以注册多个,只显示在一个上。
- 下面是改造provider,我们知道Provider的每个列表项是treeItem,那么本地就是实现的本地的列表项,而线上实现的线上的列表项。而数据源就是getChildren或者construcor里获取:
class Provider implements vscode.TreeDataProvider<NovelItem> {
// 发布订阅的事件
public refreshEvent: vscode.EventEmitter<NovelItem | null> = new vscode.EventEmitter<NovelItem | null>();
// 挂到该方法上实现刷新
onDidChangeTreeData: vscode.Event<NovelItem | null> = this.refreshEvent
.event;
// 判断列表是本地还是在线
public isOnline = false;
public treeNode: NovelItem[] = [];
constructor() {
// 默认列表上先加载本地
getLocalBooks().then((res) => {
this.treeNode = res;
});
}
// 封装一个本地和在线的切换方法
refresh(isOnline: boolean) {
this.isOnline = isOnline;
this.refreshEvent.fire(null);
}
// 根据本地还是在线会加载不同的列表项
getTreeItem(info: NovelItem): NovelTreeItem {
if (this.isOnline) {
return new OnlineTreeItem(info);
}
return new NovelTreeItem(info);
}
// 现在把列表每项的数据放在treenode上,除了在线小说展开章节的情况
async getChildren(element?: NovelItem | undefined): Promise<NovelItem[]> {
if (element) {
return await getChapter(element.path);
}
return this.treeNode;
}
}
- novelItem是自己定义的类型,结合实际需要:
export interface NovelItem {
name: string;
path: string;
isDirectory: boolean;
type: string;
}
- treeItem的数据类型是Provider传来的,你可以在constructor里获取到类型,继承改写它内置的属性方法来实现显示文字,点击触发命令等等:
export default class OnlineTreeItem extends TreeItem {
constructor(info: NovelItem) {
super(`${info.name}`);
const tips = [`名称: ${info.name}`];
this.tooltip = tips.join("\r\n");
// 根据isDirectory属性判断是不是可折叠的组
this.collapsibleState = info.isDirectory
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None;
// 这里命令也换了一个,换成openOnlineNovel(注意注册一下)
this.command = info.isDirectory
? undefined
: {
command: "openOnlineNovel",
title: "打开该网络小说",
arguments: [{ name: info.name, path: info.path }],
};
}
contextValue = "online";
}
- 点击搜索时跳出inputbox,并注册进前面贡献点那配置的命令,这样当搜索后,provider刷新,获得了新的数据源,从而渲染线上资源。
export const searchOnline = async function (provider: Provider) {
const msg = await vscode.window.showInputBox({
password: false,
ignoreFocusOut: false,
placeHolder: "请输入小说的名字",
prompt: "",
});
if (msg) {
provider.treeNode = await search(msg);
provider.refresh(true);
}
};
- statusBar是vscode底部的玩意,它可以设置在左边还是在右边,当然,你还需要处理它销毁,否则它不会随着webview关闭主动消失。所以对statusBar进行个简单封装
class StatusBar {
private statusBar: vscode.StatusBarItem | undefined;
constructor() {
this.statusBar = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right
);
this.statusBar.text = "yehuozhili";
this.statusBar.show();
}
set(val: string) {
if (this.statusBar) {
this.statusBar.text = val;
}
}
dispose() {
if (this.statusBar) {
this.statusBar.dispose();
}
}
}
- 爬虫部分不是本文重点,这里就不说了:
import * as cheerio from "cheerio";
import * as https from "https";
import { NovelItem } from "./extension";
const request = async (url: string): Promise<string> => {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let chunks = "";
if (!res || res.statusCode !== 200) {
reject(new Error("网络请求错误!"));
return;
}
res.on("data", (chunk) => {
chunks += chunk.toString("utf8");
});
res.on("end", () => {
resolve(chunks);
});
});
});
};
// 我们从笔趣阁小说站抓页面
const DOMAIN = "https://www.biquge.com.cn";
// 搜索关键词相对应的小说
export const search = async (keyword: string) => {
const result = [] as any;
try {
const res = await request(
DOMAIN + "/search.php?q=" + encodeURI(keyword)
);
console.log(res);
const $ = cheerio.load(res);
$(".result-list .result-item.result-game-item").each(function (
i: number,
elem: any
) {
const title = $(elem)
.find("a.result-game-item-title-link span")
.text();
const author = $(elem)
.find(
".result-game-item-info .result-game-item-info-tag:nth-child(1) span:nth-child(2)"
)
.text();
const path = $(elem).find("a.result-game-item-pic-link").attr()
.href;
console.log(title, author, path);
result.push({
type: ".biquge",
name: `${title} - ${author}`,
isDirectory: true,
path,
});
});
} catch (error) {
console.warn(error);
}
return result;
};
// 搜索该小说对应的章节
export const getChapter = async (pathStr: string) => {
const result: NovelItem[] = [];
try {
const res = await request(DOMAIN + pathStr);
const $ = cheerio.load(res);
$("#list dd").each(function (i: number, elem: any) {
const name = $(elem).find("a").text();
const path = $(elem).find("a").attr().href;
result.push({
type: ".biquge",
name,
isDirectory: false,
path,
});
});
} catch (error) {
console.warn(error);
}
return result;
};
// 获取章节对应的内容并html化
export const getContent = async (pathStr: string) => {
let result = "";
try {
const res = await request(DOMAIN + pathStr);
const $ = cheerio.load(res);
const html = $("#content").html();
result = html ? html : "";
} catch (error) {
console.warn(error);
}
return result;
};
- 下面需要整一下webview。当点击treeItem时,触发其command,打开pannel,webview是挂在pannel上的,所以对于statusBar的处理可以通过pannel的onDidChangeViewState来判断它是不是激活状态。如果是激活状态,则new一个statusBar出来显示,如果是非激活状态,则把statusBar干掉,保证只有一个statusBar出现。另外在pannel关闭时,需要把statusBar给关掉。
- webview与vscode的通信靠postmessage,由于我们需要记录每个小说的位置,那么就激活命令时创建一个对象,每个webview会写入这个对象看的进度:
const progressSetting: Record<
string,
any
> = vscode.workspace.getConfiguration().get("novel.progress", {});
- 最后靠通信每次打开时滚动到对应位置,在看的过程当中,每隔一段时间向vscode写入位置即可。
const oponline = vscode.commands.registerCommand(
"openOnlineNovel",
async function (args) {
const panel = vscode.window.createWebviewPanel(
"novelReadWebview",
args.name,
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
}
);
let status: StatusBar;
panel.onDidChangeViewState((e) => {
const active = e.webviewPanel.active;
if (active) {
status = new StatusBar();
} else {
if (status) {
status.dispose();
}
}
});
const handleMessage = (message: {
command: string;
progress: number;
}) => {
progressSetting[args.name] = message.progress;
if (status) {
status.set((message.progress * 100).toFixed(2) + "%");
}
switch (message.command) {
case "updateProgress":
return vscode.workspace
.getConfiguration()
.update("novel.progress", progressSetting, true);
}
};
const content = await getContent(args.path);
panel.webview.html = `<html><script>
const vscode = acquireVsCodeApi();
setInterval(() => {
vscode.postMessage({
command: 'updateProgress',
progress: window.scrollY / document.body.scrollHeight
})
}, 1000);
window.addEventListener('message', event => {
const message = event.data;
switch (message.command) {
case 'goProgress':
window.scrollTo(0, document.body.scrollHeight * message.progress);
break;
}
});
</script><body>${content}</body></html>`;
panel.webview.onDidReceiveMessage(
handleMessage,
undefined,
context.subscriptions
);
panel.webview.postMessage({
command: "goProgress",
progress: progressSetting[args.name],
});
panel.onDidDispose((e) => {
status.dispose();
});
}
);
- 这样插件就完成了。可以参考前面文章进行打包发布。
更多推荐
所有评论(0)