mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
docs(readme): restructure around Why/Features, cut prose and boilerplate (#48)
* feat(view): explain a dead Ctrl+R instead of failing silently (#46) When shell integration never engages in a pane — typically because a figterm-style PTY shim (kiro-cli-term, qterm) exec'd over the shell and swallowed its OSC 133 reports — the whole command-editor overlay is absent by design, and Ctrl+R used to fall through to the raw PTY with no hint of why the history menu didn't appear. Now that raw-path Ctrl+R raises a one-shot, per-pane notice (floating bottom-right) saying integration hasn't engaged, refined off-thread with the daemon's foreground-process name when it matches a known shim: the wrapper is the culprit worth naming, since "install integration" advice would mislead — the hooks are installed, something between the shell and tty7 is eating their output. The chord still reaches the PTY, so the shell's own reverse-i-search keeps working as the fallback. Guards keep it honest: silent inside an 8s startup grace window (slow rc files legitimately haven't reported yet), on the alt screen, or once integration has engaged (a running foreground command is then the obvious reason); retracted if a slow shell engages late; dismissed by the next keystroke or a 15s timeout. * docs(readme): restructure around Why/Features, cut prose and boilerplate Replace the prose About section with a four-point Why tty7 list, split Features into prompt vs window groups written as one-line benefit bullets, drop emoji section headers, fold acknowledgements/contributing/ license into a one-line footer, and keep zh-CN in sync throughout.
This commit is contained in:
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- When shell integration never engages in a pane, pressing Ctrl+R now explains
|
||||
why the history menu can't appear (once per pane, dismissed by the next
|
||||
keystroke) instead of failing silently — naming the wrapper when a
|
||||
figterm-style PTY shim (`kiro-cli-term`, `figterm`, `qterm`) is intercepting
|
||||
the shell's OSC 133 reports. The chord still reaches the shell, so its own
|
||||
reverse-i-search keeps working. (#46)
|
||||
|
||||
## [0.9.0] - 2026-07-10
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/s3dethqz2V)
|
||||
|
||||
[**Install**](#-install) · [**Benchmarks**](#-benchmarks) · [**Shortcuts**](#️-shortcuts) · [**Contributing**](#-contributing)
|
||||
[**Why tty7**](#why-tty7) · [**Install**](#install) · [**Features**](#features) · [**Benchmarks**](#benchmarks) · [**Keybindings**](#keybindings)
|
||||
|
||||
<sub>English · [简体中文](README.zh-CN.md)</sub>
|
||||
|
||||
@@ -25,48 +25,50 @@
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
## Why tty7
|
||||
|
||||
tty7 is a GPU-rendered terminal. An 11 MB `cat` finishes in **95 ms** — about
|
||||
2× faster than Alacritty, Ghostty, or Kitty on the same hardware — and the
|
||||
built-in prompt gives you inline completion, syntax highlighting, and per-flag
|
||||
hints for common commands. Pure Rust, native on macOS, Windows, and Linux, no
|
||||
configuration needed.
|
||||
- **Fast** — about 2× the throughput of Alacritty, Ghostty, or Kitty on the
|
||||
same hardware ([benchmarks](#benchmarks))
|
||||
- **A modern prompt** — completion, syntax highlighting, and history search
|
||||
built in; no plugins to assemble
|
||||
- **Sessions that survive** — close the window or quit the app, your shells
|
||||
keep running; no tmux
|
||||
- **Zero config** — zsh, bash, fish, and PowerShell work out of the box
|
||||
|
||||
- ⚡ **Fast** — an 11 MB `cat` completes in **95 ms**, versus
|
||||
179–239 ms for Alacritty/Ghostty/Kitty; DOOM-fire renders at **888 fps**
|
||||
against their 485–617. Same machine, same grid; the harness is in the repo
|
||||
([benchmarks](#-benchmarks)).
|
||||
- ⌨️ **Prompt with completions** — inline completion, syntax highlighting,
|
||||
history, and in-terminal search. Type
|
||||
`git commit --`, `kubectl`, or `npm` and every flag and subcommand shows up
|
||||
with its description — signatures for ~100 common commands, generated
|
||||
from Fig's spec corpus.
|
||||
- 🧠 **Shell-aware, zero config** — new tabs and splits open in the current
|
||||
working directory, and path completion always follows where you are. zsh,
|
||||
bash, fish, and PowerShell are wired up automatically.
|
||||
- 🔌 **Sessions that survive** — shells run in a background daemon, so closing a
|
||||
window, quitting the app, or swapping in a new build never takes a shell down.
|
||||
Detach and reattach, no tmux.
|
||||
Native builds for macOS, Windows, and Linux ship with every release.
|
||||
|
||||
Also included: tabs (drag to reorder, inline rename, number keys to switch) and
|
||||
resizable splits, a command palette, click-to-open links, desktop notifications,
|
||||
and focus-follows-mouse. Eight built-in themes from light to dark, with the
|
||||
native window chrome following the one you pick, plus CJK/IME input.
|
||||
## Install
|
||||
|
||||
Native builds for macOS, Windows, and Linux — every release ships all three.
|
||||
Download the build for your platform from
|
||||
[**Releases**](https://github.com/l0ng-ai/tty7/releases):
|
||||
|
||||
<br />
|
||||
- **macOS** — `tty7-<version>-macos-arm64.dmg` (Apple Silicon) or `…-x86_64.dmg`
|
||||
(Intel); open it and drag `tty7.app` into Applications.
|
||||
- **Windows** — `…-windows-x86_64-setup.exe` (installer: Start Menu shortcut +
|
||||
uninstall entry), or `…-windows-x86_64.zip` (portable: unzip and run
|
||||
`tty7.exe`).
|
||||
- **Linux** — `…-linux-x86_64.tar.gz`; extract and run `./tty7` (needs the usual
|
||||
x11/wayland runtime libraries).
|
||||
|
||||
<div align="center">
|
||||
## Features
|
||||
|
||||
**[Download the latest release ▶](https://github.com/l0ng-ai/tty7/releases/latest)**
|
||||
### At the prompt
|
||||
|
||||
</div>
|
||||
- **Ghost suggestions** — your history completes the whole line as you type; <kbd>→</kbd> to accept
|
||||
- **Tab completion that explains** — every flag and subcommand with its description, for ~100 common commands
|
||||
- **Syntax highlighting** — as you type, nothing to install
|
||||
- **Fuzzy history search** — <kbd>⌃ R</kbd> shows what you ran, where, and whether it failed
|
||||
- **History from day one** — your existing shell history just works, and carries across sessions
|
||||
- **Real line editing** — selection, word motion, undo
|
||||
|
||||
<br />
|
||||
### In the window
|
||||
|
||||
## 📊 Benchmarks
|
||||
- **Tabs & splits** — always open in the current directory
|
||||
- **Command palette** <kbd>⌘ P</kbd> · scrollback search <kbd>⌘ F</kbd>
|
||||
- **⌘-click links** · desktop notifications
|
||||
- **Eight themes** · CJK / IME input
|
||||
|
||||
## Benchmarks
|
||||
|
||||
All four terminals measured back-to-back on the same machine, same day, same
|
||||
155×40 grid — Apple M1 Pro, macOS 26.3.1, five-run averages (2026-07-04):
|
||||
@@ -79,28 +81,17 @@ All four terminals measured back-to-back on the same machine, same day, same
|
||||
|
||||
<sub>¹ GUI 105 MB + the persistent daemon 11 MB.</sub>
|
||||
|
||||
tty7 reads the PTY at device speed and parses it in large batches off the render
|
||||
path, and the hot paths are lock-free — so a big `cat` never waits on drawing.
|
||||
(That's also what the background daemon buys you: it can run up to 16 MiB ahead
|
||||
of the window before backpressure applies.)
|
||||
Where the speed comes from:
|
||||
|
||||
- The PTY is read at device speed and parsed in large batches, off the render path
|
||||
- Hot paths are lock-free — a big `cat` never waits on drawing
|
||||
- The daemon buffers up to 16 MiB ahead of the window before backpressure applies
|
||||
|
||||
Methodology (how each terminal is driven, grid fairness, known pitfalls) and
|
||||
one-command reproduction live in [`scripts/bench/`](scripts/bench/README.md) —
|
||||
run it yourself.
|
||||
|
||||
## 🚀 Install
|
||||
|
||||
Download the build for your platform from [**Releases**](https://github.com/l0ng-ai/tty7/releases):
|
||||
|
||||
- **macOS** — `tty7-<version>-macos-arm64.dmg` (Apple Silicon) or `…-x86_64.dmg`
|
||||
(Intel); open it and drag `tty7.app` into Applications.
|
||||
- **Windows** — `…-windows-x86_64-setup.exe` (installer: Start Menu shortcut +
|
||||
uninstall entry), or `…-windows-x86_64.zip` (portable: unzip and run
|
||||
`tty7.exe`).
|
||||
- **Linux** — `…-linux-x86_64.tar.gz`; extract and run `./tty7` (needs the usual
|
||||
x11/wayland runtime libraries).
|
||||
|
||||
## ⌨️ Shortcuts
|
||||
## Keybindings
|
||||
|
||||
Keys are shown in macOS notation — on Windows and Linux, read <kbd>⌘</kbd> as
|
||||
<kbd>Ctrl</kbd>. Open Settings with <kbd>⌘ ,</kbd> to browse or remap them all.
|
||||
@@ -120,29 +111,12 @@ The essentials:
|
||||
|
||||
The full list — and any overrides — lives in **Settings → Keybindings**.
|
||||
|
||||
## 💭 Built with & inspired by
|
||||
|
||||
- [gpui](https://github.com/zed-industries/zed) — Zed's GPU-accelerated UI framework
|
||||
- [`alacritty_terminal`](https://github.com/zed-industries/alacritty) (Zed's fork) — VT emulator, grid, and PTY
|
||||
- [gpui-component](https://github.com/longbridge/gpui-component) — UI widgets, via a [pinned fork](https://github.com/l0ng-ai/gpui-component/tree/tty7)
|
||||
- [tmux](https://github.com/tmux/tmux) — the inspiration for the persistent-daemon design
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Bug reports and PRs are welcome. For questions and ideas, join the
|
||||
[Discord](https://discord.gg/s3dethqz2V). Notable changes land in the
|
||||
[CHANGELOG](CHANGELOG.md).
|
||||
|
||||
## 📝 License
|
||||
|
||||
[Apache License 2.0](LICENSE) · © 2026 l0ng-ai
|
||||
|
||||
<br />
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>
|
||||
|
||||
<img src="assets/app-icon.svg" alt="" width="28" height="28" />
|
||||
|
||||
<sub><b>tty7</b> — a GPU-rendered terminal in pure Rust.</sub>
|
||||
Built on [gpui](https://github.com/zed-industries/zed) and [`alacritty_terminal`](https://github.com/zed-industries/alacritty) · [Apache-2.0](LICENSE) · [Discord](https://discord.gg/s3dethqz2V) · [Changelog](CHANGELOG.md)
|
||||
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
+42
-63
@@ -15,7 +15,7 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/s3dethqz2V)
|
||||
|
||||
[**安装**](#-安装) · [**基准测试**](#-基准测试) · [**快捷键**](#️-快捷键) · [**参与贡献**](#-参与贡献)
|
||||
[**为什么是 tty7**](#为什么是-tty7) · [**安装**](#安装) · [**功能**](#功能) · [**基准测试**](#基准测试) · [**快捷键**](#快捷键)
|
||||
|
||||
<sub>[English](README.md) · 简体中文</sub>
|
||||
|
||||
@@ -25,41 +25,46 @@
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
## 为什么是 tty7
|
||||
|
||||
tty7 是一款 GPU 渲染的终端。11 MB 的 `cat` 用时 **95 ms**,大约是
|
||||
Alacritty、Ghostty、Kitty 的两倍速度(同一台机器);内置提示符提供内联补全、
|
||||
语法高亮,以及常用命令的逐 flag 说明。纯 Rust 编写,macOS、Windows、Linux
|
||||
均为原生构建,无需额外配置。
|
||||
- **快** —— 同一台机器上,吞吐约为 Alacritty、Ghostty、Kitty 的
|
||||
2 倍([基准测试](#基准测试))
|
||||
- **现代的提示符** —— 补全、语法高亮、历史搜索全部内置,不用东拼西凑插件
|
||||
- **会话不死** —— 关窗口、退应用,shell 照样运行;无需 tmux
|
||||
- **零配置** —— zsh、bash、fish、PowerShell 开箱即用
|
||||
|
||||
- ⚡ **快** —— 11 MB 的 `cat` 花 **95 ms**,Alacritty/Ghostty/Kitty
|
||||
要 179–239 ms;DOOM-fire 跑到 **888 fps**,它们在 485–617 之间。同一台机器、
|
||||
同一网格测出来的,脚本就在仓库里(见[基准测试](#-基准测试))。
|
||||
- ⌨️ **带补全的提示符** —— 内联补全、语法高亮、历史记录、终端内搜索。输入 `git commit --`、`kubectl` 或 `npm`,每个 flag 和子命令
|
||||
都会带着说明一并列出 —— 覆盖约 100 个常用命令,数据取自 Fig 的语料。
|
||||
- 🧠 **懂 shell,零配置** —— 新标签页和分屏都开在当前目录,路径补全也始终跟随
|
||||
你所在的位置。zsh、bash、fish、PowerShell 均自动接入。
|
||||
- 🔌 **会话不中断** —— shell 运行在后台守护进程中,关窗口、退应用、乃至换上新
|
||||
版程序,都不会中断任何一个 shell。随时断开,随时接回,无需 tmux。
|
||||
macOS、Windows、Linux 三平台原生构建,每个 release 一起打出。
|
||||
|
||||
其他功能:标签页(拖拽重排、双击重命名、数字键切换)、可拖动分隔线
|
||||
调节比例的分屏、命令面板、点击打开链接、桌面通知、焦点随鼠标移动。内置 8 套
|
||||
主题(由浅及深),系统标题栏的明暗跟随所选主题;CJK 与输入法组合输入也一并
|
||||
支持。
|
||||
## 安装
|
||||
|
||||
macOS、Windows、Linux 三个平台都有原生构建,每个 release 一起打出。
|
||||
到 [**Releases**](https://github.com/l0ng-ai/tty7/releases) 下载对应平台的构建:
|
||||
|
||||
<br />
|
||||
- **macOS** —— `tty7-<version>-macos-arm64.dmg`(Apple Silicon)或 `…-x86_64.dmg`
|
||||
(Intel);打开后把 `tty7.app` 拖进「应用程序」即可。
|
||||
- **Windows** —— `…-windows-x86_64-setup.exe`(安装包:带开始菜单快捷方式和
|
||||
卸载入口),或 `…-windows-x86_64.zip`(便携版:解压后运行 `tty7.exe`)。
|
||||
- **Linux** —— `…-linux-x86_64.tar.gz`;解压后运行 `./tty7`(需要常见的
|
||||
x11/wayland 运行时库)。
|
||||
|
||||
<div align="center">
|
||||
## 功能
|
||||
|
||||
**[下载最新版本 ▶](https://github.com/l0ng-ai/tty7/releases/latest)**
|
||||
### 提示符
|
||||
|
||||
</div>
|
||||
- **影子建议** —— 边打字边用你的历史补全整条命令,<kbd>→</kbd> 接受
|
||||
- **会解释的 Tab 补全** —— 每个 flag、每个子命令都带说明,覆盖约 100 个常用命令
|
||||
- **语法高亮** —— 边打边亮,什么都不用装
|
||||
- **模糊历史搜索** —— <kbd>⌃ R</kbd> 看到每条命令在哪跑的、什么时候、有没有失败
|
||||
- **历史开箱即用** —— 你已有的 shell 历史直接生效,并跨会话延续
|
||||
- **真正的行编辑** —— 选区、词级移动、撤销
|
||||
|
||||
<br />
|
||||
### 窗口
|
||||
|
||||
## 📊 基准测试
|
||||
- **标签页与分屏** —— 永远开在当前目录
|
||||
- **命令面板** <kbd>⌘ P</kbd> · 回滚搜索 <kbd>⌘ F</kbd>
|
||||
- **⌘ 点击打开链接** · 桌面通知
|
||||
- **8 套主题** · CJK / 输入法输入
|
||||
|
||||
## 基准测试
|
||||
|
||||
四款终端在同一台机器上依次测完,网格统一为 155×40 —— Apple M1 Pro,
|
||||
macOS 26.3.1,取五次运行的平均值(2026-07-04):
|
||||
@@ -72,25 +77,16 @@ macOS 26.3.1,取五次运行的平均值(2026-07-04):
|
||||
|
||||
<sub>¹ GUI 105 MB + 常驻守护进程 11 MB。</sub>
|
||||
|
||||
tty7 以设备速度读取 PTY,并在渲染路径之外成批解析输出,热路径全程无锁 ——
|
||||
再大的 `cat` 也不会阻塞在渲染上。(后台守护进程亦服务于此:触发背压前,它最多
|
||||
可领先窗口 16 MiB。)
|
||||
速度从哪来:
|
||||
|
||||
- 以设备速度读取 PTY,在渲染路径之外成批解析
|
||||
- 热路径全程无锁 —— 再大的 `cat` 也不会阻塞在渲染上
|
||||
- 触发背压前,守护进程最多可领先窗口缓冲 16 MiB
|
||||
|
||||
测试方法(每款终端怎么驱动、网格是否公平、有哪些坑)连同一键复现脚本,都放在
|
||||
[`scripts/bench/`](scripts/bench/README.md),欢迎自己跑一遍。
|
||||
|
||||
## 🚀 安装
|
||||
|
||||
到 [**Releases**](https://github.com/l0ng-ai/tty7/releases) 下载对应平台的构建:
|
||||
|
||||
- **macOS** —— `tty7-<version>-macos-arm64.dmg`(Apple Silicon)或 `…-x86_64.dmg`
|
||||
(Intel);打开后把 `tty7.app` 拖进「应用程序」即可。
|
||||
- **Windows** —— `…-windows-x86_64-setup.exe`(安装包:带开始菜单快捷方式和
|
||||
卸载入口),或 `…-windows-x86_64.zip`(便携版:解压后运行 `tty7.exe`)。
|
||||
- **Linux** —— `…-linux-x86_64.tar.gz`;解压后运行 `./tty7`(需要常见的
|
||||
x11/wayland 运行时库)。
|
||||
|
||||
## ⌨️ 快捷键
|
||||
## 快捷键
|
||||
|
||||
下表按 macOS 记法书写 —— 在 Windows 和 Linux 上,把 <kbd>⌘</kbd> 读作
|
||||
<kbd>Ctrl</kbd>。按 <kbd>⌘ ,</kbd> 打开设置,可查看或重新映射全部键位。最常用的几个:
|
||||
@@ -104,34 +100,17 @@ tty7 以设备速度读取 PTY,并在渲染路径之外成批解析输出,
|
||||
| <kbd>⌘ K</kbd> | 清屏并清空回滚缓冲区 |
|
||||
| <kbd>⌘ P</kbd> | 命令面板 |
|
||||
| <kbd>⌘ F</kbd> | 搜索回滚缓冲区 |
|
||||
| <kbd>⌃ R</kbd> | 反向搜索 shell 历史 |
|
||||
| <kbd>⌃ R</kbd> | 模糊搜索 shell 历史 |
|
||||
| <kbd>⌘ +</kbd> · <kbd>⌘ −</kbd> · <kbd>⌘ 0</kbd> | 字号增大 · 减小 · 重置 |
|
||||
|
||||
完整列表(以及你改过的自定义键位)在 **Settings → Keybindings**。
|
||||
|
||||
## 💭 站在这些之上
|
||||
|
||||
- [gpui](https://github.com/zed-industries/zed) —— Zed 的 GPU 加速 UI 框架
|
||||
- [`alacritty_terminal`](https://github.com/zed-industries/alacritty)(Zed 的 fork)—— VT 模拟器、网格与 PTY
|
||||
- [gpui-component](https://github.com/longbridge/gpui-component) —— UI 组件,经由一个[固定版本的 fork](https://github.com/l0ng-ai/gpui-component/tree/tty7)
|
||||
- [tmux](https://github.com/tmux/tmux) —— 常驻守护进程设计的灵感来源
|
||||
|
||||
## 🤝 参与贡献
|
||||
|
||||
欢迎提 bug 和 PR。有问题、有想法,欢迎来
|
||||
[Discord](https://discord.gg/s3dethqz2V) 交流。重要改动都记在
|
||||
[CHANGELOG](CHANGELOG.md)。
|
||||
|
||||
## 📝 许可证
|
||||
|
||||
[Apache License 2.0](LICENSE) · © 2026 l0ng-ai
|
||||
|
||||
<br />
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>
|
||||
|
||||
<img src="assets/app-icon.svg" alt="" width="28" height="28" />
|
||||
|
||||
<sub><b>tty7</b> —— 纯 Rust 编写的 GPU 渲染终端。</sub>
|
||||
基于 [gpui](https://github.com/zed-industries/zed) 与 [`alacritty_terminal`](https://github.com/zed-industries/alacritty) 构建 · [Apache-2.0](LICENSE) · [Discord](https://discord.gg/s3dethqz2V) · [更新日志](CHANGELOG.md)
|
||||
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
@@ -224,6 +224,19 @@ pub struct TerminalView {
|
||||
/// selection into the line, Cmd+Enter runs it outright, and Escape/Ctrl+G
|
||||
/// cancels.
|
||||
reverse_search: Option<ReverseSearch>,
|
||||
/// One-shot "shell integration didn't engage" notice (#46). Set when Ctrl+R
|
||||
/// is pressed in a pane whose shell never reported OSC 133 — the history
|
||||
/// menu the user is reaching for can't appear, and without this the feature
|
||||
/// just looks broken. A figterm-style PTY shim (kiro-cli-term, qterm) that
|
||||
/// swallowed the reports is the usual culprit, so the message names the
|
||||
/// wrapper when the daemon's foreground query recognizes one. Cleared on
|
||||
/// the next keystroke, after a timeout, or if integration engages late.
|
||||
integration_notice: Option<String>,
|
||||
/// Latch so the notice shows at most once per pane — a diagnostic, not a nag.
|
||||
integration_notice_shown: bool,
|
||||
/// When this view was created. Ctrl+R inside the startup grace window stays
|
||||
/// silent: slow rc files mean integration legitimately hasn't reported yet.
|
||||
created_at: std::time::Instant,
|
||||
/// True while a left-drag that began on the command-editor line is in progress,
|
||||
/// so mouse-move extends the editor selection rather than the terminal's.
|
||||
editor_selecting: bool,
|
||||
@@ -300,6 +313,43 @@ const LONG_COMMAND: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
/// instant once the window lapses.
|
||||
const HOLD_WINDOW: std::time::Duration = std::time::Duration::from_millis(150);
|
||||
|
||||
/// How long after pane creation Ctrl+R stays silent about missing shell
|
||||
/// integration: slow rc files can take several seconds to reach the first
|
||||
/// prompt report, and calling integration broken while the shell is still
|
||||
/// starting up would be a false alarm.
|
||||
const INTEGRATION_GRACE: std::time::Duration = std::time::Duration::from_secs(8);
|
||||
|
||||
/// How long the integration notice stays up when no keystroke dismisses it.
|
||||
const INTEGRATION_NOTICE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
|
||||
/// Fig-descended PTY shims known to exec over the shell we spawned and re-host
|
||||
/// it on a nested PTY without forwarding OSC 133 — which starves shell
|
||||
/// integration and silently kills the whole command-editor overlay (#46).
|
||||
/// Matched against the foreground process name the daemon reports; `contains`
|
||||
/// because the shim may present as e.g. `zsh (kiro-cli-term)`.
|
||||
fn known_pty_shim(fg: &str) -> Option<&'static str> {
|
||||
["kiro-cli-term", "figterm", "qterm", "cwterm"]
|
||||
.into_iter()
|
||||
.find(|shim| fg.contains(shim))
|
||||
}
|
||||
|
||||
/// The integration-notice text. Naming the shim matters: "install shell
|
||||
/// integration" advice would mislead — the hooks *are* installed, something
|
||||
/// between the shell and tty7 is eating their reports.
|
||||
fn integration_notice_message(wrapper: Option<&str>) -> String {
|
||||
match wrapper {
|
||||
Some(w) => format!(
|
||||
"tty7 shell integration is blocked in this pane — \u{201c}{w}\u{201d} is intercepting \
|
||||
shell reports, so inline completion and the Ctrl+R menu are unavailable. \
|
||||
The shell's own history search still works."
|
||||
),
|
||||
None => "tty7 shell integration hasn't engaged in this pane, so inline completion and \
|
||||
the Ctrl+R menu are unavailable. A PTY wrapper (figterm-style) or an \
|
||||
unsupported shell setup can cause this."
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Post a desktop notification that a command finished. Best-effort and
|
||||
/// non-blocking: routed through [`super::remote::notify_desktop`] (the single
|
||||
/// `notify-rust` entry point shared with the escape-sequence path), so there's no
|
||||
@@ -703,6 +753,9 @@ impl TerminalView {
|
||||
pending_history: None,
|
||||
completion: None,
|
||||
reverse_search: None,
|
||||
integration_notice: None,
|
||||
integration_notice_shown: false,
|
||||
created_at: std::time::Instant::now(),
|
||||
editor_selecting: false,
|
||||
editor_select_gesture: false,
|
||||
hovered_link: None,
|
||||
@@ -839,6 +892,12 @@ impl TerminalView {
|
||||
if self.terminal.exited {
|
||||
return;
|
||||
}
|
||||
// Any keystroke dismisses a visible integration notice — it has been
|
||||
// read. The Ctrl+R that raises it runs later in this same dispatch, so
|
||||
// the raising chord never clears its own notice.
|
||||
if self.integration_notice.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
// macOS Option-key policy (see `input::reshape_option_keystroke`):
|
||||
// reshape the chord once, up front, so every consumer below — the ⌘
|
||||
// dispatcher, the prompt editor, the raw PTY encoder — sees the same
|
||||
@@ -931,6 +990,16 @@ impl TerminalView {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+R reaching this raw path means the tty7 history menu the user is
|
||||
// probably reaching for cannot appear here. When that's because shell
|
||||
// integration never engaged — not because a foreground command owns the
|
||||
// PTY — say so once instead of failing silently (#46). The chord still
|
||||
// goes to the PTY below, so the shell's own reverse-i-search keeps
|
||||
// working as the fallback.
|
||||
if m.control && !m.platform && !m.alt && ks.key == "r" {
|
||||
self.note_integration_gap(cx);
|
||||
}
|
||||
|
||||
let kitty = self.kitty_flags();
|
||||
if let Some(bytes) = super::input::keystroke_to_bytes(ks, kitty) {
|
||||
let plain = !m.control && !m.alt && !m.platform;
|
||||
@@ -1816,6 +1885,15 @@ impl TerminalView {
|
||||
self.rerank_history(Some(&cwd));
|
||||
}
|
||||
|
||||
// Shell integration engaging late (a slow rc file finally reported)
|
||||
// makes a visible integration notice wrong — retract it. The
|
||||
// once-per-pane latch stays set: the overlay works now, there is
|
||||
// nothing left to explain.
|
||||
if self.integration_notice.is_some() && self.terminal.shell_active() {
|
||||
self.integration_notice = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// Redraw when the prompt/running state flips, so the line editor shows or
|
||||
// hides promptly even when the shell produced no output to trigger a
|
||||
// repaint (e.g. a command that prints nothing). Without this the editor's
|
||||
@@ -2240,6 +2318,63 @@ impl TerminalView {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Raise the one-shot integration notice if this Ctrl+R fell through to the
|
||||
/// raw PTY path because shell integration never engaged (#46). Silent when
|
||||
/// the raw path is expected instead: integration did engage and a foreground
|
||||
/// command merely owns the PTY, a full-screen TUI owns the pane, or the
|
||||
/// shell is still inside its startup grace window (slow rc files haven't
|
||||
/// reached the first prompt report yet).
|
||||
///
|
||||
/// Shows a generic message immediately, then refines it off-thread: the
|
||||
/// daemon's foreground query sees the process actually holding the PTY, and
|
||||
/// when that is a known shim (it exec'd over the shell we spawned), naming
|
||||
/// it turns "the feature looks broken" into "here is the culprit".
|
||||
fn note_integration_gap(&mut self, cx: &mut Context<Self>) {
|
||||
if self.integration_notice_shown
|
||||
|| self.terminal.shell_active()
|
||||
|| self.on_alt_screen()
|
||||
|| self.created_at.elapsed() < INTEGRATION_GRACE
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.integration_notice_shown = true;
|
||||
self.integration_notice = Some(integration_notice_message(None));
|
||||
cx.notify();
|
||||
|
||||
let pane_id = self.pane_id;
|
||||
cx.spawn(async move |this, cx| {
|
||||
// Best-effort: no daemon / unknown pane / unreadable process just
|
||||
// leaves the generic message standing.
|
||||
let fg = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
RemoteTerminal::list_panes()
|
||||
.into_iter()
|
||||
.find(|p| p.pane_id == pane_id)
|
||||
.map(|p| p.title)
|
||||
})
|
||||
.await;
|
||||
if let Some(shim) = fg.as_deref().and_then(known_pty_shim) {
|
||||
let _ = this.update(cx, |view, cx| {
|
||||
if view.integration_notice.is_some() {
|
||||
view.integration_notice = Some(integration_notice_message(Some(shim)));
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Reading time is over either way; a keystroke usually beat us here.
|
||||
cx.background_executor()
|
||||
.timer(INTEGRATION_NOTICE_TIMEOUT)
|
||||
.await;
|
||||
let _ = this.update(cx, |view, cx| {
|
||||
if view.integration_notice.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Begin a Ctrl+R history search (no-op if one is already active). Opens
|
||||
/// with the empty query's frecency listing, so the menu is browsable
|
||||
/// before a single key is typed.
|
||||
@@ -3347,6 +3482,34 @@ impl TerminalView {
|
||||
)
|
||||
}
|
||||
|
||||
/// The one-shot "shell integration didn't engage" notice (#46): a single
|
||||
/// floating line, bottom-right so it reads as a status aside rather than
|
||||
/// part of the prompt. Rendered whenever set — unlike the editor overlays
|
||||
/// it exists precisely because `input_active()` is false.
|
||||
fn render_integration_notice(
|
||||
&self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement + use<>> {
|
||||
let text = self.integration_notice.clone()?;
|
||||
let theme = cx.theme();
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.bottom(px(8.))
|
||||
.right(px(16.))
|
||||
.max_w(px(560.))
|
||||
.px_3()
|
||||
.py_1()
|
||||
.bg(theme.popover)
|
||||
.border_1()
|
||||
.border_color(theme.border)
|
||||
.rounded(px(6.))
|
||||
.text_size(px(12.))
|
||||
.text_color(theme.muted_foreground)
|
||||
.child(text),
|
||||
)
|
||||
}
|
||||
|
||||
/// Map a highlighter token kind to a theme color.
|
||||
fn kind_color(&self, kind: TokenKind, cx: &App) -> gpui::Hsla {
|
||||
let theme = cx.theme();
|
||||
@@ -3413,6 +3576,9 @@ impl Render for TerminalView {
|
||||
.input_active()
|
||||
.then(|| self.render_reverse_search_menu(cx))
|
||||
.flatten();
|
||||
// Not gated on `input_active()`: the notice explains why the editor
|
||||
// overlays are absent, so it renders exactly when they can't.
|
||||
let integration_notice = self.render_integration_notice(cx);
|
||||
|
||||
// Captured for the right-click menu: the focus handle routes dispatched
|
||||
// actions to this terminal (and lets tab/split ones bubble to the root),
|
||||
@@ -3494,6 +3660,7 @@ impl Render for TerminalView {
|
||||
.children(input_bar)
|
||||
.children(completion_menu)
|
||||
.children(reverse_search_menu)
|
||||
.children(integration_notice)
|
||||
// Right-click context menu (gpui-component PopupMenu).
|
||||
.context_menu(move |menu, _window, _cx| {
|
||||
// Small size = tighter 20px rows; the default 26px felt too airy.
|
||||
@@ -4606,6 +4773,91 @@ mod gpui_tests {
|
||||
gpui::Keystroke::parse(spec).expect("valid keystroke spec")
|
||||
}
|
||||
|
||||
/// The notice names only known fig-style shims — an ordinary foreground
|
||||
/// command (`ssh`) must not be blamed for intercepting anything, and the
|
||||
/// generic message must not claim interception it can't prove.
|
||||
#[test]
|
||||
fn shim_detection_names_known_wrappers_only() {
|
||||
assert_eq!(known_pty_shim("zsh (kiro-cli-term)"), Some("kiro-cli-term"));
|
||||
assert_eq!(known_pty_shim("figterm"), Some("figterm"));
|
||||
assert_eq!(known_pty_shim("qterm"), Some("qterm"));
|
||||
assert_eq!(known_pty_shim("ssh"), None);
|
||||
assert_eq!(known_pty_shim("wezterm"), None);
|
||||
assert_eq!(known_pty_shim(""), None);
|
||||
assert!(integration_notice_message(Some("kiro-cli-term")).contains("kiro-cli-term"));
|
||||
assert!(!integration_notice_message(None).contains("intercepting"));
|
||||
}
|
||||
|
||||
/// The Ctrl+R integration notice (#46), through the real key dispatcher:
|
||||
/// silent inside the startup grace window, raised once integration has had
|
||||
/// time to engage and never did, dismissed by the next keystroke, and
|
||||
/// one-shot per pane. The chord itself still reaches the PTY throughout
|
||||
/// (the shell's own reverse-i-search is the fallback).
|
||||
#[gpui::test]
|
||||
fn ctrl_r_without_integration_raises_the_notice_once(cx: &mut TestAppContext) {
|
||||
// `note_integration_gap` queries the daemon for the pane's foreground
|
||||
// process; pin the config dir to a scratch so the control connection
|
||||
// fails cleanly instead of reaching a real user daemon.
|
||||
let dir = std::env::temp_dir().join(format!("tty7-noticetest-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).ok();
|
||||
crate::core::config::set_config_dir(dir);
|
||||
|
||||
let (window, _daemon) = harness(cx);
|
||||
window
|
||||
.update(cx, |view, window, cx| {
|
||||
let ctrl_r = KeyDownEvent {
|
||||
keystroke: key("ctrl-r"),
|
||||
is_held: false,
|
||||
prefer_character_input: false,
|
||||
};
|
||||
// Fresh pane: the shell may legitimately not have reported yet.
|
||||
view.on_key_down(&ctrl_r, window, cx);
|
||||
assert!(
|
||||
view.integration_notice.is_none(),
|
||||
"the grace window stays silent"
|
||||
);
|
||||
|
||||
// Past the grace window with no OSC 133 ever seen → notice.
|
||||
view.created_at = std::time::Instant::now() - INTEGRATION_GRACE * 2;
|
||||
view.on_key_down(&ctrl_r, window, cx);
|
||||
assert!(
|
||||
view.integration_notice.is_some(),
|
||||
"Ctrl+R raises the notice"
|
||||
);
|
||||
cx.notify();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Let the notified frame actually draw — a panic in the notice layout
|
||||
// fails the test here.
|
||||
cx.run_until_parked();
|
||||
window
|
||||
.update(cx, |view, window, cx| {
|
||||
assert!(
|
||||
view.integration_notice.is_some(),
|
||||
"the notice survives a real render pass"
|
||||
);
|
||||
|
||||
// The next keystroke dismisses it; the latch keeps it one-shot.
|
||||
let ctrl_r = KeyDownEvent {
|
||||
keystroke: key("ctrl-r"),
|
||||
is_held: false,
|
||||
prefer_character_input: false,
|
||||
};
|
||||
view.on_key_down(&ctrl_r, window, cx);
|
||||
assert!(
|
||||
view.integration_notice.is_none(),
|
||||
"a keystroke dismisses the notice"
|
||||
);
|
||||
view.on_key_down(&ctrl_r, window, cx);
|
||||
assert!(
|
||||
view.integration_notice.is_none(),
|
||||
"the notice is one-shot per pane"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// The Ctrl+R flow end-to-end at the editor dispatcher: Ctrl+R opens the
|
||||
/// search, typed text (the IME/commit path) edits the query with fuzzy
|
||||
/// matching, Enter loads the selection into the editor without running it.
|
||||
|
||||
Reference in New Issue
Block a user