Skip to content

[CPLAT-12912] Pane-scoped drag selection in the session TUI and picker - #179

Merged
gavin-jeong merged 1 commit into
masterfrom
CPLAT-12912-drag-selection
Oct 21, 2026
Merged

gavin-jeong merged 1 commit into
masterfrom
CPLAT-12912-drag-selection

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-12912

What

Mouse drag now selects text inside the pane under the press, the way the Claude Code mod engine does (fleet convention E14, github.com/keyolk/tuikit/selection at c19aa4f). Mouse capture had turned off the terminal's own drag-select, and Shift-drag crossed the divider and both panes.

  • Columns are clamped to the pane the press landed in; never the divider or the neighbor pane.
  • Previews pass their full viewport content (Pane.Lines + YOffset): dragging past the top/bottom scrolls the preview through the same viewport scroll the wheel uses, and the copy keeps the lines that scrolled out.
  • Release copies (selection.Copy, off the UI thread); the result shows as a footer toast (Copied N chars / Copy failed: ...). The highlight stays until the next press or key.
  • A press that does not move is still the old click; the existing click/double-click code only runs when Result.Consumed is false.

Panes

Surface Text (double = word, triple = line) List (second click on selected row = Enter)
session TUI: sessions / config / plugins plain previews lists
conversation - list, execution rail, fold-aware preview (its double click toggles the block fold, so it keeps that)
global stats stats / stats detail viewport -
overlays help, full text, confirm every other menu / palette / picker
ccx pick picker (internal/cli) preview list

While an overlay is open it is the only selectable pane (its rect comes from the existing overlayRect), so nothing under a modal can be selected. In the picker the ref picker plays that role. A drag inside the picker's search state selects but never edits the term or moves the cursor. Copy mode keeps its own keyboard selection and gets no drag panes.

How

  • internal/tui/drag_selection.go: pane list built from the same ListWidth / PreviewWidth / ContentHeight / conversationLayoutHeight / executionRailTop numbers the renderers and hit tests use. It holds no geometry of its own.
  • app.go (wiring only): the old View body is now renderScreen() (pure, no highlight, no Kitty layer). View = renderScreen + sel.Render + Kitty layer. The mouse path hit-tests against renderScreen() directly instead of caching a frame; overlayRect now uses it too, so it no longer draws Kitty escapes for its diff. sel.Clear() sits next to the mouse.Clicks reset in the top-level KeyMsg arm.
  • The divider's resize grab zone reaches one cell into each pane; while dragResizing is set, motion belongs to the resize.
  • The highlight is reverse video on a renderer pinned to the ANSI profile, because the default renderer drops every SGR (reverse included) under NO_COLOR.
  • tuikit bumped to c19aa4f; termenv becomes a direct dependency.

Tests

Clicks and drags use coordinates found in the rendered View() output. Copies are asserted on the selection text or on the constructed tea.Cmd; the copy command is never run.

Each guarded behavior was checked with a compiling mutation, then restored:

Mutation Failing test(s)
preview pane starts on the divider TestDragFromPreviewTowardListStaysInPreview, TestDragPastPreviewBottomScrollsAndKeepsLines
ignore Result.Consumed TestDragFromListTowardPreviewStaysInList, TestDragPastPreviewBottomScrollsAndKeepsLines, TestPreviewDoubleClickSelectsWordNotEnter, TestConversationPreviewKeepsFoldDoubleClickAndSelects
no Clear on key TestKeyClearsSelection
base panes passed under an overlay TestOverlayOwnsSelection
drop drag scroll TestDragPastPreviewBottomScrollsAndKeepsLines
fold preview treated as Text TestConversationPreviewKeepsFoldDoubleClickAndSelects
no highlight in View TestDragFromListTowardPreviewStaysInList, TestKeyClearsSelection, TestSelectionHighlightSurvivesNoColor
drop the dragResizing guard TestDragNextToDividerResizesInsteadOfSelecting
preview Offset = 0 TestDragInScrolledPreviewCopiesWhatIsShown, TestDragPastPreviewBottomScrollsAndKeepsLines
default-renderer highlight (tui / picker) TestSelectionHighlightSurvivesNoColor / TestPickerHighlightSurvivesNoColor
picker preview rect includes border+padding TestPickerDragFromPreviewTowardListStaysInPreview, TestPickerDragPastPreviewBottomScrolls, TestPickerDragSelectsWhileSearching
picker: no Clear on key TestPickerKeyClearsSelectionAndNotice
picker: list selectable under the ref picker TestPickerRefPickerOwnsSelection
picker: drop drag scroll TestPickerDragPastPreviewBottomScrolls
picker: search guard before selection TestPickerDragSelectsWhileSearching
picker: ignore Consumed TestPickerDragFromListStopsAtPreview, TestPickerDragPastPreviewBottomScrolls, TestPickerPreviewDoubleClickSelectsWord

gofmt -l is clean. go vet ./... passes. go test ./..., go test -race ./..., and env -u LC_ALL -u LC_CTYPE LANG=ko_KR.UTF-8 go test ./... all pass.

Note: the existing find/where test helpers measure with go-runewidth. Under a CJK locale that counts the ambiguous-width divider │ as 2 cells, which shifts every column right of it. The split draws the preview at a fixed column (CHA) and the selection measures with x/ansi, so the new tests use findCell/whereCell (x/ansi). Existing click tests only click left of the divider and are unaffected.

@upwind-code-us

upwind-code-us Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Upwind Upwind Code Scan - ✅ Passed

0 newly introduced vulnerabilities · 0 resolved · 2 total in this PR vs the default branch

Total breakdown: 🔶 2 High

View full analysis in Upwind Console

Scan completed in 10s

Scan history (2 scans)
Commit Scanned at New Resolved Net
3bbddc2 2026-10-21 03:17 UTC — — —
3bbddc2 < 2026-10-21 03:17 UTC 0 0 0

Last scanned: 3bbddc2 · 2026-10-21 03:17 UTC

@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Oct 21, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@gavin-jeong
gavin-jeong merged commit 1ae50b3 into master Oct 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants