feat(ui): /mouse toggle — enable terminal text selection/copy by default
Mouse-wheel tracking (xterm ?1000h) was always on so the wheel could scroll the transcript, but it also captures mouse events, which prevents the terminal's native text selection/drag-to-copy — a frequent ask. Now mouse tracking defaults to OFF, so selecting/dragging output to copy works out of the box. PageUp/PageDown still scroll (they always did). Toggle the wheel back on with '/mouse on' (re-captures mouse events, so selection won't work while on); '/mouse off' restores selection. '/mouse' shows the current state. Documented in the /help list and README. Verified: typecheck clean, build 257.55 KB, 263 tests pass.
This commit is contained in:
@@ -115,6 +115,7 @@ Note: even models with genuine native tool-calling support occasionally emit a t
|
||||
/model <name> switch the model used for the current backend
|
||||
/backend <name> switch backend (ollama | lmstudio), keeps current model
|
||||
/mode <name> view or force tool-call mode (native | fallback)
|
||||
/mouse [on|off] toggle mouse-wheel scroll (off by default so terminal text selection/copy works)
|
||||
/perm [mode] cycle or set permission mode (default | plan | auto-edit | auto-accept)
|
||||
/status show current model, backend, tool-call mode, and cwd
|
||||
/dashboard show session stats: token I/O, elapsed/model time, turns, tool calls
|
||||
|
||||
+22
-2
@@ -136,6 +136,11 @@ export function App({
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [permission, setPermission] = useState<PendingPermission | null>(null);
|
||||
const [exportPrompt, setExportPrompt] = useState<{ defaultName: string } | null>(null);
|
||||
// Mouse-wheel tracking (xterm ?1000h) lets the wheel scroll the transcript, but it ALSO
|
||||
// captures mouse events so the terminal can't select/drag text to copy it. Default off — copy/
|
||||
// drag is more important than wheel scroll, and PageUp/PageDown already scroll. Toggle with
|
||||
// `/mouse on|off`. When off the wheel does nothing in-app; the terminal's native selection works.
|
||||
const [mouseMode, setMouseMode] = useState(false);
|
||||
const [streamingText, setStreamingText] = useState<string | null>(null);
|
||||
const [isThinking, setIsThinking] = useState(false);
|
||||
const [permMode, setPermMode] = useState<PermissionMode>("default");
|
||||
@@ -275,13 +280,13 @@ export function App({
|
||||
// open would otherwise type them straight into the filename — simplest fix is to stop the
|
||||
// terminal from sending them at all rather than filtering inside a dependency we don't control.
|
||||
useEffect(() => {
|
||||
if (!isRawModeSupported || phase !== "input" || exportPrompt) return;
|
||||
if (!isRawModeSupported || phase !== "input" || exportPrompt || !mouseMode) return;
|
||||
stdout.write("[?1000h[?1006h");
|
||||
setRawMode(true);
|
||||
return () => {
|
||||
stdout.write("[?1006l[?1000l");
|
||||
};
|
||||
}, [phase, isRawModeSupported, stdout, setRawMode, exportPrompt]);
|
||||
}, [phase, isRawModeSupported, stdout, setRawMode, exportPrompt, mouseMode]);
|
||||
|
||||
// A backgrounded bash job (see Ctrl+B below) can finish long after the turn that started it has
|
||||
// ended — this is how its completion still reaches the transcript.
|
||||
@@ -985,6 +990,21 @@ export function App({
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (trimmed.startsWith("/mouse")) {
|
||||
const arg = trimmed.slice("/mouse".length).trim().toLowerCase();
|
||||
if (!arg) {
|
||||
push({ kind: "notice", text: `Mouse wheel scroll: ${mouseMode ? "on" : "off"}. Use /mouse on|off.` });
|
||||
} else if (arg === "on") {
|
||||
setMouseMode(true);
|
||||
push({ kind: "notice", text: "Mouse wheel scroll on (note: this captures mouse events, so terminal text selection/drag-to-copy won't work while on). Use /mouse off to copy text." });
|
||||
} else if (arg === "off") {
|
||||
setMouseMode(false);
|
||||
push({ kind: "notice", text: "Mouse wheel scroll off — you can now select/drag terminal text to copy. Use PageUp/PageDown to scroll, or /mouse on to re-enable the wheel." });
|
||||
} else {
|
||||
push({ kind: "notice", text: `Unknown option "${arg}". Use /mouse on or /mouse off.`, isError: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// UserPromptSubmit hooks see the raw text before plugin-command expansion or @mention
|
||||
// resolution — a hook can block the message outright, or inject extra context (appended
|
||||
|
||||
Reference in New Issue
Block a user