编写你的第一款VSCode插件
锋利的VSCode
工欲善其事,必先利其器。对于一名程序猿来说,好的编辑器能够大大提高写代码的效率。VSCode作为微软主推的开源跨平台编辑器,是前端开发的利器,它拥有各种丰富的插件,更是使得其如虎添翼。
但是插件市场上的插件都是面向大家普适的需求。如何去定制一款专属于自己的插件,在特殊场景下提高开发效率,减少时间成本?希望下面的内容能够有一些帮助。
HelloWolrd
首先,你需要安装Node.js以及NPM,这里推荐使用NVM进行安装。方便以后Node的版本切换。
接下来使用NPM安装Yeoman和 VSCode Extention Generator
npm install -g yo generator-code
Yeoman 是一个代码生成器,能帮你创建一个新插件的骨架模版。官网链接戳我⬅️
generator-code 是VSCode团队编写的VSCode插件模版。
运行以下命令:
yo code
选择你想创建脚手架类型(建议选择TypeScript,原因稍后再说),输入其他项目信息。等待安装……
你的第一个VSCode插件已经完成了!来看看效果:
cd hello-code
code .
如果提示没有code命令,你需要在~/.zshrc中加入以下内容:
code () {
if [[ $# = 0 ]]
then
open -a "Visual Studio Code"
else
[[ $1 = /* ]] && F="$1" || F="$PWD/${1#./}"
open -a "Visual Studio Code" --args "$F"
fi
}
source ~/.zshrc使之生效。
打开项目之后,点击F5或者Debug按钮。会弹出来一个新的VSCode窗口。 这个新的VS Code实例会运行在特殊环境中(Extension Development Host),专门用于插件的调试。
在新窗口的命令面板(⇧⌘P)中输入Hello World命令。右下角就会出现Hello World的弹窗哦。
恭喜你,已经运行起了你的第一个VSCode插件。
插件的目录结构
下面,我们将要打造专属于自己的VSCode插件。首先来了解一下VSCode插件的目录结构:
.
├── .gitignore
├── .vscode // VS Code 文件
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .vscodeignore // 发布插件时忽略的文件
├── README.md
├── src
│ └── extension.ts // 插件的入口(源文件)
├── test // 测试文件夹
│ ├── extension.test.ts // extension.test.js, 如果是 JavaScript 插件的话
│ └── index.ts // index.js, 如果是 JavaScript 插件的话
├── node_modules
│ ├── vscode // 包含了vscode插件开发时的类型定义文件
│ └── typescript // typescript的编译器 (TypeScript only)
├── out // 编译出口 (TypeScript only)
│ ├── extension.js // 插件入口
│ ├── extension.js.map
│ └── test
│ ├── extension.test.js
│ ├── extension.test.js.map
│ ├── index.js
│ └── index.js.map
├── package.json // 插件清单
├── tsconfig.json // jsconfig.json, 如果是 JavaScript 插件的话
└── vsc-extension-quickstart.md // 快速上手插件开发
插件的入口就在package.json当中。其中最重要的部分如下:
"contributes": {
"commands": [{
"command": "extension.sayHello",
"title": "Hello World"
}]
}
它为命令面板定义了一个叫做Hello world的入口,会调用'extension.sayHello'。
在src/extension.ts文件下,存放着这个sayHello函数的定义。
所以我们转到src/extension.ts文件下。
import * as vscode from 'vscode';
// vscode在开启时调用activate函数
export function activate(context: vscode.ExtensionContext) {
// 用console输出诊断信息(console.log)和错误(console.error)
// 下面的代码只会执行一次
console.log('Congratulations, your extension "my-first-extension" is now active!');
// 入口命令已经在package.json文件中定义好了,现在调用registerCommand方法
// registerCommand中的参数必须与package.json中的command保持一致,用于在VSCode环境中注册这个命令函数
let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
// 把你的代码写在这里,每次命令执行时都会调用这里的代码
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
开始定制自己的插件
在维护旧代码时,经常会有这样的状况:
由于开启了ESLint的Strings must use singlequote规则,JS代码中必须使用单引号。作为一名代码洁癖症患者,满屏飘红坚决不能忍。我们可以考虑使用VSCode的全局替换功能,把js文件中的双引号替换成单引号。
但是,React项目中,JSX语法中className却又要使用双引号。这样我们又得去一个个修改JSX得引号。
很麻烦对吧?如何写一款插件来帮我们解决这个问题呢?
在启动模版上面改动,只需要几行代码:
let disposable = vscode.commands.registerCommand('extension.replaceQuotationMarks', () => {
// 获取当前激活的编辑页面
let editor = vscode.window.activeTextEditor;
if (!editor) {
return; // 没有打开编辑器
}
// 获取选中对象
let selection = editor.selection;
// 获取选中的文本
let text = editor.document.getText(selection);
// 替换文本,注意引号需要转义,并且需要开启RegExp的全局搜索标志,不然只会替换第一个引号
let replacedText = text.replace(/\"/g, '\'');
editor.edit( editBuilder => {
// 替换内容,替换的位置可以有选中的对象获取
editBuilder.replace(selection, replacedText);
});
// 给用户一个消息提示框
vscode.window.showInformationMessage("Replace Done!");
});
VSCode 提供了一系列的API,可以对文本内容进行操作和更改。详细的文档可以查看这里:https://code.visualstudio.com/api/references/vscode-api
可以看到,VSCode 的文档十分完善,由于VSCode使用TypeScript,这些API都标注了传入和返回值的类型,并且通过超链接进行关联。使用TypeScript编写插件时,也会有详尽的代码提示。这也是为什么推荐使用TypeScript编写插件。
编写完成,我们来测试一下效果:用鼠标选中需要替换的内容,呼出命令面板,输入Replace命令。替换瞬间完成。
但这样感觉还是很麻烦,下面有几个优化方案:
自定义快捷键
VSCode支持插件自定义快捷键,并且只需要在package.json中进行简单的配置:
"contributes": {
"keybindings": [{
// 指定快捷键执行的操作
"command": "extension.replaceQuotationMarks",
// windows下快捷键
"key": "f2",
// mac下快捷键
"mac": "f2",
// 快捷键何时生效
"when": "editorTextFocus"
}]
}
选中后按下f2,插件将会立即被执行!
使用正则自动替换
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('extension.replaceQuotationMarks', () => {
let editor = vscode.window.activeTextEditor;
if (!editor) {
return; // 没有打开的编辑器
}
let text = editor.document.getText();
let replacedText = text.replace(/\"/g, '\'');
replacedText = replacedText.replace(/className\s*=\s*\'(.+)\'/g, 'className=\"$1\"');
const fullRange = new vscode.Range(
editor.document.positionAt(0),
editor.document.positionAt(text.length - 1)
);
editor.edit( editBuilder => {
editBuilder.replace(fullRange , replacedText);
});
// 给用户一个消息提示框
vscode.window.showInformationMessage("Replace Done!");
});
context.subscriptions.push(disposable);
// 立即执行!
vscode.commands.executeCommand('extension.replaceQuotationMarks');
}
在package.json中配置打开javascript文件,并且该文件存在react内容时,自动执行该插件。
"activationEvents": [
"onLanguage:javascriptreact"
],
在本地安装插件
到目前为止,你的插件都还跑在插件开发模式中,要想让你的插件在正常的VS Code中运行起来将你的插件复制到.vscode/extensions目录下。
一些思路?
这里已我个人的场景为例,来探索VSCode插件有哪些潜在的新玩法~
我平时在写博客文章时,习惯先用VSCode在本地写好,再去博客上面发布,所以,发布一片博客,需要这么几步:
- 打开VSCode,新建markdown文档。
- 写作✏️
- 将所有的图片上传至图床
- 替换本地博客的文档的图片路径为图床路径
- 打开浏览器
- 打开我的博客地址
- 复制、粘贴内容
- 点击发布
太繁琐了,简直影响写博客的热情有木有!!!
那么,就编写一款插件,在VSCode中实现一键发布文章。
主要的思路如下:
- 先用VSCode的API获取所有文本内容
- 使用正则表达式筛选图片,并上传图片到图床
- 使用图床的图片地址替换本地的地址
- 使用博客的API发布
在这里强烈安利Ghost博客,使用Node打造的Ghost博客,相较于年迈的Wordpress,拥有着更好看的主题,更小的内存占用(使用SQlite),还提供官方的API可供调用。
整体代码如下:
class Publisher {
// 文本替换,将replace变为async
private asyncReplace(str: string, re: RegExp, replacer: Function) {
return Promise.resolve().then(() => {
const fns: any[] = [];
str.replace(re, (m, ...args) => {
fns.push(replacer(m, ...args));
return m;
});
return Promise.all(fns).then(replacements => {
return str.replace(re, () => replacements.shift());
});
});
}
// 发布图片到图床,这里使用sm.ms图床
private async sendImage(path: string): Promise<string> {
const form = new formdata();
if (!fs.existsSync(path)) {
return '替换错误,请检查该文件是否存在';
}
const stream = fs.createReadStream(path);
form.append('smfile', stream);
const result = await fetch('https://sm.ms/api/upload', {
method: 'POST',
body: form
})
.then(
(res: any) => res.json()
)
.then(
(json: any) => {
vscode.window.showInformationMessage(`${json.data.url}: ok`);
return json.data.url;
});
return result;
}
public publish() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
vscode.window.showInformationMessage(`上传开始……`);
const currentlyOpenTabfilePath = editor.document.fileName;
const dirName = path.dirname(currentlyOpenTabfilePath);
const fileName = path.basename(currentlyOpenTabfilePath, '.md');
let text = editor.document.getText();
// 筛选图片地址的正则表达式
const imageReg = /!\[.*?\]\((.*?\.(png|jpg|webp|gif))\)/g;
this.asyncReplace(text, imageReg, async (_: any, fileName: string) => {
const imagePath = path.join(dirName, fileName);
const result = await this.sendImage(imagePath);
return `![](${result})`;
}).then(text => {
// 发布博客使用Ghost博客提供的API
const api = new GhostAdminAPI({
url: 'youblogurl',
key: 'yourkey',
version: 'v2'
});
const html = md.render(text);
console.log(html);
api.posts.add(
{ title: fileName, html ,status: "published"},
{ source: 'html' } // Tell the API to use HTML as the content source,
)
.then((res:any) => vscode.window.showInformationMessage('文章发布成功!!!'));
});
}
}
最后让我们来看下效果如何:
短短100来行代码,我们就实现了这个功能。赶快来尝试开发专属于你自己的VSCode插件吧!