feat(ui): add mouse-drag text selection and clipboard copy#306
Conversation
- Drag-selection model (copySelection.ts) supports single-row, multi-row, double-click word expansion, and triple-click line selection, with column-level precision for both stack and split layouts - Selection highlight rendered at character-level granularity across diff rows, scoped to the active split side (left = A, right = B) - Triple-click in split mode automatically scopes to the clicked pane instead of selecting across both panes - Decorated copy preserves diff rails, line numbers, gutters, and file headers; code-only copy strips all decoration - B-side copy with decorations uses correct pane-local column offsets so selection, highlight, and clipboard agree - View > Copy decorations menu toggle (default off), controlled by copy_decorations config key - Pinned-header click extends selection into the stream body - Clipboard output via OSC 52 with transient status feedback
Greptile SummaryThis PR adds mouse-drag text selection to Hunk's diff views, with OSC 52 clipboard copy on drag release, double/triple-click word and line selection, split-pane side awareness, and a
Confidence Score: 3/5The copy path works end-to-end and is well tested, but the default value for The default value in src/core/config.ts — the Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant DiffPane
participant copySelection
participant renderRows
participant OSC52
User->>DiffPane: onMouseDown (drag start)
DiffPane->>copySelection: resolveCopySelectionPoint(event)
copySelection-->>DiffPane: CopySelectionPoint (anchor)
DiffPane->>DiffPane: "setCopySelectionDrag({anchor, focus, moved:false})"
User->>DiffPane: onMouseDrag (pointer moves)
DiffPane->>copySelection: resolveCopySelectionPoint(event)
copySelection-->>DiffPane: CopySelectionPoint (focus)
DiffPane->>DiffPane: "setCopySelectionDrag({anchor, focus, moved:true})"
DiffPane->>renderRows: buildCopySelectedRowKeys (visual highlight)
User->>DiffPane: onMouseUp / onMouseDragEnd
DiffPane->>copySelection: normalizeCopySelectionRange(anchor, focus)
copySelection-->>DiffPane: "{start, end}"
DiffPane->>renderRows: renderCopySelectionText(context, start, end, side)
renderRows-->>DiffPane: clipboard text string
DiffPane->>OSC52: copyToClipboardOSC52(text)
DiffPane->>App: onCopyFeedback("Copied selection to clipboard")
App->>User: StatusBar transient notice
|
| @@ -15,6 +15,7 @@ const DEFAULT_VIEW_PREFERENCES: PersistedViewPreferences = { | |||
| wrapLines: false, | |||
There was a problem hiding this comment.
Default value contradicts documented behavior
The PR description and UI label both say "Copy decorations" is off by default, but DEFAULT_VIEW_PREFERENCES.copyDecorations: true initialises it as on. This flows through resolveConfiguredCliInput → loadAppBootstrap → App, so new users without a config file will start with decorations enabled, copying diff rails, line numbers and gutters instead of bare code text. The value should be false to match the documented default.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/config.ts
Line: 15
Comment:
**Default value contradicts documented behavior**
The PR description and UI label both say "Copy decorations" is _off by default_, but `DEFAULT_VIEW_PREFERENCES.copyDecorations: true` initialises it as on. This flows through `resolveConfiguredCliInput` → `loadAppBootstrap` → `App`, so new users without a config file will start with decorations enabled, copying diff rails, line numbers and gutters instead of bare code text. The value should be `false` to match the documented default.
How can I resolve this? If you propose a fix, please make it concise.| showAgentNotes: false, | ||
| copyDecorations: true, | ||
| }; |
There was a problem hiding this comment.
Set
copyDecorations to false so the out-of-the-box experience copies only code text, matching the PR description and menu label ("off by default").
| showAgentNotes: false, | |
| copyDecorations: true, | |
| }; | |
| showAgentNotes: false, | |
| copyDecorations: false, | |
| }; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/config.ts
Line: 17-19
Comment:
Set `copyDecorations` to `false` so the out-of-the-box experience copies only code text, matching the PR description and menu label ("off by default").
```suggestion
showAgentNotes: false,
copyDecorations: false,
};
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| buildCopySelectedRowKeys({ | ||
| drag: copySelectionDrag, | ||
| fileSectionLayouts, | ||
| sectionGeometry, | ||
| width: diffContentWidth, | ||
| }), |
There was a problem hiding this comment.
Stale
copySelectionDrag on the first drag event
Inside updateCopySelection, event.preventDefault() / event.stopPropagation() are gated on the closed-over copySelectionDrag value. On the very first onMouseDrag event that fires immediately after beginCopySelection sets the drag state, React has not yet re-rendered, so copySelectionDrag is still null and the two calls are skipped. The state-setter path (functional update inside setCopySelectionDrag) correctly sees the latest value, but the native-selection suppression fires a render cycle late. The consequence is a brief native-selection flash on drag start.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/components/panes/DiffPane.tsx
Line: 596-601
Comment:
**Stale `copySelectionDrag` on the first drag event**
Inside `updateCopySelection`, `event.preventDefault()` / `event.stopPropagation()` are gated on the closed-over `copySelectionDrag` value. On the very first `onMouseDrag` event that fires immediately after `beginCopySelection` sets the drag state, React has not yet re-rendered, so `copySelectionDrag` is still `null` and the two calls are skipped. The state-setter path (functional update inside `setCopySelectionDrag`) correctly sees the latest value, but the native-selection suppression fires a render cycle late. The consequence is a brief native-selection flash on drag start.
How can I resolve this? If you propose a fix, please make it concise.
Hunk is very cool, but it's missing some basic features. Chipping in this PR to add selections via OpenTUI's OSC 52 support.
Concretely, this adds:
Mouse-drag text selection. Click anywhere on a diff row and drag to select a range of text. The selection spans multiple rows when you drag across them, and a character-level visual highlight follows the pointer. Releasing the drag copies the selected text to your clipboard via OSC 52.
Double-click and triple-click. Double-click expands the selection to word boundaries within the clicked line. Triple-click copies the entire line.
Split mode awareness. Triple-click and drag selection automatically stay within the side you started on. When copy decorations are enabled, column offsets are computed relative to the correct pane, so what you see highlighted is exactly what lands on the clipboard.
Copy decorations toggle. A View > Copy decorations menu item (off by default, or set copy_decorations in config) controls whether copied text includes diff rails, line numbers, gutters, and file headers. When off, only the changed code text is copied.