feat(vim): added Vim ex :join and :join! support for line joining

- Implemented parsing of `:j`/`:join` and `:j!`/`:join!` ex commands and mapped them to a new `join` command shape with a whitespace-trim flag.
- Added VimEngine handling for `join` to concatenate addressed lines (or the current plus next line by default), optionally normalizing whitespace and reporting line count in the status message.
- Updated vim prompt guidance, changelog notes, and tests to document and verify both normal and `:join!` join behavior.
This commit is contained in:
can1357
2026-04-13 23:06:24 +02:00
parent 1bfbf79fea
commit 742cc5c163
6 changed files with 50 additions and 4 deletions
@@ -294,6 +294,9 @@ export function parseExCommand(input: string, context?: VimExParseContext): VimE
const flags = rest.slice(4).trim();
return { kind: "sort", range: range ?? undefined, flags };
}
if (rest === "j" || rest === "join" || rest === "j!" || rest === "join!") {
return { kind: "join", range: range ?? undefined, trimWhitespace: !rest.endsWith("!") };
}
if (rest.startsWith("substitute")) {
const segments = parseDelimitedSegments(rest.slice("substitute".length));
+28
View File
@@ -2298,6 +2298,34 @@ export class VimEngine {
this.statusMessage = `Sorted ${endLine - startLine + 1} line${endLine === startLine ? "" : "s"}`;
return;
}
case "join": {
const currentLine = this.buffer.cursor.line + 1;
const baseRange = this.#resolveExRange(
command.range,
currentLine,
command.range ? undefined : Math.min(this.buffer.lineCount(), currentLine + 1),
);
const startLine = baseRange.start;
const endLine =
baseRange.start === baseRange.end ? Math.min(this.buffer.lineCount(), baseRange.end + 1) : baseRange.end;
const lineCount = endLine - startLine + 1;
if (lineCount < 2) {
this.statusMessage = "Nothing to join";
return;
}
await this.#applyAtomicChange([":join"], () => {
const startIndex = startLine - 1;
if (command.trimWhitespace) {
this.buffer.joinLines(startIndex, lineCount - 1);
return;
}
const joined = this.buffer.lines.slice(startIndex, endLine).join("");
this.buffer.lines.splice(startIndex, lineCount, joined);
this.buffer.setCursor({ line: startIndex, col: Math.max(0, joined.length - 1) });
});
this.statusMessage = `Joined ${lineCount} lines`;
return;
}
case "append": {
const anchorRange = this.#resolveExRange(command.range, this.buffer.cursor.line + 1);
const anchorLine = anchorRange.end;
+1
View File
@@ -140,6 +140,7 @@ export type VimExCommand =
| { kind: "copy"; range?: VimLineRange | "all"; destination: number }
| { kind: "move"; range?: VimLineRange | "all"; destination: number }
| { kind: "sort"; range?: VimLineRange | "all"; flags: string }
| { kind: "join"; range?: VimLineRange | "all"; trimWhitespace: boolean }
| { kind: "global"; range?: VimLineRange | "all"; pattern: string; command: string; invert: boolean }
| { kind: "append"; range?: VimLineRange; text: string }
| { kind: "insert-before"; range?: VimLineRange; text: string };