Skip to content

feat: implement text selection for terminal #14

Merged
sreya merged 12 commits intomainfrom
textselect
Nov 11, 2025
Merged

feat: implement text selection for terminal #14
sreya merged 12 commits intomainfrom
textselect

Conversation

@sreya
Copy link
Copy Markdown
Contributor

@sreya sreya commented Nov 11, 2025

No description provided.

sreya added 11 commits November 11, 2025 17:17
- Add SelectionManager class for mouse-based text selection
  * Click and drag to select text
  * Double-click to select word
  * Auto-copy to clipboard on selection
  * Visual selection overlay with semi-transparent highlight

- Integrate SelectionManager into Terminal class
  * Add getSelection(), hasSelection(), clearSelection(), selectAll() API
  * Add onSelectionChange event
  * Auto-clear selection when writing new data

- Update CanvasRenderer to draw selection overlay
  * Add setSelectionManager() and getCanvas() methods
  * Render semi-transparent selection highlight

- Update library exports to include SelectionManager

- Clean up demo placeholder selection code
  * Remove broken enableTextSelection() function
  * Document that selection is now built-in

- Add workflow best practice to AGENTS.md
  * Always pull from main before starting work

Features:
- xterm.js-compatible selection API
- Mouse drag selection with coordinate conversion
- Word boundary detection for double-click
- Wide character support (CJK, emoji)
- Multi-line selection with newlines
- Clipboard integration
- Selection clears on terminal write

Tests: 88 pass (including 2 new selection tests)
Type check: ✅ passes
- Force re-render of selection-affected lines
- Add right-click context menu handler for copy
- Add debug logging to track selection rendering
- Create test-selection.html for manual testing

Issues being debugged:
- Selection appears opaque instead of semi-transparent
- Selection doesn't clear properly
- Right-click shows 'Copy Image' instead of text
ROOT CAUSE: The 60fps render loop was drawing the selection overlay
every frame. With alpha=0.5, drawing 6+ times makes it opaque.

FIX:
- Only redraw selection when selection-affected lines are being redrawn
- Mark selection lines as needing redraw (so they clear old overlay)
- Draw selection overlay once after lines are cleared/redrawn
- Remove excessive debug logging

This ensures selection is drawn exactly once per frame, maintaining
the semi-transparent effect.

Testing: Selection should now be semi-transparent blue overlay
Issues Fixed:
1. Selection highlight persists after clicking elsewhere
   - Now tracks previousSelection coordinates
   - Renderer redraws those lines to clear old overlay
   - Selection clears immediately on mousedown

2. No feedback for clipboard operations
   - Added console.log for successful copy
   - Added error messages with helpful hints
   - Shows if Clipboard API is unavailable

3. Improved mousedown behavior
   - Always clears selection on new click
   - Starts fresh selection from click point

Changes:
- Track previousSelection in SelectionManager
- Add getPreviousSelectionCoords() and clearPreviousSelection() methods
- Renderer checks previousSelection and redraws those lines
- Enhanced copyToClipboard() with success/error logging
- Simplified mousedown handler

Testing: Selection should now clear when clicking elsewhere
Issue: Clipboard API requires HTTPS or specifically 'localhost' hostname.
Custom domains like 'mux.coder' are not considered secure contexts even
when they resolve to localhost.

Solution:
- Try modern navigator.clipboard API first (works on HTTPS/localhost)
- Fall back to document.execCommand('copy') for non-secure contexts
- execCommand works on custom domains and older browsers
- Provide clear console feedback about which method was used

This uses the "textarea trick":
1. Create hidden textarea with selected text
2. Select its content
3. Call document.execCommand('copy')
4. Remove textarea

Result: Copy works on mux.coder and similar custom domains.

Testing: Should now see '✅ Copied to clipboard (fallback)' message
and be able to paste the selected text.
CRITICAL BUG: Keyboard input stopped working after adding selection.

Root Cause:
- InputHandler attaches to container element (has tabindex, receives keys)
- SelectionManager adds mousedown listener to canvas element
- Clicks on canvas don't propagate focus to container
- Container loses focus → no keyboard events → no typing

Solution:
- On mousedown, explicitly focus the parent container
- This ensures keyboard events continue to work
- Selection still works as expected

Testing:
- Click on terminal → should be able to type
- Select text → should still be able to type afterward
- Focus is maintained on the container at all times
Issue 1 - Can't type after copy:
- copyToClipboardFallback creates textarea and calls textarea.focus()
- This steals focus from terminal container
- User can't type because container doesn't have focus anymore

Fix:
- Save document.activeElement before copy
- Restore focus after copy completes
- Works even if copy fails (try/catch)

Issue 2 - Slow input:
- Selection lines were being redrawn EVERY frame (60fps)
- Even when selection was static and not changing
- Caused noticeable input lag

Fix:
- Only redraw selection lines when actively selecting (mouse is down)
- Or when clearing previous selection
- When typing, terminal.write() clears selection anyway
- This prevents unnecessary redraws during normal typing

Performance:
- Before: 24 lines redrawn every frame with selectAll()
- After: 0 lines redrawn when selection is static
- Result: Input is fast again!

Testing: Input should be responsive, paste should work repeatedly
Problem: When dragging to select text and releasing mouse outside canvas
bounds, the selection would continue on mousemove because the canvas
mouseup event never fired.

Solution: Move mouseup listener from canvas to document to catch releases
anywhere on the page. This is the standard pattern for drag operations.

