Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5
831 字
4 分钟
不经意发现的vscode对于md格式链接识别一个小特性
2026-08-06

vscode对于md格式链接识别存在误判原因分析#

前言#

  1. 有一阵没发博客,于是水一篇,最近在学习java反序列化的几个cc链路,感觉一时半会发不出来,在编写markdown文件时发现了一个有趣的现象(并不有趣,我差点以为是我电脑bug了,排查了半天),md中附图片的代码![alt](xx.png)按住ctrl悬停在图片名的时候竟无法点击打开图片,也不展示为链接,经过尝试,初步发现了一个规律,当紧贴着的上个代码块中有空行时,该链接识别会失效,而以预览模式打开图片仍能够正常加载,于是在ai的协助下找到了vscode对于判断内联代码的逻辑。
  2. 因水平有限,文章主要用于个人学习记录,内容仅供参考,若有错误或补充欢迎通过主页联系方式告知。

发现过程及源码分析#

  1. 如图,当上段代码块存在空行,且到下个代码块中无空行,ctrl悬停是无法点开图像链接的,大致判断是反引号代码块对链接的影响。 alt
  2. 在github一些讨论贴中,发现一些讨论帖,其中有人提到具有检测是否为代码块内链接的代码文件,我用ai找了一下实际实现链接判断的部分,如下面所示。
  3. 在vscode的gituhb仓库’https://github.com/microsoft/vscode-markdown-languageservice/blob/main/src/util/noLinkRanges.ts’也展示了该检测内联代码区域的代码,用一个反引号配对正则判断链接是否处于代码内,但这个正则不懂围栏代码块,且配对时穿不过空行。当代码块含空行时,配对被打乱,代码块收尾的 ``` 被当成新的代码块开头,配到下一个代码块的开头反引号,把中间的链接误判为在内联代码里,于是链接被过滤掉。不过,markdown解析时,能正确识别围栏,所以vscode所带预览功能不受影响。以下是noLinkRanges.ts的代码:
import * as lsp from 'vscode-languageserver-protocol';
import { Token } from '../parser.js';
import { rangeContains } from '../types/range.js';
import { ITextDocument } from '../types/textDocument.js';
const inlineCodePattern = /(?<!`)(`+)((?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1)(?!`)/gm;
class InlineRanges {
public static create() {
return new InlineRanges();
}
readonly #map: Map</* line number */ number, lsp.Range[]>;
private constructor(data?: ReadonlyMap<number, lsp.Range[]>) {
this.#map = new Map(data);
}
public get(line: number): lsp.Range[] {
return this.#map.get(line) || [];
}
public add(range: lsp.Range): void {
// Register the range for all lines that it covers
for (let line = range.start.line; line <= range.end.line; line++) {
let ranges = this.#map.get(line);
if (!ranges) {
ranges = [];
this.#map.set(line, ranges);
}
ranges.push(range);
}
}
public concat(newRanges: Iterable<lsp.Range>): InlineRanges {
const result = new InlineRanges(this.#map);
for (const range of newRanges) {
result.add(range);
}
return result;
}
}
export class NoLinkRanges {
public static compute(tokens: readonly Token[], document: ITextDocument): NoLinkRanges {
const multiline = tokens
.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map)
.map(t => ({ type: t.type, range: t.map as [number, number] }));
const inlineRanges = InlineRanges.create();
const text = document.getText();
for (const match of text.matchAll(inlineCodePattern)) {
const startOffset = match.index ?? 0;
const startPosition = document.positionAt(startOffset);
inlineRanges.add(lsp.Range.create(startPosition, document.positionAt(startOffset + match[0].length)));
}
return new NoLinkRanges(multiline, inlineRanges);
}
private constructor(
/**
* Block element ranges, such as code blocks. Represented by [line_start, line_end).
*/
public readonly multiline: ReadonlyArray<{ type: string, range: [number, number] }>,
/**
* Inline code spans where links should not be detected
*/
public readonly inline: InlineRanges,
) { }
contains(position: lsp.Position, excludeType = ''): boolean {
return this.multiline.some(({ type, range }) => type !== excludeType && position.line >= range[0] && position.line < range[1]) ||
!!this.inline.get(position.line)?.some(inlineRange => rangeContains(inlineRange, position));
}
concatInline(inlineRanges: Iterable<lsp.Range>): NoLinkRanges {
return new NoLinkRanges(this.multiline, this.inline.concat(inlineRanges));
}
}
不经意发现的vscode对于md格式链接识别一个小特性
https://www.dxowo8.top/posts/post-35/35/
作者
Ne+N3k_O
发布于
2026-08-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时