[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
357
components/settings/tabs/TerminalBehaviorSettings.tsx
Normal file
357
components/settings/tabs/TerminalBehaviorSettings.tsx
Normal file
@@ -0,0 +1,357 @@
|
||||
import React from "react";
|
||||
import { DEFAULT_TERMINAL_WORD_SEPARATORS } from "../../../domain/models";
|
||||
import type { DisconnectedNoticeMode, DynamicTabTitleMode, LinkModifier, MiddleClickBehavior, OscNotificationMode, RightClickBehavior, TerminalSettings } from "../../../domain/models";
|
||||
import { Input } from "../../ui/input";
|
||||
import { Label } from "../../ui/label";
|
||||
import { SectionHeader, Select, SettingsAnchor, SettingRow, Toggle } from "../settings-ui";
|
||||
|
||||
type Translate = (key: string) => string;
|
||||
|
||||
interface TerminalBehaviorSettingsProps {
|
||||
t: Translate;
|
||||
terminalSettings: TerminalSettings;
|
||||
updateTerminalSetting: <K extends keyof TerminalSettings>(key: K, value: TerminalSettings[K]) => void;
|
||||
}
|
||||
|
||||
export const MIDDLE_CLICK_BEHAVIOR_OPTIONS: Array<{
|
||||
value: MiddleClickBehavior;
|
||||
labelKey: string;
|
||||
}> = [
|
||||
{ value: "context-menu", labelKey: "settings.terminal.behavior.middleClick.menu" },
|
||||
{ value: "paste", labelKey: "settings.terminal.behavior.middleClick.paste" },
|
||||
{ value: "disabled", labelKey: "settings.terminal.behavior.middleClick.disabled" },
|
||||
];
|
||||
|
||||
export const DYNAMIC_TAB_TITLE_MODE_OPTIONS: Array<{
|
||||
value: DynamicTabTitleMode;
|
||||
labelKey: string;
|
||||
}> = [
|
||||
{ value: "off", labelKey: "settings.terminal.behavior.dynamicTabTitle.off" },
|
||||
{ value: "agent", labelKey: "settings.terminal.behavior.dynamicTabTitle.agent" },
|
||||
{ value: "all", labelKey: "settings.terminal.behavior.dynamicTabTitle.all" },
|
||||
];
|
||||
|
||||
export const TerminalBehaviorSettings: React.FC<TerminalBehaviorSettingsProps> = ({
|
||||
t,
|
||||
terminalSettings,
|
||||
updateTerminalSetting,
|
||||
}) => (
|
||||
<>
|
||||
<SectionHeader title={t("settings.terminal.section.behavior")} />
|
||||
<div className="space-y-0 divide-y divide-border rounded-lg border bg-card px-4">
|
||||
<SettingRow
|
||||
anchorId="terminal-auto-close-on-exit"
|
||||
label={t("settings.terminal.behavior.autoCloseOnExit")}
|
||||
description={t("settings.terminal.behavior.autoCloseOnExit.desc")}
|
||||
>
|
||||
<Toggle
|
||||
checked={terminalSettings.autoCloseOnExit}
|
||||
onChange={(v) => updateTerminalSetting("autoCloseOnExit", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-disconnected-notice"
|
||||
label={t("settings.terminal.behavior.disconnectedNotice")}
|
||||
description={t("settings.terminal.behavior.disconnectedNotice.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.disconnectedNoticeMode}
|
||||
options={[
|
||||
{ value: "terminal", label: t("settings.terminal.behavior.disconnectedNotice.terminal") },
|
||||
{ value: "dialog", label: t("settings.terminal.behavior.disconnectedNotice.dialog") },
|
||||
]}
|
||||
onChange={(v) => updateTerminalSetting("disconnectedNoticeMode", v as DisconnectedNoticeMode)}
|
||||
className="w-40"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-right-click"
|
||||
label={t("settings.terminal.behavior.rightClick")}
|
||||
description={t("settings.terminal.behavior.rightClick.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.rightClickBehavior}
|
||||
options={[
|
||||
{ value: "context-menu", label: t("settings.terminal.behavior.rightClick.menu") },
|
||||
{ value: "paste", label: t("settings.terminal.behavior.rightClick.paste") },
|
||||
{ value: "select-word", label: t("settings.terminal.behavior.rightClick.selectWord") },
|
||||
]}
|
||||
onChange={(v) => updateTerminalSetting("rightClickBehavior", v as RightClickBehavior)}
|
||||
className="w-36"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.rightClick.fullscreenMenu")}
|
||||
description={t("settings.terminal.behavior.rightClick.fullscreenMenu.desc")}
|
||||
>
|
||||
<Toggle
|
||||
checked={terminalSettings.showContextMenuOverFullscreenApps}
|
||||
onChange={(v) => updateTerminalSetting("showContextMenuOverFullscreenApps", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-copy-on-select"
|
||||
label={t("settings.terminal.behavior.copyOnSelect")}
|
||||
description={t("settings.terminal.behavior.copyOnSelect.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.copyOnSelect} onChange={(v) => updateTerminalSetting("copyOnSelect", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-normalize-text-on-copy"
|
||||
label={t("settings.terminal.behavior.normalizeTextOnCopy")}
|
||||
description={t("settings.terminal.behavior.normalizeTextOnCopy.desc")}
|
||||
>
|
||||
<Toggle
|
||||
checked={terminalSettings.normalizeTextOnCopy ?? true}
|
||||
onChange={(v) => updateTerminalSetting("normalizeTextOnCopy", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-middle-click"
|
||||
label={t("settings.terminal.behavior.middleClick")}
|
||||
description={t("settings.terminal.behavior.middleClick.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.middleClickBehavior}
|
||||
options={MIDDLE_CLICK_BEHAVIOR_OPTIONS.map((option) => ({
|
||||
value: option.value,
|
||||
label: t(option.labelKey),
|
||||
}))}
|
||||
onChange={(v) => updateTerminalSetting("middleClickBehavior", v as MiddleClickBehavior)}
|
||||
className="w-36"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-word-separators"
|
||||
label={t("settings.terminal.behavior.wordSeparators")}
|
||||
description={t("settings.terminal.behavior.wordSeparators.desc")}
|
||||
>
|
||||
<Input
|
||||
value={terminalSettings.wordSeparators}
|
||||
onChange={(e) => updateTerminalSetting("wordSeparators", e.target.value)}
|
||||
placeholder={`${DEFAULT_TERMINAL_WORD_SEPARATORS}=,:`}
|
||||
className="w-56 font-mono"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-bracketed-paste"
|
||||
label={t("settings.terminal.behavior.bracketedPaste")}
|
||||
description={t("settings.terminal.behavior.bracketedPaste.desc")}
|
||||
>
|
||||
<Toggle checked={!terminalSettings.disableBracketedPaste} onChange={(v) => updateTerminalSetting("disableBracketedPaste", !v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-auto-upload-clipboard-image"
|
||||
label={t("settings.terminal.behavior.autoUploadClipboardImage")}
|
||||
description={t("settings.terminal.behavior.autoUploadClipboardImage.desc")}
|
||||
>
|
||||
<Toggle
|
||||
checked={terminalSettings.autoUploadClipboardImageOnPaste ?? false}
|
||||
onChange={(v) => updateTerminalSetting("autoUploadClipboardImageOnPaste", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-shift-enter-newline"
|
||||
label={t("settings.terminal.behavior.shiftEnterNewline")}
|
||||
description={t("settings.terminal.behavior.shiftEnterNewline.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.shiftEnterNewlineEnabled ?? true} onChange={(v) => updateTerminalSetting("shiftEnterNewlineEnabled", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.shiftEnterNewlineText")}
|
||||
description={t("settings.terminal.behavior.shiftEnterNewlineText.desc")}
|
||||
>
|
||||
<Input
|
||||
value={terminalSettings.shiftEnterNewlineText ?? "\\n"}
|
||||
onChange={(e) => updateTerminalSetting("shiftEnterNewlineText", e.target.value)}
|
||||
placeholder="\\n"
|
||||
className="w-56 font-mono"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-clear-wipes-scrollback"
|
||||
label={t("settings.terminal.behavior.clearWipesScrollback")}
|
||||
description={t("settings.terminal.behavior.clearWipesScrollback.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.clearWipesScrollback ?? true} onChange={(v) => updateTerminalSetting("clearWipesScrollback", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.preserveSelectionOnInput")}
|
||||
description={t("settings.terminal.behavior.preserveSelectionOnInput.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.preserveSelectionOnInput ?? false} onChange={(v) => updateTerminalSetting("preserveSelectionOnInput", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.forcePromptNewLine")}
|
||||
description={t("settings.terminal.behavior.forcePromptNewLine.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.forcePromptNewLine ?? false} onChange={(v) => updateTerminalSetting("forcePromptNewLine", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-dynamic-tab-title"
|
||||
label={t("settings.terminal.behavior.dynamicTabTitle")}
|
||||
description={t("settings.terminal.behavior.dynamicTabTitle.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.dynamicTabTitleMode ?? "agent"}
|
||||
options={DYNAMIC_TAB_TITLE_MODE_OPTIONS.map((option) => ({
|
||||
value: option.value,
|
||||
label: t(option.labelKey),
|
||||
}))}
|
||||
onChange={(v) => updateTerminalSetting("dynamicTabTitleMode", v as DynamicTabTitleMode)}
|
||||
className="w-44"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-osc-notifications"
|
||||
label={t("settings.terminal.behavior.oscNotifications")}
|
||||
description={t("settings.terminal.behavior.oscNotifications.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.oscNotifications ?? "always"}
|
||||
options={[
|
||||
{ value: "always", label: t("settings.terminal.behavior.oscNotifications.always") },
|
||||
{ value: "unfocused", label: t("settings.terminal.behavior.oscNotifications.unfocused") },
|
||||
{ value: "off", label: t("settings.terminal.behavior.oscNotifications.off") },
|
||||
]}
|
||||
onChange={(v) => updateTerminalSetting("oscNotifications", v as OscNotificationMode)}
|
||||
className="w-40"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
anchorId="terminal-osc52-clipboard"
|
||||
label={t("settings.terminal.behavior.osc52Clipboard")}
|
||||
description={t("settings.terminal.behavior.osc52Clipboard.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.osc52Clipboard ?? 'write-only'}
|
||||
options={[
|
||||
{ value: "off", label: t("settings.terminal.behavior.osc52Clipboard.off") },
|
||||
{ value: "write-only", label: t("settings.terminal.behavior.osc52Clipboard.writeOnly") },
|
||||
{ value: "read-write", label: t("settings.terminal.behavior.osc52Clipboard.readWrite") },
|
||||
{ value: "prompt", label: t("settings.terminal.behavior.osc52Clipboard.prompt") },
|
||||
]}
|
||||
onChange={(v) => updateTerminalSetting("osc52Clipboard", v as "off" | "write-only" | "read-write" | "prompt")}
|
||||
className="w-40"
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.scrollOnInput")}
|
||||
description={t("settings.terminal.behavior.scrollOnInput.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.scrollOnInput} onChange={(v) => updateTerminalSetting("scrollOnInput", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.scrollOnOutput")}
|
||||
description={t("settings.terminal.behavior.scrollOnOutput.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.scrollOnOutput} onChange={(v) => updateTerminalSetting("scrollOnOutput", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.scrollOnKeyPress")}
|
||||
description={t("settings.terminal.behavior.scrollOnKeyPress.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.scrollOnKeyPress} onChange={(v) => updateTerminalSetting("scrollOnKeyPress", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.scrollOnPaste")}
|
||||
description={t("settings.terminal.behavior.scrollOnPaste.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.scrollOnPaste} onChange={(v) => updateTerminalSetting("scrollOnPaste", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.smoothScrolling")}
|
||||
description={t("settings.terminal.behavior.smoothScrolling.desc")}
|
||||
>
|
||||
<Toggle checked={terminalSettings.smoothScrolling} onChange={(v) => updateTerminalSetting("smoothScrolling", v)} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("settings.terminal.behavior.linkModifier")}
|
||||
description={t("settings.terminal.behavior.linkModifier.desc")}
|
||||
>
|
||||
<Select
|
||||
value={terminalSettings.linkModifier}
|
||||
options={[
|
||||
{ value: "none", label: t("settings.terminal.behavior.linkModifier.none") },
|
||||
{ value: "ctrl", label: t("settings.terminal.behavior.linkModifier.ctrl") },
|
||||
{ value: "alt", label: t("settings.terminal.behavior.linkModifier.alt") },
|
||||
{ value: "meta", label: t("settings.terminal.behavior.linkModifier.meta") },
|
||||
]}
|
||||
onChange={(v) => updateTerminalSetting("linkModifier", v as LinkModifier)}
|
||||
className="w-48"
|
||||
/>
|
||||
</SettingRow>
|
||||
</div>
|
||||
|
||||
<SectionHeader title={t("settings.terminal.section.scrollback")} />
|
||||
<SettingsAnchor anchorId="terminal-scrollback-rows" className="rounded-lg border bg-card p-4">
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
{t("settings.terminal.scrollback.desc")}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{t("settings.terminal.scrollback.rows")}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={100000}
|
||||
value={terminalSettings.scrollback}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
if (!isNaN(val) && val >= 0 && val <= 100000) {
|
||||
updateTerminalSetting("scrollback", val);
|
||||
}
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</SettingsAnchor>
|
||||
|
||||
<SectionHeader title={t("settings.terminal.section.startupCommand")} />
|
||||
<SettingsAnchor anchorId="terminal-startup-command-delay" className="rounded-lg border bg-card p-4">
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
{t("settings.terminal.startupCommandDelay.desc")}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{t("settings.terminal.startupCommandDelay.label")}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={10000}
|
||||
value={terminalSettings.startupCommandDelayMs}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
if (!isNaN(val) && val >= 0 && val <= 10000) {
|
||||
updateTerminalSetting("startupCommandDelayMs", val);
|
||||
}
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</SettingsAnchor>
|
||||
</>
|
||||
);
|
||||
Reference in New Issue
Block a user