Changes:
- Move mouseup event listener to document scope
- Add mouseleave handler for better debugging
- Add console.log statements to track isSelecting state
- Store boundMouseUpHandler for proper cleanup in dispose()
- Update AGENTS.md to avoid committing summary markdown files

Test: Drag selection and release mouse outside terminal bounds - selection
should stop properly instead of continuing on hover.
Remove agent-generated files that shouldn't be tracked:
- DEBUG_SELECTION.md
- IMPLEMENTATION_SUMMARY.md
- SELECTION_TESTING.md
- test-selection.html
- test-selection-alpha.html

These were used for debugging during development.
@sreya sreya merged commit 2262634 into main Nov 11, 2025
sreya added a commit that referenced this pull request Nov 13, 2025
* feat: implement text selection for terminal

- Add SelectionManager class for mouse-based text selection
  * Click and drag to select text
  * Double-click to select word
  * Auto-copy to clipboard on selection
  * Visual selection overlay with semi-transparent highlight

- Integrate SelectionManager into Terminal class
  * Add getSelection(), hasSelection(), clearSelection(), selectAll() API
  * Add onSelectionChange event
  * Auto-clear selection when writing new data

- Update CanvasRenderer to draw selection overlay
  * Add setSelectionManager() and getCanvas() methods
  * Render semi-transparent selection highlight
Testing: Selection should now be semi-transparent blue overlay

* fix: selection clearing and clipboard feedback

Issues Fixed:
1. Selection highlight persists after clicking elsewhere
   - Now tracks previousSelection coordinates
   - Renderer redraws those lines to clear old overlay
   - Selection clears immediately on mousedown

2. No feedback for clipboard operations
   - Added console.log for successful copy
   - Added error messages with helpful hints
   - Shows if Clipboard API is unavailable

3. Improved mousedown behavior
   - Always clears selection on new click
   - Starts fresh selection from click point

Changes:
- Track previousSelection in SelectionManager
- Add getPreviousSelectionCoords() and clearPreviousSelection() methods
- Renderer checks previousSelection and redraws those lines
- Enhanced copyToClipboard() with success/error logging
- Simplified mousedown handler

Testing: Selection should now clear when clicking elsewhere

* fix: add clipboard fallback for non-secure contexts

Issue: Clipboard API requires HTTPS or specifically 'localhost' hostname.
Custom domains like 'mux.coder' are not considered secure contexts even
when they resolve to localhost.

Solution:
- Try modern navigator.clipboard API first (works on HTTPS/localhost)
- Fall back to document.execCommand('copy') for non-secure contexts
- execCommand works on custom domains and older browsers
- Provide clear console feedback about which method was used

This uses the "textarea trick":
1. Create hidden textarea with selected text
2. Select its content
3. Call document.execCommand('copy')
4. Remove textarea

Result: Copy works on mux.coder and similar custom domains.

Testing: Should now see '✅ Copied to clipboard (fallback)' message
and be able to paste the selected text.

* fix: restore keyboard input by focusing container on mousedown

CRITICAL BUG: Keyboard input stopped working after adding selection.

Root Cause:
- InputHandler attaches to container element (has tabindex, receives keys)
- SelectionManager adds mousedown listener to canvas element
- Clicks on canvas don't propagate focus to container
- Container loses focus → no keyboard events → no typing

Solution:
- On mousedown, explicitly focus the parent container
- This ensures keyboard events continue to work
- Selection still works as expected

Testing:
- Click on terminal → should be able to type
- Select text → should still be able to type afterward
- Focus is maintained on the container at all times

* fix: restore focus after clipboard copy and optimize selection rendering

Issue 1 - Can't type after copy:
- copyToClipboardFallback creates textarea and calls textarea.focus()
- This steals focus from terminal container
- User can't type because container doesn't have focus anymore

Fix:
- Save document.activeElement before copy
- Restore focus after copy completes
- Works even if copy fails (try/catch)

Issue 2 - Slow input:
- Selection lines were being redrawn EVERY frame (60fps)
- Even when selection was static and not changing
- Caused noticeable input lag

Fix:
- Only redraw selection lines when actively selecting (mouse is down)
- Or when clearing previous selection
- When typing, terminal.write() clears selection anyway
- This prevents unnecessary redraws during normal typing

Performance:
- Before: 24 lines redrawn every frame with selectAll()
- After: 0 lines redrawn when selection is static
- Result: Input is fast again!

Testing: Input should be responsive, paste should work repeatedly

* Fix: Selection continues after mouse release outside canvas

Problem: When dragging to select text and releasing mouse outside canvas
bounds, the selection would continue on mousemove because the canvas
mouseup event never fired.

Solution: Move mouseup listener from canvas to document to catch releases
anywhere on the page. This is the standard pattern for drag operations.

Changes:
- Move mouseup event listener to document scope
- Add mouseleave handler for better debugging
- Add console.log statements to track isSelecting state
- Store boundMouseUpHandler for proper cleanup in dispose()
- Update AGENTS.md to avoid committing summary markdown files

Test: Drag selection and release mouse outside terminal bounds - selection
should stop properly instead of continuing on hover.

* chore: remove temporary test and doc files

Remove agent-generated files that shouldn't be tracked:
- DEBUG_SELECTION.md
- IMPLEMENTATION_SUMMARY.md
- SELECTION_TESTING.md
- test-selection.html
- test-selection-alpha.html

These were used for debugging during development.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant