惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Martin Fowler
Martin Fowler
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
O
OpenAI News
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LangChain Blog
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
The Register - Security
The Register - Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
C
Cisco Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
S
Security Affairs
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
T
Tor Project blog
大猫的无限游戏
大猫的无限游戏
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 【当耐特】
V
V2EX
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
雷峰网
雷峰网
WordPress大学
WordPress大学
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 最新话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives

如鱼饮水

2026 年 IMO 的几何题的解答 本地部署Gemma4-26B-A4B模型 不间断空格的处理方法 一个VSCode插件:支持TikZ预览 在Neovim中支持LuaLaTeX高亮 2026年全国I卷压轴题的解答 2026年北京高联预赛的几何题的解答 垂足三角形、等角共轭点与「六点圆」 本地部署 Hy-MT2 翻译模型 Vibe Coding一个Python版本的pdf2svg 2026 年 USAMO 的几何题的解答(一) 使用天地图API进行坐标反查 尝试使用 DeepSeek-OCR 2 尝试使用MinerU 使用VSCode编辑Markdown的几个常用设置 在WSL上挂载U盘 在使用LuaLaTeX时控制中英文字符的间距 使用vLLM框架加速PaddleOCR-VL 关于PaddleOCR-VL和PaddleOCR对数学类书籍识别的对比 尝试使用PaddleOCR-VL 关于DeepSeek-OCR和PaddleOCR对数学类书籍识别的对比 白嫖Kaggle平台部署DeepSeek-OCR 关于联想拯救者R9000P的若干问题的解决方法 尝试使用DeepSeek-OCR 使用text-autospace为中英文混排自动添加空格 优化深色模式下的评论系统 让过长的 KaTeX 公式支持横向滚动 在深色模式下自动切换 SVG 的颜色 2025 年高联二试(B 卷)几何题的解答 2025 年高联二试(A 卷)几何题的解答 2025 年 CGMO 的几何题的解答(二)
使用 Vibe Coding 编写一个属于自己的 VSCode 插件
西风冷香 · 2026-02-16 · via 如鱼饮水

在写这个博客里的内容以及类似的文章的时候,我一般使用VSCode和Typora两个软件进行编辑。

Typora的优点是可以实时预览文章内容,可以正确显示博客里引用的图片。Hexo博客会将图片存在与markdown文件同名的文件夹中[1],而Typora支持通过在YAML Front Matter里面指定typora-root-url[2]作为图片路径的前缀。

而VSCode的优点在于输入公式比较方便,可以在编辑数学公式的时候自动切换中英文[3],通过配置[4]也可以支持正确添加图片,但是图片文件的路径是相对于markdown文件所在文件夹的路径,这个Hexo的设置是不一样的。一旦修改成Hexo需要的路径之后,VSCode就无法找到正确的图片了。

这点在写新的文章的时候还不太要紧,因为可以在写完文章后统一修改图片路径。但是在修改旧的文章的时候,由于无法正确预览图片,就不太方便了。

要让VSCode实现这个功能,就必须通过插件来完成。但是由于我不懂VSCode插件的开发,甚至不熟悉Typescript的语法,这个想法一直被我放下了。最近看到了oh-my-opencode项目,于是想着能否利用vibe coding来写一个插件,满足我上面的要求。

1. 第一次尝试

去年在MiniMax M2.1刚发布的时候,限时免费了一段时间,加上赠送的代金券,我尝试用它配合Cline来日常使用,效果还可以。因此我这次打算用opencode免费提供的minimax-m2.1-free模型,配oh-my-opencode的ulw模式使用。

就在我写这个插件的第2天,Minimax发布了M2.5模型,之后第3天opencode就把免费模型替换成了新的M2.5模型。所以实际上是两个模型混着用的。

起初我把需求丢给opencode,结果确实写出了一个像模像样的插件,包括了扩展入口、一个markdown-it插件和一个yaml front matter解析器。尝试对它进行调试,它还在调试日志中告诉你「插件已成功加载」,但就是没有任何实际的作用。

尝试让它自己去debug,但是多次尝试始终未能成功。

无奈,只能手动去查看文档。

