vscode插件code-template实现
# vscode插件code-template实现
# 插件功能
- 读取已经持久化的代码插入文件或者插入代码块。
- 保存代码块或者文件以便之后使用
# 实现流程
- 安装依赖
npm install -g yo
// 可能会出现Error: EACCES: permission denied, open '/Users/sherry/Library/Preferences/insight-nodejs/insight-yo.json.1293917385'
// 解决方案 sudo chown -R 用户名 /Users/用户名/Library/Preferences/insight-nodejs
npm install -g generator-code
1
2
3
4
5
2
3
4
5
- 生成初始化代码
yo code
// 第一个问题选择New Extension (JavaScript)
// 后面无特殊需求可以一直下一步
1
2
3
2
3
项目结构 ├── CHANGELOG.md ├── README.md ├── out ├── package.json // 除了基本的依赖信息, 还包含插件配置信息。后续会详细介绍 ├── src ├── extension.ts // 插件入口文件 ├── tsconfig.json ├── vsc-extension-quickstart.md └── yarn.lock
插件截图

插件package.json配置详解
{
"activationEvents": [
"onCommand:pluginName.commandName"
// 防止与其他插件冲突采取命名空间.指令名称的方式
// :前面的是指令的类型,不同的功能需要不同的类型。常见的有onCommand(触发式指令)、onView(视图指令)
/** 所有的指令都需要在这里声明。否则
* vscode.commands.registerCommand('pluginName.commandName', (node) => {})
* 注册的指令不会执行
**/
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "pluginName.commandName",
"title": "标题"
},
/** 针对activationEvents声明的指令添加描述
* 通常有指令的标题,图标等...
**/
],
"viewsContainers": {
"activitybar": [
{
"id": "viewID",
"title": "",
"icon": ""
}
]
// 注册视图入口按钮。通过id: viewID和views.viewID给视图容器绑定多个视图
},
"views": {
"viewID": [
{
"id": "pluginName.commandName", // 这里的id需要与activationEvents声明的onView: pluginName.commandName对映
"name": ""
},
]
},
"viewsWelcome": [
{
"view": "pluginName.commandName",
"contents": "当视图内容为空的时候显示的内容"
},
],
"menus": {
"editor/context": [],
"view/title": [
{
"command": "pluginName.commandName",
"when": ""
},
// 给视图声明按钮。command表示点击按钮触发的指令,when控制按钮的显示/隐藏
],
"explorer/context": [
{
"command": "pluginName.commandName",
"when": ""
}
// 给explorer添加菜单。command表示点击菜单触发的指令,when控制按钮的显示/隐藏
],
"view/item/context": [
{
"command": "pluginName.commandName",
"when": ""
}
// 给视图树添加右击菜单。。command表示点击菜单触发的指令,when控制按钮的显示/隐藏
]
},
"configuration": [
{
"title": "code-template",
"properties": {
// 初始化声明插件需要用到的属性。可以通过scode.workspace.getConfiguration('pluginName')获取/设置属性
"pluginName.port": {
"type": "number",
"default": 22,
"scope": "application",
"description": "端口"
},
}
}
]
},
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- 具体实现之本地视图-通过设置本地路径读取目录结构及文件 6.1 设置本地路径
vscode.window.showInputBox({
placeHolder: '请输入本地绝对地址',
value: getLocalPath(),
validateInput: s => validatePath(s)
});
1
2
3
4
5
2
3
4
5
6.2 在extension.ts中注册视图
vscode.window.registerTreeDataProvider(
'pluginName.commandName', // 对映pageage.json中"onView:pluginName.commandName"
xxxTreeDataProvider
);
1
2
3
4
2
3
4
6.3 xxxTreeDataProvider是视图的具体实现
export class LocalCodeTreeDataProvider implements vscode.TreeDataProvider<CodeTree>{
onDidChangeTreeDataEvent: vscode.EventEmitter<unknown>;
onDidChangeTreeData: any;
constructor() {
this.onDidChangeTreeDataEvent = new vscode.EventEmitter();
this.onDidChangeTreeData = this.onDidChangeTreeDataEvent.event;
}
async refresh() {
// 刷新树
this.onDidChangeTreeDataEvent.fire(null);
}
getTreeItem(element: CodeTree): vscode.TreeItem {
// 可以基于element数据为节点添加command
return {
...element,
command: {
title: "",
command: "pluginName.commandName",
arguments: [element],
},
};
}
getChildren(element: CodeTree): Thenable<CodeTree[]> {
return Promise.reslove(getChildByPath())
}
private async getChildByPath(dirPath: string): Promise<CodeTree[]> {
// 通过node fs模块根据文件夹路径获取子节点
}
}
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
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
- 具体实现之本地视图-通过设置远程服务器和路径读取目录结构及文件 该实现和第6点实现基本一致。只在设置服务器信息之后利用ssh2 创建了一个sftp的链接。通过sftp读取目录结构及文件 7.1 设置服务器信息
export class ConfigLongRangeProvider extends event.EventEmitter {
constructor() {
super();
}
async inputLongRangeInfo() {
const longRangeIP = await vscode.window.showInputBox({
placeHolder: '请输入服务器IP',
value: getLongRangeIP(),
validateInput: s => validateIP(s)
});
vscode.window.showInformationMessage('服务器信息配置完成!');
this.emit("rangePathConfigComplate");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
7.2 设置完成服务器信息后创建sftp链接
const { Client } = require('ssh2');
import * as vscode from 'vscode';
class ConnectLongRange {
private conn: any | null;
constructor() {
this.conn = null;
};
connect(config: any, callback: Function) {
const {
longRangeIP,
longRangePass,
longRangePort,
longRangeUser
} = config;
const conn = new Client();
conn.on('ready', async () => {
conn.sftp((err: any, sftp: any) => {
if (err) {
vscode.window.showErrorMessage(err.toString());
return;
}
this.conn = conn;
callback(sftp);
});
})
.connect({
host: longRangeIP,
port: longRangePort,
username: longRangeUser,
password: longRangePass
});
}
destroy() {
if (this.conn) {
this.conn.end();
}
}
}
export default ConnectLongRange;
configLongRangeProvider.on("rangePathConfigComplate", (info: any) => {
connectLongRange.destroy();
connectLongRange.connect(info, (sftp: any) => {
longRangeCodeTreeDataProvider.init(sftp);
});
});
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
35
36
37
38
39
40
41
42
43
44
45
46
47
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
35
36
37
38
39
40
41
42
43
44
45
46
47
7.3 利用sftp实现getChildByPath方法
private async getChildByPath(dirPath: string): Promise<CodeTree[]> {
return new Promise((resolve: any, reject: any) => {
this.sftp.readdir(dirPath, (err: any, list: any) => {
resolve(fileList);
});
});
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 上面介绍的是vscode视图展示的实现,下面给视图添加相应的功能。

8.1 package.json配置
"menus": {
"editor/context": [],
"view/title": [ // 视图右上角按钮
{
"command": "",
"when": "",
"group": ""
}
],
"view/item/context": [ // 视图右击菜单
{
"command": "",
"group": "",
"when": ""
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
其他的步骤和视图的实现基本一致(注册指令,编写具体实现逻辑代码)。
- 编辑区域菜单实现
9.1 package.json配置
"submenus": [
{
"id": "相同的子菜单名称",
"label": "xxx"
}
]
"menus": {
"editor/context": [
{
"submenu": "相同的子菜单名称",
"when": "editorFocus"
}
],
"相同的子菜单名称": [ // 子菜单列表
{
"command": "xxx"
},
{
"command": "xxx"
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
9.2 读取代码块功能实现

export const getCodeBlockProvider = async () => {
const quickPick = vscode.window.createQuickPick();
quickPick.placeholder = "placeholder";
quickPick.items = [] // 包含label detail等字段的数组,按照自己喜欢的持久化方式读取到的数据
quickPick.onDidChangeSelection(selection => {
const { detail } = selection[0]; // 选中的item
let editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
// 把代码块插入到光标的位置
let snippet = new vscode.SnippetString(detail);
editor.insertSnippet(snippet);
});
quickPick.show();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9.3 保存代码块功能
export const saveCodeBlockProvider = async () => {
let editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const {
selection
} = editor;
const detail = editor.document.getText(selection); // 获取到选中的代码块
const label: any = await vscode.window.showInputBox({ // 输入代码块名称
placeHolder: '请输入代码块名称',
});
// 最后持久化
...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15