Compare commits

..

3 Commits

Author SHA1 Message Date
ecaa42fa77 Remove unused useShallow import to fix tsc build
All checks were successful
Build App / compute-version (push) Successful in 3s
Build App / build-macos (push) Successful in 2m27s
Build App / build-windows (push) Successful in 3m33s
Build App / build-linux (push) Successful in 4m49s
Build App / create-tag (push) Successful in 2s
Build App / sync-to-github (push) Successful in 10s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:20:08 -07:00
280358166a Show copy hint in status bar when terminal text is selected
Some checks failed
Build App / compute-version (push) Successful in 3s
Build App / build-macos (push) Failing after 7s
Build App / build-windows (push) Failing after 19s
Build App / build-linux (push) Failing after 1m57s
Build App / create-tag (push) Has been skipped
Build App / sync-to-github (push) Has been skipped
When the user highlights text in the terminal, a "Ctrl+Shift+C to copy"
hint appears in the status bar next to the project/terminal counts.
The hint disappears when the selection is cleared.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:14:08 -07:00
4732feb33e Add Ctrl+Shift+C keyboard shortcut for copying terminal text
All checks were successful
Build App / compute-version (push) Successful in 5s
Build App / build-macos (push) Successful in 2m22s
Build App / build-windows (push) Successful in 3m25s
Build App / build-linux (push) Successful in 5m33s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 10s
Ctrl+C in the terminal sends SIGINT which cancels running Claude work.
This adds a custom key handler so Ctrl+Shift+C copies selected text to
the clipboard without interrupting the container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:05:10 -07:00
3 changed files with 36 additions and 2 deletions

View File

@@ -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() {
<span>
{sessions.length} terminal{sessions.length !== 1 ? "s" : ""}
</span>
{terminalHasSelection && (
<>
<span className="mx-2">|</span>
<span className="text-[var(--accent)]">Ctrl+Shift+C to copy</span>
</>
)}
</div>
);
}

View File

@@ -24,6 +24,7 @@ export default function TerminalView({ sessionId, active }: Props) {
const webglRef = useRef<WebglAddon | null>(null);
const detectorRef = useRef<UrlDetector | null>(null);
const { sendInput, pasteImage, resize, onOutput, onExit } = useTerminal();
const setTerminalHasSelection = useAppState(s => s.setTerminalHasSelection);
const ssoBufferRef = useRef("");
const ssoTriggeredRef = useRef(false);
@@ -80,6 +81,22 @@ export default function TerminalView({ sessionId, active }: Props) {
term.open(containerRef.current);
// Ctrl+Shift+C copies selected terminal text to clipboard.
// This prevents the keystroke from reaching the container (where
// Ctrl+C would send SIGINT and cancel running work).
term.attachCustomKeyEventHandler((event) => {
if (event.type === "keydown" && event.ctrlKey && event.shiftKey && event.key === "C") {
const sel = term.getSelection();
if (sel) {
navigator.clipboard.writeText(sel).catch((e) =>
console.error("Ctrl+Shift+C clipboard write failed:", e),
);
}
return false; // prevent xterm from processing this key
}
return true;
});
// WebGL addon is loaded/disposed dynamically in the active effect
// to avoid exhausting the browser's limited WebGL context pool.
@@ -120,6 +137,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) => {
@@ -222,6 +244,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?.());

View File

@@ -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<AppState>((set) => ({
})),
// UI state
terminalHasSelection: false,
setTerminalHasSelection: (has) => set({ terminalHasSelection: has }),
sidebarView: "projects",
setSidebarView: (view) => set({ sidebarView: view }),
dockerAvailable: null,