Files
tty7/Cargo.toml
T
l0ng-ai ecb5ab1ed7 fix(input): keep Option chords out of the IME when Option is Meta
Option-as-Meta had no effect for anyone typing with a CJK input source.
The whole setting was dead for them: with Pinyin selected, macOS reports
Option chords as printable text (Option+B composes the special
character), so gpui routed them to the IME before the key handler ran.
The IME committed the composed character and swallowed the event --
on_key_down never ran, and reshape_option_keystroke never got a say.
Switching to ABC made it work again, which is why this looked
intermittent. Verified on the wire: with Pinyin active Option+F/Option+B
put c692 / e288ab on the PTY where ESC f / ESC b belong.

The routing decision lives in gpui's macOS backend and is asked once per
view, with no keystroke in hand, so it could not answer "IME for text,
but not for this chord". gpui now comes from our fork, whose one commit
passes the keystroke to prefers_ime_for_printable_keys; the default
implementation ignores it, so no existing handler changes behavior. The
terminal answers per key: an Option chord with the setting on stays on
the dispatch path, everything else still prefers the IME.

Gated on the setting deliberately. With Option-as-Meta off the chord is
text input and the IME is the right owner -- it is what makes dead keys
(Option+E then E -> e-acute) compose at all.

The fork is wired in with [patch] on the source rather than by editing
the gpui pins, because gpui-component declares its own gpui from the
upstream URL and a pin swap would put two incompatible copies of gpui in
the tree. Fetching a repo that size needs the git CLI; cargo's built-in
libgit2 transfer times out partway through.

Fixes #177
2026-07-26 20:31:32 +08:00

333 lines
16 KiB
TOML

