diff --git a/app/src/components/layout/StatusBar.tsx b/app/src/components/layout/StatusBar.tsx index 01e915a..2ff04fc 100644 --- a/app/src/components/layout/StatusBar.tsx +++ b/app/src/components/layout/StatusBar.tsx @@ -2,8 +2,8 @@ import { useShallow } from "zustand/react/shallow"; import { useAppState } from "../../store/appState"; export default function StatusBar() { - const { projects, sessions } = useAppState( - useShallow(s => ({ projects: s.projects, sessions: s.sessions })) + const { projects, sessions, terminalHasSelection } = useAppState( + useShallow(s => ({ projects: s.projects, sessions: s.sessions, terminalHasSelection: s.terminalHasSelection })) ); const running = projects.filter((p) => p.status === "running").length; @@ -20,6 +20,12 @@ export default function StatusBar() { {sessions.length} terminal{sessions.length !== 1 ? "s" : ""} + {terminalHasSelection && ( + <> + | + Ctrl+Shift+C to copy + + )} ); } diff --git a/app/src/components/terminal/TerminalView.tsx b/app/src/components/terminal/TerminalView.tsx index a4102fc..2113e4e 100644 --- a/app/src/components/terminal/TerminalView.tsx +++ b/app/src/components/terminal/TerminalView.tsx @@ -7,6 +7,7 @@ import { openUrl } from "@tauri-apps/plugin-opener"; import "@xterm/xterm/css/xterm.css"; import { useTerminal } from "../../hooks/useTerminal"; import { useAppState } from "../../store/appState"; +import { useShallow } from "zustand/react/shallow"; import { awsSsoRefresh } from "../../lib/tauri-commands"; import { UrlDetector } from "../../lib/urlDetector"; import UrlToast from "./UrlToast"; @@ -24,6 +25,7 @@ export default function TerminalView({ sessionId, active }: Props) { const webglRef = useRef(null); const detectorRef = useRef(null); const { sendInput, pasteImage, resize, onOutput, onExit } = useTerminal(); + const setTerminalHasSelection = useAppState(s => s.setTerminalHasSelection); const ssoBufferRef = useRef(""); const ssoTriggeredRef = useRef(false); @@ -136,6 +138,11 @@ export default function TerminalView({ sessionId, active }: Props) { setIsAtBottom(buf.viewportY >= buf.baseY); }); + // Track text selection to show copy hint in status bar + const selectionDisposable = term.onSelectionChange(() => { + setTerminalHasSelection(term.hasSelection()); + }); + // Handle image paste: intercept paste events with image data, // upload to the container, and inject the file path into terminal input. const handlePaste = (e: ClipboardEvent) => { @@ -238,6 +245,8 @@ export default function TerminalView({ sessionId, active }: Props) { osc52Disposable.dispose(); inputDisposable.dispose(); scrollDisposable.dispose(); + selectionDisposable.dispose(); + setTerminalHasSelection(false); containerRef.current?.removeEventListener("paste", handlePaste, { capture: true }); outputPromise.then((fn) => fn?.()); exitPromise.then((fn) => fn?.()); diff --git a/app/src/store/appState.ts b/app/src/store/appState.ts index 757c11c..37de139 100644 --- a/app/src/store/appState.ts +++ b/app/src/store/appState.ts @@ -24,6 +24,8 @@ interface AppState { removeMcpServerFromList: (id: string) => void; // UI state + terminalHasSelection: boolean; + setTerminalHasSelection: (has: boolean) => void; sidebarView: "projects" | "mcp" | "settings"; setSidebarView: (view: "projects" | "mcp" | "settings") => void; dockerAvailable: boolean | null; @@ -100,6 +102,8 @@ export const useAppState = create((set) => ({ })), // UI state + terminalHasSelection: false, + setTerminalHasSelection: (has) => set({ terminalHasSelection: has }), sidebarView: "projects", setSidebarView: (view) => set({ sidebarView: view }), dockerAvailable: null,