Files
llmx/codex-cli/tests/text-buffer-word.test.ts
Alpha Diop 6829a5a012 fix(text-buffer): correct word deletion logic for trailing spaces (Ctrl+Backspace) (#131)
## Description
This fix resolves a bug where Ctrl+Backspace (hex 0x17) deletes the
entire line when the cursor is positioned after a trailing space.

## Problem
When the user has a line like "I want you to refactor my view " (with a
space at the end) and the cursor is after that space, Ctrl+Backspace
deletes the entire line instead of just removing the word "view".

## Solution
- Added a check to detect if the cursor is after spaces
- Modified the logic to delete only one space at a time in this case
- Added a unit test to verify this behavior

## Tests
All tests pass, including the new test that verifies the corrected
behavior.

---------

Signed-off-by: Alpha Diop <alphakhoss@gmail.com>
Co-authored-by: Thibault Sottiaux <tibo@openai.com>
2025-04-16 16:25:08 -07:00

125 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import TextBuffer from "../src/text-buffer.js";
import { describe, test, expect } from "vitest";
describe("TextBuffer wordwise navigation & deletion", () => {
test("wordRight moves to endofline when no further boundary", () => {
const tb = new TextBuffer("hello");
// Move the caret inside the word (index 3)
tb.move("right");
tb.move("right");
tb.move("right");
tb.move("wordRight");
const [, col] = tb.getCursor();
expect(col).toBe(5); // end of the word / line
});
test("Ctrl+Backspace on raw byte deletes previous word", () => {
const tb = new TextBuffer("hello world");
const vp = { height: 10, width: 80 } as const;
// Place caret at end
tb.move("end");
// Simulate terminal sending DEL (0x7f) byte with ctrl modifier Ink
// usually does *not* set `key.backspace` in this path.
tb.handleInput("\x7f", { ctrl: true }, vp);
expect(tb.getText()).toBe("hello ");
});
test("Option/Alt+Backspace deletes previous word", () => {
const tb = new TextBuffer("foo bar baz");
const vp = { height: 10, width: 80 } as const;
// caret at end
tb.move("end");
// Simulate Option+Backspace (alt): Ink sets key.backspace = true, key.alt = true (no raw byte)
tb.handleInput(undefined, { backspace: true, alt: true }, vp);
expect(tb.getText()).toBe("foo bar ");
});
test("Option/Alt+Delete deletes next word", () => {
const tb = new TextBuffer("foo bar baz");
const vp = { height: 10, width: 80 } as const;
// Move caret between first and second word (after space)
tb.move("wordRight"); // after foo
tb.move("right"); // skip space -> start of bar
// Option+Delete
tb.handleInput(undefined, { delete: true, alt: true }, vp);
expect(tb.getText()).toBe("foo baz"); // note double space removed later maybe
});
test("wordLeft eventually reaches column 0", () => {
const tb = new TextBuffer("hello world");
// Move to end of line first
tb.move("end");
// two wordLefts should land at start of line
tb.move("wordLeft");
tb.move("wordLeft");
const [, col] = tb.getCursor();
expect(col).toBe(0);
});
test("wordRight jumps over a delimiter into the next word", () => {
const tb = new TextBuffer("hello world");
tb.move("wordRight"); // from start should land after "hello" (between space & w)
let [, col] = tb.getCursor();
expect(col).toBe(5);
// Next wordRight should move to end of line (after "world")
tb.move("wordRight");
[, col] = tb.getCursor();
expect(col).toBe(11);
});
test("deleteWordLeft after trailing space only deletes the last word, not the whole line", () => {
const tb = new TextBuffer("I want you to refactor my view ");
tb.move("end"); // Place caret after the space
tb.deleteWordLeft();
expect(tb.getText()).toBe("I want you to refactor my ");
const [, col] = tb.getCursor();
expect(col).toBe("I want you to refactor my ".length);
});
test("deleteWordLeft removes the previous word and positions the caret correctly", () => {
const tb = new TextBuffer("hello world");
// Place caret at end of line
tb.move("end");
// Act
tb.deleteWordLeft();
expect(tb.getText()).toBe("hello ");
const [, col] = tb.getCursor();
expect(col).toBe(6); // after the space
});
test("deleteWordRight removes the following word", () => {
const tb = new TextBuffer("hello world");
// Move caret to start of "world"
tb.move("wordRight"); // caret after "hello"
tb.move("right"); // skip the space, now at index 6 (start of world)
// Act
tb.deleteWordRight();
expect(tb.getText()).toBe("hello ");
const [, col] = tb.getCursor();
expect(col).toBe(6);
});
});