feat(edit): enabled Lid= and LidA..LidB replacements to support backslash continuation

- Updated the Atom grammar to parse replacement blocks via a set block rule that no longer targets range replacements only.
- Extended continuation preprocessing to allow backslash lines after single-line replace operations (including legacy `@` and `|` forms) while preserving the active-replacement check.
- Added tests and prompt documentation for single-line continuation cases and for rejection of backslash-like text outside an active replacement.
This commit is contained in:
can1357
2026-04-30 00:05:37 +02:00
parent c124c74b5d
commit 222e1cd348
4 changed files with 59 additions and 16 deletions
@@ -8,8 +8,8 @@ file_header: "---" filename LF
filename: /(.+)/
line_change: line* mutation_line line*
line: insert_line | delete_line | set_line | continuation_line | move_line | blank
mutation_line: insert_line | delete_line | set_line
line: insert_line | delete_line | set_block | move_line | blank
mutation_line: insert_line | delete_line | set_block
whole_file_change: blank* whole_file_line blank*
whole_file_line: remove_file | move_file
@@ -20,6 +20,7 @@ destination: /(?:[^ \t\r\n]+|"[^"\r\n]+"|'[^'\r\n]+')/
insert_line: "+" /(.*)/ LF
delete_line: "-" (LID ".." LID | LID) LF
set_line: (LID ".." LID | LID) WS? "=" /(.*)/ LF
set_block: set_line continuation_line*
continuation_line: "\\" /(.*)/ LF
move_line: ("@" LID | "^" LID | "^" | "$") LF
+23 -8
View File
@@ -8,7 +8,7 @@
* Lid=TEXT set the anchored line to TEXT and move cursor after it
* -Lid delete the anchored line and move cursor to its slot
* LidA..LidB=TEXT replace a range; following \TEXT lines continue it
* \TEXT append TEXT to the active range replacement
* \TEXT append TEXT to the active replacement (set or range)
* +TEXT insert TEXT at the cursor
* ^ move cursor to beginning of file
* $ move cursor to end of file
@@ -485,22 +485,35 @@ function isRangeReplaceStart(line: string): boolean {
return /^[1-9]\d*[a-z]{2}\.\.[1-9]\d*[a-z]{2}[ \t]*=/.test(line);
}
// Explicit range continuation uses `\TEXT` after `LidA..LidB=FIRST`.
// The leading backslash is the continuation marker; the rest of the line is
// inserted literally, so `\\TEXT` inserts a line starting with `\TEXT`.
// Raw unprefixed continuation remains an undocumented best-effort recovery
// for old transcripts, but canonical patches should use backslash lines.
// A single-line `Lid=TEXT` (or legacy `Lid|TEXT`, with optional leading `@`)
// also opens a replacement that `\TEXT` continuation lines may extend. The
// continuation lines become inserts at the cursor (which sits on the just-set
// line), turning `Lid=A` + `\B` + `\C` into "set the line to A, then insert B
// and C below it" — i.e. a multi-line rewrite of one anchor without forcing
// the user to switch to the `LidA..LidB=` range form.
function isReplaceStart(line: string): boolean {
if (isRangeReplaceStart(line)) return true;
const stripped = line.startsWith("@") ? line.slice(1) : line;
return /^[1-9]\d*[a-z]{2}[ \t]*[=|]/.test(stripped);
}
// Explicit continuation uses `\TEXT` after a replacement op (`Lid=FIRST` or
// `LidA..LidB=FIRST`). The leading backslash is the continuation marker; the
// rest of the line is inserted literally, so `\\TEXT` inserts a line starting
// with `\TEXT`. Raw unprefixed continuation remains an undocumented
// best-effort recovery for range replacements only, kept for old transcripts.
function preprocessRangeReplaceContinuation(diff: string): string {
const lines = diff.split("\n");
let inRangeReplace = false;
let inReplace = false;
for (let i = 0; i < lines.length; i++) {
const rawLine = lines[i];
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
if (line.startsWith("\\")) {
if (!inRangeReplace) {
if (!inReplace) {
throw new Error(
`Diff line ${i + 1}: \\TEXT continuation is only valid immediately after a LidA..LidB=FIRST_LINE range replacement.`,
`Diff line ${i + 1}: \\TEXT continuation is only valid immediately after a Lid=TEXT or LidA..LidB=FIRST_LINE replacement.`,
);
}
lines[i] = `+${RANGE_CONTINUATION_SENTINEL}${rawLine.slice(1)}`;
@@ -510,6 +523,7 @@ function preprocessRangeReplaceContinuation(diff: string): string {
if (inRangeReplace) {
if (line.length === 0 || OP_LINE_HEAD_RE.test(line)) {
inRangeReplace = isRangeReplaceStart(line);
inReplace = isReplaceStart(line);
continue;
}
@@ -518,6 +532,7 @@ function preprocessRangeReplaceContinuation(diff: string): string {
}
inRangeReplace = isRangeReplaceStart(line);
inReplace = isReplaceStart(line);
}
return lines.join("\n");
}