相信你也有这样的感受,在工作中,有时候会被一些简单重复的操作所影响效率,这个插件查了不少资料,也趟过一些坑,现在终于完成了一个初版,将代码贴出来供给大家参考。
PS: 全英文文档对我来说真的是有点不友好
vscode插件开发主要流程介绍(正文请下滑)
VSCode插件开发文档
微软为了方便vscode插件开发,有推出一个vscode的脚手架,通过此脚手架可以快速开发,可以通过以下命令安装:
npm install -g yo generator-code
安装好之后,输入
yo code
初始化项目,会出现提示选择语言,在我的项目中,我选择的是js,这里就不进行贴图了。
extension.js文件:
extension.js是一个入口文件,在这个文件中,我们可以看到有两个方法,分别是 activate和deactivate activate: 在插件被激活的时候会调用此方法(在package.json中的activationEvents来定义插件被激活的条件),我们可以在这个方法中去对package.json的方法来进行注册
function activate(context){
let disposable = vscode.commands.registerCommand('start-plug.helloWorld', () => {
//此处填入该命令被执行时候所需执行的代码(建议封装单独js文件)
});
}
deactivate:当扩展被停用时,将调用此方法,可以选择在此处加入一些友好提示
function deactivate(){ }
package.json文件:
package.json可以对插件进行一些配置,主要字段如下:
activationEvents:
用来定义插件激活的事件,或者说是条件,可以是文件后缀名,或者是命令等。
取值可以是
- onLanguag
- onCommand
- onDebug
- onDebugInitialConfigurations
- onDebugResolve
- workspaceContains
- onFileSystem
- onView
- onUri
- ......
"activationEvents": [
"onStartupFinished"
]
main:
用来定义入口文件的路径
contributes:
contributes可以看作一种声明
比如contributes的commands,在文档中的这段话有体现出来:"当调用命令时(从键绑定、从命令面板、任何其他菜单或以编程方式),VS Code 将发出 activationEvent onCommand:${command}";也就是说,在接收到用户端发出的commands(指令)的时候,会去activationEvent中找commands中的符合用户输入的command指令的行为,找到后执行,默认在命令面板( Ctrl+Shift+P ) 中;
而menus: 用来定义当鼠标选中某个文件的时候,所展示出来的菜单可选项,而menu中的command,表示选中该菜单栏时,所需要执行的命令,菜单栏的title,对应的是menu中的command在commands中的title
- breakpoints
- colors
- commands
- customEditors
- debuggers
- keybindings
- languages
- menus
- ........
具体列表参见:contributes声明文档
正文
现在接手的这个项目有些特殊,在生产环境下的时候需要将调用了某方法的代码注释掉,否则会导致js文件报错,而在预发布环境下,又需要将之前注释掉的方法恢复回来,令其重新生效。这就导致了每次在迭代的时候,都需要在js文件中去搜索这个方法然后一个个注释,预发布时再一个个取消注释,非常麻烦。
作为码农,怎么甘心接受这种低级而又重复的操作呢!于是我想着能否开发一个vsCode插件,来帮助我遍历、查找、替换,同时既然是插件,肯定是不能只针对我接手的这个项目进行开发,为了兼容日后更多的情况,可以采用文本框输入的方式来确定所需要注释掉的代码。撸起袖子,说干就干!
在脚手架中使用javascript创建的目录:
首先来看看入口文件extension.js
其中所用到的FileHanding是自己创建的文件,建议将处理文件的方法单独封装成一个或几个js;
// The module 'vscode' contains the VS Code extensibility API模块'vscode'包含VS Code扩展API
// Import the module and reference it with the alias vscode in your code below导入模块并使用下面代码中的别名vscode引用它
const vscode = require('vscode');
const FileHanding = require('./fileHanding')
// fileHanding属于自定义的js方法
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('context',context);
//TODO : 在package.json中的activationEvents来定义插件被激活的条件
// 这一行代码只会在你的扩展被激活时执行一次
console.log('Congratulations, your extension "start-plug" is now active!');
vscode.window.showInformationMessage('插件已被激活');
// 该命令已经在包中定义json文件
// 在package中定义的事件你需要在此处注册
// 参数必须与package.json中的command字段匹配
let codeClear = vscode.commands.registerCommand('custom-code.clear', (params) => {
//调用vscode的输入框API
vscode.window.showInputBox({
password: false,
placeHolder: '请输入需要将其注释的内容(逐行匹配,匹配开头,忽略空格)',
}).then(msg => {
// 这部分异步回调会在输入框出现的时候执行一次,此时的msg值为undefined,所以需要过滤掉此次执行,对msg值进行判断
if (msg) {
const fileOptions = {
encoding: 'utf-8'
};
FileHanding.handleMain(params.fsPath, fileOptions,`${msg}`).then(res => {
vscode.window.showInformationMessage('文件已更新,可以进行查看咯');
},err => {
vscode.window.showErrorMessage(err)
})
}
})
});
context.subscriptions.push(disposable, codeClear);
}
// 当您的扩展被停用时,将调用此方法
function deactivate() {}
module.exports = {
activate,
deactivate
}
这是用于处理文件内容的fileHanding.js文件:在这个文件中我引入了node的fs模块,对文件进行读取和写入.
//TODO:想做一个类似日志的txt文件,用来保存每次更改的记录,可是面对mac和windows的目录差异,如何确定放置位置?
//TODO: 是否需要增加类型过滤?
const FileSystem = require('fs');
function isFolder(path) { //判断是否是文件夹
return FileSystem.lstatSync(path).isDirectory();
}
function handleMain(path, options, msg){
return new Promise((resolve,reject) => {
if (isFolder(path)) {//如果是文件夹,开始递归,更改文件夹下所有文件
const filePathList = FileSystem.readdirSync(path);
console.log('文件列表',filePathList,path);
if (filePathList.length) {
for (let index = 0; index < filePathList.length; index++) {
const childPath = `${path}/${filePathList[index]}`;
handleMain(childPath,options, msg)
}
}else{
reject('文件夹为空')
}
}else{
const result = readFile(path, options, msg)
result.state ? resolve() : reject(result.errCode);
}
})
}
function readFile(path, options, msg) {
if (!path || !msg) {
return {
state: false,
errCode: '当前状态异常'
}
}
if (!FileSystem.existsSync(path)) {// 判断文件是否存在
return {
state: false,
errCode: '现有路径没有找到文件'
}
}
try {
const fileData = FileSystem.readFileSync(path, options);
const fileCodeList = fileData.toString().split('\n'); //将Buffer转为string类型后以行为单位分割
clearHandleFile(path, fileCodeList, options, msg); // 路径、代码行数组、字符类型、用户输入文本
return {
state: true
}
} catch (error) {
return {
state: false,
errCode: `未知错误${error}`
}
}
}
function clearHandleFile(path, fileStringList, options, msg) {
const handleCodeList = [];
let isUpdate = false;
for (let index = 0; index < fileStringList.length; index++) {
const element = fileStringList[index];
if (isAccordCondition(element.replace(/(^\s*)/g, ""), msg)) { //去除左边空格
const insertIndex = element.search(msg);
const handleString = `${element.slice(0,insertIndex)}// ${element.slice(insertIndex)}`;
handleCodeList.push(handleString);
isUpdate = true;
} else {
handleCodeList.push(element)
}
}
if (isUpdate) { //如果该文件进行了处理则重新写入,没有变更的话不写入
const handleFileStr = handleCodeList.join('\n');
FileSystem.writeFileSync(path, handleFileStr, options);
}
}
function isAccordCondition(str, msg) {
//TODO: 此处判断注释太过单一,后续需将/* */考虑在内
if (/^\/\//.test(str)) { //如果是注释则返回false,不做改变
return false
}
let exp = RegExp(`^${msg}`); //采用输入框动态传入,匹配开头
return exp.test(str);
}
module.exports = {
readFile,
handleMain
}
我们可以来看一下 package.json文件的内容:
contributes声明文档上面已发,此处我是用的激活条件是onStartupFinished
// 此处我是用的激活条件是onStartupFinished,此激活事件会在VS Code
启动后的一段时间内发出,感兴趣的扩展将被激活。这与*激活事件类似,但不会减慢 VS Code 的启动速度。目前,此事件在所有*激活的扩展完成激活后发出。
{
"name": "start-plug",
"displayName": "yln-plug",
"description": "private plug",
"version": "0.0.1",
"engines": {
"vscode": "^1.60.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onStartupFinished"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "custom-code.clear",
"title": "code-clear"
}
],
"menus": {
"explorer/context": [
{
"command": "custom-code.clear",
"when": "filesExplorerFocus",
"group": "navigation@1"
}
]
}
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "node ./test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.60.0",
"@types/glob": "^7.1.4",
"@types/mocha": "^9.0.0",
"@types/node": "14.x",
"eslint": "^7.32.0",
"glob": "^7.1.7",
"mocha": "^9.1.1",
"typescript": "^4.4.3",
"@vscode/test-electron": "^1.6.2"
}
}
最终实现结果
按F5即可启动插件进行调试,然后在mac上用command+shift+P调出命令行,输入所配置的命令即可,因为我在package.json中配置了menu,所以对文件直接右键即可
在弹出框输入string后
至此,该插件开发已经告一段落,平时可以用于在项目上线前,将console.log清理一下,或是根据业务需要进行一些批量操作,当然,在这里面还有很多不完善的地方,比如:
- 暂时不支持/* */格式的注释匹配
- 只能以行为单位匹配每行代码的开头
- 没有一键恢复被注释代码的功能
- 我一直想做个更改日志,还没有行动...
- .......
怎么说呢,这只是第一个版本,如果后续业务需要,或者兴趣使然,我也会找个时间将它继续拓展、完善功能,因为最难的一步我们已经走完了↓
种一棵树最好的时间是十年前,其次是现在。
另外,在插件的开发过程中,借鉴了寒草大神的思路,加上自己的一些小想法,促进了这个插件以及这篇博文的诞生,也算是自己一次较为创新的尝试吧。在插件开发并测试完成的那一刻,我深吸一口气,你看,空气中都弥漫着成就感。
特别鸣谢:寒草:「教你用十分钟开发一款提升工作体验的vscode插件🌿 」console, debugger一键删除|自定义代码模板