vscode 插件开发经历

本贴最后更新于 2061 天前,其中的信息可能已经事过境迁

对 vscode 一无所知的前提下开发了聊天室插件
https://github.com/ferried/hacpai-cr

1.明确需求

首先,明确了自己的需求和开发步骤

1.必须登录得到 token 设置到 cookie 里
2.通过查看浏览器得到了 wss 的链接,并通过 D 大获取了必要的 ws 头信息
3.得出了需要让用户输入用户名密码,基于 nodejs 发送请求到 hacpai 换取 token
4.换取 token 存储到 vscode 中等待 wss 连接时使用
5.wss 设置头 User-Agent 等等等
6.wss 返回信息类型划分为 online 为获取当前在线用户 msg 为用户输入的信息

所以我需要

1.一个输入框(用户名密码发消息等)
2.一个消息列表(用来显示消息)
3.一个在线用户列表

通过官方的 GettingStart 一步步走下去了解到
1.输入框为 vscode.window.InputBox
2.vscode 原生列表(文件列表)为树格式需要自己写 provider 提供 data

最后找到一个官方项目集合示例
https://github.com/Microsoft/vscode-extension-samples/tree/master/tree-view-sample
并仿照尝试,可以渲染数据才开始进行开发

2.分阶段开发

将需求拆分成几个步骤
1.用户认证
2.长链接
3.渲染数据

3.进行开发

1.首先安装脚手架生成项目

npm install -g yo generator-code yo projectName

2.command 和 package.json

Ctrl+Shift+P 呼出命令行界面输入插件命令何以出发函数主要是通过 package.json 定义好的 command 名称作为入口

commands 就是配置该插件当前所有的命令比如下面配置出了如下几个命令

//extension.hacpaicr.signin-命令id,在函数中使用 //Hacpaicr: Signin-命令值,在 Ctrl+Shift+P 命令输入框中使用 extension.hacpaicr.signin:Hacpaicr: Signin extension.hacpaicr.signout:Hacpaicr: Signout

最后把配置的命令加入到 activationEvents 数组中表示启用此命令

"activationEvents": [ "onCommand:extension.hacpaicr.signin", "onCommand:extension.hacpaicr.signout", "onCommand:extension.hacpaicr.connect", "onCommand:extension.hacpaicr.send", "onView:view.hacpai.cr", "onView:view.hacpai.cru" ], "contributes": { "viewsContainers": { "activitybar": [{ "id": "hacpaicr", "title": "hacpaicr", "icon": "resources/hacpai.svg" }] }, "views": { "hacpaicr": [{ "id": "view.hacpai.cr", "name": "ChatRoom" }, { "id": "view.hacpai.cru", "name": "Users" } ] }, "commands": [{ "command": "extension.hacpaicr.signin", "title": "Hacpaicr: Signin" }, { "command": "extension.hacpaicr.signout", "title": "Hacpaicr: Signout" }, { "command": "extension.hacpaicr.connect", "title": "Hacpaicr: Connect" }, { "command": "extension.hacpaicr.send", "title": "Hacpaicr: Send" } ] }

用户认证

项目入口为 src/xtension.ts

// context为当前插件的域对象 export function activate(context: vscode.ExtensionContext) { // 创建一个User对象并将当前域传入其中 const user = new User(context); // vscode注册命令,传入的值为`extension.hacpaicr.signin`也就是package.json中声明的命令id,当用户输入此命令的Title敲击回车,将出发此函数 vscode.commands.registerCommand(COMMADN_SIGN_IN, async() = >{ await user.sign(); }); ......

user.sign()

Ws 连接
// 当用户输入 connect命令时触发该函数 vscode.commands.registerCommand(COMMAND_CONNECT, async() = >{ await chatRoomClient.connect(); vscode.window.registerTreeDataProvider(VIEW_CHAT_ROOM, chatRoomMessageProvider); vscode.window.registerTreeDataProvider(VIEW_CHAT_ROOM_USERS, chatRoomUserProvider); }); // 连接 connect() { // ws头必须设置好了 let headers = { Host: $HACPAI_HOST, Cookie: this.headerUtil.cookie, "User-Agent": this.headerUtil.cookie, Upgrade: "websocket" }; // 这里用的webscoket包的client连接服务器 this.client.on("connect", (connection: WebSocket.connection) = >{ // 异常处理直接右下角提示 connection.on("error", error = >{ vscode.window.showErrorMessage(error.message); }); connection.on("close", (code, desc) = >{ vscode.window.showErrorMessage(`$ { code }: $ { desc }`); }); // 这里获取服务器推送的消息 connection.on("message", data = >{ if (data.type === "utf8") { let msg = JSON.parse(data.utf8Data as string); // 我丢?这个console没删干净 console.log(msg); switch (msg.type) { // online类型标识当前在线用户 case "online": let users: Array < ChatRoomUser > =[]; msg.users.forEach((user: any) = >{ users.push(new ChatRoomUser(user.userName)); }); // 这个一会儿再说,就是给视图服务推了一个数据去刷新vscode视图了 this.userDataProvider.setUsers(users); break; case "msg": // 接收到的消息推送给Message视图服务刷新视图 this.messageDataProvider.add(new ChatRoomMessage(msg.content)); break; } } }); }); this.client.on("connectFailed", error = >{ vscode.window.showErrorMessage(error.message); }); // LINKSTART 连接开始 this.client.connect(this.headerUtil.wsuri, [], $HACPAI_ORIGIN, headers, { headers: headers }); }

