feat: shortcut recorder, configurable terminal toolbar, offline changelog
- Global shortcut setting: press-to-record input (Esc cancels, Backspace clears); requires a modifier or F-key so plain typing can't be hijacked - Terminal toolbar: three settings-gated buttons — session-log record (default off), open logs dir (default off), open working directory (default on, new; local sessions only, cwd tracked via cd/OSC 7) - Input suggestions + command history now default off, with a one-time migration that resets persisted true values for existing installs - Settings dialog: fix broken scrolling — antd v6 renamed the Tabs scroll container to .ant-tabs-body-holder; theme gallery drops its nested scroll (single outer scrollbar) - Offline changelog: CHANGELOG.md at repo root bundled via ?raw; About tab reads it first, network sources stay as fallback; scripts/sync-changelog.cjs merges RELEASE_NOTES.md per release (release.cjs fails without an entry) - commands.ts history prefs default aligned with new off-by-default
This commit is contained in:
1 parent
c1744184a3
commit
34094d607b
17 files changed
+319
-41
No files matched your search
@@ -376,7 +376,7 @@ function loadHistoryPrefs(): { historyEnabled: boolean; historyLimit: number } {
|
||||
try {
|
||||
const terminal = loadSettings().terminal
|
||||
return {
|
||||
historyEnabled: terminal.historyEnabled !== false,
|
||||
historyEnabled: terminal.historyEnabled === true,
|
||||
historyLimit: normalizeLimit(terminal.historyLimit)
|
||||
}
|
||||
} catch {
|
||||
|
||||
+15
-1
@@ -1,6 +1,7 @@
|
||||
import { app, ipcMain } from 'electron'
|
||||
import { app, ipcMain, shell } from 'electron'
|
||||
import fontList from 'font-list'
|
||||
import { homedir } from 'os'
|
||||
import { statSync } from 'fs'
|
||||
import { Ipc, type AppInfo, type LayoutMeta, type PtyCreateOptions } from '../shared/ipc'
|
||||
import type { HostKeyAction, SessionOpenOptions, SshConnection, SshConnectionInput } from '../shared/connections'
|
||||
import { getLayout, listLayouts, saveLayout, deleteLayout } from './layouts'
|
||||
@@ -181,4 +182,17 @@ export function registerIpc(): void {
|
||||
resolveCwd(current, arg)
|
||||
)
|
||||
ipcMain.handle(Ipc.CWD_REPORT, (_event, payload: string) => normalizeReportedCwd(payload))
|
||||
|
||||
// Open a local directory in the file manager. Validates the path is an
|
||||
// existing directory so renderer-supplied values can't open arbitrary files.
|
||||
ipcMain.handle(Ipc.CWD_OPEN, (_event, dir: unknown): boolean => {
|
||||
if (typeof dir !== 'string' || dir === '') return false
|
||||
try {
|
||||
if (!statSync(dir).isDirectory()) return false
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
void shell.openPath(dir)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app, ipcMain, powerSaveBlocker } from 'electron'
|
||||
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { Ipc } from '../shared/ipc'
|
||||
import {
|
||||
@@ -161,6 +161,32 @@ export function loadSettings(): AppSettings {
|
||||
|
||||
let settingsQueue: Promise<unknown> = Promise.resolve()
|
||||
|
||||
/**
|
||||
* One-time reset for existing installs: input suggestions and command history
|
||||
* are now off by default, but installs that upgraded from earlier versions
|
||||
* already have `true` persisted — flip those off exactly once, then leave a
|
||||
* marker so the user's later choices are never touched again.
|
||||
*/
|
||||
function migrateDefaultsOnce(): void {
|
||||
try {
|
||||
const flag = join(app.getPath('userData'), '.migrate-defaults-v2')
|
||||
if (existsSync(flag)) return
|
||||
const current = loadSettings()
|
||||
if (current.terminal.suggestEnabled || current.terminal.historyEnabled) {
|
||||
current.terminal.suggestEnabled = false
|
||||
current.terminal.historyEnabled = false
|
||||
mkdirSync(app.getPath('userData'), { recursive: true })
|
||||
const path = settingsPath()
|
||||
const tmp = `${path}.tmp`
|
||||
writeFileSync(tmp, JSON.stringify(current, null, 2), 'utf8')
|
||||
renameSync(tmp, path)
|
||||
}
|
||||
writeFileSync(flag, '', 'utf8')
|
||||
} catch {
|
||||
// best effort: a failed migration just keeps the old values
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a settings mutation against every other writer. Each queued step
|
||||
* re-reads the file so the mutation applies on top of the latest state — a
|
||||
@@ -196,6 +222,7 @@ export function saveSettings(next: AppSettings): Promise<AppSettings> {
|
||||
}
|
||||
|
||||
export function registerSettingsIpc(): void {
|
||||
migrateDefaultsOnce()
|
||||
ipcMain.handle(Ipc.SETTINGS_GET, () => loadSettings())
|
||||
ipcMain.handle(Ipc.SETTINGS_SET, (_event, next: AppSettings) => saveSettings(next))
|
||||
}
|
||||
@@ -80,6 +80,7 @@ const api: AppApi = {
|
||||
logStop: (sessionId: string) => ipcRenderer.invoke(Ipc.LOG_STOP, sessionId),
|
||||
listSessionLogs: () => ipcRenderer.invoke(Ipc.LOG_LIST),
|
||||
openLogsDir: () => ipcRenderer.send(Ipc.LOG_OPEN_DIR),
|
||||
openDirectory: (dir: string) => ipcRenderer.invoke(Ipc.CWD_OPEN, dir),
|
||||
|
||||
listConnections: () => ipcRenderer.invoke(Ipc.CONNECTIONS_LIST),
|
||||
saveConnection: (input: SshConnectionInput) => ipcRenderer.invoke(Ipc.CONNECTIONS_SAVE, input),
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.commands-panel .ant-tabs-content-holder {
|
||||
/* antd v6 renamed the layout container: content-holder -> body-holder. */
|
||||
.commands-panel .ant-tabs-body-holder {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.commands-panel .ant-tabs-body,
|
||||
.commands-panel .ant-tabs-content,
|
||||
.commands-panel .ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
|
||||
@@ -57,6 +57,7 @@ if (typeof window !== 'undefined' && !window.api) {
|
||||
logStop: async () => undefined,
|
||||
listSessionLogs: async () => [],
|
||||
openLogsDir: noop,
|
||||
openDirectory: async () => false,
|
||||
listConnections: async () => [],
|
||||
saveConnection: async (input) => ({
|
||||
id: input.id ?? stubId(),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Progress, Select, Switch, Tag } from 'antd'
|
||||
import type { AppInfo, ReleaseNote, UpdateState, UpdateStatus } from '@shared/ipc'
|
||||
import { useSettingsStore } from './store'
|
||||
import appIconUrl from '../../../../build/icon.png'
|
||||
import changelogMd from '../../../../CHANGELOG.md?raw'
|
||||
|
||||
const STATUS_TEXT: Record<UpdateStatus, string> = {
|
||||
idle: '点击检查更新',
|
||||
@@ -15,7 +16,22 @@ const STATUS_TEXT: Record<UpdateStatus, string> = {
|
||||
dev: '开发模式不支持更新检查'
|
||||
}
|
||||
|
||||
/** 关于页:版本信息 + 更新检查/下载/安装 + 更新日志(国内源优先,GitHub 兜底)。 */
|
||||
/** Parse the bundled CHANGELOG.md into release notes. Sections look like
|
||||
* `## v1.0.12 - 2026-09-20`; the body below the heading is kept verbatim. */
|
||||
function parseChangelog(md: string): ReleaseNote[] {
|
||||
const notes: ReleaseNote[] = []
|
||||
for (const section of md.split(/^## /m).slice(1)) {
|
||||
const lineEnd = section.indexOf('\n')
|
||||
if (lineEnd < 0) continue
|
||||
const head = section.slice(0, lineEnd).trim()
|
||||
const m = /^(v[\d.]+)\s*-\s*(\d{4}-\d{2}-\d{2})/.exec(head)
|
||||
const body = section.slice(lineEnd + 1).trim()
|
||||
if (m && body) notes.push({ version: m[1], date: m[2], body })
|
||||
}
|
||||
return notes
|
||||
}
|
||||
|
||||
/** 关于页:版本信息 + 更新检查/下载/安装 + 更新日志(内置 CHANGELOG 优先,网络兜底)。 */
|
||||
export function AboutTab(): React.JSX.Element {
|
||||
const [version, setVersion] = useState('')
|
||||
const [state, setState] = useState<UpdateState | null>(null)
|
||||
@@ -25,7 +41,10 @@ export function AboutTab(): React.JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
void window.api.appInfo().then((i: AppInfo) => setVersion(i.appVersion))
|
||||
void window.api.updateChangelog().then(setNotes).catch(() => undefined)
|
||||
// Embedded changelog first (works offline); network sources as fallback.
|
||||
const local = parseChangelog(changelogMd)
|
||||
if (local.length > 0) setNotes(local)
|
||||
else void window.api.updateChangelog().then(setNotes).catch(() => undefined)
|
||||
return window.api.onUpdateState(setState)
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ export function RenderSettingsTab(): React.JSX.Element {
|
||||
desc="关闭后不再记录新命令;已保存的历史仍然保留"
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.historyEnabled !== false}
|
||||
checked={settings.terminal.historyEnabled}
|
||||
onChange={(checked) => void updateTerminal({ historyEnabled: checked })}
|
||||
/>
|
||||
}
|
||||
@@ -315,6 +315,36 @@ export function RenderSettingsTab(): React.JSX.Element {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="终端工具栏 · 录制"
|
||||
desc="右上角显示「记录会话日志」按钮"
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.showRecButton}
|
||||
onChange={(checked) => void updateTerminal({ showRecButton: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="终端工具栏 · 打开日志目录"
|
||||
desc="右上角显示「打开日志目录」按钮"
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.showOpenLogsButton}
|
||||
onChange={(checked) => void updateTerminal({ showOpenLogsButton: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="终端工具栏 · 打开工作区目录"
|
||||
desc="右上角显示「打开当前目录」按钮(SSH 会话不显示;目录随 cd 实时跟踪,需开启 shell 集成或使用 cd 命令)"
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.showOpenCwdButton}
|
||||
onChange={(checked) => void updateTerminal({ showOpenCwdButton: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -382,13 +412,11 @@ export function SystemSettingsTab(): React.JSX.Element {
|
||||
/>
|
||||
<SettingRow
|
||||
label="全局唤起快捷键"
|
||||
desc="Control+Shift+Alt+T 等,留空禁用"
|
||||
desc="点击输入框后直接按下组合键;Esc 取消,退格清空禁用"
|
||||
control={
|
||||
<Input
|
||||
className="settings-select"
|
||||
placeholder="如 Control+Shift+Alt+T,留空禁用"
|
||||
<ShortcutInput
|
||||
value={settings.system.globalShowHide ?? ''}
|
||||
onChange={(e) => void updateSystem({ globalShowHide: e.target.value })}
|
||||
onChange={(v) => void updateSystem({ globalShowHide: v })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -399,6 +427,86 @@ export function SystemSettingsTab(): React.JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a keydown into an Electron globalShortcut accelerator (e.g.
|
||||
* "Control+Shift+Alt+T"). Returns null while only modifiers are held or for
|
||||
* keys we don't want to bind. The Win key maps to Super.
|
||||
*/
|
||||
function acceleratorFromEvent(e: React.KeyboardEvent<HTMLInputElement>): string | null {
|
||||
const modifierCodes = new Set([
|
||||
'ControlLeft', 'ControlRight', 'AltLeft', 'AltRight',
|
||||
'ShiftLeft', 'ShiftRight', 'MetaLeft', 'MetaRight'
|
||||
])
|
||||
if (modifierCodes.has(e.code)) return null
|
||||
const parts: string[] = []
|
||||
if (e.ctrlKey) parts.push('Control')
|
||||
if (e.altKey) parts.push('Alt')
|
||||
if (e.shiftKey) parts.push('Shift')
|
||||
if (e.metaKey) parts.push('Super')
|
||||
let key: string | null = null
|
||||
if (/^Key[A-Z]$/.test(e.code)) key = e.code.slice(3)
|
||||
else if (/^Digit\d$/.test(e.code)) key = e.code.slice(5)
|
||||
else if (/^F\d{1,2}$/.test(e.code)) key = e.code
|
||||
else if (/^Numpad[0-9]$/.test(e.code)) key = `num${e.code.slice(6)}`
|
||||
else {
|
||||
const named: Record<string, string> = {
|
||||
Space: 'Space', Tab: 'Tab', Enter: 'Return', NumpadEnter: 'Return',
|
||||
ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left', ArrowRight: 'Right',
|
||||
Home: 'Home', End: 'End', PageUp: 'PageUp', PageDown: 'PageDown',
|
||||
Insert: 'Insert', Delete: 'Delete', Backspace: 'Backspace',
|
||||
Minus: '-', Equal: '=', BracketLeft: '[', BracketRight: ']', Backslash: '\\',
|
||||
Semicolon: ';', Quote: "'", Comma: ',', Period: '.', Slash: '/', Backquote: '`',
|
||||
NumpadAdd: 'numadd', NumpadSubtract: 'numsub',
|
||||
NumpadMultiply: 'nummult', NumpadDivide: 'numdiv', NumpadDecimal: 'numdec'
|
||||
}
|
||||
key = named[e.code] ?? null
|
||||
}
|
||||
if (!key) return null
|
||||
// Guard against hijacking plain typing: a global shortcut must involve a
|
||||
// modifier, unless it is a function key (F1-F12 are safe standalone).
|
||||
const isFunctionKey = /^F\d{1,2}$/.test(key)
|
||||
if (parts.length === 0 && !isFunctionKey) return null
|
||||
parts.push(key)
|
||||
return parts.join('+')
|
||||
}
|
||||
|
||||
/**
|
||||
* Press-to-record input for the global shortcut: focus it, hit the combo, the
|
||||
* accelerator is captured and saved. Esc cancels recording, Backspace/Delete
|
||||
* clears (disables) the shortcut. Read-only so no stray text can be typed.
|
||||
*/
|
||||
function ShortcutInput({ value, onChange }: { value: string; onChange: (v: string) => void }): React.JSX.Element {
|
||||
const [recording, setRecording] = useState(false)
|
||||
return (
|
||||
<Input
|
||||
className={`settings-select shortcut-input${recording ? ' is-recording' : ''}`}
|
||||
readOnly
|
||||
value={recording ? '按下组合键…' : value}
|
||||
placeholder="点击后按下快捷键,留空禁用"
|
||||
onFocus={() => setRecording(true)}
|
||||
onBlur={() => setRecording(false)}
|
||||
onKeyDown={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (e.key === 'Escape') {
|
||||
e.currentTarget.blur()
|
||||
return
|
||||
}
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
onChange('')
|
||||
e.currentTarget.blur()
|
||||
return
|
||||
}
|
||||
const accel = acceleratorFromEvent(e)
|
||||
if (accel) {
|
||||
onChange(accel)
|
||||
e.currentTarget.blur()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Live preview of the current font settings, rendered as an `ls -la`-style
|
||||
* terminal screen with ANSI-colored tokens on the active theme background.
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.settings-body .ant-tabs-content-holder {
|
||||
/* antd v6 renamed the scroll container: content-holder -> body-holder. */
|
||||
.settings-body .ant-tabs-body-holder {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -240,8 +241,6 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 2px 12px;
|
||||
}
|
||||
|
||||
@@ -534,3 +533,15 @@
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ---------- shortcut recorder (press-to-record input) ---------- */
|
||||
|
||||
.shortcut-input {
|
||||
cursor: pointer;
|
||||
font-family: 'Cascadia Mono', Consolas, 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.shortcut-input.is-recording,
|
||||
.shortcut-input.is-recording .ant-input {
|
||||
border-color: var(--tab-accent, #3fb950);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { FitAddon } from '@xterm/addon-fit'
|
||||
import { WebglAddon } from '@xterm/addon-webgl'
|
||||
import { SearchAddon } from '@xterm/addon-search'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { ClearOutlined, CopyOutlined, FolderOpenOutlined, PauseOutlined, SearchOutlined, SelectOutlined, SnippetsOutlined, SoundOutlined } from '@ant-design/icons'
|
||||
import { ClearOutlined, CopyOutlined, ExportOutlined, FolderOpenOutlined, PauseOutlined, SearchOutlined, SelectOutlined, SnippetsOutlined, SoundOutlined } from '@ant-design/icons'
|
||||
import { Checkbox, Modal } from 'antd'
|
||||
import { getThemeById } from '@shared/theme'
|
||||
import type { CommandItem } from '@shared/commands'
|
||||
@@ -83,6 +83,8 @@ export interface TerminalViewProps {
|
||||
/** called once after the pty process exits and the user closes the dead pane */
|
||||
onClose: () => void
|
||||
className?: string
|
||||
/** ssh sessions have a remote cwd — the open-directory button is hidden */
|
||||
isSsh?: boolean
|
||||
}
|
||||
|
||||
interface SearchBarProps {
|
||||
@@ -248,7 +250,7 @@ function differenceWrite(sessionId: string, full: string, buf: string): void {
|
||||
}
|
||||
|
||||
export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?: Ref<TerminalHandle> }> =
|
||||
forwardRef<TerminalHandle, TerminalViewProps>(function TerminalViewInner({ sessionId, onClose, className }, ref) {
|
||||
forwardRef<TerminalHandle, TerminalViewProps>(function TerminalViewInner({ sessionId, onClose, className, isSsh = false }, ref) {
|
||||
const settings = useSettingsStore((s) => s.settings)
|
||||
|
||||
const hostRef = useRef<HTMLDivElement | null>(null)
|
||||
@@ -1164,27 +1166,45 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
||||
/>
|
||||
)}
|
||||
<div className="term-recbar">
|
||||
<button
|
||||
type="button"
|
||||
className={`term-rec-btn${recording ? ' is-recording' : ''}`}
|
||||
title={recording ? '停止记录会话日志' : '开始记录会话日志'}
|
||||
aria-label={recording ? '停止记录' : '开始记录'}
|
||||
onClick={() => {
|
||||
if (recording) void handleLogStop()
|
||||
else void handleLogStart()
|
||||
}}
|
||||
>
|
||||
{recording ? <PauseOutlined /> : <SoundOutlined />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="term-rec-btn"
|
||||
title="打开日志目录"
|
||||
aria-label="打开日志目录"
|
||||
onClick={() => window.api.openLogsDir()}
|
||||
>
|
||||
<FolderOpenOutlined />
|
||||
</button>
|
||||
{settings.terminal.showRecButton && (
|
||||
<button
|
||||
type="button"
|
||||
className={`term-rec-btn${recording ? ' is-recording' : ''}`}
|
||||
title={recording ? '停止记录会话日志' : '开始记录会话日志'}
|
||||
aria-label={recording ? '停止记录' : '开始记录'}
|
||||
onClick={() => {
|
||||
if (recording) void handleLogStop()
|
||||
else void handleLogStart()
|
||||
}}
|
||||
>
|
||||
{recording ? <PauseOutlined /> : <SoundOutlined />}
|
||||
</button>
|
||||
)}
|
||||
{settings.terminal.showOpenLogsButton && (
|
||||
<button
|
||||
type="button"
|
||||
className="term-rec-btn"
|
||||
title="打开日志目录"
|
||||
aria-label="打开日志目录"
|
||||
onClick={() => window.api.openLogsDir()}
|
||||
>
|
||||
<FolderOpenOutlined />
|
||||
</button>
|
||||
)}
|
||||
{settings.terminal.showOpenCwdButton && !isSsh && (
|
||||
<button
|
||||
type="button"
|
||||
className="term-rec-btn"
|
||||
title="打开工作区目录"
|
||||
aria-label="打开工作区目录"
|
||||
onClick={() => {
|
||||
const dir = getSessionCwd(sessionId)
|
||||
if (dir) void window.api.openDirectory(dir)
|
||||
}}
|
||||
>
|
||||
<ExportOutlined />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Modal
|
||||
open={paste !== null}
|
||||
|
||||
@@ -59,6 +59,7 @@ export function TerminalPanel({ params, api, onSessionDead }: TerminalPanelProps
|
||||
const terminal = (
|
||||
<TerminalView
|
||||
sessionId={sessionId}
|
||||
isSsh={isSsh}
|
||||
className="workspace-terminal-view"
|
||||
onClose={() => {
|
||||
if (closedRef.current) return
|
||||
|
||||
@@ -80,6 +80,8 @@ export interface AppApi {
|
||||
logStop(sessionId: string): Promise<void>
|
||||
listSessionLogs(): Promise<SessionLogMeta[]>
|
||||
openLogsDir(): void
|
||||
/** open a local directory in the OS file manager; false if not a directory */
|
||||
openDirectory(dir: string): Promise<boolean>
|
||||
|
||||
// ---- ssh connections ----
|
||||
listConnections(): Promise<SshConnection[]>
|
||||
|
||||
+3
-1
@@ -106,7 +106,9 @@ export const Ipc = {
|
||||
/** resolve a cd-style argument against the current cwd, platform-aware */
|
||||
CWD_RESOLVE: 'session:cwdResolve',
|
||||
/** normalize a cwd reported by the shell (OSC 7 / OSC 9;9) */
|
||||
CWD_REPORT: 'session:cwdReport'
|
||||
CWD_REPORT: 'session:cwdReport',
|
||||
/** open a local directory in the OS file manager (terminal toolbar) */
|
||||
CWD_OPEN: 'session:cwdOpen'
|
||||
} as const
|
||||
|
||||
export interface PtyCreateOptions {
|
||||
|
||||
+11
-2
@@ -40,6 +40,12 @@ export interface TerminalSettings {
|
||||
historyEnabled: boolean
|
||||
/** max entries kept in command history */
|
||||
historyLimit: number
|
||||
/** terminal toolbar: session-log record button */
|
||||
showRecButton: boolean
|
||||
/** terminal toolbar: open logs folder button */
|
||||
showOpenLogsButton: boolean
|
||||
/** terminal toolbar: open working-directory button (local sessions only) */
|
||||
showOpenCwdButton: boolean
|
||||
/** accent color for the active tab outline, rail indicator and SSH badges */
|
||||
tabAccentColor: string
|
||||
}
|
||||
@@ -117,9 +123,12 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
autoWrap: true,
|
||||
copyOnSelect: false,
|
||||
pasteRiskConfirm: true,
|
||||
suggestEnabled: true,
|
||||
historyEnabled: true,
|
||||
suggestEnabled: false,
|
||||
historyEnabled: false,
|
||||
historyLimit: 100,
|
||||
showRecButton: false,
|
||||
showOpenLogsButton: false,
|
||||
showOpenCwdButton: true,
|
||||
tabAccentColor: '#3fb950'
|
||||
},
|
||||
customThemes: [],
|
||||
|
||||
Reference in new issue
Block a user