Skip to content

feat(desktop): add copy/paste to terminal right-click context menu#1509

Merged
AviPeltz merged 1 commit into
mainfrom
right-click-copy
Feb 16, 2026
Merged

feat(desktop): add copy/paste to terminal right-click context menu#1509
AviPeltz merged 1 commit into
mainfrom
right-click-copy

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Feb 16, 2026

Summary

  • Add Copy and Paste items to the existing terminal right-click context menu
  • Users can now right-click to copy selected terminal text or paste from clipboard without relying solely on keyboard shortcuts
  • Copy trims trailing whitespace per line, consistent with existing Cmd+C behavior
  • Paste uses xterm.paste() which handles newline normalization and bracketed paste mode for TUI apps

Changes

  • terminal-callbacks.ts — Add getSelectionCallbacks and pasteCallbacks maps to the terminal callbacks store
  • useTerminalRefs.ts — Add refs for registering/unregistering the new callbacks
  • useTerminalLifecycle.ts — Register handleGetSelection and handlePaste callbacks on mount, unregister on cleanup
  • Terminal.tsx — Pass the new refs through to useTerminalLifecycle
  • TabContentContextMenu.tsx — Add optional Copy/Paste menu items above existing items (Split, Clear, Move, Close)
  • TabPane.tsx — Retrieve callbacks from the store and pass to TabContentContextMenu

Test Plan

  • Right-click terminal with text selected → Copy is enabled, copies trimmed text to clipboard
  • Right-click terminal with no selection → Copy is disabled
  • Right-click terminal with clipboard content → Paste is enabled, pastes into terminal
  • Right-click terminal with empty clipboard → Paste is disabled
  • Existing context menu items (Split, Clear, Scroll to Bottom, Move to Tab, Close) still work
  • Paste works correctly in TUI apps (vim, etc.) with bracketed paste mode

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Copy and Paste menu items to the terminal context menu with platform-specific keyboard shortcuts (Cmd on macOS, Ctrl on Windows/Linux)
    • Copy option appears when text is selected; Paste option displays when clipboard content is available
    • Clipboard actions include icons and are visually separated from other menu options for improved usability

Add Copy and Paste items to the existing terminal context menu so users
can right-click to copy selected text or paste from clipboard without
relying solely on keyboard shortcuts.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

This PR adds clipboard functionality to terminal panes, introducing Copy and Paste menu items in the context menu. The feature registers per-pane selection and paste callbacks during terminal lifecycle, then uses them to dynamically enable/disable menu items and execute copy/paste operations when triggered.

Changes

Cohort / File(s) Summary
Context Menu UI
TabContentContextMenu.tsx
Adds Copy and Paste menu items with selection and clipboard state tracking. Items render conditionally based on getSelection and onPaste props, with platform-specific modifier key labels for shortcuts. Selection/clipboard state populates on menu open via onOpenChange handler.
Component Integration
TabPane.tsx
Wires getSelection and onPaste callbacks from terminal callbacks store to TabContentContextMenu, enabling per-pane selection retrieval and paste handling.
Terminal Component & Refs
Terminal.tsx, useTerminalRefs.ts
Adds four new callback refs (registerGetSelection, unregisterGetSelection, registerPaste, unregisterPaste) to the terminal refs interface, with corresponding type definitions for callback signatures.
Terminal Lifecycle
useTerminalLifecycle.ts
Implements handleGetSelection (returns trimmed terminal selection) and handlePaste (pastes text to xterm) handlers, registering them on mount via new lifecycle refs and unregistering on cleanup.
Callback Store
terminal-callbacks.ts
Adds two callback maps (getSelectionCallbacks, pasteCallbacks) with register/unregister/get methods for managing per-pane selection and paste callbacks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ContextMenu
    participant TabPane
    participant Terminal
    participant TerminalLifecycle
    participant CallbackStore

    Terminal->>TerminalLifecycle: Mount terminal
    TerminalLifecycle->>TerminalLifecycle: Create handleGetSelection<br/>and handlePaste
    TerminalLifecycle->>CallbackStore: Register callbacks<br/>for paneId
    
    User->>ContextMenu: Right-click (onOpenChange)
    ContextMenu->>TabPane: getSelection callback invoked
    TabPane->>CallbackStore: getGetSelectionCallback(paneId)
    CallbackStore->>TerminalLifecycle: Returns handleGetSelection
    TerminalLifecycle-->>ContextMenu: Current selection string
    
    ContextMenu->>ContextMenu: Update Copy item state<br/>(enable if selection exists)
    ContextMenu->>ContextMenu: Check clipboard availability<br/>for Paste item
    
    User->>ContextMenu: Click Copy/Paste
    ContextMenu->>TabPane: onPaste or copy action
    TabPane->>CallbackStore: getPasteCallback(paneId)
    CallbackStore->>TerminalLifecycle: Returns handlePaste
    TerminalLifecycle->>Terminal: xterm.paste(text)
    
    Terminal->>TerminalLifecycle: Unmount
    TerminalLifecycle->>CallbackStore: Unregister callbacks
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Kitenite