视图

视图容器由 package.json 配置,id 为 hacpaicr 一个容器中可以有多个 viewviews 中配置

"viewsContainers": { "activitybar": [{ "id": "hacpaicr", "title": "hacpaicr", "icon": "resources/hacpai.svg" }] }, "views": { "hacpaicr": [{ "id": "view.hacpai.cr", "name": "ChatRoom" }, { "id": "view.hacpai.cru", "name": "Users" } ] },

在入口中创建视图数据提供器并注册给视图通过视图 ID 就可以将数据和视图绑定了

const chatRoomMessageProvider = new ChatRoomMessageProvider(); const chatRoomUserProvider = new ChatRoomUserProvider(); // 第一参为视图ID:如view.hacpai.cr vscode.window.registerTreeDataProvider( VIEW_CHAT_ROOM, chatRoomMessageProvider ); vscode.window.registerTreeDataProvider( VIEW_CHAT_ROOM_USERS, chatRoomUserProvider ); });

视图数据提供器

// 这是一个消息数据提供器,需要实现`vscode.TreeDataProvider`以标识这是TreeDataView数据提供器 export class ChatRoomMessageProvider implements vscode.TreeDataProvider < ChatRoomMessage > , vscode.Disposable { // 这个数组是用来显示消息列表的所以是一个数组 private history: Array < ChatRoomMessage > =[]; // tree数据改变事件 private _onDidChangeTreeData: vscode.EventEmitter < ChatRoomMessage | undefined > =new vscode.EventEmitter < ChatRoomMessage | undefined > (); readonly onDidChangeTreeData: vscode.Event < ChatRoomMessage | undefined > =this._onDidChangeTreeData.event; // 一个TreeView有多个TreeItem,这个TreeItem类似拦截器可以拦截每一个TreeItem进行修改 getTreeItem(element: ChatRoomMessage) : vscode.TreeItem | Thenable < vscode.TreeItem > { return element; } // 这里主要是返回该TreeView数据用的,直接返回声明的消息列表 getChildren(element ? :ChatRoomMessage | undefined) : vscode.ProviderResult < ChatRoomMessage[] > { return Promise.resolve(this.history); } // 刷新当前View refresh() { this._onDidChangeTreeData.fire(); } // 这里主要是给WebSocketClient用的,当收到信息后,插入到队列最前方,然后刷新视图 add(chatRoomMessage: ChatRoomMessage) { this.history.unshift(chatRoomMessage); this.refresh(); } // 忘了... dispose() { this.history = []; this._onDidChangeTreeData.dispose(); } // 清楚消息列表 clean() { this.history = []; this.refresh(); } } // TreeView要显示的TreeItem类型 export class ChatRoomMessage extends vscode.TreeItem {}

再来看如何在 WebSocket 中使用定义的数据提供器

// 主要通过构造函数把引用传递过来 export class ChatRoomClient { // 存储域 private context: vscode.ExtensionContext; // 消息数据提供器 private messageDataProvider: ChatRoomMessageProvider; // 用户数据提供器 private userDataProvider: ChatRoomUserProvider; private client: WebSocket.client; private headerUtil: HeaderUtil; constructor(context: vscode.ExtensionContext, messageDataProvider: ChatRoomMessageProvider, userDataProvider: ChatRoomUserProvider) { this.context = context; // 这里传递引用 this.messageDataProvider = messageDataProvider; this.userDataProvider = userDataProvider; this.headerUtil = new HeaderUtil(context); this.client = new WebSocket.client(); } ... //接收到消息直接调用提供器的函数添加数据数据类型为TreeItem类型,这里只用了一个content参数,还有toolip?desc?没有用到 case "msg": this.messageDataProvider.add(new ChatRoomMessage(msg.content)); break;

TODOLIST

希望社区朋友们可以推 PR,写了三天了,今天老大问我你禅道东西怎么那么多没做,差点被锤......我去研究 davinci

1.注销功能 2.注销后清除数据列表和数据提供器 3.现在消息列表全为html,所以需要过滤信息和圈人的信息(@88250) 4.消息太长时不太友好 5.消息清除功能没有写 6.没有显示出是谁发的几点发的消息 7.没有设置应用市场图标和应用市场详细信息

为何写这个

1.没写过插件,想试试
2.聊天室挺好玩
3.希望大佬们可以抽时间完善这个或者重写一个更好用的,推荐 vscode webView 这样代码鸭图片呀啥的就可以解析出来了类似 Slack

鸣谢

推 pr 帮改下 README.md 写个鸣谢列表呗,嘿嘿嘿

@88250 大哥联调
@Vanessa svg 图标
@jinjianh 正则表达式
@csfwff 老哥提供建议

  • VSCode
    38 引用 • 55 回帖 • 1 关注
  • 插件
    102 引用 • 623 回帖 • 3 关注
4 操作
someone27889 在 2019-07-31 22:38:30 更新了该帖
someone27889 在 2019-07-31 22:21:20 更新了该帖
someone27889 在 2019-07-31 22:14:00 更新了该帖
someone27889 在 2019-07-31 22:00:11 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
请输入回帖内容 ...