Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cff5454346 |
No files matched your search
@@ -116,6 +116,7 @@ Electron + electron-vite + React 终端工具(本地终端 / SSH / SFTP)。
|
||||
- 会话计数不靠累加器:`releaseSession` 直接扫 `api.panels` 判断还有没有面板在显示该会话——累加器与「`updateParameters` 原地换会话」「整块布局替换」这类无事件变化脱节,会漏杀或误杀
|
||||
- **旧版(M6.1 前单 dockview)模板只喂 terminal dockview**:成功路径的 rebind 与收尾 kill 都必须按 `sshTouched` / `live` 集合门控——rebind 会把面板参数改写成新**本地**会话,对没被 fromJSON 触碰的 ssh dockview 跑一遍等于把活 SSH 面板降级成终端再杀会话
|
||||
- 主机密钥队列只渲染队头,且主进程 `DEFAULT_TIMEOUTS.prompt`(120s)超时后**不再广播**:渲染层用同 TTL 的定时清扫(`HOST_KEY_PROMPT_TTL_MS`)移除过期条目,否则陈旧弹窗永远占住队头挡住后续主机;两处 TTL 必须同步改。`respondHostKey` 等 IPC 调用**不得写进 setState 的 updater**(React 可能重放 updater)
|
||||
- **tab 关闭二次确认**(`settings.terminal.confirmTabClose` / `confirmSshTabClose`,默认关):拦截点在 `TerminalTab.tsx` 的 `requestClose`——dockview 8.2.0 没有可否决的关闭前钩子,杀会话发生在 `onDidRemovePanel` 之后无法回滚,所以所有用户关闭入口(× 按钮、右键三关闭项、键盘 Delete/Backspace)必须在调用 `api.close()` 之前过它;键盘路径靠挂在 `.dv-tab` 外壳(自定义 div 的 dockview 祖先元素,DOM 焦点落点)上的原生捕获监听拦截。**新增用户可触发的关闭入口必须走 `requestClose`**,会话已死自动关闭/模板应用等程序性路径不走
|
||||
|
||||
## 关键词高亮
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# OpenTerminal Changelog
|
||||
|
||||
## v1.0.27 - 2026-10-15
|
||||
|
||||
This release adds close confirmation for terminal tabs: an accidental close no longer kills a terminal or SSH session you are working in — especially the frequent case of pressing Backspace right after clicking a tab.
|
||||
|
||||
### New: confirm before closing tabs
|
||||
- Two new switches under Settings → Rendering & Behavior (**off by default**; leaving them off keeps the previous behaviour exactly):
|
||||
- **Confirm before closing terminal tabs**: ask before closing a local terminal tab
|
||||
- **Confirm before closing SSH tabs**: ask before closing an SSH connection tab (SSH disconnects are costly, so you can enable it for SSH only)
|
||||
- Covers every user-initiated close path: the tab × button, the context menu's Close / Close others / Close right, and the Delete / Backspace keys
|
||||
- "Close others" / "Close right" ask once for the whole batch, with the number of tabs shown in the dialog
|
||||
- Pressing Backspace / Delete while a tab has keyboard focus (the main source of accidental closes) no longer closes it directly — it goes through the same confirmation
|
||||
- Switches take effect immediately after saving, no restart needed; typing Backspace / Delete inside the terminal is completely unaffected
|
||||
- Programmatic closes (a session exiting on its own, applying a layout template) never prompt
|
||||
|
||||
## v1.0.26 - 2026-10-11
|
||||
|
||||
This release is about performance and diagnosability: it fixes the multi-second UI freezes when dragging to resize the window or when a terminal floods output, and adds an on-disk diagnostic log (on by default, auto-cleaned after 7 days) so freeze reports finally come with evidence.
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# OpenTerminal 更新履歴
|
||||
|
||||
## v1.0.27 - 2026-10-15
|
||||
|
||||
本リリースではタブを閉じる前の確認機能を追加しました。うっかり閉じて作業中のターミナルや SSH セッションを終了させてしまう——特にタブをクリックした直後に Backspace を押してタブごと閉じてしまう操作——を防ぎます。
|
||||
|
||||
### 新機能:タブを閉じる前の確認
|
||||
- 設定 → 描画と動作 に 2 つのスイッチを追加(**既定はオフ**。オフのままなら従来の動作と完全に同じ):
|
||||
- **ターミナルタブを閉じる前に確認**:ローカルターミナルタブを閉じる前に確認ダイアログを表示
|
||||
- **SSH タブを閉じる前に確認**:SSH 接続タブを閉じる前に確認ダイアログを表示(SSH の切断はコストが高いため、SSH のみ有効にすることもできます)
|
||||
- ユーザーによるすべての閉じ操作を対象:タブの × ボタン、右クリックメニューの「閉じる / ほかを閉じる / 右側を閉じる」、および Delete / Backspace キー
|
||||
- 「ほかを閉じる / 右側を閉じる」はバッチ全体で 1 回だけ確認し、閉じるタブ数を表示します
|
||||
- タブにキーボードフォーカスがある状態での Backspace / Delete は直接閉じず、同じ確認フローを通ります——誤操作の主な原因でした
|
||||
- スイッチは保存後すぐに反映され、再起動は不要です。ターミナル内での通常の Backspace / Delete 入力には一切影響しません
|
||||
- セッションの自然終了やレイアウトテンプレート適用などのプログラム的な閉じでは確認しません
|
||||
|
||||
## v1.0.26 - 2026-10-11
|
||||
|
||||
今回の重点はパフォーマンスと診断性です:ウィンドウのドラッグリサイズや端末の高頻度出力で UI 全体が数秒間フリーズする問題を修正し、診断ログの保存(デフォルト有効・7 日で自動削除)を追加しました。フリーズ報告に証拠が添えられるようになります。
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# OpenTerminal 更新日志
|
||||
|
||||
## v1.0.27 - 2026-10-15
|
||||
|
||||
本次新增标签页关闭二次确认:误触关闭不再直接杀掉正在工作的终端或 SSH 会话,尤其是点击标签后顺手按 Backspace 把整个 tab 关掉的高频误操作。
|
||||
|
||||
### 新增:关闭标签二次确认
|
||||
- 设置 → 渲染与行为 新增两个开关(**默认关闭**,不开启则与原行为完全一致):
|
||||
- **关闭终端标签二次确认**:关闭本地终端标签前弹出确认框
|
||||
- **关闭 SSH 连接标签二次确认**:关闭 SSH 连接标签前弹出确认框(SSH 断开代价高,可以只对它开启)
|
||||
- 覆盖所有用户关闭入口:标签 × 按钮、右键菜单的「关闭 / 关闭其他 / 关闭右侧」、以及键盘 Delete / Backspace
|
||||
- 「关闭其他 / 关闭右侧」对整批只弹一次确认,确认框会显示将关闭的数量
|
||||
- 点击标签获得焦点后按 Backspace / Delete 不再直接关闭标签,而是走同样的确认逻辑——这是误关闭的主要来源
|
||||
- 开关保存后立即生效,无需重启;终端内正常输入退格、删除完全不受影响
|
||||
- 会话自然退出、应用布局模板等程序性关闭不弹确认
|
||||
|
||||
## v1.0.26 - 2026-10-11
|
||||
|
||||
本次重点是性能与可诊断性:修复了拖动缩放窗口或终端高频输出时整个界面冻结数秒的问题;新增落盘的诊断日志(默认开启,7 天自动清理),以后反馈卡顿可以附上日志,定位不再靠猜。
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# OpenTerminal 更新日誌
|
||||
|
||||
## v1.0.27 - 2026-10-15
|
||||
|
||||
本次新增分頁關閉二次確認:誤觸關閉不再直接殺掉正在工作的終端機或 SSH 連線,尤其是點擊分頁後順手按 Backspace 把整個分頁關掉的高頻誤操作。
|
||||
|
||||
### 新增:關閉分頁二次確認
|
||||
- 設定 → 渲染與行為 新增兩個開關(**預設關閉**,不開啟則與原行為完全一致):
|
||||
- **關閉終端機標籤二次確認**:關閉本地終端機分頁前彈出確認框
|
||||
- **關閉 SSH 連線標籤二次確認**:關閉 SSH 連線分頁前彈出確認框(SSH 斷開代價高,可以只對它開啟)
|
||||
- 涵蓋所有使用者關閉入口:分頁 × 按鈕、右鍵選單的「關閉 / 關閉其他 / 關閉右側」、以及鍵盤 Delete / Backspace
|
||||
- 「關閉其他 / 關閉右側」對整批只彈一次確認,確認框會顯示將關閉的數量
|
||||
- 點擊分頁取得焦點後按 Backspace / Delete 不再直接關閉分頁,而是走同樣的確認邏輯——這是誤關閉的主要來源
|
||||
- 開關儲存後立即生效,無需重啟;終端機內正常輸入退格、刪除完全不受影響
|
||||
- 連線自然退出、套用版面配置範本等程式性關閉不彈確認
|
||||
|
||||
## v1.0.26 - 2026-10-11
|
||||
|
||||
本次重點是效能與可診斷性:修復拖曳縮放視窗或終端機高頻輸出時整個介面凍結數秒的問題;新增落盤的診斷日誌(預設開啟,7 天自動清理),日後回報卡頓可附上月誌,定位不再靠猜。
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open-terminal",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.27",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-terminal",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.27",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lydell/node-pty": "^1.2.0-beta.15",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "open-terminal",
|
||||
"productName": "OpenTerminal",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.27",
|
||||
"description": "Open-source terminal with SSH, split panes, themes and fonts",
|
||||
"main": "out/main/index.js",
|
||||
"author": "CodingPlan.Site",
|
||||
|
||||
@@ -275,6 +275,26 @@ export function RenderSettingsTab(): React.JSX.Element {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label={t('settings.render.confirmTabClose')}
|
||||
desc={t('settings.render.confirmTabCloseDesc')}
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.confirmTabClose}
|
||||
onChange={(checked) => void updateTerminal({ confirmTabClose: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label={t('settings.render.confirmSshTabClose')}
|
||||
desc={t('settings.render.confirmSshTabCloseDesc')}
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.confirmSshTabClose}
|
||||
onChange={(checked) => void updateTerminal({ confirmSshTabClose: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label={t('settings.render.suggest')}
|
||||
desc={t('settings.render.suggestDesc')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { App, Button, Dropdown, Input, Modal, Popconfirm, Space } from 'antd'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { CloudServerOutlined } from '@ant-design/icons'
|
||||
@@ -22,13 +22,18 @@ export type TerminalTabProps = IDockviewPanelHeaderProps<{
|
||||
* 关闭 / 关闭其他 / 关闭右侧
|
||||
*
|
||||
* SSH sessions get a 🖥 server icon and a green left-border accent so they're
|
||||
* visually distinct from local terminals. All menu actions close panels
|
||||
* directly through `api.close()`. The workspace's `onDidRemovePanel` handles
|
||||
* the pty kill, so nothing leaks.
|
||||
* visually distinct from local terminals. Every user-initiated close button,
|
||||
* menu action and the tablist's Delete/Backspace accelerator goes through
|
||||
* `requestClose`, which may ask for confirmation first (see below). Whatever
|
||||
* the path, the workspace's `onDidRemovePanel` handles the pty kill, so
|
||||
* nothing leaks.
|
||||
*/
|
||||
export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Element {
|
||||
const panel = api.group // bound panel via the panel api
|
||||
type TabPanel = (typeof panel.panels)[number]
|
||||
const [title, setTitle] = useState<string | undefined>(api.title)
|
||||
const [pending, setPending] = useState<TabPanel[] | null>(null)
|
||||
const rootRef = useRef<HTMLDivElement>(null)
|
||||
const selfIndex = panel.panels.findIndex((p) => p.api === api)
|
||||
|
||||
// dockview keeps the tab component in its own render slot — a re-render of
|
||||
@@ -56,10 +61,86 @@ export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Elemen
|
||||
return () => disposable.dispose()
|
||||
}, [api])
|
||||
|
||||
const closePanel = (target: typeof panel.panels[number]): void => {
|
||||
target.api.close()
|
||||
// `api.group` is read live rather than reusing the render-time `panel`:
|
||||
// dragging this tab into another group re-parents the DOM without re-mounting
|
||||
// the React component (dockview keeps the portal element and only swaps the
|
||||
// `.dv-tab` shell around it), so anything captured at render would still name
|
||||
// the group this tab has left.
|
||||
const selfPanel = (): TabPanel | undefined => api.group.panels.find((p) => p.api === api)
|
||||
|
||||
// Confirmation has to happen *before* `api.close()`: dockview has no
|
||||
// vetoable close hook, and the workspace kills the session from
|
||||
// `onDidRemovePanel`, which is already too late to ask. Every user-initiated
|
||||
// close entry point funnels through here; programmatic closes (a dead
|
||||
// session, applying a layout template) call `api.close()` directly.
|
||||
//
|
||||
// Read the settings at call time: this runs from long-lived menu / keydown
|
||||
// handlers, so a value captured at render would go stale after a change.
|
||||
const needsConfirmFor = (targets: TabPanel[]): boolean => {
|
||||
const terminal = useSettingsStore.getState().settings.terminal
|
||||
return targets.some((p) =>
|
||||
(p.params as { sessionKind?: 'local' | 'ssh' } | undefined)?.sessionKind === 'ssh'
|
||||
? terminal.confirmSshTabClose
|
||||
: terminal.confirmTabClose
|
||||
)
|
||||
}
|
||||
|
||||
const requestClose = (targets: TabPanel[]): void => {
|
||||
if (targets.length === 0) return
|
||||
if (!needsConfirmFor(targets)) {
|
||||
for (const target of targets) target.api.close()
|
||||
return
|
||||
}
|
||||
setPending(targets)
|
||||
}
|
||||
|
||||
const closePending = (): void => {
|
||||
if (!pending) return
|
||||
// Liveness filter: something else (a dead session, another tab's menu) may
|
||||
// have closed a target while the dialog was up. Asked of the panel itself —
|
||||
// its `group` follows it across groups, unlike a render-time snapshot.
|
||||
for (const target of pending) if (target.group.panels.includes(target)) target.api.close()
|
||||
setPending(null)
|
||||
}
|
||||
|
||||
// dockview closes the focused tab on Delete/Backspace from a keydown listener
|
||||
// on the tab list, but only when the event target *is* the `.dv-tab` shell —
|
||||
// which is what has focus, and an ancestor of this component's root, so a
|
||||
// React handler on the root never sees the key. The listener therefore has to
|
||||
// be native, on the shell, in the capture phase (dockview listens during the
|
||||
// bubble phase). It is attached to the document rather than to the shell
|
||||
// itself because dragging a tab into another group re-parents this component's
|
||||
// DOM into a fresh shell *without* re-mounting or re-rendering it: a
|
||||
// shell-scoped listener would be stranded on the discarded element and
|
||||
// Delete/Backspace would silently stop working for that tab. Everything the
|
||||
// handler reads is resolved live (`api.group`, the settings), so the closure
|
||||
// from the first render stays correct.
|
||||
//
|
||||
// The event is only swallowed when a confirmation is actually required;
|
||||
// otherwise dockview's own handler runs as before, focus moved to the
|
||||
// neighbouring tab included, so "off" is indistinguishable from the old
|
||||
// behaviour.
|
||||
useEffect(() => {
|
||||
const doc = rootRef.current?.ownerDocument ?? document
|
||||
const onKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key !== 'Delete' && event.key !== 'Backspace') return
|
||||
const shell = rootRef.current?.closest('.dv-tab')
|
||||
if (!shell || event.target !== shell) return
|
||||
// Only the real tab strip: the overflow dropdown renders a second
|
||||
// `.dv-tab`-classed wrapper around a copy of this component, and dockview's
|
||||
// own handler is scoped to the tab list, so match that scope instead of
|
||||
// closing a panel the platform would have left alone.
|
||||
if (!shell.closest('.dv-tabs-container')) return
|
||||
const self = selfPanel()
|
||||
if (!self || !needsConfirmFor([self])) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
requestClose([self])
|
||||
}
|
||||
doc.addEventListener('keydown', onKeyDown, true)
|
||||
return () => doc.removeEventListener('keydown', onKeyDown, true)
|
||||
}, [api])
|
||||
|
||||
const menu: MenuProps = {
|
||||
items: [
|
||||
{ key: 'close', label: tFor(language, 'common.close') },
|
||||
@@ -67,26 +148,32 @@ export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Elemen
|
||||
{ key: 'closeRight', label: tFor(language, 'workspace.tab.closeRight'), disabled: selfIndex === -1 || selfIndex === panel.panels.length - 1 }
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'close') {
|
||||
const self = selfPanel()
|
||||
if (!self) {
|
||||
api.close()
|
||||
} else if (key === 'closeOthers') {
|
||||
// `panel.panels` is the live group array and `close()` splices it, so
|
||||
return
|
||||
}
|
||||
// `api.group.panels` is the live group array and `close()` splices it, so
|
||||
// the targets must be snapshotted first — otherwise every element that
|
||||
// shifts into the current index is skipped.
|
||||
for (const sibling of [...panel.panels]) {
|
||||
if (sibling.api !== api) closePanel(sibling)
|
||||
}
|
||||
// shifts into the current index is skipped. Read through `api.group`
|
||||
// rather than a render-time copy in case the tab has since been dragged
|
||||
// into another group.
|
||||
const siblings = [...api.group.panels]
|
||||
if (key === 'close') {
|
||||
requestClose([self])
|
||||
} else if (key === 'closeOthers') {
|
||||
requestClose(siblings.filter((sibling) => sibling.api !== api))
|
||||
} else if (key === 'closeRight') {
|
||||
for (const target of [...panel.panels].slice(selfIndex + 1)) {
|
||||
closePanel(target)
|
||||
}
|
||||
requestClose(siblings.slice(siblings.indexOf(self) + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown menu={menu} trigger={['contextMenu']}>
|
||||
<div
|
||||
ref={rootRef}
|
||||
className={`workspace-terminal-tab${isSsh ? ' workspace-terminal-tab-ssh' : ''}`}
|
||||
title={tooltip}
|
||||
>
|
||||
@@ -110,13 +197,29 @@ export function TerminalTab({ api, params }: TerminalTabProps): React.JSX.Elemen
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.close()
|
||||
const self = selfPanel()
|
||||
if (self) requestClose([self])
|
||||
}}
|
||||
>
|
||||
<CloseGlyph />
|
||||
</button>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Modal
|
||||
open={pending !== null}
|
||||
title={tFor(language, 'workspace.tab.closeConfirmTitle')}
|
||||
okText={tFor(language, 'workspace.tab.closeConfirmOk')}
|
||||
okButtonProps={{ danger: true }}
|
||||
cancelText={tFor(language, 'common.cancel')}
|
||||
maskClosable={false}
|
||||
onOk={closePending}
|
||||
onCancel={() => setPending(null)}
|
||||
>
|
||||
{pending && pending.length === 1
|
||||
? tFor(language, 'workspace.tab.closeConfirmOne', { title: pending[0].title ?? '' })
|
||||
: tFor(language, 'workspace.tab.closeConfirmMany', { count: pending?.length ?? 0 })}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,12 @@ const settings: Record<string, string> = {
|
||||
'settings.render.pasteRiskConfirm': 'Confirm risky paste',
|
||||
'settings.render.pasteRiskConfirmDesc':
|
||||
'Ask before pasting multi-line text (or a very long single line); single lines paste directly',
|
||||
'settings.render.confirmTabClose': 'Confirm before closing terminal tabs',
|
||||
'settings.render.confirmTabCloseDesc':
|
||||
'Ask for confirmation before closing a local terminal tab',
|
||||
'settings.render.confirmSshTabClose': 'Confirm before closing SSH tabs',
|
||||
'settings.render.confirmSshTabCloseDesc':
|
||||
'Ask for confirmation before closing an SSH connection tab',
|
||||
'settings.render.suggest': 'Input suggestions',
|
||||
'settings.render.suggestDesc':
|
||||
'Show command suggestions while typing; Tab accepts, Enter always runs the command right away',
|
||||
|
||||
@@ -4,6 +4,10 @@ const workspace: Record<string, string> = {
|
||||
'workspace.tab.localTerminal': 'Local terminal',
|
||||
'workspace.tab.closeOthers': 'Close others',
|
||||
'workspace.tab.closeRight': 'Close to the right',
|
||||
'workspace.tab.closeConfirmTitle': 'Close tab?',
|
||||
'workspace.tab.closeConfirmOne': 'Close "{title}"? Its session will be terminated.',
|
||||
'workspace.tab.closeConfirmMany': 'Close {count} tabs? Their sessions will be terminated.',
|
||||
'workspace.tab.closeConfirmOk': 'Close',
|
||||
'workspace.tab.broadcastTarget': 'Broadcast target',
|
||||
'workspace.tab.broadcastBadge': 'B',
|
||||
'workspace.tab.addInSplit': 'New terminal in this split',
|
||||
|
||||
@@ -72,6 +72,12 @@ const settings: Record<string, string> = {
|
||||
'settings.render.pasteRiskConfirm': '貼り付け前の確認',
|
||||
'settings.render.pasteRiskConfirmDesc':
|
||||
'複数行(または極端に長い 1 行)を貼り付ける前に確認します。1 行はそのまま貼り付けます',
|
||||
'settings.render.confirmTabClose': 'ターミナルタブを閉じる前に確認',
|
||||
'settings.render.confirmTabCloseDesc':
|
||||
'ローカルターミナルタブを閉じる前に確認ダイアログを表示',
|
||||
'settings.render.confirmSshTabClose': 'SSH タブを閉じる前に確認',
|
||||
'settings.render.confirmSshTabCloseDesc':
|
||||
'SSH 接続タブを閉じる前に確認ダイアログを表示',
|
||||
'settings.render.suggest': '入力候補',
|
||||
'settings.render.suggestDesc':
|
||||
'入力中にコマンド候補を表示します。Tab で確定、Enter は常にそのまま実行します',
|
||||
|
||||
@@ -4,6 +4,10 @@ const workspace: Record<string, string> = {
|
||||
'workspace.tab.localTerminal': 'ローカルターミナル',
|
||||
'workspace.tab.closeOthers': 'ほかを閉じる',
|
||||
'workspace.tab.closeRight': '右側を閉じる',
|
||||
'workspace.tab.closeConfirmTitle': 'タブを閉じますか?',
|
||||
'workspace.tab.closeConfirmOne': '「{title}」を閉じますか?対応するセッションは終了します。',
|
||||
'workspace.tab.closeConfirmMany': '{count} 個のタブを閉じますか?対応するセッションは終了します。',
|
||||
'workspace.tab.closeConfirmOk': '閉じる',
|
||||
'workspace.tab.broadcastTarget': 'ブロードキャスト対象',
|
||||
'workspace.tab.broadcastBadge': '配',
|
||||
'workspace.tab.addInSplit': 'この分割に新しいターミナル',
|
||||
|
||||
@@ -71,6 +71,10 @@ const settings: Record<string, string> = {
|
||||
'settings.render.copyOnSelectDesc': '鼠标选中文字时自动复制',
|
||||
'settings.render.pasteRiskConfirm': '粘贴风险确认',
|
||||
'settings.render.pasteRiskConfirmDesc': '多行文本(或超长单行)粘贴前弹出确认;单行直接粘贴',
|
||||
'settings.render.confirmTabClose': '关闭终端标签二次确认',
|
||||
'settings.render.confirmTabCloseDesc': '关闭本地终端标签前弹出确认框',
|
||||
'settings.render.confirmSshTabClose': '关闭 SSH 连接标签二次确认',
|
||||
'settings.render.confirmSshTabCloseDesc': '关闭 SSH 连接标签前弹出确认框',
|
||||
'settings.render.suggest': '输入建议',
|
||||
'settings.render.suggestDesc': '输入时弹出命令建议;Tab 接受,回车始终直接执行',
|
||||
'settings.render.history': '记录命令历史',
|
||||
|
||||
@@ -4,6 +4,10 @@ const workspace: Record<string, string> = {
|
||||
'workspace.tab.localTerminal': '本地终端',
|
||||
'workspace.tab.closeOthers': '关闭其他',
|
||||
'workspace.tab.closeRight': '关闭右侧',
|
||||
'workspace.tab.closeConfirmTitle': '确认关闭标签',
|
||||
'workspace.tab.closeConfirmOne': '确定要关闭「{title}」吗?对应的会话将被终止。',
|
||||
'workspace.tab.closeConfirmMany': '确定要关闭 {count} 个标签吗?对应的会话将被终止。',
|
||||
'workspace.tab.closeConfirmOk': '关闭',
|
||||
'workspace.tab.broadcastTarget': '广播目标',
|
||||
'workspace.tab.broadcastBadge': '播',
|
||||
'workspace.tab.addInSplit': '在此分屏中新建终端',
|
||||
|
||||
@@ -71,6 +71,10 @@ const settings: Record<string, string> = {
|
||||
'settings.render.copyOnSelectDesc': '以滑鼠選取文字時自動複製',
|
||||
'settings.render.pasteRiskConfirm': '貼上風險確認',
|
||||
'settings.render.pasteRiskConfirmDesc': '多行文字(或超長單行)貼上前彈出確認;單行直接貼上',
|
||||
'settings.render.confirmTabClose': '關閉終端標籤二次確認',
|
||||
'settings.render.confirmTabCloseDesc': '關閉本地終端標籤前彈出確認框',
|
||||
'settings.render.confirmSshTabClose': '關閉 SSH 連線標籤二次確認',
|
||||
'settings.render.confirmSshTabCloseDesc': '關閉 SSH 連線標籤前彈出確認框',
|
||||
'settings.render.suggest': '輸入建議',
|
||||
'settings.render.suggestDesc': '輸入時顯示指令建議;Tab 接受,Enter 一律直接執行',
|
||||
'settings.render.history': '記錄指令歷史',
|
||||
|
||||
@@ -4,6 +4,10 @@ const workspace: Record<string, string> = {
|
||||
'workspace.tab.localTerminal': '本機終端機',
|
||||
'workspace.tab.closeOthers': '關閉其他',
|
||||
'workspace.tab.closeRight': '關閉右側',
|
||||
'workspace.tab.closeConfirmTitle': '確認關閉標籤',
|
||||
'workspace.tab.closeConfirmOne': '確定要關閉「{title}」嗎?對應的會話將被終止。',
|
||||
'workspace.tab.closeConfirmMany': '確定要關閉 {count} 個標籤嗎?對應的會話將被終止。',
|
||||
'workspace.tab.closeConfirmOk': '關閉',
|
||||
'workspace.tab.broadcastTarget': '廣播目標',
|
||||
'workspace.tab.broadcastBadge': '播',
|
||||
'workspace.tab.addInSplit': '在此分割中新增終端機',
|
||||
|
||||
@@ -66,6 +66,10 @@ export interface TerminalSettings {
|
||||
copyOnSelect: boolean
|
||||
/** confirm before pasting multi-line / suspicious text */
|
||||
pasteRiskConfirm: boolean
|
||||
/** Ask for confirmation before a user-initiated close of a local terminal tab. */
|
||||
confirmTabClose: boolean
|
||||
/** Same for SSH connection tabs. */
|
||||
confirmSshTabClose: boolean
|
||||
/** inline command suggestion popup while typing (history + library) */
|
||||
suggestEnabled: boolean
|
||||
/** record executed commands into the history panel */
|
||||
@@ -476,6 +480,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
rendererMode: 'auto',
|
||||
copyOnSelect: false,
|
||||
pasteRiskConfirm: true,
|
||||
confirmTabClose: false,
|
||||
confirmSshTabClose: false,
|
||||
suggestEnabled: false,
|
||||
historyEnabled: false,
|
||||
historyLimit: 100,
|
||||
|
||||
@@ -185,6 +185,17 @@ console.log('[value bands]')
|
||||
ok(back.terminal.highlightStats === on, `the stats switch '${on}' survives the round trip`)
|
||||
}
|
||||
}
|
||||
{
|
||||
for (const on of [true, false]) {
|
||||
const back = await roundTrip({
|
||||
terminal: { ...store.loadSettings().terminal, confirmTabClose: on, confirmSshTabClose: on }
|
||||
})
|
||||
ok(
|
||||
back.terminal.confirmTabClose === on && back.terminal.confirmSshTabClose === on,
|
||||
`tab close confirm switches '${on}' survive the round trip`
|
||||
)
|
||||
}
|
||||
}
|
||||
{
|
||||
const rules = [rule({ id: 'keep-1' }), rule({ id: 'keep-2' })]
|
||||
await roundTrip({ highlightRules: rules })
|
||||
|
||||
Reference in new issue
Block a user