发现主要的问题出现在两个地方:

  1. 注册markdown-it插件的方法完全是错误的。
  2. 在markdown-it插件里面,获取文档内容的方法完全是错误的,致使实际上无法获取yaml front matter的内容。

这就导致了插件彻底无法工作。而且,在debug的过程中,它完全没有想到要求修改这两个地方。

也就是说,整个插件都是废的。

猜测主要原因是这两部分的语法没有问题,「表面」上的语义也没有问题,但实际上它用到的东西根本不存在。

2. 手动搭建框架

无奈,还是只能从头开始手写。

首先,使用yo code搭建项目架构。根据官方文档,VSCode使用markdown-it来解析markdown文件,因此我们只需要实现一个markdown-it插件即可。

我们先实现一个markdown-it插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import type MarkdownIt from 'markdown-it';
import path from 'node:path';

interface ImageOptions {
imageDir?: string;
}

export function prefixifyImageURL(md: MarkdownIt, pluginOptions?:ImageOptions) {
const imageDir = pluginOptions?.imageDir || ".";

const original = md.renderer.rules.image!;
md.renderer.rules.image = (tokens, idx, options, env, self) => {
const token = tokens[idx];
const src = token?.attrGet('src');
if (src) {
token.attrSet('src', path.join(imageDir, src));
}

return original(tokens, idx, options, env, self);
};
}

核心代码是重写了图片渲染逻辑md.renderer.rules.image,在正常渲染图片之前修改了图片的src属性。

然后使用extendMarkdownIt调用插件:

1
2
3
4
5
6
7
export function activate(context: vscode.ExtensionContext) {
return {
extendMarkdownIt(md: MarkdownIt) {
return md.use(prefixifyImageURL, {imageDir: "assets"});
}
};
}

这里为了检查代码逻辑,图方便先写死了imageDir。

另外,还需要在package.json中将这个扩展注册为markdown-it插件:

1
2
3
"contributes": {
"markdown.markdownItPlugins": true
}

调试插件,终于能够正常修改图片的路径了。

3. 动态加载图片路径

接下来,要解决的问题就是如何针对每个markdown文件获取typora-root-url信息,并使用它作为图片所在目录。

我的第一个想法是把imageDir存到env中,这样只需要读取一次,然后之后直接从env中调用即可:

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
import fs from 'fs';
import matter from 'gray-matter';

function getImageDir(filePath: string): string | null {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const { data } = matter(fileContent);
return data['typora-root-url'] ?? null;
}

export function prefixifyImageURL(md: MarkdownIt) {
const original = md.renderer.rules.image!;
md.renderer.rules.image = (tokens, idx, options, env, self) => {
let imageDir = env.imageDir;

if (imageDir === undefined) {
const filePath = env.currentDocument.path;
imageDir = getImageDir(filePath);
env.imageDir = imageDir;
}

if (imageDir !== null) {
const token = tokens[idx];
const src = token?.attrGet('src');
if (src) {
token.attrSet('src', path.join(imageDir, src));
}
}

return original(tokens, idx, options, env, self);
};
}

上面的代码确实能够满足要求,但是有一个问题,就是对修改typora-root-url的响应非常不及时,有很大的滞后性。

于是,我又尝试了第二种方法,就是每次从YAML Front Matter里动态获取imageDir

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
import frontMatter from 'markdown-it-front-matter';
import yaml from 'js-yaml';

let dynamicImageDir: any;

interface ImageOptions {
imageDir?: Function;
}

export function prefixifyImageURL(md: MarkdownIt, pluginOptions?: ImageOptions) {
const original = md.renderer.rules.image!;
md.renderer.rules.image = (tokens, idx, options, env, self) => {
const token = tokens[idx];
const src = token?.attrGet('src');
const imageDir = pluginOptions?.imageDir?.();
if (imageDir && src) {
token.attrSet('src', path.join(imageDir, src));
}

return original(tokens, idx, options, env, self);
};
}

export function activate(context: vscode.ExtensionContext) {
return {
extendMarkdownIt(md: MarkdownIt) {
return md.use(frontMatter, (fm: string) => {
const fmData = yaml.load(fm) as Record<string, any>;
const imageDir = fmData?.['typora-root-url'];
dynamicImageDir = imageDir;
}).use(prefixifyImageURL, {
imageDir: () => dynamicImageDir,
});
}
};
}

