mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
refactor(settings): rescope the About page
About had grown three sections that change system state and that nobody looks for under "About": a PATH install, a registry write, and a daemon restart. Two of them move out. The `tty7` CLI goes to Agents. That page already describes tty7 <-> agent integration in one direction (hooks reporting session status); the CLI is the other direction, and its own description leads with "so scripts and coding agents can drive tty7". The Loading and Unavailable arms there no longer return early, since the CLI toggle is about this GUI's own host rather than whichever machine the hook rows describe. The Windows Explorer context menu goes to the installer, which is where VS Code and Git for Windows put theirs: writing shell verbs is an install-time decision, not a runtime preference. A task checkbox drives new `--register-explorer-menu` / `--unregister-explorer-menu` flags, so the key layout stays in core::explorer_context_menu instead of being copied into the .iss. `status()` existed only to paint the settings UI and goes with it. The uninstaller unregisters unconditionally: an install that registered once and was later upgraded without the box ticked still holds keys that would otherwise point at a deleted exe. Server restart stays — it is about the app itself. Also fixes localization the About section had skipped: eight hardcoded English strings in the update block now have keys, and the orphaned SettingsCheckUpdatesDesc key (which still claimed "tty7 never updates itself", contradicted by the macOS in-app updater) is reused for a one-line description in place of a 60-word account of the updater's internals. Finally, terminology in the Chinese UI. hook, agent, worktree, diff and fork are read and spoken in English by Chinese developers, so translating them lost more than it gained. Scrollback was worse than a style question: 回滚 means rollback, the opposite direction. 窗格 for pane is kept — that one is standard.
This commit is contained in:
@@ -51,6 +51,12 @@ RestartApplications=no
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
; Writing shell verbs is an install-time decision, the way VS Code and Git for
|
||||
; Windows treat theirs — not a runtime preference, so tty7 has no setting for
|
||||
; it. Off by default: the registry is user-visible system state. The keys land
|
||||
; under HKCU even for an all-users install, so this only ever affects whoever
|
||||
; ran the installer. Inno restores the previous choice when upgrading.
|
||||
Name: "explorermenu"; Description: "Add ""Open in tty7"" to the folder context menu"; GroupDescription: "Shell integration:"; Flags: unchecked
|
||||
|
||||
; Builds before the tty7/tty7-app split installed the GUI as tty7.exe. Upgrading
|
||||
; only *adds* tty7-app.exe, so the old binary would stay on disk — and a taskbar
|
||||
@@ -89,6 +95,11 @@ Name: "{autoprograms}\tty7"; Filename: "{app}\tty7-app.exe"
|
||||
Name: "{autodesktop}\tty7"; Filename: "{app}\tty7-app.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
; The registry shape lives in core::explorer_context_menu, not here: the app
|
||||
; reads those same keys to decide whether an existing registration still points
|
||||
; at this install, and two hand-kept copies of the layout would drift. Runs
|
||||
; before the launch entry below so a first start already sees the final state.
|
||||
Filename: "{app}\tty7-app.exe"; Parameters: "--register-explorer-menu"; Tasks: explorermenu; Flags: runhidden waituntilterminated
|
||||
Filename: "{app}\tty7-app.exe"; Description: "{cm:LaunchProgram,tty7}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[UninstallRun]
|
||||
@@ -99,6 +110,11 @@ Filename: "{app}\tty7-app.exe"; Description: "{cm:LaunchProgram,tty7}"; Flags: n
|
||||
; the call returns without opening a window. RunOnceId keys the entry so a repeated
|
||||
; uninstall doesn't run it twice.
|
||||
Filename: "{app}\tty7-app.exe"; Parameters: "--stop-daemon"; Flags: runhidden waituntilterminated; RunOnceId: "StopDaemon"
|
||||
; Unconditional, and deliberately not gated on the task: an install that had the
|
||||
; menu registered and was later upgraded without the box ticked still holds the
|
||||
; keys, and verbs pointing at a deleted exe are worse than a no-op. Removing keys
|
||||
; that were never written succeeds silently.
|
||||
Filename: "{app}\tty7-app.exe"; Parameters: "--unregister-explorer-menu"; Flags: runhidden waituntilterminated; RunOnceId: "UnregisterExplorerMenu"
|
||||
|
||||
[Code]
|
||||
(* Gracefully stop the persistent daemon before we overwrite tty7-app.exe. We can't
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ it never wraps or replaces the agent.
|
||||
- **Tray icon** — a system tray / menu bar item that flips to an attention state the moment any agent needs your input; its menu lists every agent pane (brand avatar + status dot, click to reveal), switches the notification policy, and offers *Quit and Stop Daemon* alongside the plain session-keeping quit (`show_tray_icon`, on by default)
|
||||
- **`tty7 wait`** — the CLI's orchestration primitive: block until a pane's agent needs input or finishes its turn (`tty7 wait %3 --until waiting,done --changed --timeout 600`, exit 124 on timeout), so one agent can sleep until its peer blocks on a permission prompt instead of screen-scraping — then `tty7 capture %3 --plain` to read the result. The agent status is a level, not an event, so `--changed` ignores the state the pane was already in when the wait began; without it, the JSON's `stale` flag says whether the answer might belong to the previous turn
|
||||
- **Orchestration skill** — a switch (Settings → Agents) that installs a Claude Code skill (`~/.claude/skills/tty7-orchestration`) teaching a *primary* agent the delegation loop — spawn a worker pane, send it a bounded task, `wait` on it, capture the result. A skill rather than a global instruction on purpose: only its one-line description rides in context until explicitly invoked, and worker agents never inherit orchestration authority
|
||||
- **`tty7` on PATH** — the CLI ships inside every installer and is put on PATH at launch, so a script or a coding agent can drive tty7 from any terminal. Inside a tty7 pane it works regardless, since panes inherit the app's environment. On Unix it is a symlink into whichever of `/opt/homebrew/bin`, `/usr/local/bin`, `~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers; on Windows the install directory is appended to your user PATH, and the uninstaller takes it back out. A `tty7` you installed yourself is left alone, never replaced. Off via Settings → About or `install_cli_on_path: false` in `config.json`
|
||||
- **`tty7` on PATH** — the CLI ships inside every installer and is put on PATH at launch, so a script or a coding agent can drive tty7 from any terminal. Inside a tty7 pane it works regardless, since panes inherit the app's environment. On Unix it is a symlink into whichever of `/opt/homebrew/bin`, `/usr/local/bin`, `~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers; on Windows the install directory is appended to your user PATH, and the uninstaller takes it back out. A `tty7` you installed yourself is left alone, never replaced. Off via Settings → Agents or `install_cli_on_path: false` in `config.json`
|
||||
|
||||
## SSH
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
- **标签页与分屏** —— 永远开在当前目录
|
||||
- **侧栏按仓库分组** —— 左侧标签栏按 git 仓库分组、每组一个标题行,不在仓库里的标签归入末尾的 *Scratch* 组;切分支、仓库内 `cd` 都不会挪动行(`config.json` 的 `sidebar_grouping`:默认 `repo`,`none` 恢复扁平列表)
|
||||
- **命令面板** <kbd>⌘ P</kbd> · 回滚搜索 <kbd>⌘ F</kbd>
|
||||
- **命令面板** <kbd>⌘ P</kbd> · scrollback 搜索 <kbd>⌘ F</kbd>
|
||||
- **⌘ 点击打开链接** · 桌面通知 · 划选即复制(可选,设置 → 终端 → 剪贴板)
|
||||
- **智能双击选中** —— 双击直接选中整条 URL、文件路径、括号/引号对,中文按词典分词出词;Shift 点击扩展选区(设置 → 终端 → 鼠标可开关;分隔符用 `config.json` 的 `word_separators` 配置)
|
||||
- **9 套主题,也能自定义** — YAML 种子主题,背景支持纯色、渐变或图片;可导入 iTerm2 `.itermcolors`;应用内颜色编辑器带背景图选择
|
||||
@@ -63,7 +63,7 @@ Aider、Amp、OpenCode 等约 17 个)并在其外围加功能 —— 绝不包
|
||||
- **托盘图标** —— 系统托盘 / 菜单栏常驻图标,任何 agent 等你输入时立即切换为提醒态;菜单列出所有 agent pane(品牌头像 + 状态点,点击直达)、可切换通知策略,并在保留会话的普通退出之外提供 *Quit and Stop Daemon*(`show_tray_icon`,默认开启)
|
||||
- **`tty7 wait`** —— CLI 的编排原语:阻塞到某个 pane 的 agent 等待输入或完成一轮(`tty7 wait %3 --until waiting,done --changed --timeout 600`,超时退出码 124),让一个 agent 睡到同伴卡在权限确认的那一刻,而不是抓屏猜——然后 `tty7 capture %3 --plain` 收结果。agent 状态是电平不是边沿,所以 `--changed` 会忽略 wait 开始时 pane 本来就处在的那个状态;不加它的话,JSON 里的 `stale` 标记会告诉你这个答案是不是上一轮留下的
|
||||
- **Orchestration skill** —— 一个开关(设置 → Agents),安装一个 Claude Code skill(`~/.claude/skills/tty7-orchestration`),教 *primary* agent 完整的委派循环——开 worker pane、发一个边界清晰的任务、`wait` 等待、收结果。特意做成 skill 而非全局指令:平时只有一行描述占上下文,显式调用才加载全文,worker agent 也不会继承编排权限
|
||||
- **`tty7` 上 PATH** —— CLI 随每个安装包一起发布,启动时自动放到 PATH 上,脚本和 coding agent 在任何终端里都能驱动 tty7。tty7 自己的 pane 里则一定可用,因为 pane 继承 app 的环境。Unix 上是往 `/opt/homebrew/bin`、`/usr/local/bin`、`~/.local/bin`、`~/bin`、`~/.cargo/bin` 中你 PATH 已经覆盖的那个目录里放一个软链;Windows 上是把安装目录追加到用户 PATH,卸载时再摘掉。你自己装的 `tty7` 一律保持原样,不会被覆盖。关掉:设置 → About,或 `config.json` 里 `install_cli_on_path: false`
|
||||
- **`tty7` 上 PATH** —— CLI 随每个安装包一起发布,启动时自动放到 PATH 上,脚本和 coding agent 在任何终端里都能驱动 tty7。tty7 自己的 pane 里则一定可用,因为 pane 继承 app 的环境。Unix 上是往 `/opt/homebrew/bin`、`/usr/local/bin`、`~/.local/bin`、`~/bin`、`~/.cargo/bin` 中你 PATH 已经覆盖的那个目录里放一个软链;Windows 上是把安装目录追加到用户 PATH,卸载时再摘掉。你自己装的 `tty7` 一律保持原样,不会被覆盖。关掉:设置 → Agents,或 `config.json` 里 `install_cli_on_path: false`
|
||||
|
||||
## SSH
|
||||
|
||||
@@ -96,9 +96,9 @@ Aider、Amp、OpenCode 等约 17 个)并在其外围加功能 —— 绝不包
|
||||
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | 下一个窗格 · 上一个窗格 |
|
||||
| <kbd>⌘ ⌥ ←→↑↓</kbd> | 按方向切换焦点窗格 |
|
||||
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | 切换全屏 · 最大化 / 还原窗格 |
|
||||
| <kbd>⌘ K</kbd> | 清屏并清空回滚缓冲区 |
|
||||
| <kbd>⌘ K</kbd> | 清屏并清空 scrollback |
|
||||
| <kbd>⌘ P</kbd> | 命令面板 |
|
||||
| <kbd>⌘ F</kbd> | 搜索回滚缓冲区 |
|
||||
| <kbd>⌘ F</kbd> | 搜索 scrollback |
|
||||
| <kbd>⌃ R</kbd> | 模糊搜索 shell 历史 |
|
||||
| <kbd>⌘ +</kbd> · <kbd>⌘ −</kbd> · <kbd>⌘ 0</kbd> | 字号增大 · 减小 · 重置 |
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//! Optional Windows Explorer context-menu integration.
|
||||
//!
|
||||
//! tty7 deliberately does not register shell verbs during installation or
|
||||
//! startup. The registry is user-visible system state, so only the explicit
|
||||
//! buttons in Settings call [`register`] or [`unregister`]. Both verbs invoke
|
||||
//! the GUI-subsystem `tty7-app.exe` directly so Explorer never allocates a
|
||||
//! transient console. The app first offers the path to an already running GUI
|
||||
//! through `GuiOpen`, then continues normal startup when no GUI receives it.
|
||||
//! The registry is user-visible system state, so tty7 never writes these verbs
|
||||
//! on its own: the Windows installer offers a task checkbox and invokes
|
||||
//! [`register`], and its uninstaller always invokes [`unregister`]. There is no
|
||||
//! runtime setting — the same install-time-only treatment VS Code and Git for
|
||||
//! Windows give their shell entries. Keeping the key layout here rather than in
|
||||
//! the .iss keeps one description of it in the tree.
|
||||
//!
|
||||
//! Both verbs invoke the GUI-subsystem `tty7-app.exe` directly so Explorer never
|
||||
//! allocates a transient console. The app first offers the path to an already
|
||||
//! running GUI through `GuiOpen`, then continues normal startup when no GUI
|
||||
//! receives it.
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
@@ -15,19 +20,6 @@ use anyhow::{Context as _, Result};
|
||||
const DIRECTORY_KEY: &str = r"Software\Classes\Directory\shell\tty7";
|
||||
const BACKGROUND_KEY: &str = r"Software\Classes\Directory\Background\shell\tty7";
|
||||
|
||||
/// The state shown in Settings.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
/// Neither tty7 verb exists for this user.
|
||||
NotRegistered,
|
||||
/// Both verbs exactly describe the currently running tty7 installation.
|
||||
Registered,
|
||||
/// At least one verb exists, but the pair is incomplete or points elsewhere.
|
||||
NeedsUpdate,
|
||||
/// Explorer shell verbs are unavailable on this operating system.
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Location {
|
||||
Directory,
|
||||
@@ -66,23 +58,6 @@ struct Registration {
|
||||
command: OsString,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct RegistryShape {
|
||||
values: u32,
|
||||
subkeys: u32,
|
||||
}
|
||||
|
||||
fn registration_tree_is_exact(root: RegistryShape, command: RegistryShape) -> bool {
|
||||
root == (RegistryShape {
|
||||
values: 2,
|
||||
subkeys: 1,
|
||||
}) && command
|
||||
== (RegistryShape {
|
||||
values: 1,
|
||||
subkeys: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn replace_entry_with(
|
||||
registration: &Registration,
|
||||
delete: impl FnOnce(&str) -> Result<()>,
|
||||
@@ -105,11 +80,6 @@ impl Registration {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the live per-user registration state.
|
||||
pub fn status() -> Result<Status> {
|
||||
platform_status()
|
||||
}
|
||||
|
||||
/// Register both Explorer verbs for the current user.
|
||||
pub fn register() -> Result<()> {
|
||||
platform_register()
|
||||
@@ -151,6 +121,7 @@ fn registrations(app: &Path) -> [Registration; 2] {
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use super::*;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt as _;
|
||||
|
||||
use windows_sys::Win32::Foundation::{
|
||||
@@ -175,21 +146,10 @@ mod windows {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum EntryState {
|
||||
Missing,
|
||||
Matching,
|
||||
Different,
|
||||
}
|
||||
|
||||
fn wide(value: &OsStr) -> Vec<u16> {
|
||||
value.encode_wide().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
fn units(value: &OsStr) -> Vec<u16> {
|
||||
value.encode_wide().collect()
|
||||
}
|
||||
|
||||
fn io_error(action: &str, code: u32) -> anyhow::Error {
|
||||
anyhow::anyhow!(
|
||||
"{action}: {}",
|
||||
@@ -197,20 +157,6 @@ mod windows {
|
||||
)
|
||||
}
|
||||
|
||||
fn open_key(path: &str) -> Result<Option<RegistryKey>> {
|
||||
let path = wide(OsStr::new(path));
|
||||
let mut key: HKEY = std::ptr::null_mut();
|
||||
// SAFETY: `path` is NUL-terminated and alive for the call; `key` is a
|
||||
// valid out-parameter and is wrapped only when the call succeeds.
|
||||
let code =
|
||||
unsafe { RegOpenKeyExW(HKEY_CURRENT_USER, path.as_ptr(), 0, KEY_READ, &mut key) };
|
||||
match code {
|
||||
ERROR_SUCCESS => Ok(Some(RegistryKey(key))),
|
||||
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => Ok(None),
|
||||
other => Err(io_error("opening the tty7 Explorer registry key", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_key(path: &str) -> Result<RegistryKey> {
|
||||
let path = wide(OsStr::new(path));
|
||||
let mut key: HKEY = std::ptr::null_mut();
|
||||
@@ -235,56 +181,6 @@ mod windows {
|
||||
Ok(RegistryKey(key))
|
||||
}
|
||||
|
||||
fn query_string(key: &RegistryKey, name: Option<&OsStr>) -> Result<Option<Vec<u16>>> {
|
||||
let name = name.map(wide);
|
||||
let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr());
|
||||
let mut kind = 0u32;
|
||||
let mut bytes = 0u32;
|
||||
// SAFETY: the key is live, the optional value-name pointer is either
|
||||
// null or NUL-terminated, and the size/type out-parameters are valid.
|
||||
let code = unsafe {
|
||||
RegQueryValueExW(
|
||||
key.0,
|
||||
name_ptr,
|
||||
std::ptr::null(),
|
||||
&mut kind,
|
||||
std::ptr::null_mut(),
|
||||
&mut bytes,
|
||||
)
|
||||
};
|
||||
if matches!(code, ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND) {
|
||||
return Ok(None);
|
||||
}
|
||||
if code != ERROR_SUCCESS {
|
||||
return Err(io_error("reading a tty7 Explorer registry value", code));
|
||||
}
|
||||
if kind != REG_SZ || !bytes.is_multiple_of(2) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut value = vec![0u16; (bytes as usize).div_ceil(2)];
|
||||
// SAFETY: `value` is sized from the preceding query and remains live;
|
||||
// Win32 receives its capacity in bytes through `bytes`.
|
||||
let code = unsafe {
|
||||
RegQueryValueExW(
|
||||
key.0,
|
||||
name_ptr,
|
||||
std::ptr::null(),
|
||||
&mut kind,
|
||||
value.as_mut_ptr().cast(),
|
||||
&mut bytes,
|
||||
)
|
||||
};
|
||||
if code != ERROR_SUCCESS {
|
||||
return Err(io_error("reading a tty7 Explorer registry value", code));
|
||||
}
|
||||
value.truncate(bytes as usize / 2);
|
||||
while value.last() == Some(&0) {
|
||||
value.pop();
|
||||
}
|
||||
Ok(Some(value))
|
||||
}
|
||||
|
||||
fn set_string(key: &RegistryKey, name: Option<&OsStr>, value: &OsStr) -> Result<()> {
|
||||
let name = name.map(wide);
|
||||
let name_ptr = name.as_ref().map_or(std::ptr::null(), |name| name.as_ptr());
|
||||
@@ -302,59 +198,6 @@ mod windows {
|
||||
}
|
||||
}
|
||||
|
||||
fn key_shape(key: &RegistryKey) -> Result<RegistryShape> {
|
||||
let mut subkeys = 0u32;
|
||||
let mut values = 0u32;
|
||||
// SAFETY: `key` is live and the two count pointers reference writable
|
||||
// locals. Every optional output that is not needed is passed as null,
|
||||
// which `RegQueryInfoKeyW` explicitly permits.
|
||||
let code = unsafe {
|
||||
RegQueryInfoKeyW(
|
||||
key.0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null(),
|
||||
&mut subkeys,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
&mut values,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if code == ERROR_SUCCESS {
|
||||
Ok(RegistryShape { values, subkeys })
|
||||
} else {
|
||||
Err(io_error("inspecting the tty7 Explorer registry key", code))
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_state(registration: &Registration) -> Result<EntryState> {
|
||||
let Some(root) = open_key(registration.location.key())? else {
|
||||
return Ok(EntryState::Missing);
|
||||
};
|
||||
let label = query_string(&root, None)?;
|
||||
let icon = query_string(&root, Some(OsStr::new("Icon")))?;
|
||||
|
||||
let command_path = format!(r"{}\command", registration.location.key());
|
||||
let command_key = match open_key(&command_path)? {
|
||||
Some(key) => key,
|
||||
None => return Ok(EntryState::Different),
|
||||
};
|
||||
let command = query_string(&command_key, None)?;
|
||||
let matches = label.as_deref() == Some(&units(OsStr::new(registration.location.label())))
|
||||
&& icon.as_deref() == Some(&units(®istration.icon))
|
||||
&& command.as_deref() == Some(&units(®istration.command))
|
||||
&& registration_tree_is_exact(key_shape(&root)?, key_shape(&command_key)?);
|
||||
Ok(if matches {
|
||||
EntryState::Matching
|
||||
} else {
|
||||
EntryState::Different
|
||||
})
|
||||
}
|
||||
|
||||
fn write_entry_contents(registration: &Registration) -> Result<()> {
|
||||
let root = create_key(registration.location.key())?;
|
||||
set_string(&root, None, OsStr::new(registration.location.label()))?;
|
||||
@@ -389,18 +232,6 @@ mod windows {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn status() -> Result<Status> {
|
||||
let app = application_path()?;
|
||||
let states = registrations(&app).map(|entry| entry_state(&entry));
|
||||
let [directory, background] = states;
|
||||
let (directory, background) = (directory?, background?);
|
||||
Ok(match (directory, background) {
|
||||
(EntryState::Missing, EntryState::Missing) => Status::NotRegistered,
|
||||
(EntryState::Matching, EntryState::Matching) => Status::Registered,
|
||||
_ => Status::NeedsUpdate,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn register() -> Result<()> {
|
||||
let app = application_path()?;
|
||||
for registration in registrations(&app) {
|
||||
@@ -422,11 +253,6 @@ mod windows {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn platform_status() -> Result<Status> {
|
||||
windows::status()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn platform_register() -> Result<()> {
|
||||
windows::register()
|
||||
@@ -437,11 +263,6 @@ fn platform_unregister() -> Result<()> {
|
||||
windows::unregister()
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn platform_status() -> Result<Status> {
|
||||
Ok(Status::Unsupported)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn platform_register() -> Result<()> {
|
||||
anyhow::bail!("Windows Explorer integration is only available on Windows")
|
||||
@@ -486,50 +307,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_shape_rejects_every_extra_value_or_subkey() {
|
||||
assert!(registration_tree_is_exact(
|
||||
RegistryShape {
|
||||
values: 2,
|
||||
subkeys: 1,
|
||||
},
|
||||
RegistryShape {
|
||||
values: 1,
|
||||
subkeys: 0,
|
||||
},
|
||||
));
|
||||
assert!(!registration_tree_is_exact(
|
||||
RegistryShape {
|
||||
values: 3,
|
||||
subkeys: 1,
|
||||
},
|
||||
RegistryShape {
|
||||
values: 1,
|
||||
subkeys: 0,
|
||||
},
|
||||
));
|
||||
assert!(!registration_tree_is_exact(
|
||||
RegistryShape {
|
||||
values: 2,
|
||||
subkeys: 1,
|
||||
},
|
||||
RegistryShape {
|
||||
values: 2,
|
||||
subkeys: 0,
|
||||
},
|
||||
));
|
||||
assert!(!registration_tree_is_exact(
|
||||
RegistryShape {
|
||||
values: 2,
|
||||
subkeys: 2,
|
||||
},
|
||||
RegistryShape {
|
||||
values: 1,
|
||||
subkeys: 0,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_replaces_the_owned_tree_before_writing() {
|
||||
let registration = Registration::new(
|
||||
|
||||
+51
-1
@@ -143,6 +143,15 @@ fn open_path_from(
|
||||
None
|
||||
}
|
||||
|
||||
/// `Some(true)` to register the Explorer verbs, `Some(false)` to remove them.
|
||||
fn explorer_menu_action_from(args: &[std::ffi::OsString]) -> Option<bool> {
|
||||
args.iter().find_map(|arg| match arg.as_os_str() {
|
||||
a if a == std::ffi::OsStr::new("--register-explorer-menu") => Some(true),
|
||||
a if a == std::ffi::OsStr::new("--unregister-explorer-menu") => Some(false),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Offers an explicit launch path to an already running local GUI.
|
||||
///
|
||||
/// The dispatcher is injected so the startup decision can be tested without
|
||||
@@ -294,6 +303,25 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// The Windows installer owns the Explorer context menu: a task checkbox
|
||||
// runs these, and the uninstaller always runs the unregister half. Keeping
|
||||
// the registry shape in `explorer_context_menu` rather than in the .iss
|
||||
// means the installer and the running app can never disagree about it.
|
||||
if let Some(register) = explorer_menu_action_from(&args) {
|
||||
let result = if register {
|
||||
crate::core::explorer_context_menu::register()
|
||||
} else {
|
||||
crate::core::explorer_context_menu::unregister()
|
||||
};
|
||||
if let Err(error) = result {
|
||||
// No console on the GUI subsystem; the exit code is what the
|
||||
// installer reads.
|
||||
log::error!("the Explorer context-menu update failed: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
apply_config_dir_arg(&args);
|
||||
|
||||
let daemon = args
|
||||
@@ -400,11 +428,33 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod argument_tests {
|
||||
use super::{config_dir_from, forward_open_path_with, open_path_from};
|
||||
use super::{
|
||||
config_dir_from, explorer_menu_action_from, forward_open_path_with, open_path_from,
|
||||
};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use tty7_core::daemon::control::ReplyOk;
|
||||
|
||||
/// The installer passes exactly one of these; every other launch — above
|
||||
/// all a plain `--open-path` from the very menu they register — must fall
|
||||
/// through to normal startup instead of rewriting the registry and exiting.
|
||||
#[test]
|
||||
fn explorer_menu_flags_are_distinguished_and_otherwise_absent() {
|
||||
assert_eq!(
|
||||
explorer_menu_action_from(&[OsString::from("--register-explorer-menu")]),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
explorer_menu_action_from(&[OsString::from("--unregister-explorer-menu")]),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
explorer_menu_action_from(&[OsString::from("--open-path"), OsString::from("/work")]),
|
||||
None
|
||||
);
|
||||
assert_eq!(explorer_menu_action_from(&[]), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_path_accepts_separate_and_equals_forms() {
|
||||
let separate = open_path_from(
|
||||
|
||||
+1
-37
@@ -34,8 +34,7 @@ use crate::ui::palette::{
|
||||
use crate::ui::pane::{CloseOutcome, Dir, Pane, PaneSlot};
|
||||
use crate::ui::presets::Fill;
|
||||
use crate::ui::settings::{
|
||||
ExplorerContextMenuNote, Recording, SettingsSection, SettingsState, ThemeEditor,
|
||||
humanize_action,
|
||||
Recording, SettingsSection, SettingsState, ThemeEditor, humanize_action,
|
||||
};
|
||||
use crate::ui::theme::{apply_theme, set_menus, window_background};
|
||||
|
||||
@@ -1941,38 +1940,6 @@ impl Tty7App {
|
||||
self.update_config(cx, |cfg| cfg.install_cli_on_path = on);
|
||||
}
|
||||
|
||||
pub(crate) fn register_explorer_context_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.run_explorer_context_menu_action(true, cx);
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_explorer_context_menu(&mut self, cx: &mut Context<Self>) {
|
||||
self.run_explorer_context_menu_action(false, cx);
|
||||
}
|
||||
|
||||
fn run_explorer_context_menu_action(&mut self, register: bool, cx: &mut Context<Self>) {
|
||||
// Registry operations are tiny and synchronous. Keeping the action on
|
||||
// the UI thread also makes the displayed status correspond to the
|
||||
// completed write, without a task racing a closed Settings window.
|
||||
let result = if register {
|
||||
crate::core::explorer_context_menu::register()
|
||||
} else {
|
||||
crate::core::explorer_context_menu::unregister()
|
||||
};
|
||||
let note = match result {
|
||||
Ok(()) if register => ExplorerContextMenuNote::Registered,
|
||||
Ok(()) => ExplorerContextMenuNote::Unregistered,
|
||||
Err(error) if register => ExplorerContextMenuNote::RegisterFailed(error.to_string()),
|
||||
Err(error) => ExplorerContextMenuNote::UnregisterFailed(error.to_string()),
|
||||
};
|
||||
let status =
|
||||
crate::core::explorer_context_menu::status().map_err(|error| error.to_string());
|
||||
if let Some(settings) = self.settings.as_mut() {
|
||||
settings.explorer_context_menu_status = status;
|
||||
settings.explorer_context_menu_note = Some(note);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_dim_inactive_panes(&mut self, on: bool, cx: &mut Context<Self>) {
|
||||
self.update_config(cx, |cfg| cfg.dim_inactive_panes = on);
|
||||
}
|
||||
@@ -3614,9 +3581,6 @@ impl Tty7App {
|
||||
theme_search,
|
||||
recording: None,
|
||||
rebinding_note: None,
|
||||
explorer_context_menu_status: crate::core::explorer_context_menu::status()
|
||||
.map_err(|error| error.to_string()),
|
||||
explorer_context_menu_note: None,
|
||||
ssh_form: None,
|
||||
ssh_detail: crate::ui::settings::SshDetail::None,
|
||||
ssh_filter,
|
||||
|
||||
+105
-179
@@ -359,24 +359,16 @@ pub enum L10nKey {
|
||||
SettingsVersionAvailable,
|
||||
SettingsCheckUpdatesDesc,
|
||||
SettingsCheckUpdatesOnLaunch,
|
||||
SettingsUpdateInstall,
|
||||
SettingsUpdateViewRelease,
|
||||
SettingsUpdateCheckNow,
|
||||
SettingsUpdateChecking,
|
||||
SettingsUpdateUpToDate,
|
||||
SettingsUpdateDownloading,
|
||||
SettingsUpdateInstalling,
|
||||
SettingsCommandLine,
|
||||
SettingsCommandLineDesc,
|
||||
SettingsInstallCliOnPath,
|
||||
SettingsExplorerContextMenu,
|
||||
SettingsExplorerContextMenuDesc,
|
||||
SettingsExplorerNotRegistered,
|
||||
SettingsExplorerRegistered,
|
||||
SettingsExplorerNeedsUpdate,
|
||||
SettingsExplorerUnavailable,
|
||||
SettingsExplorerStatusUnavailable,
|
||||
SettingsExplorerRegister,
|
||||
SettingsExplorerUpdate,
|
||||
SettingsExplorerUnregister,
|
||||
SettingsExplorerRegisteredNote,
|
||||
SettingsExplorerUnregisteredNote,
|
||||
SettingsExplorerRegisterFailed,
|
||||
SettingsExplorerUnregisterFailed,
|
||||
SettingsExplorerWindows11Note,
|
||||
SettingsServer,
|
||||
SettingsServerDesc,
|
||||
SettingsRestartServer,
|
||||
@@ -404,7 +396,6 @@ pub enum L10nKey {
|
||||
SettingsSearchDetectUrlsKeywords,
|
||||
SettingsSearchDiffPreviewFromCountsKeywords,
|
||||
SettingsSearchDimInactivePanesKeywords,
|
||||
SettingsSearchExplorerContextMenuKeywords,
|
||||
SettingsSearchFocusFollowsMouseKeywords,
|
||||
SettingsSearchFontFamilyKeywords,
|
||||
SettingsSearchFontLigaturesKeywords,
|
||||
@@ -1079,7 +1070,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsNavTerminal => ("Terminal", "终端"),
|
||||
L10nKey::SettingsNavInput => ("Input", "输入"),
|
||||
L10nKey::SettingsNavSsh => ("SSH", "SSH"),
|
||||
L10nKey::SettingsNavAgents => ("Agents", "智能体"),
|
||||
L10nKey::SettingsNavAgents => ("Agents", "Agents"),
|
||||
L10nKey::SettingsNavWindowTabs => ("Window & Tabs", "窗口与标签页"),
|
||||
L10nKey::SettingsNavKeybindings => ("Keybindings", "按键绑定"),
|
||||
L10nKey::SettingsNavAbout => ("About", "关于"),
|
||||
@@ -1420,7 +1411,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
"仅适用于没有可继承目录的 shell,例如窗口的第一个标签页。新标签页和分屏仍会继承活动窗格的目录,已经打开的 shell 会继续运行。",
|
||||
),
|
||||
L10nKey::SettingsScrolling => ("Scrolling", "滚动"),
|
||||
L10nKey::SettingsScrollback => ("Scrollback", "回滚缓冲"),
|
||||
L10nKey::SettingsScrollback => ("Scrollback", "Scrollback"),
|
||||
L10nKey::SettingsScrollbackDesc => (
|
||||
"Lines of history kept per pane. Applies to new panes.",
|
||||
"每个窗格保留的历史行数。仅适用于新窗格。",
|
||||
@@ -1510,14 +1501,14 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
"⌥+key sends the escape chord shells expect (⌥B = back one word) instead of typing a special character (∫).",
|
||||
"⌥+按键 发送 shell 期望的转义组合键(⌥B = 后退一个词),而不是输入特殊字符(∫)。",
|
||||
),
|
||||
L10nKey::SettingsAgentsIntro => ("Agents", "智能体"),
|
||||
L10nKey::SettingsAgentsIntro => ("Agents", "Agents"),
|
||||
L10nKey::SettingsAgentsIntroDesc => (
|
||||
"Hook integrations give panes running these agents live session status (working / waiting / done) in the tab bar. Only active inside tty7.",
|
||||
"钩子集成让标签栏中的窗格实时显示这些智能体的会话状态(进行中 / 等待中 / 已完成)。仅在 tty7 内生效。",
|
||||
"hook 集成让标签栏中的窗格实时显示这些 agent 的会话状态(进行中 / 等待中 / 已完成)。仅在 tty7 内生效。",
|
||||
),
|
||||
L10nKey::SettingsReadingAgentConfig => (
|
||||
"Reading this machine's agent config…",
|
||||
"正在读取这台机器的智能体配置…",
|
||||
"正在读取这台机器的 agent 配置…",
|
||||
),
|
||||
L10nKey::SettingsStatusNotInstalled => ("Not installed", "未安装"),
|
||||
L10nKey::SettingsStatusInstalled => ("Installed", "已安装"),
|
||||
@@ -1528,7 +1519,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsUninstall => ("Uninstall", "卸载"),
|
||||
L10nKey::SettingsOfflineMachines => (
|
||||
"{count} more saved machines are not connected — open a workspace on one to install its hooks there.",
|
||||
"还有 {count} 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装钩子。",
|
||||
"还有 {count} 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装 hook。",
|
||||
),
|
||||
L10nKey::SettingsSyncWithSystem => ("Sync with system", "跟随系统"),
|
||||
L10nKey::SettingsSyncWithSystemDesc => (
|
||||
@@ -1579,7 +1570,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsShowTrayIcon => ("Show tray icon", "显示托盘图标"),
|
||||
L10nKey::SettingsShowTrayIconDesc => (
|
||||
"Keep a status item in the system tray / menu bar: it signals when a coding agent needs your input, and its menu jumps to agent panes.",
|
||||
"在系统托盘/菜单栏保留状态项:当编码智能体需要输入时发出提示,其菜单可跳转到智能体窗格。",
|
||||
"在系统托盘/菜单栏保留状态项:当编码 agent 需要输入时发出提示,其菜单可跳转到该 agent 的窗格。",
|
||||
),
|
||||
L10nKey::SettingsTabs => ("Tabs", "标签页"),
|
||||
L10nKey::SettingsNewTabPosition => ("New tab position", "新标签页位置"),
|
||||
@@ -1599,11 +1590,11 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsDiffPreviewFromCounts => (
|
||||
"Open diff preview from sidebar counts",
|
||||
"从侧栏计数打开差异预览",
|
||||
"从侧栏计数打开 diff 预览",
|
||||
),
|
||||
L10nKey::SettingsDiffPreviewFromCountsDesc => (
|
||||
"Click a row's +N −N to open the working-tree diff in an overlay. Off keeps the branch and the counts on the row and just stops them being clickable.",
|
||||
"点击行上的 +N −N 可在浮层中打开工作树差异。关闭时行上仍显示分支和计数,但不再可点击。",
|
||||
"点击行上的 +N −N 可在浮层中打开 worktree diff。关闭时行上仍显示分支和计数,但不再可点击。",
|
||||
),
|
||||
L10nKey::SettingsNotifications => ("Notifications", "通知"),
|
||||
L10nKey::SettingsNotifyOnCommandFinish => ("Notify on command finish", "命令完成时通知"),
|
||||
@@ -1648,11 +1639,11 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsRestoreAllDefaults => ("Restore all defaults", "恢复全部默认值"),
|
||||
L10nKey::SettingsAboutDesc1 => (
|
||||
"A terminal workbench: shells, workspaces, SSH, coding agents.",
|
||||
"一个终端工作台:shell、工作区、SSH、编码智能体。",
|
||||
"一个终端工作台:shell、工作区、SSH、编码 agent。",
|
||||
),
|
||||
L10nKey::SettingsAboutDesc2 => (
|
||||
"Editor-grade input in every shell, shells that outlive quits and reboots without tmux, a native SSH stack with profiles and port forwarding, and live status for panes running coding agents.",
|
||||
"每个 shell 都具备编辑器级输入;无需 tmux 也能让 shell 在退出和重启后继续运行;原生的 SSH 栈支持主机配置和端口转发;为运行编码智能体的窗格提供实时状态。",
|
||||
"每个 shell 都具备编辑器级输入;无需 tmux 也能让 shell 在退出和重启后继续运行;原生的 SSH 栈支持主机配置和端口转发;为运行编码 agent 的窗格提供实时状态。",
|
||||
),
|
||||
L10nKey::SettingsAboutTech => (
|
||||
"Pure Rust · GPU rendering on Zed's gpui · VT core from Alacritty",
|
||||
@@ -1664,50 +1655,33 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
("Version {version} is available.", "新版本 {version} 可用。")
|
||||
}
|
||||
L10nKey::SettingsCheckUpdatesDesc => (
|
||||
"Check GitHub for a newer release on launch and show it here. tty7 never updates itself — downloading happens on the Releases page.",
|
||||
"启动时检查 GitHub 是否有新版本并在此显示。tty7 不会自行更新——下载在 Releases 页面完成。",
|
||||
"Check GitHub for new stable releases. Packaged macOS builds can install the update and relaunch; every other platform opens the release page.",
|
||||
"检查 GitHub 上是否有新的稳定版。打包的 macOS 版本可以直接安装更新并重启;其他平台会打开发布页面。",
|
||||
),
|
||||
L10nKey::SettingsCheckUpdatesOnLaunch => ("Check for updates on launch", "启动时检查更新"),
|
||||
L10nKey::SettingsUpdateInstall => ("Update and Relaunch", "更新并重启"),
|
||||
L10nKey::SettingsUpdateViewRelease => ("View Release", "查看发布页"),
|
||||
L10nKey::SettingsUpdateCheckNow => ("Check Now", "立即检查"),
|
||||
L10nKey::SettingsUpdateChecking => ("Checking…", "检查中…"),
|
||||
L10nKey::SettingsUpdateUpToDate => {
|
||||
("You're running the latest version.", "当前已是最新版本。")
|
||||
}
|
||||
L10nKey::SettingsUpdateDownloading => (
|
||||
"Downloading and verifying the update…",
|
||||
"正在下载并校验更新…",
|
||||
),
|
||||
L10nKey::SettingsUpdateInstalling => {
|
||||
("Relaunching with the update…", "正在重启以应用更新…")
|
||||
}
|
||||
L10nKey::SettingsCommandLine => ("Command line", "命令行"),
|
||||
L10nKey::SettingsCommandLineDesc => (
|
||||
"Put the bundled `tty7` command on your PATH at launch, so scripts and coding agents can drive tty7 from any terminal. Inside a tty7 pane it works either way. Turn this off if you keep your own `tty7` — one you built or installed yourself — and do not want it shadowed. Takes effect at next launch.",
|
||||
"启动时将自带的 `tty7` 命令加入 PATH,让脚本和编码智能体可在任意终端驱动 tty7。在 tty7 窗格内两种情况都可用。如果你自己构建或安装了 `tty7` 且不希望被遮蔽,请关闭此选项。下次启动时生效。",
|
||||
"启动时将自带的 `tty7` 命令加入 PATH,让脚本和编码 agent 可在任意终端驱动 tty7。在 tty7 窗格内两种情况都可用。如果你自己构建或安装了 `tty7` 且不希望被遮蔽,请关闭此选项。下次启动时生效。",
|
||||
),
|
||||
L10nKey::SettingsInstallCliOnPath => (
|
||||
"Install the `tty7` command on PATH",
|
||||
"将 `tty7` 命令安装到 PATH",
|
||||
),
|
||||
L10nKey::SettingsExplorerContextMenu => ("Windows Explorer", "Windows 文件资源管理器"),
|
||||
L10nKey::SettingsExplorerContextMenuDesc => (
|
||||
"Add “Open in tty7” when you right-click a folder and “Open tty7 here” when you right-click a folder background. This is off by default and is registered only for your Windows account.",
|
||||
"右键单击文件夹时添加“Open in tty7”,右键单击文件夹背景时添加“Open tty7 here”。此功能默认关闭,且只为当前 Windows 账户注册。",
|
||||
),
|
||||
L10nKey::SettingsExplorerNotRegistered => ("Not registered", "未注册"),
|
||||
L10nKey::SettingsExplorerRegistered => ("Registered", "已注册"),
|
||||
L10nKey::SettingsExplorerNeedsUpdate => ("Needs update", "需要更新"),
|
||||
L10nKey::SettingsExplorerUnavailable => ("Unavailable", "不可用"),
|
||||
L10nKey::SettingsExplorerStatusUnavailable => ("Status unavailable", "无法获取状态"),
|
||||
L10nKey::SettingsExplorerRegister => ("Register", "注册"),
|
||||
L10nKey::SettingsExplorerUpdate => ("Update", "更新"),
|
||||
L10nKey::SettingsExplorerUnregister => ("Unregister", "取消注册"),
|
||||
L10nKey::SettingsExplorerRegisteredNote => (
|
||||
"Registered. Right-click a folder or folder background in Explorer to open it in tty7.",
|
||||
"已注册。现在可以在文件资源管理器中右键单击文件夹或文件夹背景,以在 tty7 中打开。",
|
||||
),
|
||||
L10nKey::SettingsExplorerUnregisteredNote => (
|
||||
"Unregistered from Windows Explorer.",
|
||||
"已从 Windows 文件资源管理器中取消注册。",
|
||||
),
|
||||
L10nKey::SettingsExplorerRegisterFailed => {
|
||||
("Could not register: {error}", "无法注册:{error}")
|
||||
}
|
||||
L10nKey::SettingsExplorerUnregisterFailed => {
|
||||
("Could not unregister: {error}", "无法取消注册:{error}")
|
||||
}
|
||||
L10nKey::SettingsExplorerWindows11Note => (
|
||||
"On Windows 11, classic shell entries may appear under “Show more options”.",
|
||||
"在 Windows 11 上,经典右键菜单项可能显示在“显示更多选项”中。",
|
||||
),
|
||||
L10nKey::SettingsServer => ("Server", "服务器"),
|
||||
L10nKey::SettingsServerDesc => (
|
||||
"Restart the server on this computer to pick up a newly granted macOS permission, recover if it stops responding, or start from a clean slate. This ends all running shells here; your tabs and layout reopen with fresh shells. A remote machine's server is restarted from its own menu in the workspace switcher.",
|
||||
@@ -1742,11 +1716,11 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchClaudeCodeKeywords => (
|
||||
"agent integration hooks install uninstall status rich session working waiting tab bar sidebar badge claude",
|
||||
"Claude Code 智能体 集成 钩子 安装 卸载 状态 会话 claude agent integration hooks install",
|
||||
"Claude Code agent 集成 hook 安装 卸载 状态 会话 claude agent integration hooks install",
|
||||
),
|
||||
L10nKey::SettingsSearchCodexKeywords => (
|
||||
"agent integration hooks install openai codex",
|
||||
"Codex 智能体 集成 钩子 安装 OpenAI codex agent integration hooks install",
|
||||
"Codex agent 集成 hook 安装 OpenAI codex agent integration hooks install",
|
||||
),
|
||||
L10nKey::SettingsSearchCommandLineToolKeywords => (
|
||||
"cli tty7 path shell command install symlink terminal iterm agent script",
|
||||
@@ -1759,7 +1733,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchCopilotCliKeywords => (
|
||||
"agent integration hooks install github copilot",
|
||||
"Copilot CLI 智能体 集成 钩子 安装 GitHub copilot agent integration hooks install",
|
||||
"Copilot CLI agent 集成 hook 安装 GitHub copilot agent integration hooks install",
|
||||
),
|
||||
L10nKey::SettingsSearchCopyOnSelectKeywords => (
|
||||
"clipboard selection yank mouse",
|
||||
@@ -1783,16 +1757,12 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchDiffPreviewFromCountsKeywords => (
|
||||
"diff overlay preview sidebar counts git changes click branch lines",
|
||||
"从侧栏计数打开差异预览 差异 预览 侧栏 git diff preview sidebar counts git changes",
|
||||
"从侧栏计数打开 diff 预览 diff 预览 侧栏 git diff preview sidebar counts git changes",
|
||||
),
|
||||
L10nKey::SettingsSearchDimInactivePanesKeywords => (
|
||||
"fade unfocused inactive split pane focus opacity highlight active dimming",
|
||||
"调暗 非活动窗格 淡化 未聚焦 分屏 高亮 active dimming pane focus",
|
||||
),
|
||||
L10nKey::SettingsSearchExplorerContextMenuKeywords => (
|
||||
"windows explorer context menu right click folder directory background shell menu register unregister open here",
|
||||
"Windows 文件资源管理器 右键 菜单 文件夹 目录 背景 注册 取消注册 打开 explorer context menu right click folder directory background shell register unregister open here",
|
||||
),
|
||||
L10nKey::SettingsSearchFocusFollowsMouseKeywords => (
|
||||
"pane hover activate",
|
||||
"焦点跟随鼠标 悬停 激活 窗格 focus follows mouse hover activate pane",
|
||||
@@ -1815,7 +1785,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchGrokBuildKeywords => (
|
||||
"agent integration hooks install xai grok build",
|
||||
"Grok Build 智能体 集成 钩子 安装 xai grok build agent integration hooks install",
|
||||
"Grok Build agent 集成 hook 安装 xai grok build agent integration hooks install",
|
||||
),
|
||||
L10nKey::SettingsSearchHideMouseWhileTypingKeywords => (
|
||||
"cursor pointer autohide",
|
||||
@@ -1868,7 +1838,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchOpencodeKeywords => (
|
||||
"agent integration plugin install opencode",
|
||||
"OpenCode 智能体 集成 插件 安装 opencode agent integration plugin install",
|
||||
"OpenCode agent 集成 插件 安装 opencode agent integration plugin install",
|
||||
),
|
||||
L10nKey::SettingsSearchOptionAsMetaKeywords => (
|
||||
"alt keyboard modifier escape macos option meta option acts as meta",
|
||||
@@ -1876,7 +1846,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchPiKeywords => (
|
||||
"agent integration extension install pi",
|
||||
"Pi 智能体 集成 扩展 安装 pi agent integration extension install",
|
||||
"Pi agent 集成 扩展 安装 pi agent integration extension install",
|
||||
),
|
||||
L10nKey::SettingsSearchPortForwardingKeywords => (
|
||||
"ssh tunnel local remote dynamic socks forward rule",
|
||||
@@ -1904,7 +1874,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::SettingsSearchScrollbackKeywords => (
|
||||
"history buffer lines scroll",
|
||||
"回滚 历史 缓冲区 行数 scrollback history buffer lines",
|
||||
"scrollback 回看 向上滚动 历史 缓冲区 行数 scrollback history buffer lines",
|
||||
),
|
||||
L10nKey::SettingsSearchShowTrayIconKeywords => (
|
||||
"tray menu bar status item agent attention system icon",
|
||||
@@ -2021,7 +1991,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::FileTreeContextOpen => ("Open", "打开"),
|
||||
L10nKey::FileTreeContextCdHere => ("cd Here", "cd 到此处"),
|
||||
L10nKey::FileTreeContextInsertPath => ("Insert Path in Terminal", "在终端中插入路径"),
|
||||
L10nKey::FileTreeContextAttachAgent => ("Attach to Agent", "附加到智能体"),
|
||||
L10nKey::FileTreeContextAttachAgent => ("Attach to Agent", "附加到 agent"),
|
||||
L10nKey::FileTreeContextNewFile => ("New File", "新建文件"),
|
||||
L10nKey::FileTreeContextNewFolder => ("New Folder", "新建文件夹"),
|
||||
L10nKey::FileTreeContextRename => ("Rename", "重命名"),
|
||||
@@ -2086,7 +2056,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
"进入 git 仓库后,此标签页会列出未提交的变更。",
|
||||
),
|
||||
L10nKey::PanelNoChanges => ("No uncommitted changes.", "没有未提交的变更。"),
|
||||
L10nKey::PanelNoChangesHint => ("The working tree is clean.", "工作树是干净的。"),
|
||||
L10nKey::PanelNoChangesHint => ("The working tree is clean.", "worktree 是干净的。"),
|
||||
L10nKey::PanelSessionSubtitle => ("Session", "会话"),
|
||||
L10nKey::PanelProcessesSubtitle => ("Processes", "进程"),
|
||||
L10nKey::PanelPortsSubtitle => ("Ports", "端口"),
|
||||
@@ -2095,7 +2065,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::PanelSsh => ("ssh", "ssh"),
|
||||
L10nKey::PanelBranch => ("branch", "分支"),
|
||||
L10nKey::PanelChangesRow => ("changes", "变更"),
|
||||
L10nKey::PanelAgent => ("agent", "智能体"),
|
||||
L10nKey::PanelAgent => ("agent", "agent"),
|
||||
L10nKey::PanelAgentIdle => ("idle", "空闲"),
|
||||
L10nKey::PanelAgentWorking => ("working", "进行中"),
|
||||
L10nKey::PanelAgentWaiting => ("waiting", "等待中"),
|
||||
@@ -2122,14 +2092,14 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
"{count} running shells will be ended and the layout forgotten.",
|
||||
"{count} 个正在运行的 shell 将会被终止,布局也将被清除。",
|
||||
),
|
||||
L10nKey::DiffReading => ("Reading diff…", "正在读取差异…"),
|
||||
L10nKey::DiffReading => ("Reading diff…", "正在读取 diff…"),
|
||||
L10nKey::DiffNotARepo => ("Not a git repository", "不是 git 仓库"),
|
||||
L10nKey::DiffReadFailed => (
|
||||
"Couldn't read the working-tree diff — retrying on the next refresh.",
|
||||
"无法读取工作树差异——下次刷新时重试。",
|
||||
"无法读取 worktree diff——下次刷新时重试。",
|
||||
),
|
||||
L10nKey::DiffWorkingTreeClean => ("Working tree clean", "工作树干净"),
|
||||
L10nKey::DiffCloseTooltip => ("Close Diff (Esc)", "关闭差异 (Esc)"),
|
||||
L10nKey::DiffWorkingTreeClean => ("Working tree clean", "worktree 干净"),
|
||||
L10nKey::DiffCloseTooltip => ("Close Diff (Esc)", "关闭 diff (Esc)"),
|
||||
L10nKey::DiffChangedFiles => ("{count} changed files", "{count} 个变更文件"),
|
||||
L10nKey::DiffUntrackedCount => (" · {count} untracked", " · {count} 个未跟踪文件"),
|
||||
L10nKey::DiffMoreFiles => (
|
||||
@@ -2138,25 +2108,25 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::DiffOversizedNotice => (
|
||||
"This working tree is too large to render efficiently ({summary}). Every file is collapsed — expand individual files, or run `git diff` in the terminal.",
|
||||
"此工作树太大,无法高效渲染({summary})。每个文件都已折叠——可展开单个文件,或在终端中运行 `git diff`。",
|
||||
"此 worktree 太大,无法高效渲染({summary})。每个文件都已折叠——可展开单个文件,或在终端中运行 `git diff`。",
|
||||
),
|
||||
L10nKey::DiffTruncatedPerFile => (
|
||||
"Diff truncated at {limit} lines — run `git diff` in the terminal for the rest.",
|
||||
"差异在 {limit} 行处截断——在终端中运行 `git diff` 查看其余部分。",
|
||||
"diff 在 {limit} 行处截断——在终端中运行 `git diff` 查看其余部分。",
|
||||
),
|
||||
L10nKey::DiffTruncatedBudget => (
|
||||
"Body not loaded — this working tree is past tty7's diff budget. Run `git diff` in the terminal for this file.",
|
||||
"内容未加载——此工作树已超出 tty7 的差异预算。在终端中运行 `git diff` 查看此文件。",
|
||||
"内容未加载——此 worktree 已超出 tty7 的 diff 预算。在终端中运行 `git diff` 查看此文件。",
|
||||
),
|
||||
L10nKey::DiffUntrackedHeader => ("Untracked files ({count})", "未跟踪文件 ({count})"),
|
||||
L10nKey::DiffMoreUntracked => (
|
||||
"… and {count} more — run `git status` in the terminal to see them.",
|
||||
"…还有 {count} 个——在终端中运行 `git status` 查看。",
|
||||
),
|
||||
L10nKey::DiffLines => ("{count} diff lines", "{count} 行差异"),
|
||||
L10nKey::DiffLines => ("{count} diff lines", "{count} 行 diff"),
|
||||
L10nKey::DiffChangedLines => (
|
||||
"{total} changed lines, {loaded} diff rows loaded before {cap} cut the rest",
|
||||
"{total} 行变更,在 {cap} 截断前已加载 {loaded} 行差异",
|
||||
"{total} 行变更,在 {cap} 截断前已加载 {loaded} 行 diff",
|
||||
),
|
||||
L10nKey::DiffBudgetAndCap => (
|
||||
"tty7's budget and the per-file cap",
|
||||
@@ -2167,16 +2137,17 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::DiffUntrackedSummary => ("{count} untracked", "{count} 个未跟踪"),
|
||||
L10nKey::PendingConnecting => ("Connecting to {machine}…", "正在连接 {machine}…"),
|
||||
L10nKey::PendingUnreachable => ("Couldn't reach {machine}", "无法连接到 {machine}"),
|
||||
L10nKey::WorktreePromptNeedsName => ("The worktree needs a name", "工作树需要一个名称"),
|
||||
L10nKey::WorktreePromptTitle => ("New Worktree Tab", "新建工作树标签页"),
|
||||
L10nKey::WorktreePromptName => ("Worktree Name", "工作树名称"),
|
||||
L10nKey::WorktreePromptNeedsName => ("The worktree needs a name", "worktree 需要一个名称"),
|
||||
L10nKey::WorktreePromptTitle => ("New Worktree Tab", "新建 worktree 标签页"),
|
||||
L10nKey::WorktreePromptName => ("Worktree Name", "worktree 名称"),
|
||||
L10nKey::WorktreePromptBranch => ("New Branch", "新分支"),
|
||||
L10nKey::WorktreePromptBase => ("Start From", "起始分支"),
|
||||
L10nKey::WorktreePromptCreating => ("Creating…", "正在创建…"),
|
||||
L10nKey::WorktreePromptCreate => ("Create", "创建"),
|
||||
L10nKey::AppNewWorktreeFailed => {
|
||||
("New worktree failed: {error}", "新建工作树失败:{error}")
|
||||
}
|
||||
L10nKey::AppNewWorktreeFailed => (
|
||||
"New worktree failed: {error}",
|
||||
"新建 worktree 失败:{error}",
|
||||
),
|
||||
L10nKey::HomeTimeJustNow => ("just now", "刚刚"),
|
||||
L10nKey::HomeTimeMinutesAgo => ("{count} min ago", "{count} 分钟前"),
|
||||
L10nKey::HomeTimeHourAgo => ("1 hour ago", "1 小时前"),
|
||||
@@ -2374,7 +2345,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
}
|
||||
L10nKey::AppNoRunningCodingAgent => (
|
||||
"No running coding agent found — start one (claude, codex, …) in a pane first.",
|
||||
"未找到运行中的编码智能体——请先在某个窗格中启动一个(claude、codex 等)。",
|
||||
"未找到运行中的编码 agent——请先在某个窗格中启动一个(claude、codex 等)。",
|
||||
),
|
||||
L10nKey::SwitcherThisComputer => ("This Computer", "本机"),
|
||||
L10nKey::SwitcherRestartingServer => ("Restarting tty7's server…", "正在重启 tty7 服务器…"),
|
||||
@@ -2416,10 +2387,10 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::CmdGroupView => ("View", "视图"),
|
||||
L10nKey::CmdGroupTerminal => ("Terminal", "终端"),
|
||||
L10nKey::CmdGroupSsh => ("SSH", "SSH"),
|
||||
L10nKey::CmdGroupAgents => ("Agents", "智能体"),
|
||||
L10nKey::CmdGroupAgents => ("Agents", "Agents"),
|
||||
L10nKey::CmdGroupApplication => ("Application", "应用"),
|
||||
L10nKey::CmdNewTab => ("New Tab", "新标签页"),
|
||||
L10nKey::CmdNewWorktreeTab => ("New Worktree Tab", "新建工作树标签页"),
|
||||
L10nKey::CmdNewWorktreeTab => ("New Worktree Tab", "新建 worktree 标签页"),
|
||||
L10nKey::CmdNewWorktreeTabSubtitle => (
|
||||
"isolated checkout on a fresh branch",
|
||||
"在全新分支上独立检出",
|
||||
@@ -2446,12 +2417,12 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::CmdCopySessionId => ("Copy Session ID", "复制会话 ID"),
|
||||
L10nKey::CmdCopySessionIdSubtitle => (
|
||||
"the coding agent's own session id",
|
||||
"编码智能体自身的会话 ID",
|
||||
"编码 agent 自身的会话 ID",
|
||||
),
|
||||
L10nKey::CmdForkSession => ("Fork Session", "派生会话"),
|
||||
L10nKey::CmdForkSession => ("Fork Session", "Fork 会话"),
|
||||
L10nKey::CmdForkSessionSubtitle => (
|
||||
"branch this agent session into a new tab",
|
||||
"将此智能体会话派生到新标签页",
|
||||
"将此 agent 会话 fork 到新标签页",
|
||||
),
|
||||
L10nKey::CmdMarkTabAsUnread => ("Mark Tab as Unread", "将标签页标记为未读"),
|
||||
L10nKey::CmdClosePaneTab => ("Close Pane / Tab", "关闭窗格/标签页"),
|
||||
@@ -2487,7 +2458,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::CmdChangeTheme => ("Change Theme…", "更改主题…"),
|
||||
L10nKey::CmdResetFontSize => ("Reset Font Size", "重置字号"),
|
||||
L10nKey::CmdEnterFullScreen => ("Enter Full Screen", "进入全屏"),
|
||||
L10nKey::CmdClearScrollback => ("Clear Scrollback", "清除回滚"),
|
||||
L10nKey::CmdClearScrollback => ("Clear Scrollback", "清除 scrollback"),
|
||||
L10nKey::CmdFindInTerminal => ("Find in Terminal…", "在终端中查找…"),
|
||||
L10nKey::CmdFindNext => ("Find Next", "查找下一个"),
|
||||
L10nKey::CmdFindPrevious => ("Find Previous", "查找上一个"),
|
||||
@@ -2501,18 +2472,18 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::CmdSshRemoteFiles => ("SSH: Remote Files", "SSH:远程文件"),
|
||||
L10nKey::CmdSshPortForwarding => ("SSH: Port Forwarding", "SSH:端口转发"),
|
||||
L10nKey::CmdSshConnectWithInput => ("SSH: Connect {input}", "SSH:连接 {input}"),
|
||||
L10nKey::CmdAgentSendSelection => ("Agent: Send Selection", "智能体:发送选区"),
|
||||
L10nKey::CmdAgentSendSelection => ("Agent: Send Selection", "Agent:发送选区"),
|
||||
L10nKey::CmdAgentSendSelectionSubtitle => (
|
||||
"selection → running coding agent",
|
||||
"选区 → 运行中的编码智能体",
|
||||
"选区 → 运行中的编码 agent",
|
||||
),
|
||||
L10nKey::CmdAgentSendGitDiffForReview => (
|
||||
"Agent: Send Git Diff for Review",
|
||||
"智能体:发送 Git diff 以供审查",
|
||||
"Agent:发送 git diff 以供审查",
|
||||
),
|
||||
L10nKey::CmdAgentSendGitDiffSubtitle => (
|
||||
"git diff → running coding agent",
|
||||
"git diff → 运行中的编码智能体",
|
||||
"git diff → 运行中的编码 agent",
|
||||
),
|
||||
L10nKey::CmdSettings => ("Settings…", "设置…"),
|
||||
L10nKey::CmdKeyboardShortcuts => ("Keyboard Shortcuts", "键盘快捷键"),
|
||||
@@ -2555,17 +2526,18 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::AppWorktreeRemoveDetailDirty => (
|
||||
"The closed tab's worktree at {path} has uncommitted changes.",
|
||||
"位于 {path} 的已关闭标签页的工作树有未提交的变更。",
|
||||
"位于 {path} 的已关闭标签页的 worktree 有未提交的变更。",
|
||||
),
|
||||
L10nKey::AppWorktreeRemoveDetailClean => (
|
||||
"The closed tab's worktree at {path} is clean.",
|
||||
"位于 {path} 的已关闭标签页的工作树是干净的。",
|
||||
"位于 {path} 的已关闭标签页的 worktree 是干净的。",
|
||||
),
|
||||
L10nKey::AppWorktreeRemoveTitle => (
|
||||
"Remove worktree \"{branch}\"?",
|
||||
"删除 worktree\"{branch}\"?",
|
||||
),
|
||||
L10nKey::AppWorktreeRemoveTitle => {
|
||||
("Remove worktree \"{branch}\"?", "删除工作树\"{branch}\"?")
|
||||
}
|
||||
L10nKey::AppWorktreeDiscardAndRemove => ("Discard Changes & Remove", "放弃变更并删除"),
|
||||
L10nKey::AppWorktreeRemove => ("Remove Worktree", "删除工作树"),
|
||||
L10nKey::AppWorktreeRemove => ("Remove Worktree", "删除 worktree"),
|
||||
L10nKey::AppWorktreeKeep => ("Keep", "保留"),
|
||||
L10nKey::AppReopenTabFailed => (
|
||||
"Could not reopen the tab: no terminal started",
|
||||
@@ -2584,32 +2556,33 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::AppSplitPaneFailed => {
|
||||
("Could not split the pane: {error}", "无法拆分窗格:{error}")
|
||||
}
|
||||
L10nKey::AppWorktreeRemoved => {
|
||||
("Removed worktree \"{branch}\"", "已删除工作树\"{branch}\"")
|
||||
}
|
||||
L10nKey::AppWorktreeRemoved => (
|
||||
"Removed worktree \"{branch}\"",
|
||||
"已删除 worktree\"{branch}\"",
|
||||
),
|
||||
L10nKey::AppWorktreeRemoveFailed => (
|
||||
"Worktree removal failed: {error}",
|
||||
"删除工作树失败:{error}",
|
||||
"删除 worktree 失败:{error}",
|
||||
),
|
||||
L10nKey::AppForkStillConnecting => (
|
||||
"Could not fork: the pane is still connecting",
|
||||
"无法派生:窗格仍在连接中",
|
||||
"无法 fork:窗格仍在连接中",
|
||||
),
|
||||
L10nKey::AppPaneNoCodingAgent => (
|
||||
"This pane isn't running a coding agent",
|
||||
"此窗格未运行编码智能体",
|
||||
"此窗格未运行编码 agent",
|
||||
),
|
||||
L10nKey::AppForkNoCommand => (
|
||||
"tty7 has no fork command for {name}",
|
||||
"tty7 没有用于 {name} 的派生命令",
|
||||
"tty7 没有用于 {name} 的 fork 命令",
|
||||
),
|
||||
L10nKey::AppForkLocalOnly => (
|
||||
"{name} sessions can only be forked from a local pane",
|
||||
"{name} 会话只能从本地窗格派生",
|
||||
"{name} 会话只能从本地窗格 fork",
|
||||
),
|
||||
L10nKey::AppForkNoSessionId => (
|
||||
"tty7 hasn't seen a {name} session id in this pane — install its hooks in Settings → Agents",
|
||||
"tty7 尚未在此窗格中看到 {name} 的会话 ID——请在设置 → 智能体中安装其钩子",
|
||||
"tty7 尚未在此窗格中看到 {name} 的会话 ID——请在设置 → Agents 中安装其 hook",
|
||||
),
|
||||
L10nKey::AppForkSessionIdNotToken => (
|
||||
"{name}'s session id isn't a plain token",
|
||||
@@ -2617,7 +2590,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::AppForkMidTurn => (
|
||||
"{name} is mid-turn — the fork won't include the turn in flight",
|
||||
"{name} 正在处理中——派生不会包含进行中的这一轮",
|
||||
"{name} 正在处理中——fork 不会包含进行中的这一轮",
|
||||
),
|
||||
L10nKey::AppTabNoWorkingDirectory => (
|
||||
"This tab has no working directory yet",
|
||||
@@ -2658,7 +2631,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
),
|
||||
L10nKey::AppAgentHooksOffline => (
|
||||
"Not connected to this machine, so its agent config can't be read or written. Open a workspace on it and come back.",
|
||||
"未连接到这台机器,因此无法读取或写入其智能体配置。请在其上打开一个工作区后再回来。",
|
||||
"未连接到这台机器,因此无法读取或写入其 agent 配置。请在其上打开一个工作区后再回来。",
|
||||
),
|
||||
L10nKey::AppAgentHooksHomeDirUnresolved => {
|
||||
("cannot resolve home directory", "无法解析主目录")
|
||||
@@ -2721,13 +2694,13 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::AppMenuHelp => ("Help", "帮助"),
|
||||
L10nKey::AppMenuNewTab => ("New Tab", "新标签页"),
|
||||
L10nKey::AppMenuNewWorkspace => ("New Workspace", "新工作区"),
|
||||
L10nKey::AppMenuNewWorktreeTab => ("New Worktree Tab", "新工作树标签页"),
|
||||
L10nKey::AppMenuNewWorktreeTab => ("New Worktree Tab", "新 worktree 标签页"),
|
||||
L10nKey::AppMenuSplitRight => ("Split Right", "向右分屏"),
|
||||
L10nKey::AppMenuSplitDown => ("Split Down", "向下分屏"),
|
||||
L10nKey::AppMenuRenameTab => ("Rename Tab…", "重命名标签页…"),
|
||||
L10nKey::AppMenuCopyWorkingDirectory => ("Copy Working Directory", "复制工作目录"),
|
||||
L10nKey::AppMenuCopySessionId => ("Copy Session ID", "复制会话 ID"),
|
||||
L10nKey::AppMenuForkSession => ("Fork Session", "派生会话"),
|
||||
L10nKey::AppMenuForkSession => ("Fork Session", "Fork 会话"),
|
||||
L10nKey::AppMenuClosePaneTab => ("Close Pane / Tab", "关闭窗格 / 标签页"),
|
||||
L10nKey::AppMenuCloseOtherTabs => ("Close Other Tabs", "关闭其他标签页"),
|
||||
L10nKey::AppMenuCloseTabsRight => ("Close Tabs to the Right", "关闭右侧标签页"),
|
||||
@@ -2755,7 +2728,7 @@ fn translate(locale: Locale, key: L10nKey) -> &'static str {
|
||||
L10nKey::AppMenuFocusNextPane => ("Focus Next Pane", "聚焦下一个窗格"),
|
||||
L10nKey::AppMenuFocusPreviousPane => ("Focus Previous Pane", "聚焦上一个窗格"),
|
||||
L10nKey::AppMenuZoomPane => ("Zoom Pane", "缩放窗格"),
|
||||
L10nKey::AppMenuClearScrollback => ("Clear Scrollback", "清除回滚"),
|
||||
L10nKey::AppMenuClearScrollback => ("Clear Scrollback", "清除 scrollback"),
|
||||
L10nKey::AppMenuEnterFullscreen => ("Enter Full Screen", "进入全屏"),
|
||||
L10nKey::AppMenuDocumentation => ("tty7 Documentation", "tty7 文档"),
|
||||
L10nKey::AppMenuKeyboardShortcuts => ("Keyboard Shortcuts", "键盘快捷键"),
|
||||
@@ -2808,15 +2781,15 @@ fn translate_variant(locale: Locale, key: L10nKey, branch: &'static str) -> &'st
|
||||
// --- Offline machines ---
|
||||
(SettingsOfflineMachines, "zero") => (
|
||||
"0 more saved machines are not connected — open a workspace on one to install its hooks there.",
|
||||
"还有 0 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装钩子。",
|
||||
"还有 0 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装 hook。",
|
||||
),
|
||||
(SettingsOfflineMachines, "one") => (
|
||||
"1 more saved machine is not connected — open a workspace on it to install its hooks there.",
|
||||
"还有 1 台已保存的机器未连接——在那台机器上打开工作区,即可在那里安装钩子。",
|
||||
"还有 1 台已保存的机器未连接——在那台机器上打开工作区,即可在那里安装 hook。",
|
||||
),
|
||||
(SettingsOfflineMachines, "other") => (
|
||||
"{count} more saved machines are not connected — open a workspace on one to install its hooks there.",
|
||||
"还有 {count} 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装钩子。",
|
||||
"还有 {count} 台已保存的机器未连接——在其中一台上打开工作区,即可在那台机器上安装 hook。",
|
||||
),
|
||||
|
||||
// --- Panel untracked files ---
|
||||
@@ -3246,24 +3219,16 @@ mod tests {
|
||||
L10nKey::SettingsVersionAvailable,
|
||||
L10nKey::SettingsCheckUpdatesDesc,
|
||||
L10nKey::SettingsCheckUpdatesOnLaunch,
|
||||
L10nKey::SettingsUpdateInstall,
|
||||
L10nKey::SettingsUpdateViewRelease,
|
||||
L10nKey::SettingsUpdateCheckNow,
|
||||
L10nKey::SettingsUpdateChecking,
|
||||
L10nKey::SettingsUpdateUpToDate,
|
||||
L10nKey::SettingsUpdateDownloading,
|
||||
L10nKey::SettingsUpdateInstalling,
|
||||
L10nKey::SettingsCommandLine,
|
||||
L10nKey::SettingsCommandLineDesc,
|
||||
L10nKey::SettingsInstallCliOnPath,
|
||||
L10nKey::SettingsExplorerContextMenu,
|
||||
L10nKey::SettingsExplorerContextMenuDesc,
|
||||
L10nKey::SettingsExplorerNotRegistered,
|
||||
L10nKey::SettingsExplorerRegistered,
|
||||
L10nKey::SettingsExplorerNeedsUpdate,
|
||||
L10nKey::SettingsExplorerUnavailable,
|
||||
L10nKey::SettingsExplorerStatusUnavailable,
|
||||
L10nKey::SettingsExplorerRegister,
|
||||
L10nKey::SettingsExplorerUpdate,
|
||||
L10nKey::SettingsExplorerUnregister,
|
||||
L10nKey::SettingsExplorerRegisteredNote,
|
||||
L10nKey::SettingsExplorerUnregisteredNote,
|
||||
L10nKey::SettingsExplorerRegisterFailed,
|
||||
L10nKey::SettingsExplorerUnregisterFailed,
|
||||
L10nKey::SettingsExplorerWindows11Note,
|
||||
L10nKey::SettingsServer,
|
||||
L10nKey::SettingsServerDesc,
|
||||
L10nKey::SettingsRestartServer,
|
||||
@@ -3291,7 +3256,6 @@ mod tests {
|
||||
L10nKey::SettingsSearchDetectUrlsKeywords,
|
||||
L10nKey::SettingsSearchDiffPreviewFromCountsKeywords,
|
||||
L10nKey::SettingsSearchDimInactivePanesKeywords,
|
||||
L10nKey::SettingsSearchExplorerContextMenuKeywords,
|
||||
L10nKey::SettingsSearchFocusFollowsMouseKeywords,
|
||||
L10nKey::SettingsSearchFontFamilyKeywords,
|
||||
L10nKey::SettingsSearchFontLigaturesKeywords,
|
||||
@@ -3818,44 +3782,6 @@ mod tests {
|
||||
assert_eq!(current_locale(), Locale::En);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_settings_are_translated_with_error_details() {
|
||||
let keys = [
|
||||
L10nKey::SettingsExplorerContextMenu,
|
||||
L10nKey::SettingsExplorerContextMenuDesc,
|
||||
L10nKey::SettingsExplorerNotRegistered,
|
||||
L10nKey::SettingsExplorerRegistered,
|
||||
L10nKey::SettingsExplorerNeedsUpdate,
|
||||
L10nKey::SettingsExplorerUnavailable,
|
||||
L10nKey::SettingsExplorerStatusUnavailable,
|
||||
L10nKey::SettingsExplorerRegister,
|
||||
L10nKey::SettingsExplorerUpdate,
|
||||
L10nKey::SettingsExplorerUnregister,
|
||||
L10nKey::SettingsExplorerRegisteredNote,
|
||||
L10nKey::SettingsExplorerUnregisteredNote,
|
||||
L10nKey::SettingsExplorerRegisterFailed,
|
||||
L10nKey::SettingsExplorerUnregisterFailed,
|
||||
L10nKey::SettingsExplorerWindows11Note,
|
||||
L10nKey::SettingsSearchExplorerContextMenuKeywords,
|
||||
];
|
||||
for key in keys {
|
||||
assert_ne!(
|
||||
translate(Locale::En, key),
|
||||
translate(Locale::ZhHans, key),
|
||||
"Simplified Chinese should not fall back to English for {key:?}"
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
apply_template(
|
||||
translate(Locale::ZhHans, L10nKey::SettingsExplorerRegisterFailed),
|
||||
&[("error", "access denied")],
|
||||
None,
|
||||
),
|
||||
"无法注册:access denied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plural_and_select_branches_are_translated() {
|
||||
let plural_keys = [
|
||||
|
||||
+84
-244
@@ -85,30 +85,6 @@ struct SearchEntry {
|
||||
keywords: L10nKey,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ExplorerContextMenuNote {
|
||||
Registered,
|
||||
Unregistered,
|
||||
RegisterFailed(String),
|
||||
UnregisterFailed(String),
|
||||
}
|
||||
|
||||
impl ExplorerContextMenuNote {
|
||||
fn localized(&self) -> String {
|
||||
match self {
|
||||
Self::Registered => t(L10nKey::SettingsExplorerRegisteredNote).to_string(),
|
||||
Self::Unregistered => t(L10nKey::SettingsExplorerUnregisteredNote).to_string(),
|
||||
Self::RegisterFailed(error) => {
|
||||
t_fmt(L10nKey::SettingsExplorerRegisterFailed, &[("error", error)])
|
||||
}
|
||||
Self::UnregisterFailed(error) => t_fmt(
|
||||
L10nKey::SettingsExplorerUnregisterFailed,
|
||||
&[("error", error)],
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_search_entries() -> &'static [SearchEntry] {
|
||||
use L10nKey::*;
|
||||
use SettingsSection::*;
|
||||
@@ -404,15 +380,10 @@ fn settings_search_entries() -> &'static [SearchEntry] {
|
||||
keywords: SettingsSearchHowShellsWorkKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: About,
|
||||
section: Agents,
|
||||
title: SettingsSearchCommandLineToolTitle,
|
||||
keywords: SettingsSearchCommandLineToolKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: About,
|
||||
title: SettingsExplorerContextMenu,
|
||||
keywords: SettingsSearchExplorerContextMenuKeywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -466,9 +437,6 @@ pub(crate) struct SettingsState {
|
||||
pub(crate) theme_search: Entity<InputState>,
|
||||
pub(crate) recording: Option<Recording>,
|
||||
pub(crate) rebinding_note: Option<String>,
|
||||
pub(crate) explorer_context_menu_status:
|
||||
Result<crate::core::explorer_context_menu::Status, String>,
|
||||
pub(crate) explorer_context_menu_note: Option<ExplorerContextMenuNote>,
|
||||
pub(crate) ssh_form: Option<SshProfileForm>,
|
||||
pub(crate) ssh_detail: SshDetail,
|
||||
pub(crate) ssh_filter: Entity<InputState>,
|
||||
@@ -3695,22 +3663,21 @@ impl Tty7App {
|
||||
|
||||
page = page.children(self.agent_hooks_machine_picker(selected_host, cx));
|
||||
|
||||
// The hook rows describe whichever machine is selected above; the
|
||||
// command-line section below is always about this GUI's own host, so it
|
||||
// is appended after the match rather than inside the ready arm.
|
||||
match view {
|
||||
AgentHooksView::Loading => {
|
||||
return page
|
||||
.child(
|
||||
div()
|
||||
.py_4()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsReadingAgentConfig)),
|
||||
)
|
||||
.into_any_element();
|
||||
page = page.child(
|
||||
div()
|
||||
.py_4()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsReadingAgentConfig)),
|
||||
);
|
||||
}
|
||||
AgentHooksView::Unavailable(reason) => {
|
||||
return page
|
||||
.child(div().py_4().text_sm().text_color(warning).child(reason))
|
||||
.into_any_element();
|
||||
page = page.child(div().py_4().text_sm().text_color(warning).child(reason));
|
||||
}
|
||||
AgentHooksView::Ready(rows) => {
|
||||
for (i, row) in rows.into_iter().enumerate() {
|
||||
@@ -3785,7 +3752,46 @@ impl Tty7App {
|
||||
}
|
||||
}
|
||||
}
|
||||
page.into_any_element()
|
||||
|
||||
let install_cli_on_path = cx.global::<Config>().install_cli_on_path;
|
||||
page.child(
|
||||
v_flex()
|
||||
.mt_6()
|
||||
.gap_2()
|
||||
.child(self.section_rule(cx))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(foreground)
|
||||
.child(t(L10nKey::SettingsCommandLine)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsCommandLineDesc)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(
|
||||
crate::ui::theme::switch("install-cli-on-path", cx)
|
||||
.checked(install_cli_on_path)
|
||||
.on_click(cx.listener(|this, on: &bool, _w, cx| {
|
||||
this.set_install_cli_on_path(*on, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(foreground)
|
||||
.child(t(L10nKey::SettingsInstallCliOnPath)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn agent_hooks_machine_picker(&self, selected: HostId, cx: &mut Context<Self>) -> Option<Div> {
|
||||
@@ -4639,12 +4645,7 @@ impl Tty7App {
|
||||
|
||||
fn render_settings_about(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let theme = cx.theme();
|
||||
let (foreground, muted_fg, success, warning) = (
|
||||
theme.foreground,
|
||||
theme.muted_foreground,
|
||||
theme.success,
|
||||
theme.warning,
|
||||
);
|
||||
let (foreground, muted_fg) = (theme.foreground, theme.muted_foreground);
|
||||
|
||||
let update_status = cx
|
||||
.try_global::<crate::core::update::UpdateStatus>()
|
||||
@@ -4659,79 +4660,21 @@ impl Tty7App {
|
||||
);
|
||||
let phase_text = match &update_status.phase {
|
||||
crate::core::update::UpdatePhase::Idle => None,
|
||||
crate::core::update::UpdatePhase::Checking => Some("Checking for updates…".to_string()),
|
||||
crate::core::update::UpdatePhase::Checking => {
|
||||
Some(t(L10nKey::SettingsUpdateChecking).to_string())
|
||||
}
|
||||
crate::core::update::UpdatePhase::UpToDate => {
|
||||
Some("You're running the latest version.".to_string())
|
||||
Some(t(L10nKey::SettingsUpdateUpToDate).to_string())
|
||||
}
|
||||
crate::core::update::UpdatePhase::Downloading => {
|
||||
Some("Downloading and verifying the update…".to_string())
|
||||
Some(t(L10nKey::SettingsUpdateDownloading).to_string())
|
||||
}
|
||||
crate::core::update::UpdatePhase::Installing => {
|
||||
Some("Relaunching with the update…".to_string())
|
||||
Some(t(L10nKey::SettingsUpdateInstalling).to_string())
|
||||
}
|
||||
crate::core::update::UpdatePhase::Failed(message) => Some(message.clone()),
|
||||
};
|
||||
let check_for_updates = cx.global::<Config>().check_for_updates;
|
||||
let install_cli_on_path = cx.global::<Config>().install_cli_on_path;
|
||||
let (explorer_status, explorer_note) = self
|
||||
.active_settings()
|
||||
.map(|settings| {
|
||||
(
|
||||
settings.explorer_context_menu_status.clone(),
|
||||
settings.explorer_context_menu_note.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or((
|
||||
Ok(crate::core::explorer_context_menu::Status::Unsupported),
|
||||
None,
|
||||
));
|
||||
let (
|
||||
explorer_status_text,
|
||||
explorer_status_color,
|
||||
register_label,
|
||||
register_disabled,
|
||||
unregister_disabled,
|
||||
) = match explorer_status.as_ref() {
|
||||
Ok(crate::core::explorer_context_menu::Status::NotRegistered) => (
|
||||
t(L10nKey::SettingsExplorerNotRegistered),
|
||||
muted_fg,
|
||||
t(L10nKey::SettingsExplorerRegister),
|
||||
false,
|
||||
true,
|
||||
),
|
||||
Ok(crate::core::explorer_context_menu::Status::Registered) => (
|
||||
t(L10nKey::SettingsExplorerRegistered),
|
||||
success,
|
||||
t(L10nKey::SettingsExplorerRegister),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
Ok(crate::core::explorer_context_menu::Status::NeedsUpdate) => (
|
||||
t(L10nKey::SettingsExplorerNeedsUpdate),
|
||||
warning,
|
||||
t(L10nKey::SettingsExplorerUpdate),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
Ok(crate::core::explorer_context_menu::Status::Unsupported) => (
|
||||
t(L10nKey::SettingsExplorerUnavailable),
|
||||
muted_fg,
|
||||
t(L10nKey::SettingsExplorerRegister),
|
||||
true,
|
||||
true,
|
||||
),
|
||||
Err(_) => (
|
||||
t(L10nKey::SettingsExplorerStatusUnavailable),
|
||||
warning,
|
||||
t(L10nKey::SettingsExplorerRegister),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
};
|
||||
let explorer_feedback = explorer_note
|
||||
.as_ref()
|
||||
.map(ExplorerContextMenuNote::localized)
|
||||
.or_else(|| explorer_status.err());
|
||||
|
||||
let logo = Arc::new(Image::from_bytes(
|
||||
ImageFormat::Png,
|
||||
@@ -4805,9 +4748,9 @@ impl Tty7App {
|
||||
)
|
||||
.when_some(update, |this, upd| {
|
||||
let button_label = if upd.installable {
|
||||
"Update and Relaunch"
|
||||
t(L10nKey::SettingsUpdateInstall)
|
||||
} else {
|
||||
"View Release"
|
||||
t(L10nKey::SettingsUpdateViewRelease)
|
||||
};
|
||||
this.child(
|
||||
v_flex()
|
||||
@@ -4838,20 +4781,25 @@ impl Tty7App {
|
||||
.when_some(phase_text, |this, text| {
|
||||
this.child(div().text_sm().text_color(muted_fg).child(text))
|
||||
})
|
||||
.child(div().text_sm().text_color(muted_fg).child(
|
||||
"tty7 checks stable releases and can update packaged macOS app bundles without opening a browser. A dedicated helper verifies checksums, version, and code signing before replacement, then relaunches the GUI. Compatible servers and shells stay running; if the wire protocol changed, tty7 asks whether to restart the server after relaunch. Other platforms and unsupported layouts fall back to the release page.",
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsCheckUpdatesDesc)),
|
||||
)
|
||||
.child(
|
||||
h_flex().child(
|
||||
Button::new("check-update-now")
|
||||
.label(if matches!(
|
||||
update_status.phase,
|
||||
crate::core::update::UpdatePhase::Checking
|
||||
) {
|
||||
"Checking…"
|
||||
} else {
|
||||
"Check Now"
|
||||
})
|
||||
.label(
|
||||
if matches!(
|
||||
update_status.phase,
|
||||
crate::core::update::UpdatePhase::Checking
|
||||
) {
|
||||
t(L10nKey::SettingsUpdateChecking)
|
||||
} else {
|
||||
t(L10nKey::SettingsUpdateCheckNow)
|
||||
},
|
||||
)
|
||||
.small()
|
||||
.disabled(update_busy)
|
||||
.on_click(cx.listener(|_, _, _window, cx| {
|
||||
@@ -4878,112 +4826,6 @@ impl Tty7App {
|
||||
),
|
||||
),
|
||||
)
|
||||
.when(cfg!(windows), |page| {
|
||||
page.child(
|
||||
v_flex()
|
||||
.mt_6()
|
||||
.gap_2()
|
||||
.child(self.section_rule(cx))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(foreground)
|
||||
.child(t(L10nKey::SettingsExplorerContextMenu)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsExplorerContextMenuDesc)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(div().size_2().rounded_full().bg(explorer_status_color))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(foreground)
|
||||
.child(explorer_status_text),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("explorer-menu-register")
|
||||
.label(register_label)
|
||||
.small()
|
||||
.disabled(register_disabled)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.register_explorer_context_menu(cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("explorer-menu-unregister")
|
||||
.label(t(L10nKey::SettingsExplorerUnregister))
|
||||
.small()
|
||||
.disabled(unregister_disabled)
|
||||
.on_click(cx.listener(|this, _, _window, cx| {
|
||||
this.unregister_explorer_context_menu(cx)
|
||||
})),
|
||||
),
|
||||
)
|
||||
.when_some(explorer_feedback, |section, message| {
|
||||
section.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(muted_fg)
|
||||
.child(message),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsExplorerWindows11Note)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_6()
|
||||
.gap_2()
|
||||
.child(self.section_rule(cx))
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(foreground)
|
||||
.child(t(L10nKey::SettingsCommandLine)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(muted_fg)
|
||||
.child(t(L10nKey::SettingsCommandLineDesc)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.child(
|
||||
crate::ui::theme::switch("install-cli-on-path", cx)
|
||||
.checked(install_cli_on_path)
|
||||
.on_click(cx.listener(|this, on: &bool, _w, cx| {
|
||||
this.set_install_cli_on_path(*on, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(foreground)
|
||||
.child(t(L10nKey::SettingsInstallCliOnPath)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.mt_6()
|
||||
@@ -5081,7 +4923,7 @@ mod tests {
|
||||
("bell", Terminal),
|
||||
("known_hosts", Ssh),
|
||||
("claude", Agents),
|
||||
("right click", About),
|
||||
("symlink", Agents),
|
||||
];
|
||||
for (query, expected) in cases {
|
||||
assert_eq!(
|
||||
@@ -5093,18 +4935,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `tty7` CLI exists so scripts and coding agents can drive tty7, so it
|
||||
/// lives with the other agent integrations rather than under About.
|
||||
#[test]
|
||||
fn explorer_context_menu_search_entry_uses_localized_keys() {
|
||||
fn command_line_tool_is_searchable_under_agents() {
|
||||
let entry = settings_search_entries()
|
||||
.iter()
|
||||
.find(|entry| entry.title == L10nKey::SettingsExplorerContextMenu)
|
||||
.expect("Explorer settings should be searchable");
|
||||
.find(|entry| entry.title == L10nKey::SettingsSearchCommandLineToolTitle)
|
||||
.expect("the CLI setting should be searchable");
|
||||
|
||||
assert_eq!(entry.section.profile_label(), "settings:about");
|
||||
assert_eq!(
|
||||
entry.keywords,
|
||||
L10nKey::SettingsSearchExplorerContextMenuKeywords
|
||||
);
|
||||
assert_eq!(entry.section.profile_label(), "settings:agents");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user