72 lines
2.9 KiB
TypeScript
72 lines
2.9 KiB
TypeScript
/** 按 \n 或 \r\n 切行。 */
|
|
export function splitLines(text: string): string[] {
|
|
return text.split(/\r?\n/)
|
|
}
|
|
|
|
/** 非空行共同的前导空白(空格 / 制表符按原样比,不换算)。 */
|
|
export function commonIndent(lines: string[]): string {
|
|
let common: string | undefined
|
|
for (const line of lines) {
|
|
if (!line.trim())
|
|
continue
|
|
const indent = line.slice(0, line.length - line.trimStart().length)
|
|
if (common === undefined) {
|
|
common = indent
|
|
continue
|
|
}
|
|
let i = 0
|
|
while (i < common.length && i < indent.length && common[i] === indent[i])
|
|
i++
|
|
common = common.slice(0, i)
|
|
}
|
|
return common ?? ''
|
|
}
|
|
|
|
/**
|
|
* 块内改完的文本(webview 里是去掉共同缩进的样子)→ 写回文件的文本:
|
|
* 每个非空行补回这几行原来的共同缩进,和 BlockView.code 去缩进的做法互逆。
|
|
* @param originalLines 被替换的那几行原文
|
|
*/
|
|
export function reindentBlock(originalLines: string[], text: string): string {
|
|
const indent = commonIndent(originalLines)
|
|
return splitLines(text).map(line => line.trim() ? indent + line : line).join('\n')
|
|
}
|
|
|
|
/** 把 source 的 [startLine, endLine] 整行替换成 text(补回缩进),返回新全文;换行符沿用原文。 */
|
|
export function replaceLines(source: string, startLine: number, endLine: number, text: string): string {
|
|
const eol = source.includes('\r\n') ? '\r\n' : '\n'
|
|
const lines = splitLines(source)
|
|
const replacement = splitLines(reindentBlock(lines.slice(startLine, endLine + 1), text))
|
|
return [...lines.slice(0, startLine), ...replacement, ...lines.slice(endLine + 1)].join(eol)
|
|
}
|
|
|
|
/** 写回文件时要整行替换的一段:从 startLine 行首到 endLine 行尾(不含换行符)。 */
|
|
export interface LineReplacement {
|
|
startLine: number
|
|
endLine: number
|
|
/** endLine 这一行的长度。 */
|
|
endCharacter: number
|
|
/** 补回缩进后的新文本,行间用 \n(写进文档时 VS Code 按文档的换行符转换)。 */
|
|
text: string
|
|
/** 和原来那几行一字不差:不必写回,免得留下一次空的撤销记录。 */
|
|
unchanged: boolean
|
|
}
|
|
|
|
/**
|
|
* 预览里块内改完的文本 → 对当前文档的整行替换。行号已不在文档里(文档刚被别处改短)时返回 undefined。
|
|
* @param lineAt 读文档第 n 行(不含换行符)
|
|
*/
|
|
export function blockReplacement(lineAt: (line: number) => string, lineCount: number, startLine: number, endLine: number, text: string): LineReplacement | undefined {
|
|
if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 0 || endLine < startLine || endLine >= lineCount)
|
|
return undefined
|
|
const original = Array.from({ length: endLine - startLine + 1 }, (_, i) => lineAt(startLine + i))
|
|
const replacement = reindentBlock(original, text)
|
|
return {
|
|
startLine,
|
|
endLine,
|
|
endCharacter: original[original.length - 1].length,
|
|
text: replacement,
|
|
unchanged: replacement === original.join('\n'),
|
|
}
|
|
}
|