这样只要已修改typora-root-url的配置,立马就会在渲染中响应。

4. 支持图片链接的跳转和悬停预览

上面解决的是在右侧预览窗口里面的图片路径问题。另外还有一个需要适配的是在编辑界面的预览。

VSCode在鼠标指向图像链接的时候,会弹出该图像的预览。按住Ctrl单击,会跳转到对应的图片文件。我需要复现这两个功能。

这个部分让我自己写是完全不可能的,因此只能再次尝试vibe coding了。

好在,这次的结果还基本让人满意。

把需求喂给opencode之后,按住Ctrl单击的功能第一次就实现了,但是图像预览没有成功。不过让它自己进行debug,最终还是成功基本完成了这个任务。

不过,还是有一些小问题需要手动处理。

4.1. 问题一

其中一个是我提出的一个新的功能要求。上面实现的图像预览虽然成功了,但是由于有一些图像本身特别大,无法在预览框里完整显示(VSCode自带的图像预览是可以缩放到合适的大小的)。

尝试多次让opencode进行修改,都没有成功。无奈只能手动修改。

这里面最关键的点,在于VSCode到底是如何渲染markdown的图片的。

查看VSCode的源代码,从markdownRenderer.ts可以看到,它调用了parseHrefAndDimensions函数来识别图像的高度和宽度,该函数的定义位于htmlContent.ts,具体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function parseHrefAndDimensions(href: string): { href: string; dimensions: string[] } {
const dimensions: string[] = [];
const splitted = href.split('|').map(s => s.trim());
href = splitted[0];
const parameters = splitted[1];
if (parameters) {
const heightFromParams = /height=(\d+)/.exec(parameters);
const widthFromParams = /width=(\d+)/.exec(parameters);
const height = heightFromParams ? heightFromParams[1] : '';
const width = widthFromParams ? widthFromParams[1] : '';
const widthIsFinite = isFinite(parseInt(width));
const heightIsFinite = isFinite(parseInt(height));
if (widthIsFinite) {
dimensions.push(`width="${width}"`);
}
if (heightIsFinite) {
dimensions.push(`height="${height}"`);
}
}
return { href, dimensions };
}

从这里可以看出,VSCode接受![](a.png|height=240)这样指定图片尺寸的方式。由此,我们可以根据图片的长宽比例指定预览的宽度或高度。

4.2. 问题二

另一个问题是显示的链接下划线部分,长度和VSCode显示的不一致。尝试让opencode进行修改,但是最终还是差了1个长度,而且固执地认为算的没有问题。

奇怪的是,它在思考链中的分析其实是正确的,但是最终计算的offset值始终不对。

好在,这个错误很容易修改。

5. 总结

最终的代码我放在了vscode-markdown-hexo仓库。虽然还有一些bug,但是基本上个人使用够了。

回过头来看这次vibe coding的经历,整体的评价还是正面的,因为利用它我完成了一个之前我个人不可能完成的任务。

但是问题还是比较多的,主要是涉及到一些不是很常见的功能的时候,AI无法找到对应的代码,于是就开始瞎编了,编完了还像模像样地告诉你没有问题。

回想一下,当时应该直接把官方文档对应的页面直接扔给AI,让它根据具体的文档来写代码,应该会好一些。

另外一个藏在VSCode源代码里面的功能,这个AI不知道情有可原,因为我没有在任何公开的地方找到这个用法的说明。

下次可以尝试让AI分析VSCode的源代码,看看它能否找到这个功能的实现方法。

总体来说,和我这几年使用AI的感觉是一致的:就是AI强于逻辑,但是弱在事实核查。

说到事实核查,我不得不祭出当年GPT-3.5刚出来的时候的几张截图:

呃。。。


  1. 需要在_config.yml中启用post_asset_folder: true。 ↩︎

  2. 参考Typora的官方文档。 ↩︎

  3. 利用插件Shift IM for Math ↩︎

  4. 参考之前的文章:使用VSCode编辑Markdown的几个常用设置 ↩︎