[package]
name = "tty7"
version = "26.7.4"
edition = "2024"
description = "A terminal workbench: shells, persistent sessions, SSH, coding agents — GPU-rendered on Zed's gpui, pure Rust"
repository = "https://github.com/l0ng-ai/tty7"
license = "Apache-2.0"
readme = "README.md"
publish = false
default-run = "tty7"
[[bin]]
name = "tty7"
path = "src/main.rs"
[dependencies]
gpui = { workspace = true }
gpui_platform = { workspace = true }
gpui-component = { workspace = true }
gpui-component-assets = { workspace = true }
anyhow.workspace = true
log.workspace = true
# Smart double-click selection patterns (URL/email/path). Already in the tree
# transitively, so pinning it here adds no new native code.
regex = "1"
smol.workspace = true
smallvec.workspace = true
serde = { workspace = true }
serde_json.workspace = true
# SSH connection-manager data layer (`core::ssh_profile` / `core::keychain`).
# `uuid` mints stable profile ids (v4) and serde-serializes them as strings;
# `sha2` hashes private-key file contents to the sha512-hex account key that
# passphrases are stored under in the OS keychain (per PRD §7.2). Both are already
# in the tree transitively, so pinning them here adds no new native code. `keyring`
# 4.x is the OS credential-vault backend: its default `v1` feature auto-selects the
# platform store (macOS Keychain / Windows Credential Manager / Linux Secret
# Service), so no per-platform feature guards are needed.
uuid = { version = "1", features = ["v4", "serde"] }
sha2 = "0.10"
keyring = "4"
# Theme files. tty7 themes are authored as YAML (`~/.config/tty7/themes/*.yaml`,
# our own schema); `plist` parses imported iTerm2 `.itermcolors` schemes (XML
# plist) so the large iTerm color-scheme ecosystem drops straight in. serde_yaml
# is already in the tree transitively, so it pins no new code; plist is pure Rust.
serde_yaml = "0.9"
plist = "1"
# Clipboard-image paste (`terminal::view`). When the clipboard holds a screenshot
# and a coding-agent TUI (Claude Code &co.) is in the pane, off macOS we stage the
# image to a temp file and paste its path — agents attach an image path the same way
# they do a drag-drop. Windows screenshots arrive as BMP (`CF_DIB`), which agent
# vision won't accept, so we transcode to PNG. `image` is already in the tree via
# gpui's own clipboard code, so pinning it here adds no new native code.
image = "0.25"
# HTTP client for the startup update check (`core::update`): one GET to the
# GitHub releases API to see if a newer version has shipped. `reqwest_client`
# wraps Zed's `zed-reqwest` fork behind gpui's `http_client` trait (re-exported
# as `gpui::http_client`) and manages its own tokio runtime. That reqwest+rustls
# stack is *already* compiled into the tree via `gpui-component-assets`, so this
# pins no new native code — it only exposes what we're already building. Pinned
# to gpui's rev so the shared `http_client`/`zed-reqwest` versions stay aligned.
reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" }
# Our fork of Zed's alacritty_terminal fork. Used by the *client*
# (`terminal::remote`) for the VT parser + grid (`Term`/`ansi::Processor`) that
# renders the mirror. The daemon's PTY itself is driven by `portable-pty` below.
#
# The `tty7` branch is Zed's `fcf32fe` (the rev Zed pins) plus one commit:
# `push_keyboard_mode` capped its stack by removing from `title_stack` instead of
# `keyboard_mode_stack` — a copy-paste slip from `push_title` that compiles because
# both are `Vec`s. With `kitty_keyboard` on (see `terminal_config_from_user`) every
# overflowing push silently drops a saved title, and once the title stack is empty
# `Vec::remove(0)` panics — 4097 unpopped `CSI > 1 u` pushes, about 20KB of output,
# kill the reader thread and freeze the pane. Still present on alacritty master as
# of 852e971; drop this fork once it lands upstream.
alacritty_terminal = { git = "https://github.com/l0ng-ai/alacritty", rev = "22b1d74842732f6ea30d8f4eec768b3ac659d0b6" }
# Desktop notifications driven by OSC 9 / OSC 777 escape sequences. Cross-platform;
# the macOS backend uses the deprecated NSUserNotification (weak — a completion
# toast is fine, revisit mac-notification-sys if it proves unusable).
notify-rust = "4"
# Filesystem watcher for live config reload: watches `config.json` and reloads
# `Config` + re-applies the theme without a restart. The code panel (file tree /
# editor, `ui::code`) reuses it to refresh the tree and detect external edits.
notify = "8"
# Code panel (file tree + editor, `ui::code`): `ignore` supplies the gitignore
# matcher chain the file tree uses to dim ignored entries (same crate ripgrep
# uses). The editor itself needs nothing else — text storage and syntax
# highlighting both live inside gpui-component's `InputState`.
ignore = "0.4"
# Cross-platform PTY for the daemon: a Unix pty on Unix, ConPTY on Windows, behind
# one blocking `Read`/`Write`/`resize` API. This is what lets `daemon::pane` share
# a single code path across platforms instead of hand-rolling fd/ioctl/signal code.
portable-pty = "0.8"
# Native (pure-Rust) SSH client for the daemon's russh session engine
# (`daemon::ssh`). Replaces shelling out to the system `ssh` binary for managed
# connections: it gives us the protocol stack that GUI-hosted auth, host-key
# prompts, in-memory connection reuse, and (later) SFTP / native port-forwarding
# all need. 0.62 folded the old `russh-keys` crate in as `russh::keys` (key
# parsing + ssh-agent client). The async russh session lives on a small tokio
# runtime owned by `daemon::ssh`; the rest of the daemon stays std-threads and
# bridges to it through blocking `Read`/`Write` adapters.
russh = "0.62"
# SFTP client for the native SSH engine (`daemon::ssh::sftp`, Workstream 5).
# Not part of russh proper: `russh-sftp` drives the SFTP subsystem over any
# AsyncRead+AsyncWrite stream, which a russh session channel provides via
# `Channel::into_stream()`. Version-independent of russh (it only needs the
# channel byte stream), so it rides the same tokio runtime `daemon::ssh` owns.
russh-sftp = "2"
# tokio powers only the russh session engine — a single runtime `daemon::ssh`
# owns. The daemon's PTY/reader/writer threads remain std threads and never touch
# it; they cross into async through bounded/unbounded channels (the blocking
# `Read`/`Write` adapters in `daemon::ssh::session`).
tokio = { version = "1", features = [
"rt-multi-thread",
"net",
"io-util",
"sync",
"time",
"macros",
"process",
# `fs` powers the local side of SFTP transfers (`daemon::ssh::sftp`): async
# file/dir IO on the daemon process's own filesystem during upload/download.
"fs",
] }
# SIMD byte search for the OSC tokenizer's Ground/Ignore fast paths — the
# sniffers sit on the full-throughput output stream (100+ MB/s at full drain),
# where a per-byte state machine costs a measurable slice of the reader loop.
# Already in the tree transitively (vte et al.), so this pins no new code.
memchr = "2"
# System tray / menu bar status item (`ui::tray`). Rasterizes the bundled SVG
# logo into the tray bitmap at runtime — gpui's own SVG path only yields a
# tinted alpha mask, not raw RGBA. Pinned to the exact version already in the
# tree via gpui (0.45.x), so this adds no new native code; default features off
# drops the text/font machinery our icon SVGs don't use.
resvg = { version = "0.45", default-features = false }
# The tray icon itself, macOS + Windows: tauri's `tray-icon` (NSStatusItem /
# Shell_NotifyIcon via objc2 / windows-sys, both already in the tree). Linux is
# deliberately NOT on this crate — its Linux backend needs GTK + libappindicator,
# which tty7's AppImage doesn't bundle and the x11/wayland gpui backends don't
# pull. Linux instead uses `ksni` below.
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
tray-icon = "0.24"
# `setsid` (daemon detach) and the macOS foreground-process proc queries are the
# only libc users left, both Unix-only — so the dep is Unix-only too.
[target.'cfg(unix)'.dependencies]
libc = "0.2"
libgssapi = "0.11"
# The Windows GUI⇄daemon transport is loopback TCP, which (unlike a Unix socket)
# any local process can connect to — so the daemon authenticates each connection
# against a random token it writes into the user-private port file. `getrandom`
# is the OS CSPRNG that mints that token; already in the tree transitively, so
# this pins no new code. Windows-only, matching the transport it guards.
[target.'cfg(windows)'.dependencies]
getrandom = "0.3"
# Toolhelp process enumeration + `TerminateProcess`, used by `daemon::winproc` to
# title a pane by its foreground command and to tear down a shell's descendant
# tree on hangup (ConPTY's `kill` only reaches the shell itself). Already in the
# tree via gpui's Windows backend, so this pins no new code. Windows-only.
windows-sys = { version = "0.59", features = [
"Win32_Foundation",
"Win32_System_Console",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Threading",
] }
# Embeds `assets/favicon.ico` into the `.exe` so Windows shows the tty7 logo in
# the taskbar / window / Explorer (macOS gets its icon from the `.app` bundle via
# bundle.sh instead). Only needed at build time on Windows — see build.rs.
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"
# gpui only *reads* the macOS window appearance; it never sets it. We force the
# app appearance to match the active theme (see `ui::theme::sync_native_appearance`)
# so the native traffic-light buttons render in the right light/dark style.
[target.'cfg(target_os = "macos")'.dependencies]
# CFStringTokenizer FFI for dictionary-based CJK word segmentation on
# double-click (`terminal::smart_select`). Already in the tree transitively,
# and it makes jieba unnecessary here — see the non-macos section below.
core-foundation = "0.10"
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSResponder", "NSAppearance", "NSGraphics", "NSImage"] }
# NSData feeds the runtime Dock icon for bare (non-bundled) binaries — see
# `set_dock_icon_for_bare_binary` in main.rs.
objc2-foundation = { version = "0.3", features = ["NSData"] }
# Dictionary-based Chinese word segmentation for double-click selection
# (`terminal::smart_select`), as a *fallback* where the OS has no tokenizer of
# its own. macOS is excluded on purpose: CFStringTokenizer segments Chinese
# about as well (and Japanese/Korean far better) at zero cost, while jieba's
# table costs ~55 MB resident once built and ~2 MB of binary for the embedded
# dictionary. Keeping the dep off macOS means that dictionary isn't even
# linked into the build that can't use it.
[target.'cfg(not(target_os = "macos"))'.dependencies]
jieba-rs = "0.10"
# x11/wayland are the Linux windowing backends; only pull them on Linux. The
# Windows backend (`gpui_windows`) and macOS backend are selected by gpui_platform
# itself via `cfg`, so no feature is needed for them.
[target.'cfg(target_os = "linux")'.dependencies]
gpui_platform = { workspace = true, features = ["x11", "wayland"] }
# Linux tray (`ui::tray`): pure-Rust StatusNotifierItem over DBus (zbus — already
# in the tree). `blocking` + `async-io` gives a synchronous handle without
# requiring the app to own a tokio runtime; ksni runs its own service thread. On
# desktops without an SNI host (bare GNOME without the AppIndicator extension)
# spawning fails and the tray is silently absent — the app is unaffected.
ksni = { version = "0.3.6", default-features = false, features = ["blocking", "async-io"] }
# gpui's `test-support` unlocks `#[gpui::test]` + `TestAppContext`, the headless
# App/Window harness the view/event tests run on. Dev-only: the feature merges
# into test builds and never reaches a release binary.
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
# `test-util` unlocks tokio's paused clock (`start_paused`) so the ssh prompt
# broker's timeout/retry tests run instantly instead of in real time. Dev-only.
tokio = { version = "1", features = ["test-util", "macros", "rt"] }
[lints]
workspace = true
# ---- Standalone workspace mirroring gpui-component's pins so the git/source
# ---- caches are shared and versions stay aligned. ----
[workspace]
members = ["."]
[workspace.package]
edition = "2024"
[workspace.dependencies]
# Our fork's `tty7` branch carries the local customizations tty7 relies on:
# `PopupMenu::with_size`, the 1px hairline menu separator, and the custom-button
# label color the chrome tiles need. The exact commit is still pinned by
# Cargo.lock. For co-developing the UI crate, point these back at a sibling
# checkout: `path = "../gpui-component/crates/{ui,assets}"` — but never commit
# that, since a path dependency can't resolve on CI.
#
# `tree-sitter-languages` compiles the grammars the code editor highlights with.
gpui-component = { git = "https://github.com/l0ng-ai/gpui-component", branch = "tty7", version = "0.5.2", features = [
"tree-sitter-languages",
] }
gpui-component-assets = { git = "https://github.com/l0ng-ai/gpui-component", branch = "tty7", version = "0.5.1" }
gpui = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" }
# Base features are cross-platform (`font-kit`, `runtime_shaders` only map to the
# macOS backend; they're no-ops elsewhere). The Linux-only `x11`/`wayland`
# backends are added by the `cfg(target_os = "linux")` dependency entry in the
# package manifest, so they never reach the Windows/macOS builds.
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe", features = ["font-kit", "runtime_shaders"] }
anyhow = "1"
log = "0.4"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1"
smallvec = "1"
smol = "2"
[patch.crates-io]
# Temporary until upstream russh releases gssapi-with-mic client auth support
# (https://github.com/Eugeny/russh/pull/737) — remove this patch and take the
# crates.io release once it lands. Pinned to an exact rev (never a branch):
# russh is the SSH protocol layer handling user credentials, and a moving
# branch on a third-party fork could change what `cargo update` builds.
russh = { git = "https://github.com/ayamir/russh", rev = "0d1d073350ed823069252075cbf3db9672d5b490" }
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
type_complexity = "allow"
[profile.dev]
codegen-units = 16
debug = "limited"
split-debuginfo = "unpacked"
[profile.dev.package]
resvg = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
taffy = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
smol = { opt-level = 3 }
gpui = { opt-level = 3 }
gpui_platform = { opt-level = 3 }
gpui_macros = { opt-level = 3 }
# The VT parser + grid run on every PTY byte; at opt-level 0 a `cat bigfile`
# crawls under `cargo dev`.
alacritty_terminal = { opt-level = 3 }
# The render/parse hot path crosses the tty7 ↔ alacritty_terminal ↔ gpui crate
# boundaries, so cross-crate inlining (thin LTO, like Zed ships) buys real
# throughput there; single codegen unit for the same reason.
[profile.release]
lto = "thin"
codegen-units = 1
# ---- gpui fork ------------------------------------------------------------
# Our `tty7` branch (cut from the pinned upstream rev, one commit on top) carries
# a single patch: `prefers_ime_for_printable_keys` takes the keystroke, so an
# input handler can answer per key instead of per view. tty7 needs it for
# Option-as-Meta — macOS routes ⌥-chords to the IME whenever a CJK input source
# is active, and without the keystroke there is no way to decline just those
# chords (see `terminal::input::prefers_ime_for_printable_keys`, issue #177).
#
# Patching by source rather than editing the `gpui`/`gpui_platform` pins above is
# deliberate: `gpui-component` declares its own `gpui` from the upstream URL, and
# a plain pin swap would put two incompatible copies of gpui in the tree. `[patch]`
# rewrites the source for every dependent at once, so the fork's sibling crates
# (`gpui_macos`, `gpui_web`, …) come along through their in-repo path deps.
#
# When bumping the upstream rev: rebase the fork's `tty7` branch onto the new rev,
# push, then update the `rev` pins above — the branch here follows automatically.
[patch."https://github.com/zed-industries/zed"]
gpui = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_platform = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_macros = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_web = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
reqwest_client = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }