VS Code 图片注释悬停预览实现说明
简介
这个插件可以在不同语言的代码注释中插入如 markdown 样式的图片格式,鼠标悬停后显示图片预览。
# 在 python 中,鼠标悬停后会显示图片预览
# 
本文说明这个插件如何在代码注释中识别图片路径,并在鼠标悬停时显示图片预览。
整体流程
插件的工作可以分为两步。本文只聚焦第二步:注释生成之后,插件如何扫描注释并实现悬停预览。
- 生成注释。可以使用粘贴功能,插件会在当前位置插入规定的格式。
- 解析注释。插件会扫描文档,识别出规定的格式,然后显示图片预览。
显示图片主要有四步:
- 根据模板识别图片注释。
- 从注释中提取图片路径。
- 把图片路径解析成 VS Code 可用的
vscode.Uri。 - 给该注释行添加 Decoration,并在 Decoration 的
hoverMessage中显示图片。
1. 根据模板生成正则
默认模板是:

相关代码在 src/utils/preview.ts:
export function createPatternFromTemplate(template: string): RegExp | null {
const pathPlaceholder = '{path}';
const placeholderIndex = template.indexOf(pathPlaceholder);
if (placeholderIndex === -1) {
return null;
}
const beforePath = template.substring(0, placeholderIndex);
const afterPath = template.substring(placeholderIndex + pathPlaceholder.length);
const escapedBefore = escapeRegex(beforePath);
const escapedAfter = escapeRegex(afterPath);
const patternString = `${escapedBefore}([^\\s"'\\]\\)\\n]+?)${escapedAfter}`;
try {
return new RegExp(patternString, 'g');
} catch {
return null;
}
}
以默认模板为例:
template = ""
插件会把它拆成三段:
beforePath = ""
接着把 beforePath 和 afterPath 转义成正则安全字符串。因为 [、]、(、) 在正则中有特殊含义:
escapedBefore = "!\[image-comment\]\("
escapedAfter = "\)"
然后把 {path} 替换成路径捕获规则,意思是:捕获一段图片路径,直到遇到空白、引号等特殊字符。
([^\s"'\]\)\n]+?)
最终得到的正则大致是:
!\[image-comment\]\(([^\s"'\]\)\n]+?)\)
所以当插件扫描文档时,它可以匹配到规定的格式,并捕获出图片路径。
// 
2. 扫描文档并提取图片路径
相关代码在 src/utils/preview.ts:
export function findImageCommentsInDocument(
document: vscode.TextDocument,
template: string,
): ImageMatchResult[] {
const pattern = createPatternFromTemplate(template);
if (!pattern) {
return [];
}
const results: ImageMatchResult[] = [];
const lineCount = document.lineCount;
for (let i = 0; i < lineCount; i++) {
const line = document.lineAt(i);
const lineResults = findImageCommentsInLine(line, pattern);
results.push(...lineResults);
}
return results;
}
这一步没啥好说的,就是扫描文档,识别出规定的格式,解析出 path 字段。
3. 把路径解析成 URI
URI 可以理解为 VS Code 内部用来表示资源位置的对象。
注释里的路径只是普通字符串:
.image-comment/image-abc.png
但 VS Code API 更常使用 vscode.Uri 来表示资源:
vscode.Uri.file("D:\\MyCode\\vscode-image-comment\\.image-comment\\image-abc.png")
相关代码在 src/utils/preview.ts:
export function resolveImagePath(
commentPath: string,
documentUri: vscode.Uri,
): vscode.Uri | null {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(documentUri);
if (path.isAbsolute(commentPath)) {
if (fs.existsSync(commentPath) && isImageFile(commentPath)) {
return vscode.Uri.file(commentPath);
}
return null;
}
if (workspaceFolder) {
const workspacePath = path.join(workspaceFolder.uri.fsPath, commentPath);
if (fs.existsSync(workspacePath) && isImageFile(workspacePath)) {
return vscode.Uri.file(workspacePath);
}
}
const documentDir = path.dirname(documentUri.fsPath);
const relativeToDocPath = path.join(documentDir, commentPath);
if (fs.existsSync(relativeToDocPath) && isImageFile(relativeToDocPath)) {
return vscode.Uri.file(relativeToDocPath);
}
return null;
}
它会按顺序尝试:
- 如果注释里是绝对路径,就直接检查该文件是否存在。
- 如果是相对路径,先按 workspace 根目录拼接。
- 如果还找不到,再按当前文档所在目录拼接。
后续显示 hover 图片和打开图片预览都依赖这个 URI。
4. 使用 Decoration 添加悬停预览
Decoration 是 VS Code Extension API 的编辑器装饰能力,也就是 VS Code 支持的一种功能。它不会修改文件内容,只是在编辑器显示层增加视觉效果,例如在这个插件中使用了:
- 给图片注释行加背景高亮
- 在 gutter,也就是行号左侧区域,显示一个小图标
- 给这一行绑定 hoverMessage,鼠标悬停时显示图片预览
相关代码在 src/handlers/imageDecoration.ts:
private createDecorationType(): vscode.TextEditorDecorationType {
const config = vscode.workspace.getConfiguration('imageComment');
const showGutterIcon = config.get<boolean>('showGutterIcon', true);
const highlightBackground = config.get<boolean>('highlightBackground', true);
const decorationOptions: vscode.DecorationRenderOptions = {
overviewRulerLane: vscode.OverviewRulerLane.Right,
overviewRulerColor: 'rgba(255, 140, 0, 0.6)',
};
// 显示 gutter 图标
if (showGutterIcon) {
decorationOptions.gutterIconPath = this.getIconPath('icon.svg');
decorationOptions.gutterIconSize = '14px';
}
// 显示背景高亮
if (highlightBackground) {
decorationOptions.backgroundColor = 'rgba(255, 140, 0, 0.08)';
decorationOptions.border = '1px dashed rgba(255, 140, 0, 0.15)';
decorationOptions.borderRadius = '2px';
}
return vscode.window.createTextEditorDecorationType(decorationOptions);
}
这段代码创建的是一种装饰样式。
真正关键的是下面这行代码,里面的 hoverMessage 就是最后悬停显示的内容。代码在 src/handlers/imageDecoration.ts 的 updateDecorations 方法中:
decorations.push({
range: decorationRange,
hoverMessage: this.createEnhancedHoverMessage(imageUri, match.imagePath),
});
悬停内容由 createEnhancedHoverMessage 生成。它返回的是一个 vscode.MarkdownString,其中包含 Markdown 图片语法,因此 VS Code 的 hover 弹窗可以渲染出图片。
private createEnhancedHoverMessage(imageUri: vscode.Uri, imagePath: string): vscode.MarkdownString {
const markdown = new vscode.MarkdownString();
markdown.isTrusted = true;
const encodedUri = imageUri.toString(true);
const fsPath = imageUri.fsPath;
let fileSize = '';
try {
const stats = fs.statSync(fsPath);
fileSize = this.formatFileSize(stats.size);
} catch {}
markdown.appendMarkdown(`**📷 ${messages.imageCommentHoverTitle()}**\n\n`);
markdown.appendMarkdown(`**${messages.imagePathLabel()}:** \`${imagePath}\`\n\n`);
if (fileSize) {
markdown.appendMarkdown(`**${messages.imageSizeLabel()}:** ${fileSize}\n\n`);
}
// 显示图片预览
markdown.appendMarkdown(`---\n\n`);
markdown.appendMarkdown(`\n\n`);
markdown.appendMarkdown(`---\n\n`);
markdown.appendMarkdown(
`💡 *${messages.hoverPreviewHint(messages.previewImageLabel())}*`,
);
return markdown;
}