ccc98b90a9 fix(input): stop the Windows/Linux keymap and CRLF pastes from breaking the shell (#270)
* fix(input): stop the Windows/Linux keymap and CRLF pastes from breaking the shell

Two problems a Windows user hit within an hour of installing tty7, both from
code that only ever had macOS in mind.

A bracketed paste forwarded the clipboard byte for byte. Text copied on Windows
carries \r\n, and vim counts CR and LF as two line breaks, so pasting code into
it left a blank line under every line. Fold \r\n to \n inside the paste markers
so a Windows clipboard produces exactly what the same paste already produced on
Linux and macOS. The non-bracketed path keeps converting to CR: with no paste
mode to tell text from typing, a line break has to arrive as the byte Return
sends.

`secondary` means Cmd on macOS and Ctrl everywhere else, and the default keymap
was carried over from macOS unchanged, which put window actions on top of
terminal control codes. Ctrl+D could not send EOF, Ctrl+[ could not send ESC,
and Ctrl+W, Ctrl+K, Ctrl+P, Ctrl+J, Ctrl+T, Ctrl+Q and Ctrl+S were all taken.
These bindings carry no context, so they match before the terminal sees the key
-- apply_readline_ctrl already sends EOF for ^D, it was just unreachable.

Off macOS, ctrl-<letter> and ctrl-[ ] \ space now belong to the terminal and
window actions move to ctrl-shift-*, the convention GNOME Terminal, Konsole,
Windows Terminal and WezTerm share. Tabs move to Alt+1-9 and pane focus to
Alt+arrow, since Ctrl+Alt+arrow is workspace switching under GNOME. Ctrl+S
keeps saving in the code panel but propagates when the editor is unfocused, so
a shell still gets XOFF. Shift+Insert pastes. macOS is untouched, and a user's
own rebindings still win over the defaults.

A test walks the default table and rejects any chord that lands on a control
code, so the next binding added cannot quietly bring this back; a second one
rejects two actions claiming the same chord, which is how the reshuffle caught
NewTab colliding with ReopenClosedTab.

Refs #269

* fix(history): keep one history store per machine, not one per install

tty7 owns ^R at the prompt and shows its own fuzzy menu, but `history.rs` had
no notion of where a command had run: `load()` read one file and `append()`
wrote one file, so an ssh session was offered the commands typed on the local
machine. That is worse than an empty menu -- the entries look plausible until
one of them runs against the wrong box.

History is now scoped. The local store keeps its existing path, so nobody loses
what they have; each remote gets its own file under `history.d/`, named after
the target with a hash appended so a label that is not a filename ("me@box:/srv")
cannot escape the directory or collide with another host.

A pane picks its scope from the innermost remote it can see -- the ssh target
reported by shell integration, else its host id -- and follows it when that
changes, clearing the menu and reloading in the background rather than showing
the previous machine's entries while it waits. A reload that lands after the
pane has moved on again is dropped.

On a remote workspace, where a Host impl exists, the far end's own
~/.zsh_history and ~/.bash_history are read through the same channel git status
and file listings already use, so the first ^R on a freshly connected box has
something in it. Bare ssh has no Host to read through and falls back to what
tty7 recorded itself for that target, which is still the right machine's
commands.

Refs #269

* fix(test): the control-code guard must admit Ctrl+S, which falls through

The guard forbade every bare ctrl-letter default, but EditorSave staying on
Ctrl+S is the design: its handler propagates whenever the editor does not
have focus, so the terminal still receives XOFF. The guard's real invariant
is "no default may swallow a control code" -- an action that falls through
does not swallow. EditorSave is exempted by name, with the condition for
earning a spot written next to the list.

The test is vacuous on macOS (secondary parses to Cmd), which is why the
contradiction only surfaced on the Linux runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(history): never seed an inner ssh target from the workspace host's files

remote_shell_history_sources gated only on "scope is remote and host is
remote", so a pane that ssh'ed onward from a remote workspace -- scoped to
the inner target -- was seeded from the workspace host's ~/.zsh_history:
another machine's commands in the menu, the exact confusion scoping exists
to prevent. The Host can only reach the workspace machine's home, so seeding
is now reserved for the host's own scope; a pane with a remote_context falls
back to what tty7 recorded for that target, exactly like bare ssh from a
local pane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
Co-authored-by: thomas <thomas@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 23:11:07 +08:00

tty7

tty7

A terminal workbench: shells, sessions, SSH, coding agents.

Pure Rust · GPU rendering on Zed's gpui · VT core from Alacritty


CI Version License Discord

English · 简体中文

Why

  • Fast — ~2× the throughput of Alacritty, Ghostty, or Kitty (benchmarks)
  • Sessions persist — quit or reboot; your shells keep running, no tmux
  • Editor-grade input — completion, syntax highlighting, history search built in; zero config for zsh, bash, fish, PowerShell
  • Agent-aware — recognizes Claude Code & co. in a pane: status, notifications, session resume

Install

Native builds for each platform on Releases:

macOS …-macos-arm64.dmg · …-x86_64.dmg drag into Applications
Windows …-setup.exe · portable ….zip
Linux …-x86_64.AppImage chmod +x and run — x11/wayland libs bundled

What's inside

Input ghost suggestions from history · explained tab completion · syntax highlighting · multi-line editing · click places the caret · ⌃ R fuzzy history
Window tabs & splits · ⌘ P palette · ⌘ F scrollback search · nine themes · IME
Coding agents per-pane agent detection (~17 CLIs): status dot, notifications, branch + diff, resume after reboot, tray icon that signals "needs your input"
SSH native russh stack: profiles with keychain secrets, SFTP panel, port forwarding, jump hosts

Details for every row: docs/features.md. Keybindings: ⌘ , opens Settings — browse and remap everything, tmux preset included (full list).

Benchmarks

Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1, five-run averages (2026-07-04):

tty7 Alacritty Ghostty Kitty
Plaintext IO — 11 MB cat (lower = better) 95 ms 239 ms 179 ms 185 ms
DOOM-fire frame rate (higher = better) 888 fps 485 fps 552 fps 617 fps
Cold-launch memory 116 MB¹ 105 MB 128 MB 130 MB

¹ GUI 105 MB + the persistent daemon 11 MB.

Methodology and one-command reproduction: scripts/bench/.


S
Description
A terminal workbench in pure Rust: shells, persistent sessions, SSH, coding agents. GPU-rendered on Zed's gpui, VT core from Alacritty.
Readme Apache-2.0
34 MiB
Languages
Rust 99.1%
Shell 0.5%
PowerShell 0.2%
Inno Setup 0.1%