这次来看一下如何开发vscode插件,插件的功能是方便排查本地日志文件。
vscode文档官网地址 : Your First Extension
配置环境 请先安装好Node.js和Git,
然后安装Yeoman和VS Code Extension Generator:
1 2 3 npm install -g yo generator-code
通过脚手架生成模板项目,我们可以通过这个模板工程来开发:
输入yo code
后会出现一个命令行交互:进行对应的信息输入就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 _-----_ ╭──────────────────────────╮ | | │ Welcome to the Visual │ |--(o)--| │ Studio Code Extension │ `---------´ │ generator! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.' __ ´ ` |° ´ Y ` ? What type of extension do you want to create? New Extension (JavaScript) ? What's the name of your extension? helloWorld ? What' s the identifier of your extension? helloWorld? What's the description of your extension? helloWorld ? Enable JavaScript type checking in ' jsconfig.json'? Yes ? Initialize a git repository? No ? Which package manager to use? npm
完成交互后 命令行会出现
1 2 3 4 5 Your extension helloWorld has been created! To start editing with Visual Studio Code, use the following commands: code helloWorld
调试helloWorld 用VS Code打开helloWorld文件夹 ,按下F5,你会立即看到一个插件发开主机窗口,其中就运行着插件。
在命令面板(Ctrl+Shift+P)中输入Hello World命令。
如果你看到了Hello World提示弹窗,恭喜你成功了!
插件目录结构 1 2 3 4 5 6 7 8 9 10 . ├── .vscode │ ├── launch.json // 插件加载和调试的配置 │ └── tasks.json // 配置TypeScript编译任务 ├── .gitignore // 忽略构建输出和node_modules文件 ├── README.md // 一个友好的插件文档 ├── src │ └── extension.ts // 插件源代码 ├── package.json // 插件配置清单 ├── tsconfig.json // TypeScript配置
目前我们只有着重看package.json
和 extension.ts
文件
每个VS Code插件都必须包含一个package.json,它就是插件的配置清单。package.json混合了Node.js字段,如:scripts、dependencies,还加入了一些VS Code独有的字段,如:publisher、activationEvents、contributes等。关于这些VS Code字段说明都在插件清单参考中可以找到。
extension.ts 插件入口文件会导出两个函数,activate 和 deactivate,你注册的激活事件被触发之时执行activate,deactivate则提供了插件关闭前执行清理工作的机会。
所需的插件功能 目前基本了解一下vscode插件的大概,接下来具体看下我们可能需要哪些功能:
一个左侧pannel上显示的icon
点击icon打开的左侧面板UI
面板上需要打开文件功能
面板上需要将当前打开的文件设置为所需文件功能
一个输入筛选条件的输入框
根据筛选条件过滤文件内容的功能
将过滤后信息输出的功能
侧边icon:contributes.viewsContainers & contributes.views 在package.json中通过viewsContainers 和 views 配置来声明 左侧pannel上显示的icon 和 点击icon打开的左侧面板UI。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 "contributes" : { "viewsContainers" : { "activitybar" : [ { "id" : "ai-log-assistant" , "title" : "AI Log Assistant" , "icon" : "resources/dark/ai-kit-icon.svg" } ] } , "views" : { "ai-log-assistant" : [ { "id" : "ai-log-assistant" , "type" : "webview" , "name" : "AI Log Assistant" , "icon" : "resources/dark/dependency.svg" , "contextualTitle" : "AI Logger" } ] } , }
侧边视图:自定义webview 在 extension.ts 中注册一个 与 package.json 对应的 ai-log-assistan 侧边栏ID
1 2 3 4 5 6 7 8 9 import * as vscode from "vscode" ;import { SidebarProvider } from "./SidebarProvider" ;export function activate (context: vscode.ExtensionContext ) { const sidebarPanel = new SidebarProvider (context.extensionUri ); context.subscriptions .push ( vscode.window .registerWebviewViewProvider ("ai-log-assistant" , sidebarPanel) ); }
实现侧边栏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import * as vscode from "vscode" ;export class AILogWebviewViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'ai-log-assistant' ; private _view?: vscode.WebviewView ; private readonly _extensionUri : vscode.Uri ; private readonly _context : vscode.ExtensionContext ; constructor (extensionUri: vscode.Uri, context: vscode.ExtensionContext ) { this ._extensionUri = extensionUri; this ._context = context; } public resolveWebviewView (webviewView : vscode.WebviewView ): void { this ._view = webviewView; this ._view .webview .options = { enableScripts : true , }; const projectPath = utils.getProjectPath (this ._context ); utils.loadToken (); console .log ("projectPath: " + projectPath); this ._view .webview .onDidReceiveMessage (message => { operateMessage (this ._context , projectPath, webviewView.webview , message); }, undefined , this ._context .subscriptions ); this ._view .webview .html = this ._getHtmlForWebview (webviewView); } private _getHtmlForWebview (view : vscode.WebviewView ): string { return getWebViewContent (this ._context , 'view/ai-webview.html' , view.webview ); } }
上述代码采用面向对象的方式实现一个 AILogWebviewViewProvider类,根据 vscode.WebviewViewProvider, 其实实现所有的 WebviewViewProvider 都是是这段代码,其他代码都是相同的,因为关于 webview 中的 HTML 我们都可以使用js来生成,这不正是我们的单页面应用开发?
UI与逻辑通信 在webview html的按钮点击事件中添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function callVscode (data, cb ) { if (typeof data === 'string' ) { data = { cmd : data }; } if (cb) { const cbid = Date .now () + '' + Math .round (Math .random () * 100000 ); callbacks[cbid] = cb; data.cbid = cbid; } vscode.postMessage (data); }
在上面AILogWebviewViewProvider注册了:
1 2 3 this ._view .webview .onDidReceiveMessage (message => { operateMessage (this ._context , projectPath, webviewView.webview , message); }, undefined , this ._context .subscriptions );
然后通过operateMessage分发对应的时间请求:
1 2 3 4 5 6 function operateMessage (context: vscode.ExtensionContext, projectPath: string, webview: vscode.Webview, message: any ) { console .log ("received msg : " + message); switch (message.cmd ) { } }
打开文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 async function selectXLogFile (pcontext: vscode.ExtensionContext, webview: vscode.Webview, message: any ) { const uris = await vscode.window .showOpenDialog ({ canSelectFiles : true , canSelectFolders : false , canSelectMany : false , }); if (uris && uris.length > 0 ) { const uri = uris[0 ]; parseFilePath = uri.fsPath ; console .log ("select a log file: " + parseFilePath); invokeCallback (webview, message, parseFilePath); } }
获取当前打开的文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function parseCurLogFile (pcontext: vscode.ExtensionContext, webview: vscode.Webview, message: any ) { console .log ("start parseCurLogFile" ); curShowDocument = null ; const editor = vscode.window .activeTextEditor ; let filePath = '' ; if (editor) { filePath = editor.document .uri .fsPath ; const fileName = editor.document .fileName ; console .log ("filePath: " + filePath + " fileName:" + fileName); } if (filePath === '' ) { utils.showError ("当前文件信息为空" ); invokeCallback (webview, message, "当前文件信息为空" ); return ; } if (!filePath.endsWith (".log" )) { utils.showError (filePostfixHint); } parseFilePath = filePath; invokeCallback (webview, message, parseFilePath); }