moyf_easy-copy/src/codeBlockDetect.ts

108 lines
3.5 KiB
TypeScript
Raw Normal View History

import { CodeBlockBehavior, ContextData, ContextType } from './type';
/**
* 0
* trimStart 3
*/
function parseFenceLength(line: string): number {
const trimmed = line.trimStart();
const match = trimmed.match(/^(`{3,})/);
if (!match) return 0;
return match[1].length;
}
/**
*
* >= minLen
*/
function isClosingFence(line: string, minLen: number): boolean {
const trimmed = line.trimStart();
const match = trimmed.match(/^(`+)\s*$/);
if (!match) return false;
return match[1].length >= minLen;
}
/**
* CommonMark
* - 3 info string
* - >=
* - ```` ``` 是内容)
*
* ~~~
*
* @param lines
* @param cursorLine 0-based
* @param behavior
* @returns ContextData null
*/
export function detectCodeBlockFromLines(
lines: string[],
cursorLine: number,
behavior: CodeBlockBehavior,
): ContextData | null {
if (behavior === CodeBlockBehavior.DISABLED) return null;
const totalLines = lines.length;
// 阶段一:从文件开头向下扫描到 cursorLine。
// openLen = 0 → 不在块内openLen > 0 → 在块内,值为开始围栏的反引号数。
//
// 不在块内时遇到有效围栏行parseFenceLength >= 3→ 进入块
// 在块内时:遇到 isClosingFence长度 >= openLen 且纯反引号)→ 离开块
// 其他反引号行(内层短围栏 / 带 info string→ 忽略,视为内容
let openLen = 0;
let fenceStart = -1;
for (let i = 0; i <= cursorLine; i++) {
if (openLen === 0) {
const len = parseFenceLength(lines[i]);
if (len > 0) {
openLen = len;
fenceStart = i;
}
} else {
if (isClosingFence(lines[i], openLen)) {
openLen = 0;
fenceStart = -1;
}
}
}
// 光标不在任何代码块内
if (openLen === 0) return null;
if (fenceStart === -1) return null;
// 光标正好在开始围栏行上,不算「块内」
if (fenceStart === cursorLine) return null;
// 阶段二:从 fenceStart+1 向下找匹配的结束围栏
let fenceEnd = -1;
for (let i = fenceStart + 1; i < totalLines; i++) {
if (isClosingFence(lines[i], openLen)) {
fenceEnd = i;
break;
}
}
if (fenceEnd === -1) return null;
// 光标必须在两个围栏之间(不含两端)
if (cursorLine >= fenceEnd) return null;
if (behavior === CodeBlockBehavior.GENERATE_BLOCK_LINK) return null;
// 收集代码块内容行(不含围栏行)
const contentLines: string[] = [];
for (let i = fenceStart + 1; i < fenceEnd; i++) {
contentLines.push(lines[i]);
}
const curLineText = lines[cursorLine];
if (behavior === CodeBlockBehavior.COPY_WITH_FENCES) {
const fullBlock = [lines[fenceStart], ...contentLines, lines[fenceEnd]].join('\n');
return { type: ContextType.CODEBLOCK, curLine: curLineText, match: fullBlock, range: null };
}
// 默认 COPY_CONTENT
return { type: ContextType.CODEBLOCK, curLine: curLineText, match: contentLines.join('\n'), range: null };
}