vscode 在近几年颇受开发人员喜爱,这得益于它的轻量性和非常的插件市场。你可以在插件市场找到大量功能强大的插件帮助提高编码效率。但是(总有那么个但是?)在实际的开发中,还是会有一些特殊的定制需求找不到特别合适的插件,此时我们就可以撸起袖子,自己造一个轮子。
插件功能
vscode 赋予了插件 非常多的能力,让它们几乎可以在方方面面对 vscode 进行武装升级,插件的主要能力包括:
- 提供新主题(包括编辑器配色,文件图标等等)
- 支持新编程语言的语法(提供高亮,代码补全,错误检查等等功能)
- 提供程序调试功能
- 自定义命令,快捷键,菜单
- 自定义工作区 Webview
开发怎样的插件?
在笔者写单元测试时,常常会遇到以下问题:
- 需要在源文件和对应的单测文件来回切换,而 vscode 并不具有 webstorm 那样的源文件测试文件快速切换的功能。使得整个切换体验不佳
- 为源文件创建单测文件时,如果文原则是将源文件文件夹和测试文件文件夹分开时,往往需要递归创建测试文件。比如
src/module/child/a.ts
,需要层层创建tests/module/child/a.test.ts
文件,这尤为麻烦?
基于这些痛点,笔者开发一款 vscode 插件,用于在源文件测试文件之间快速切换,以及快速创建单测文件。插件效果:
插件功能:
- 查找源文件对应的测试文件。如果只找到一个可能的文件,则直接切换;如果找到多个,则显示下拉选择框供用户选择
- 快速创建单测文件。支持两种模式,将单测文件和源文件放在一起,或者将所有单测文件单独放在特定文件夹中,并按源文件目录结构组织
- 提供丰富的配置能力,用户可自定义单测文件后缀规范
你可以点击 此链接,或者在 vscode 插件市场搜索** Find Test File **体验:
环境准备
- nodejs,建议稳定版
- git
首先安装 yeoman 脚手架工具,以及 vscode 官方提供的脚手架工具:
npm install -g yo generator-code
接下来执行以下命令交互式创建插件项目:
yo code
项目结构
现在,插件项目已经创建完成,项目目录结构如下:
.
├── node_modules
├── CHANGELOG.md
├── README.md
├── package.json
├── src
│ ├── extension.ts
│ └── test
│ ├── runTest.ts
│ └── suite
│ ├── extension.test.ts
│ └── index.ts
├── tsconfig.json
├── vsc-extension-quickstart.md
├──.vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
└── yarn.lock
其中:
.vscode
文件夹存放程序运行和调试相关命令vsc-extension-quickstart.md
文档介绍了插件的入门知识和运行方式,是项目创建后需要首先关注的点src
文件夹中存放插件源代码,需要重点关注这一块(src/test
存放插件 e2e 测试代码,暂时不用关注)package.json
除了包含常规 node 项目配置属性外,还包含插件配置属性,也需要重点关注
默认情况下,项目已经配置好运行调试参数,按下F5
即可运行插件(其实就是运行.vscode/launch.json
中的Run Extension
命令):
我们先从package.json
入手,插件配置相关代码片段如下:
{
"name": "test",
"displayName": "test",
"engines": {
"vscode": "^1.57.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:test.helloWorld"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "test.helloWorld",
"title": "Hello World"
}
]
}
}
name
和displayName
不必多提,是插件的名字和显示名字(即在插件市场的名字)engines
中vscode
版本是指该插件所兼容的 vscode 版本categories
表示插件的分类,但是它和keywords
有所不同,只能从选择 官方指定的列表 中选择main
表示插件程序入口activationEvents
表示何时 激活插件contributes
表示如何 定制插件功能
再来看看src/extension.ts
中的内容:
import * as vscode from "vscode";
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "test" is now active!');
// 自定义命令
let disposable = vscode.commands.registerCommand("test.helloWorld", () => {
// 触发弹出框
vscode.window.showInformationMessage("Hello World from test!");
});
// 将命令注册到执行上下文中
context.subscriptions.push(disposable);
}
export function deactivate() {}
extension.ts
暴露两个方法:
active
:在插件激活时运行,通常在其中注册自定义命令deactive
:在插件禁用时运行
这两部分就是开发插件的核心,我们将整个流程拉通一下:
撸起袖子开始干
插件配置
首先在package.json
中声明插件的命令,以及插件激活的条件:
{
"activationEvents": [
"onCommand:find-test-file.jumpToTest",
"onCommand:find-test-file.createTestFile"
],
"contributes": {
"commands": [
{
"command": "find-test-file.jumpToTest",
"title": "Jump To Source/Test File",
"category": "Find Test File",
"icon": "$(preferences-open-settings)",
"enablement": "resourceExtname =~ /.[jt]sx?/"
},
{
"command": "find-test-file.createTestFile",
"title": "Create Test File For Current",
"category": "Find Test File",
"icon": "$(file-add)",
"enablement": "resourceExtname =~ /.[jt]sx?/"
}
]
}
}
在commands
配置项中,添加jumpToTest
和createTestFile
两个命令,除了常规command
和title
配置外,还多出了几项配置(更多commands
配置请查阅 官方文档):
category
:用于在命令面板中将命令分组
icon
:命令所对应图标(后续menus
配置才会用到),格式为$(name)
,vscode 自带 一套图标enablement
:表明何时在命令面板中显示命令,上述配置表明只有在当前打开的文件是ts
(tsx
),js
(jsx
)时,才能在命令面板中显示
activationEvents
保持是在执行对应命令时再激活插件(更多激活条件请查阅 官方文档)。图省事的话,也可以直接设置为*
,即在 vscode 启动后就激活,毕竟能用就行,是吧。
当然,保持在必要时候才激活插件是一个更好的实践。
通过命令面板选择命令执行总是不够方便,快捷键才是王道。可以通过keybindings
属性增加快捷键配置(更多keybindings
配置请查阅 官方文档):
{
"contributes": {
"keybindings": [
{
"command": "find-test-file.jumpToTest",
"key": "ctrl+shift+t",
"mac": "cmd+shift+t",
"when": "resourceExtname =~ /.[jt]sx?/"
},
{
"command": "find-test-file.createTestFile",
"key": "ctrl+alt+t",
"mac": "cmd+alt+t",
"when": "resourceExtname =~ /.[jt]sx?/"
}
]
}
}
- Mac 和 Window 的快捷键有所不同,Mac 中的
cmd
键等同于 Window 的ctrl
,需要分别配置 when
表示快捷键什么时候启用,和command
的enablement
配置类似
既然有了快捷键,能不能有鼠标右键之类的快捷方式呢?能!通过menus
属性增加配置(更多menus
配置请查阅 官方文档):
{
"contributes": {
"menus": {
"editor/context": [
{
"command": "find-test-file.jumpToTest",
"group": "1_find-test-file",
"when": "resourceExtname =~ /.[jt]sx?/"
},
{
"command": "find-test-file.createTestFile",
"group": "1_find-test-file",
"when": "resourceExtname =~ /.[jt]sx?/"
}
],
"editor/title": [
{
"command": "find-test-file.jumpToTest",
"group": "navigation",
"when": "resourceExtname =~ /.[jt]sx?/"
},
{
"command": "find-test-file.createTestFile",
"group": "navigation",
"when": "resourceExtname =~ /.[jt]sx?/"
}
]
}
}
}
插件还需要提供用户配置的能力。比如:不同项目对测试文件的后缀命名是不同的,有些是a.test.ts
,有些是a.spect.ts
。这部分能力由configuration
配置提供:
{
"contributes": {
"configuration": {
"title": "Find Test File",
"properties": {
"findTestFile.basic.testSuffix": {
"type": "string",
"default": "\\.(spec|test)",
"markdownDescription": "xxxxxx"
},
"findTestFile.basic.excludeFolder": {
"type": "array",
"default": [
"node_modules"
],
"items": {
"type": "string"
},
"uniqueItems": true,
"markdownDescription": "xxxxxx"
},
"findTestFile.createIfNotFind.enable": {
"type": "boolean",
"default": false,
"markdownDescription": "xxxxxx"
},
"findTestFile.createIfNotFind.preferStructureMode": {
"type": "string",
"default": "separate",
"enum": [
"separate",
"unite"
],
"markdownEnumDescriptions": [
"xxxxxx",
"xxxxxx"
],
"markdownDescription": "xxxxxx"
},
"findTestFile.createIfNotFind.preferTestDirectory": {
"type": "object",
"default": {
"separate": "__tests__",
"unite": "__tests__"
},
"properties": {
"separate": {
"type": "string"
},
"unite": {
"type": "string"
}
},
"required": [
"separate",
"unite"
],
"additionalProperties": false,
"markdownDescription": "xxxxxx"
}
}
}
}
}
在设置页面的显示结果如下:
配置项可以是数组,字符串,布尔值,枚举,对象,具体配置参见 官方文档。
插件代码
src/extension.ts
主要功能:
- 注册插件两个命令
- 判断 vscode 当前打开的文件以及当前工作目录,只有在打开了特定的文件时,才需要执行业务逻辑
import vscode from "vscode";
function doPrepare() {
// 获取当前编辑的文件对象,如果没有打开任何文件,则为空
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
return;
}
// 获取编辑文件的文件名
const activeFilePath = activeEditor.document.fileName;
// 业务代码,不用关心
const result = createValidFileReg().exec(getBasename(activeFilePath));
if (!result) {
// 弹出警告信息
vscode.window.showWarningMessage(INVALID_FILE_WARNING_MESSAGE);
return;
}
// 获取当前 vscode 所打开的工作区目录地址
const workspaceFilePath = vscode.workspace.getWorkspaceFolder(
activeEditor.document.uri
)!.uri.fsPath;
// 省略业务代码
}
export function activate(context: vscode.ExtensionContext) {
// 注册 jumpToTest 命令
const jumpToTestCommand = vscode.commands.registerCommand(
"find-test-file.jumpToTest",
() => {
// 省略业务代码
}
);
// 注册 createTestFile 命令
const createTestFileCommand = vscode.commands.registerCommand(
"find-test-file.createTestFile",
() => {
// 省略业务代码
}
);
// 将命令注册到执行上下文中
context.subscriptions.push(jumpToTestCommand, createTestFileCommand);
}
export function deactivate() {}
src/config.ts
主要功能:
- 获取插件配置信息
// src/config.js
import vscode from "vscode";
const getCfgByKey = <K1 extends keyof Config, K2 extends keyof Config[K1]>(
primary: K1,
key: K2
) => {
// 结合配置,获取用户自定义的配置,获取所有以 findTestFile 开头的配置
// 前面 configuration 配置中的 properties 各个属性名就是配置的路径, 如 findTestFile.basic.testSuffix
const config = vscode.workspace.getConfiguration("findTestFile");
// 获取具体某一项配置信息 如 basic.testSuffix
const cfg = config.get<Config[K1][K2]>(`${primary}.${key}`)!;
// 通过 inspect 方法获取默认配置,用于垫底
const defaultCfg = config.inspect<Config[K1][K2]>(`${primary}.${key}`)!
.defaultValue!;
return [cfg, defaultCfg];
};
// 省略业务代码
src/jumpToFile.ts
主要功能:
- 如果找到一个可能的目标文件,直接切换到目标文件
- 如果找到多个可能的目标文件,需要弹出选择框供用户选择
import vscode, { QuickPickItem } from "vscode";
export const openFile = async (filePath: string) => {
// 打开对应地址的文件,注意这个操作是异步的
const document = await vscode.workspace.openTextDocument(filePath);
// 将当前窗口切换到该文件,注意这个操作是也异步的
await vscode.window.showTextDocument(document);
};
export const jumpToPossibleFiles = async (
current: string,
relativeFiles: string[],
isJumpToTestFile: boolean,
createTestFileOption: CreateTestFileOption
) => {
// 显示选择框,获取用户选择结果,注意这是异步操作
const select = await vscode.window.showQuickPick(pickItems);
// 取消选择时,返回空
if (!select) {
return;
}
// 省略业务代码
};
src/createTestFile.ts
主要功能:
- 如果用户对搜索结果不满意或者未找到测试文件,需要弹出输入框,帮助用户快速新建的测试文件
import vscode, { QuickPickItem } from "vscode";
export const createTestFile = async (
{ basename, ext, parent, root }: CreateTestFileOption,
manualCreate: boolean = true
) => {
// 显示输入框
const userInputPath = await vscode.window.showInputBox({
prompt: NEW_TEST_FILE_PROMPT,
value: filePath,
valueSelection: [filePath.length, filePath.length],
// 验证输入内容是否合法,返回 null 表示验证成功
validateInput(value) {
return isValidFile(basename, ext, value, true)
? null
: INVALID_TEST_FILE_WARNING_MESSAGE;
},
});
// 取消输入,返回结果为空
if (!userInputPath) {
return;
}
};
更多 vscode api 使用参见 官方文档。
发布插件
啪的一下就写完了代码,很快啊。接下来需要将插件打包发布。
打包插件需要安装vsce
库:
npm i -g vsce
然后执行打包命令:
vsce package
打包完成后,会生成find-test-file-x.x.x.vsix
文件。此时可以通过插件市场直接安装插件,验证效果:
验证完功能的正确性后,就可以真正发布到插件市场:
- vscode 的插件市场基于微软的 Azure DevOps,插件的身份验证、托管和管理都是在这里。所以发布前,首先得注册 Azure Devops 账号,就像 npm 一样:
- 注册完成后,创建 Personal Access Token:
复制这个创建好的 token,后续要用。
- 接下来还需要在插件市场中 新建一个 publisher 用来发布插件:
- 登录 vsce 账户信息,使用之前注册好的 publisher 以及 token:
vsce login publisher-name
别忘了在package.json
中添加 publisher,icon,categories 等相关信息:
{
"publisher": "xxxx",
"icon": "xxxx/icon.png",
"categories": [
"Other"
],
}
- 登录完成后,就可以愉快的发布啦:
vsce publish
发布成功后,就可以在插件市场搜索到插件了(通常需要等几分钟),也可以在 网页管理端 查看插件的使用情况。
总结
至此就是笔者在开发插件中所运用到的 vscode 相关知识,希望本文能对你有所帮助,也欢迎大家使用笔者开发的插件和提 issue。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!