feat(desktop): add copy/paste to terminal right-click context menu#1509
Conversation
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.
📝 WalkthroughWalkthroughThis 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
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. Comment |
There was a problem hiding this comment.
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.platformis deprecated; usenavigator.userAgentDatainstead.
navigator.platformis deprecated per MDN due to privacy concerns. Replace it withnavigator.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+"; }
| 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 | ||
| } | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Summary
xterm.paste()which handles newline normalization and bracketed paste mode for TUI appsChanges
terminal-callbacks.ts— AddgetSelectionCallbacksandpasteCallbacksmaps to the terminal callbacks storeuseTerminalRefs.ts— Add refs for registering/unregistering the new callbacksuseTerminalLifecycle.ts— RegisterhandleGetSelectionandhandlePastecallbacks on mount, unregister on cleanupTerminal.tsx— Pass the new refs through touseTerminalLifecycleTabContentContextMenu.tsx— Add optional Copy/Paste menu items above existing items (Split, Clear, Move, Close)TabPane.tsx— Retrieve callbacks from the store and pass toTabContentContextMenuTest Plan
Summary by CodeRabbit
Release Notes