Repository navigation
Releases
v0.8.3
tmux-control 0.8.3
Speed
- A terminal that keeps receiving output is redrawn at most 30 times a
second, and all such terminals are redrawn together. Emacs pays for each
redraw of a frame, so drawing every chunk of a fast stream kept it busy:
four terminals each printing 200 lines a second held Emacs at 75–100% of
a core on a Mac, and now take 35–43%. The first output after a quiet
spell is drawn at once, and so is everything for half a second after you
type into the terminal, so typing feels the same. Set
tmux-control-redisplay-intervalto change the pace, or to nil to draw
every chunk as it comes. - A pane whose program stopped at its last column no longer repaints from
tmux three times over after a resize or a visit. Over SSH each repaint
cost a round trip.
Window buffers
tmux-control-window-bufferstakesall: every window of the session
gets its buffer as soon as tmux-control learns of it, and the buffer keeps
up with its window whether or not it is shown.tmux-control-window-buffer-namesset totabnames window buffers as
their tabs read, such as "*tmux dev:work 1:bash*", and renames them when
tmux renames or moves a window. The default,id, keeps
"*tmux dev:work @5*".- A window's buffer takes the size of the pane that becomes active. Closing
a split's other pane left the buffer at the split's height, blank above
its last few rows.
Sizes and tiled views
- A window made shorter keeps following its terminal's output. Output drawn
before Emacs next drew the window could leave it behind, falling further
back with each line until something repainted it. - A new connection asks tmux for its size once its window has settled. A
package that changes the window right after connecting, as Roost does when
it docks its task panel, had tmux rewrap the pane at two widths, and the
pane showed a long line from its second row. - Tiled panes keep their screens in view through a resize. They showed a
row or more of history above the screen until the next repaint. - A tiled view beside a full-height window, with a band such as a menu
across the frame's bottom, is sized by its columns. tmux was asked for
one row. - Panes stacked in a tiled view fit their rows when the mode line is taller
than a line of text, as modus themes and doom-modeline make it. They came
out a few pixels short and scrolled their screen part way out of view. - The rightmost pane of a tiled view shows its last column rather than a
$.
Dropped connections
- When a connection drops, its screen stays in view with the note on the
line under it. The window used to scroll down to the note, past blank
rows. Notes no longer move point. - A click on a dead terminal says in the echo area that
C-c C-r
reconnects. It asked whether to reconnect for each press and each
release. Typing still asks.
For packages
tmux-control-window-buffer-functionsis called with each new window
buffer once it knows its connection and window. Making a buffer doesn't
display it, so this is where a package can, for example, add it to a
perspective.
Tests and docs
- The VT oracle (
make test-vt-oracle) measures tab stops and grapheme
cells as tmux does, no longer echoes query replies onto the screen it
compares, and has 35 new cases, among them grapheme clusters and tmux's
own quirks. - The README and guide show the current look, and the README says to
require 0.8.0 fortmux-control-switch-session-functions.
v0.8.2
tmux-control 0.8.2
- Clicks keep working after a terminal misses a mouse release. A press in a
program that takes the mouse made Eat send every later click in Emacs to
the terminal if the release never reached it: while tmux wasn't replying,
when the window showed another buffer by the release, or when a question
read it. Now the press is let go of once its release is handled, and a
press outside any terminal goes to its own binding. - A split view's pane starts at its screen's top when a window
configuration brings it back, as perspective.el'spersp-switchdoes. It
showed a line or more of scrollback until the pane's next output. - Two docstrings start with a whole sentence, as checkdoc asks.
v0.8.1
tmux-control 0.8.1
- After
C-c C-rreconnects a view in place, tmux is sized to the window
with its header line, so the view's top row stays in sight. tmux kept one
row more than the window showed until the window next changed. - A connect's quiet period for background activity is no longer cut short by
the resize it makes, so a slow first repaint doesn't flag windows.
v0.8.0
tmux-control 0.8.0
Names
- The mode line names the host and tmux window, such as "dev › build", in
place of the buffer name, and the terminal's mode reads "tmux". Set
tmux-control-mode-line-window-nameto nil to keep the buffer name. - Buffers are named "*tmux HOST:SESSION*", "local" for the local server,
with the socket in parentheses when it isn't the default, and a window's,
pane's or scrollback's suffix after that, such as "*tmux dev:work @2*".
Update anything in your configuration that matches the old names.
Keys and links
- Shift+Return reaches the program as tmux's own S-Enter on tmux 3.5 and
later, so it starts a new line in Claude Code's prompt rather than sending
it. So does Option+Return, as M-Enter, in a GUI frame's semi-char mode.
Older servers get the bytes as before. - A lone ESC stays whole: the M-RET override binding is gone.
C-c C-uopens a URL shown on the screen. Links that programs mark with
OSC 8 open with a click orC-c C-uin the live view, and with a click or
RETin scrollback.C-c C-omoves to the next pane's window in a tiled view.
Split windows
- A split window keeps its tabs: the header line sits over the top-left pane,
and tmux lays the panes out one row shorter to make room. Each pane keeps
the mode line other windows have, naming its host, window and pane. - When a split closes, the view goes back to one pane, with its tabs.
- A new tiled view keeps you on the pane you were looking at, tmux's active
one, soC-c |focuses the new pane, as tmux does. - Switching sessions replaces all of a split session's panes, and returning
to it splits it again.
What you see
- Panes render as tmux does where Eat's emulation departs from it, checked
against tmux by a differential test (make test-vt-oracle).
tmux-control-correct-emulationturns the corrections off. - A normal screen repaints from tmux after any change in size, so it stays on
tmux's screen as a pane gets shorter, taller, narrower or wider, and long
lines wrap where tmux wraps them. A full-screen program repaints itself. - A repainted screen starts on a line of its own, which fixes a garbled first
row after returning to a split session. - No-break spaces show as spaces.
Speed
- Keystrokes are answered sooner, panes that keep scrolling stay fast, and a
settled window is left alone when output arrives. - Idle collection (
tmux-control-idle-gc-mode) checks from idle timers
rather than 20 times a second, and terminal buffers keep no undo. - A window showing a window buffer resizes tmux when it changes size.
Diagnostics
tmux-control-pause-log-modenotes each time Emacs stops responding for
tmux-control-pause-log-thresholdseconds, with the command, tmux output
or timer that took most of it, or else that redisplay or another program's
output most likely did, and the time spent collecting garbage.
tmux-control-diagnosticslists the latest notes.
For packages
tmux-control-switch-session-functionslets a package show a session its
own way when you switch to it from a view, as Roost does to open the task.
tmux ending a connection is said once.
v0.7.2
tmux-control 0.7.2
unload-featurenow leaves Emacs as it was:tmux-control-unload-function
removes the advice on Eat and on the pager command, the global resize hooks,
the Evil initial state, andtmux-control-idle-gc-mode's hooks and timer.
Close tmux-control buffers before unloading.- The two
window-size-change-functionshooks that follow resizes of
scrollback pagers and tiled views are added by the first tmux-control buffer
rather than when the package loads.
v0.7.1
tmux-control 0.7.1
tmux-control-kill-pane moves from C-c x to C-c C-x. Emacs reserves C-c
followed by a letter for users' own bindings, and MELPA's checks reject
packages that bind those keys. To keep the old key, add it to your
configuration:
(with-eval-after-load 'tmux-control
(keymap-set tmux-control-mode-map "C-c x" #'tmux-control-kill-pane))v0.7.0
tmux-control 0.7.0
This release adds a public API for packages built on tmux-control, so they no
longer need its internal functions and variables. It also collects the
terminal, recovery and modal-editing fixes made since 0.6.0.
Public API
tmux-control-connect-or-switch,tmux-control-send-commandand
tmux-control-queryconnect to a session and talk to tmux over its
connection.tmux-control-tiled-p,tmux-control-buffer-host,
tmux-control-buffer-socket-name,tmux-control-buffer-session,
tmux-control-active-paneandtmux-control-window-iddescribe the current
buffer.tmux-control-send-commandandtmux-control-queryaccept exactly one tmux
command, starting with the bare command name. They refuse blank and
comment-only lines, variable assignments, NUL bytes, more than one line, and
an unquoted;,{or}, any of which would detach the client or
misalign later replies.tmux-control-window-idalso works in tiled pane buffers and in session
buffers withouttmux-control-window-buffers.- The internal names these functions wrap still work.
See "For package authors" in the README for the full list.
Connections and terminal fidelity
- Add named connection bookmarks using native Emacs bookmarks and a copyable
tmux-control-diagnosticsreport with asynchronous live server details. - Separate connection identity by host, socket, and session; controller renames
no longer break reuse or render-buffer cleanup. - Retain visited tiled pane history through window switches, resizing,
repaints, and untile/retile; cached panes keep streaming while hidden. - Restore tiling on reconnect while preserving neighboring code windows/focus.
- Detect incomplete command replies, refuse additional input on an overdue
connection, and report unacknowledged input without replaying it on recovery. - Refresh pane cursor and terminal modes during in-band repaint/flow-control
recovery; ignore stale seeds from older connections or superseded requests. - Preserve canonically composable combining accents that Eat 0.9.4 omits.
Complex graphemes without a single-cell composition remain an Eat limitation.
Scrollback and the live buffer
- Scrolling back shows a clickable
↑ 120 linesindicator; clicking it
returns that window to live output. tmux-control-clear-scrollback(C-c M-o) clears both the Emacs copy of a
pane's history and tmux's, for example after a full-screen program repaints
at a new size and leaves its old frame in scrollback.- The live terminal buffer is read-only, so an editing command can no longer
change the text Eat uses as its model of the pane. Typing still reaches the
pane, and input methods keep working. tmux-control-adopt-window-sizeclears stale sizing warnings and takes
sizing back from another attached client.- Every header line redraws when the window list changes.
Modal editing and GUI frames
- Full-screen programs no longer lose rows or columns in GUI frames:
alternate-screen scrolling, tmux's separator column in horizontal tiles, and
stale horizontal scrolling are handled. - In raw mode, keys go to the pane before modal-editing maps; entering raw mode
clears xah-fly-keys' command map, and Option-Return returns to semi-char
mode. - Evil users: buffers start in
tmux-control-evil-state,insertby
default, so keys reach the pane instead of acting as Evil commands. - The README shows how to send ESC to the pane under a modal package.
Installation
- The README shows
use-package :vc(Emacs 30) andpackage-vc-install
(Emacs 29) alongside straight.el. .elpaignorekeeps the test helpers out of package installs, which had
printed load errors duringpackage-vcinstallation.
v0.6.0
tmux-control 0.6.0
This release improves scrollback loading and live-history retention, reduces
temporary output-decoding allocations, and adds optional idle-time garbage
collection for tmux-control views.
Scrollback
- History loads in smaller, 500-row extensions and begins loading three
screens before the loaded top. Both settings are configurable. - Overlap matching normalizes only the bounded seam between history chunks.
- Live buffers retain up to 1,048,576 characters by default, reducing the
chance that incoming output discards text being read. Customize
tmux-control-live-scrollback-size, or set it tonilto inherit Eat's
setting. The limit applies to new or reconnected buffers; more retained
text uses more memory per pane. - Output decoding copies literal runs together and reuses octal replacement
strings to reduce temporary allocation.
Optional idle collection
Enable M-x tmux-control-idle-gc-mode to request garbage collection during
quiet periods in live views and scrollback pagers. Run it again to disable.
The mode is off by default and leaves Emacs's automatic GC settings intact.
Use M-x tmux-control-idle-gc-status to see its requested collections.
The defaults require one second without a completed command, ten million
new cons allocations since the last collection, a selected tmux-control
view, and no pending input or active minibuffer.
In a controlled five-minute comparison, maximum software response fell
from 242 ms to 12 ms, while total GC time increased from 230 ms to 452 ms.
Input deliberately resumed during collection still saw 62–71 ms responses.
These exploratory measurements do not establish display FPS or physical
trackpad latency. Try the mode with your normal workload before enabling
it permanently. Lower decoder allocation alone did not demonstrate fewer
long scrolling pauses.
Other fixes since 0.5.0
- Handle normalized control-mode delimiters.
- Preserve carriage returns when decoding UTF-8 output streams.
- Anchor tiled pane windows to Eat's live display start.
Developer tools
New allocation benchmarks, controlled GUI scroll traces, and aggregate
analysis tools make input delays and reading-position drift measurable.
See the measurement guide for reproduction steps and
limitations, and the user guide for configuration.
v0.5.0
A month and 26 commits since v0.4.0, with one new opt-in feature and three render-correctness fixes.
New
Pane-directory tracking (#119). find-file and dired in a tmux-control buffer already default to the pane's own directory, on the pane's host. The opt-in tmux-control-pane-directory-mode goes further and makes default-directory itself follow the pane, so compile, grep, consult-ripgrep and project commands run there too:
(add-hook 'tmux-control-mode-hook #'tmux-control-pane-directory-mode)Tracking is session-wide — toggling it in any of a session's buffers applies to all of them, each following its own pane (#121).
Render correctness
Three distinct bugs, all surfaced by running the chaos soak against a remote session over SSH rather than a local socket:
- The repaint painted the wrong buffer (#124).
tmux-control--seed-screendrives its seed with tagged commands, and tagged replies are dispatched in the controller — so a repaint requested from a per-window buffer (the default view) painted that window's pane into the connect buffer, and left it there. Reached both bytmux-control-clear-and-repaint— the command the README prescribes for a drifted view — and by the opt-in auto-heal. - Drift was never repaired on arrival (#127). A window's buffer keeps streaming while you look at another window, where Eat can accumulate a row of divergence from the pane's true screen. Nothing repainted a buffer that was merely switched to, so the drift survived every switch. Arriving at a window now verifies its screen against tmux and repaints only on a real difference.
- A seed could paint a stale capture (#126). Output arriving after tmux ran
capture-pane, but before the paint, was rendered and then erased by the paint's screen clear. Cursor-only verification cannot catch it, because the cursor still agrees. - Tiled panes: the view anchor counted from the buffer's end, so a single appended
[tmux-control]note scrolled a pane permanently past the top of its own screen (#122). - Tiling entry points fixed for per-window render buffers (#115); seeds now replay pane terminal modes (alternate screen, mouse) into Eat; the
alternate-screen offwarning is throttled across reconnects.
Diagnostics
- A failed tmux command reports tmux's own words —
tmux: can't find window: @9— instead of the internal reply kind (#123). Command arguments are deliberately not echoed, since keystrokes ridesend-keys. tmux-control-scrollback-modeis guarded against being invoked in a live buffer (#120).
Verification
233 ERT tests green, clean byte-compile, and a parallel A/B soak against a remote tmux 3.6b over SSH — six seeds × 60 randomized operations, v0.4.0-era main and this release running simultaneously against separate sessions on the same host: 9 render failures → 2, with no seed worse.
v0.4.0
v0.4.0
Everything since v0.2.0. The headline is a redesigned header line, a full
security-hardening pass, and connection resilience (auto-reconnect, SSH
keepalives, flock self-heal).
Security hardening
- Command-injection defused in session/window names and the SSH host — all
attacker-influenced values are now quoted/validated before reaching the shell
or control-mode targets (#97, #75, #76, #90). - Mode-line / header-line injection closed:
%in pane-derived titles and
fields is escaped so a pane can't forge mode-line constructs (#98). - Reply-stream validation: pane ids from tmux are validated and the input
accumulator is capped, so a hostile/runaway stream can't blow up Emacs (#99). - Clipboard writes (OSC 52) ignored by default — a pane can no longer set
your system clipboard unless you opt in viatmux-control-allow-clipboard-write
(marked:risky) (#100). - Supply-chain/CI: added
SECURITY.md, Dependabot, and OpenSSF Scorecard (#95).
Redesigned header line ("here" / "elsewhere")
- The header now splits "here" (left: a persistent
host:session › windows
label, so you always know which server/session you're looking at) from
"elsewhere" (right corner: other connected sessions with unseen output,
named and grouped by server — click to jump) (#88, #86, #87). - Cross-session activity is disambiguated by host, so two hosts' default
sessions no longer collide into an indistinguishable marker (#85). - Per-tab hover highlight; local connections are named
local:session(#87).
Connection resilience
- Opt-in in-place auto-reconnect (
tmux-control-auto-reconnect, default off)
re-attaches a dropped session without losing your buffers (#104). - Hardened SSH: keepalives and a connect timeout on every invocation, so a
dead network surfaces fast instead of hanging (#101). - Flock self-heal: the tiled grid re-lays-out when a session joins or dies (#105).
- Warn once when the controlled tmux has alternate-screen disabled (#103).
Rendering & tiling
- In-band tiling — the tiled view is built over the control connection
instead of blocking SSH round-trips (#69, #83). - Opt-in auto-heal for Eat render drift (
tmux-control-auto-heal-drift,
default off): clears and repaints a desynced pane (#81). - Optional per-pane mode-line hide in tiled view for a gapless grid (#107).
- Skip resize round-trips when the requested size is unchanged (#82).
Navigation & UX
- Keyboard window jumps and a real scrollback-pager exit (
l) (#93). - In-place live-history scroll (wheel-up) is now the default (#94).
- Guards against acting on a session killed out from under the corner/switcher
(#90, #91), and a quiet-window so the controller doesn't flag its own activity (#92).
Protocol correctness (bug-hunt batch)
- Seed/focus fixes, scrollback-seam dedup (no double lines on a scrolling pane),
timer-lifecycle hygiene, and a literal-TAB-in-pane-title fix (#72, #74, #77, #78).