第一个vscode插件

起因

为啥要大费周章来写个vscode插件呢,因为我发现在写blog的时候有时需要插入一些图片,然而有些图片尺寸大,部署到服务器上访问特别慢,so! 我花了一百多大洋购置了对象存储服务来优化图片访问速度。 但是,在md中用的都是本地路径,一个个上传获取在线地址也太麻烦了! 于是我就想能不能搞个插件在md中插入图片的时候自动上传到对象存储服务上,同时自动回填图片的url,这样就可以在blog中直接使用了,大大提高效率。 废话不多说,直接开始!

脚手架安装

1
npm install -g yo generator-code

这个脚手架会生成一个可以立马开发的项目。运行生成器,然后填好下列字段:

1
2
3
4
5
6
7
8
9
10
11
12
yo code

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? md-cos-tool
? What's the identifier of your extension? md-cos-tool
? What's the description of your extension? a cos tool for md
? Initialize a git repository? Yes
? Which bundler to use? unbundled
? Which package manager to use? npm

code ./md-cos-tool

接着就是AI表演时间,给我生成!!!

extension.ts

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import * as vscode from "vscode";
// 使用 require 导入 COS SDK,因为该模块没有提供 TypeScript 类型定义
const COS = require("cos-nodejs-sdk-v5");
import * as path from "path";
import * as fs from "fs";

export function activate(context: vscode.ExtensionContext) {
console.log('Extension "md-cos-tool" is now active!');

const hoverProvider = {
provideHover(document: vscode.TextDocument, position: vscode.Position) {
const linkPattern = /\[(.*?)\]\((.*?)\)/; // 匹配Markdown链接,中括号内容可为空

const line = document.lineAt(position.line).text;
const match = line.match(linkPattern);
const range = document.getWordRangeAtPosition(position, linkPattern);

console.log(
"Matched range:",
range?.start.line,
range?.start.character,
range?.end.character
);

if (range && match) {
const command = "md-cos-tool.uploadToCOS";
const filePath = match[2]; // 获取第二个捕获组,即括号内的内容
const args = JSON.stringify({ filePath });
console.log("args", args);

const content = new vscode.MarkdownString(
`[上传到腾讯云](command:${command}?${args})`
);
content.isTrusted = true;
return new vscode.Hover(content);
}
},
};

const uploadCommand = vscode.commands.registerCommand(
"md-cos-tool.uploadToCOS",
async (args) => {
try {
// 获取配置
const config = vscode.workspace.getConfiguration("md-cos-tool");
const secretId = config.get<string>("secretId");
const secretKey = config.get<string>("secretKey");
const bucket = config.get<string>("bucket");
const region = config.get<string>("region");

// 检查配置是否完整
if (!secretId || !secretKey || !bucket || !region) {
const result = await vscode.window.showErrorMessage(
"请先配置腾讯云COS参数",
"去配置"
);
if (result === "去配置") {
await vscode.commands.executeCommand(
"workbench.action.openSettings",
"md-cos-tool"
);
}
return;
}

const params = args;
if (!params || !params.filePath) {
throw new Error("无效的文件路径参数");
}
const markdownLink = params.filePath;

// 获取文件路径
const relativePath = markdownLink; // 使用第二个捕获组,它包含了括号中的文件路径
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
throw new Error("没有打开的文件");
}

// 获取当前编辑文件所在目录
const currentDir = path.dirname(activeEditor.document.uri.fsPath);
const absolutePath = path.resolve(currentDir, relativePath);

// 验证文件是否存在
if (!fs.existsSync(absolutePath)) {
throw new Error(`文件不存在: ${absolutePath}`);
}

// 初始化COS实例
const cos = new COS({
SecretId: secretId,
SecretKey: secretKey,
});

// 创建进度提示
const progressOptions = {
location: vscode.ProgressLocation.Notification,
title: "正在上传到COS",
cancellable: true,
};

const res: { statusCode: number; Location: string } =
await vscode.window.withProgress(
progressOptions,
async (progress, token) => {
return new Promise((resolve, reject) => {
const fileName = path.basename(absolutePath);
const key = `${new Date().getTime()}_${fileName}`;

cos.putObject(
{
Bucket: bucket,
Region: region,
Key: key,
Body: fs.createReadStream(absolutePath),
onProgress: (progressData: { percent: number }) => {
if (token.isCancellationRequested) {
reject(new Error("上传已取消"));
return;
}
const percent = Math.round(progressData.percent * 100);
progress.report({ message: `${percent}%` });
},
},
(err: Error, data: any) => {
if (err) {
reject(err);
return;
}
progress.report({ message: "上传完成!" });
resolve(data);
}
);
});
}
);

console.log("ret ", res);
if (res.statusCode === 200) {
const url = `https://${res.Location}`;
// 获取当前编辑器的文档内容
const editor = vscode.window.activeTextEditor;
if (editor) {
const document = editor.document;
const text = document.getText();
const position = editor.selection.active;
const line = document.lineAt(position.line).text;
const linkPattern = /\[(.*?)\]\((.*?)\)/;
const match = line.match(linkPattern);

if (match) {
const oldPath = match[2];
const newText = line.replace(oldPath, url);
editor.edit((editBuilder) => {
editBuilder.replace(
new vscode.Range(
position.line,
0,
position.line,
line.length
),
newText
);
});
}
}
} else {
throw new Error("上传失败! StatusCode = " + res.statusCode);
}

vscode.window.showInformationMessage("文件上传成功!");
} catch (error) {
vscode.window.showErrorMessage(`上传失败: ${(error as Error).message}`);
}
}
);

context.subscriptions.push(
vscode.languages.registerHoverProvider("markdown", hoverProvider),
uploadCommand
);
}

export function deactivate() {}

package.json

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
{
"name": "md-cos-tool",
"displayName": "md-cos-tool",
"description": "a cos tool for md",
"version": "0.0.1",
"engines": {
"vscode": "^1.91.0"
},
"categories": [
"Other"
],
"activationEvents": ["*"],
"main": "./out/extension.js",
"contributes": {
"configuration": {
"title": "md-cos-tool 配置",
"properties": {
"md-cos-tool.secretId": {
"type": "string",
"description": "腾讯云 SecretId"
},
"md-cos-tool.secretKey": {
"type": "string",
"description": "腾讯云 SecretKey"
},
"md-cos-tool.bucket": {
"type": "string",
"description": "COS 存储桶名称(格式:example-1250000000)"
},
"md-cos-tool.region": {
"type": "string",
"description": "存储桶地域(如 ap-shanghai)"
}
}
},
"commands": [
{
"command": "md-cos-tool.uploadToCOS",
"title": "Upload To COS"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src",
"test": "vscode-test"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.97.0",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"eslint": "^9.19.0",
"typescript": "^5.7.3"
},
"dependencies": {
"cos-nodejs-sdk-v5": "^2.14.6"
}
}

附上一个测试效果

打包发布插件

打包插件

安装打包工具

1
npm install -g @vscode/vsce --force

打包

1
vsce package