From 171c6b520bee23bed76fd34b730332f43067dddd Mon Sep 17 00:00:00 2001 From: Volkan Sagcan Date: Thu, 6 Aug 2026 22:25:00 +0200 Subject: [PATCH] fix(tui): correct multi-click text selection (#7733) --- packages/tui/src/tui-alt-screen.ts | 4 +-- packages/tui/test/tui-alt-screen.test.ts | 33 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/tui-alt-screen.ts b/packages/tui/src/tui-alt-screen.ts index 2032bf0f4..121f1a207 100644 --- a/packages/tui/src/tui-alt-screen.ts +++ b/packages/tui/src/tui-alt-screen.ts @@ -906,14 +906,14 @@ export class TuiAltScreen extends TuiBase implements ViewportTUI { maxColumn = Math.min(this.terminal.columns, box.rect.x + box.rect.width, box.clip.x + box.clip.width); screenSelection = { start: { + ...selection.start, row: box.rect.y + selection.start.row - selection.start.scrollView.scrollTop, col: box.rect.x + selection.start.col, - scrollView: selection.start.scrollView, }, end: { + ...selection.end, row: box.rect.y + selection.end.row - selection.start.scrollView.scrollTop, col: box.rect.x + selection.end.col, - scrollView: selection.start.scrollView, }, }; } diff --git a/packages/tui/test/tui-alt-screen.test.ts b/packages/tui/test/tui-alt-screen.test.ts index c34492702..cee169284 100644 --- a/packages/tui/test/tui-alt-screen.test.ts +++ b/packages/tui/test/tui-alt-screen.test.ts @@ -763,6 +763,39 @@ describe("TuiAltScreen", () => { tui.stop(); }); + it("does not append whitespace to double-click word highlighting", async () => { + const terminal = new RecordingTerminal(20, 1); + const tui = new TuiAltScreen(terminal); + tui.addChild(new Text("foo bar", 0, 0)); + tui.start(); + await terminal.waitForRender(); + + terminal.sendInput("\x1b[<0;1;1M"); + terminal.sendInput("\x1b[<0;1;1m"); + terminal.sendInput("\x1b[<0;3;1M"); + await terminal.waitForRender(); + + assert.ok(terminal.events.some((event) => event.type === "write" && event.data.includes("foo\x1b[27m"))); + tui.stop(); + }); + + it("highlights a complete whitespace segment during a word drag", async () => { + const terminal = new RecordingTerminal(20, 1); + const tui = new TuiAltScreen(terminal); + tui.addChild(new Text("foo bar", 0, 0)); + tui.start(); + await terminal.waitForRender(); + + terminal.sendInput("\x1b[<0;1;1M"); + terminal.sendInput("\x1b[<0;1;1m"); + terminal.sendInput("\x1b[<0;2;1M"); + terminal.sendInput("\x1b[<32;4;1M"); + await terminal.waitForRender(); + + assert.ok(terminal.events.some((event) => event.type === "write" && event.data.includes("foo \x1b[27m"))); + tui.stop(); + }); + it("selects whole words on double click, extends word drags, and selects lines on triple click", async () => { const terminal = new RecordingTerminal(20, 2); const tui = new TuiAltScreen(terminal);