Poem

🐰 Copy, paste, and context so keen,
A rabbit's joy in the terminal scene!
Right-click, select, and off we go,
With clipboard magic, our text will flow! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (7 files):

⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts (content)
⚔️ apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalRefs.ts (content)
⚔️ apps/desktop/src/renderer/stores/tabs/terminal-callbacks.ts (content)
⚔️ apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature addition: copy/paste functionality in the terminal right-click context menu.
Description check ✅ Passed The description provides a comprehensive summary, related changes, test plan, and implementation details, exceeding the template requirements with clear technical context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch right-click-copy
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch right-click-copy
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AviPeltz AviPeltz merged commit 59267d9 into main Feb 16, 2026
5 of 6 checks passed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx`:
- Around line 84-98: handleCopy is an async function passed to onSelect but its
navigator.clipboard.writeText call is not guarded, creating a possible unhandled
rejection; update handleCopy (the function named handleCopy used for onSelect)
to wrap the clipboard read/write in a try/catch (mirroring handlePaste) and
handle or log the error so any thrown exception is caught before the promise
escapes the onSelect handler.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx (1)

28-31: navigator.platform is deprecated; use navigator.userAgentData instead.

navigator.platform is deprecated per MDN due to privacy concerns. Replace it with navigator.userAgentData?.platform, which returns normalized platform values like "macOS" for Apple devices. The suggested diff maintains compatibility with older environments while adopting the modern User-Agent Client Hints standard.

Suggested alternative
 function getModifierKeyLabel() {
-	const isMac = navigator.platform.toLowerCase().includes("mac");
+	const isMac = navigator.userAgentData
+		? navigator.userAgentData.platform === "macOS"
+		: navigator.platform.toLowerCase().includes("mac");
 	return isMac ? "⌘" : "Ctrl+";
 }

Comment on lines +84 to +98
const handleCopy = async () => {
const text = getSelection?.();
if (!text) return;
await navigator.clipboard.writeText(text);
};

const handlePaste = async () => {
if (!onPaste) return;
try {
const text = await navigator.clipboard.readText();
if (text) onPaste(text);
} catch {
// Clipboard access denied
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Async onSelect handlers — unhandled rejection risk is minimal but present.

Both handleCopy and handlePaste are async functions passed to onSelect, which expects (event) => void. Any thrown error becomes an unhandled promise rejection. The try/catch in handlePaste covers clipboard failures, but handleCopy's navigator.clipboard.writeText call at line 87 is not wrapped in a try/catch.

Wrap handleCopy in try/catch for consistency
 const handleCopy = async () => {
 	const text = getSelection?.();
 	if (!text) return;
-	await navigator.clipboard.writeText(text);
+	try {
+		await navigator.clipboard.writeText(text);
+	} catch {
+		// Clipboard write failed
+	}
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleCopy = async () => {
const text = getSelection?.();
if (!text) return;
await navigator.clipboard.writeText(text);
};
const handlePaste = async () => {
if (!onPaste) return;
try {
const text = await navigator.clipboard.readText();
if (text) onPaste(text);
} catch {
// Clipboard access denied
}
};
const handleCopy = async () => {
const text = getSelection?.();
if (!text) return;
try {
await navigator.clipboard.writeText(text);
} catch {
// Clipboard write failed
}
};
const handlePaste = async () => {
if (!onPaste) return;
try {
const text = await navigator.clipboard.readText();
if (text) onPaste(text);
} catch {
// Clipboard access denied
}
};
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx`
around lines 84 - 98, handleCopy is an async function passed to onSelect but its
navigator.clipboard.writeText call is not guarded, creating a possible unhandled
rejection; update handleCopy (the function named handleCopy used for onSelect)
to wrap the clipboard read/write in a try/catch (mirroring handlePaste) and
handle or log the error so any thrown exception is caught before the promise
escapes the onSelect handler.

@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app
  • ⚠️ Streams Fly.io app

Thank you for your contribution! 🎉

@Kitenite Kitenite deleted the right-click-copy branch February 19, 2026 21:44
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