diff --git a/extensions/cli/src/ui/TextBuffer.test.ts b/extensions/cli/src/ui/TextBuffer.test.ts index c6cc18cda53..1a3651d3b7e 100644 --- a/extensions/cli/src/ui/TextBuffer.test.ts +++ b/extensions/cli/src/ui/TextBuffer.test.ts @@ -664,11 +664,11 @@ describe("TextBuffer", () => { expect(buffer.cursor).toBe(2); }); - it("should handle delete key", () => { + it("should handle delete key as forward deletion", () => { const result = buffer.handleInput("", { delete: true } as any); expect(result).toBe(true); - expect(buffer.text).toBe("hllo"); - expect(buffer.cursor).toBe(1); + expect(buffer.text).toBe("helo"); + expect(buffer.cursor).toBe(2); }); it("should handle backspace key", () => { @@ -678,6 +678,60 @@ describe("TextBuffer", () => { expect(buffer.cursor).toBe(1); }); + it("should handle home and end keys on single-line text", () => { + let result = buffer.handleInput("", { home: true } as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(0); + + result = buffer.handleInput("", { end: true } as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(5); + }); + + it("should handle home and end keys on multi-line text", () => { + const text = "first line\nsecond line\nthird line"; + buffer.setText(text); + const secondLineStart = text.indexOf("second line"); + buffer.setCursor(secondLineStart + 4); + + let result = buffer.handleInput("", { home: true } as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(secondLineStart); + + result = buffer.handleInput("", { end: true } as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(secondLineStart + "second line".length); + }); + + it("should handle escape sequences for home, end, and delete", () => { + // Home sequence \u001b[1~ + let result = buffer.handleInput("\u001b[1~", {} as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(0); + + // End sequence \u001b[4~ + result = buffer.handleInput("\u001b[4~", {} as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(5); + + // Alternative Home sequence \u001b[H + result = buffer.handleInput("\u001b[H", {} as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(0); + + // Alternative End sequence \u001b[F + result = buffer.handleInput("\u001b[F", {} as any); + expect(result).toBe(true); + expect(buffer.cursor).toBe(5); + + // Forward delete sequence \u001b[3~ + buffer.setCursor(2); + result = buffer.handleInput("\u001b[3~", {} as any); + expect(result).toBe(true); + expect(buffer.text).toBe("helo"); + expect(buffer.cursor).toBe(2); + }); + it("should handle meta key combinations", () => { const result = buffer.handleInput("", { meta: true, diff --git a/extensions/cli/src/ui/TextBuffer.ts b/extensions/cli/src/ui/TextBuffer.ts index 768ab0b4b60..be50022e91f 100644 --- a/extensions/cli/src/ui/TextBuffer.ts +++ b/extensions/cli/src/ui/TextBuffer.ts @@ -177,6 +177,16 @@ export class TextBuffer { this._cursor = this._text.length; } + moveToLineStart(): void { + const lastNewline = this._text.lastIndexOf("\n", this._cursor - 1); + this._cursor = lastNewline === -1 ? 0 : lastNewline + 1; + } + + moveToLineEnd(): void { + const nextNewline = this._text.indexOf("\n", this._cursor); + this._cursor = nextNewline === -1 ? this._text.length : nextNewline; + } + moveLeft(): void { this._cursor = Math.max(0, this._cursor - 1); } @@ -420,6 +430,19 @@ export class TextBuffer { this.deleteWordBackward(); return true; } + // Standard terminal escape sequences for navigation / editing + if (sequence === "[1~" || sequence === "[H" || sequence === "OH") { + this.moveToLineStart(); + return true; + } + if (sequence === "[4~" || sequence === "[F" || sequence === "OF") { + this.moveToLineEnd(); + return true; + } + if (sequence === "[3~") { + this.deleteForward(); + return true; + } return true; // Consume other option sequences } @@ -477,6 +500,19 @@ export class TextBuffer { return false; } + private handleNavigationKeys(key: Key): boolean { + const navKey = key as Key & { home?: boolean; end?: boolean }; + if (navKey.home && !key.meta) { + this.moveToLineStart(); + return true; + } + if (navKey.end && !key.meta) { + this.moveToLineEnd(); + return true; + } + return false; + } + private handleArrowKeys(key: Key): boolean { if (key.leftArrow && !key.meta) { this.moveLeft(); @@ -490,8 +526,14 @@ export class TextBuffer { } private handleDeleteKeys(key: Key): boolean { - // On Mac, backspace key registers as key.delete, so treat it as backward deletion - if ((key.delete || key.backspace) && !key.meta) { + if (key.meta) { + return false; + } + if (key.delete) { + this.deleteForward(); + return true; + } + if (key.backspace) { this.deleteBackward(); return true; } @@ -517,6 +559,11 @@ export class TextBuffer { return true; } + // Handle home / end navigation keys + if (this.handleNavigationKeys(key)) { + return true; + } + // Handle arrow keys if (this.handleArrowKeys(key)) { return true;