fix: widen eat pane in 1-session layout to fit heavy TUIs#59
Merged
Conversation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5 tasks
phasetr
pushed a commit
that referenced
this pull request
May 9, 2026
Several enkan-repl parser/lookup helpers in enkan-repl-utils.el did not account for Emacs's UNIQUE flag on `rename-buffer', which appends <2>, <3>, ... when multiple eat sessions are opened for the same project path. This left those buffers undetectable by path extraction and lookup. Changes: - enkan-repl--buffer-name->path: regex extended to accept optional <N> suffix; also tightened to require non-empty path (prevents misparsing malformed "*ws:01 enkan:*"). - enkan-repl--buffer-name->instance: new helper returning 1-based instance index (1 when no suffix, N when "<N>", nil for non-enkan names). - enkan-repl--path->buffer-name: optional INSTANCE argument; appends "<N>" when N >= 2. - enkan-repl--buffer-matches-directory: optional INSTANCE filter; matches on extracted path so buffers with <N> suffix are recognized; falls back to "any instance" when INSTANCE is nil. Tests: - Four new ERTs covering: multi-instance path extraction, instance index extraction, instance-aware buffer-name generation, and directory match with optional instance filter. - Fixed pre-existing failing test test-workspace-window-recreation-after-switch (mock for split-window-right needed to accept optional SIZE arg added by PR #59 layout tweak). Test suite: 162/162 pass (previously 157/158 with one unrelated failure). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
phasetr
added a commit
that referenced
this pull request
May 9, 2026
* chore: start feat/terminal-backend-abstraction Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal): introduce backend dispatch layer (eat backend only) New file enkan-repl-terminal.el centralizes terminal-backend-specific operations behind a generic API selected by `enkan-repl-terminal-backend' (default 'eat for full backward compatibility). Public API: - enkan-repl--terminal-start - enkan-repl--terminal-send / send-key - enkan-repl--terminal-alive-p - enkan-repl--terminal-list / kill / display Phase 1 ships only the eat backend, which wraps existing in-tree behavior without changing semantics. tmux backend functions are stubs that raise user-error until a later phase. Existing call sites in enkan-repl.el are not yet rewired to the new dispatch; that follows in subsequent commits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(parser): handle multi-instance buffer name suffix <N> Several enkan-repl parser/lookup helpers in enkan-repl-utils.el did not account for Emacs's UNIQUE flag on `rename-buffer', which appends <2>, <3>, ... when multiple eat sessions are opened for the same project path. This left those buffers undetectable by path extraction and lookup. Changes: - enkan-repl--buffer-name->path: regex extended to accept optional <N> suffix; also tightened to require non-empty path (prevents misparsing malformed "*ws:01 enkan:*"). - enkan-repl--buffer-name->instance: new helper returning 1-based instance index (1 when no suffix, N when "<N>", nil for non-enkan names). - enkan-repl--path->buffer-name: optional INSTANCE argument; appends "<N>" when N >= 2. - enkan-repl--buffer-matches-directory: optional INSTANCE filter; matches on extracted path so buffers with <N> suffix are recognized; falls back to "any instance" when INSTANCE is nil. Tests: - Four new ERTs covering: multi-instance path extraction, instance index extraction, instance-aware buffer-name generation, and directory match with optional instance filter. - Fixed pre-existing failing test test-workspace-window-recreation-after-switch (mock for split-window-right needed to accept optional SIZE arg added by PR #59 layout tweak). Test suite: 162/162 pass (previously 157/158 with one unrelated failure). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(session): track multi-instance index in session-list entries Session-list entries previously stored just a project-name string in the cdr. This made it impossible to distinguish multiple terminal instances of the same project, which Emacs's UNIQUE rename-buffer flag silently permits and which the user actually relies on. Entry format: - Single instance (default): (session-number . "project-name") [unchanged] - Multi-instance (index >= 2): (session-number . ("project-name" . N)) The legacy string form is kept for instance=1 to preserve serialization compatibility with existing state files and ERTs. All readers go through the new accessors so on-disk format can evolve transparently. New helpers in enkan-repl-utils.el: - enkan-repl--session-entry-project (extract project name; both forms) - enkan-repl--session-entry-instance (extract instance index, defaults 1) - enkan-repl--make-session-entry-value (constructor) Updated: - enkan-repl--register-session: optional INSTANCE argument - enkan-repl--terminate-all-session-buffers: passes instance to lookup - enkan-repl--get-buffer-for-directory: optional INSTANCE filter - enkan-repl--get-current-session-state-info: shows <N> in display - examples/window-layouts.el: setup-window-eat-buffer-pure honors instance in generated buffer name Test fixes (mock signature compatibility): - test-enkan-repl--get-buffer-for-directory-with-workspace - test-enkan-repl-terminate-workspace-buffers Test suite: 162/162 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(enkan-repl): route eat call sites through terminal abstraction enkan-repl-start-eat, enkan-repl--send-primitive-action, and enkan-repl--send-text-to-buffer no longer call eat directly. They go through enkan-repl--terminal-* dispatch so the active backend (eat or tmux, per `enkan-repl-terminal-backend') handles transport. Changes: - enkan-repl.el: require enkan-repl-terminal at load time. - enkan-repl-start-eat: use enkan-repl--terminal-start to spawn the session, then enkan-repl--terminal-id-instance to capture the multi-instance index for register-session. - enkan-repl--send-primitive-action: collapse to one enkan-repl--terminal-send call (newline=t for text actions, nil otherwise). - enkan-repl--send-text-to-buffer: same dispatch, plus alive check via enkan-repl--terminal-alive-p. Cursor positioning timer kept, gated on bufferp so it remains a no-op for non-buffer terminal ids. - enkan-repl-terminal.el: add enkan-repl--terminal-id-instance dispatch (eat: parse buffer-name <N> suffix; tmux stub returns 1). Test fixes: - Add per-test cleanup of stale "*ws:NN enkan:...*" buffers in tests that exercise enkan-repl-start-eat: leftover same-named buffers from earlier tests would otherwise inflate the detected instance index. - test-multiple-eat-sessions-in-workspace: assert second instance produces the new (proj . 2) cons form (correctness improvement: the function now actually distinguishes the second instance). Test suite: 162/162 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal): implement tmux backend (start/send/alive/list/kill) Adds the tmux backend implementations behind the existing enkan-repl--terminal-* dispatch. When `enkan-repl-terminal-backend' is set to `tmux', enkan-repl spawns and drives an external tmux server instead of running an in-Emacs eat session, so heavy TUIs (claude, codex, ...) no longer compete with Emacs' main loop for repaint. Mapping: - workspace NN -> tmux session "enkan-NN" - per project -> tmux window in that session, named after dir basename - multi-instance same-dir -> window names "lat", "lat-2", "lat-3", ... Configuration: - enkan-repl-tmux-executable (default "tmux") - enkan-repl-tmux-session-prefix (default "enkan-") Implementations: - enkan-repl--terminal-tmux-start: ensure session, add window for dir - enkan-repl--terminal-tmux-send: send-keys -l (literal) + optional Enter - enkan-repl--terminal-tmux-send-key: maps escape/enter/digit - enkan-repl--terminal-tmux-alive-p: has-session + window membership - enkan-repl--terminal-tmux-list: window targets in current ws session - enkan-repl--terminal-tmux-kill: tmux kill-window - enkan-repl--terminal-tmux-id-instance: parse "-N" from window name - enkan-repl--terminal-tmux-display: still stub (mirror buffer is later) Tests: - test/enkan-repl-terminal-test.el: 9 new ERTs covering pure helpers (id parsing, base-name derivation, session-name builder, next-instance-name with mocked list-windows, eat id-instance via the abstraction). Tests requiring a real tmux server are skipped. Test suite: 171/171 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(state): persist workspace state to disk + tmux reconcile New file enkan-repl-state.el adds atomic disk persistence for `enkan-repl--workspaces' so tmux sessions can be reattached after Emacs restart / crash. Also valuable as bookkeeping for the eat backend even though those terminals don't survive Emacs. API: - enkan-repl-state-save (&optional FILE) atomic write via .tmp+rename - enkan-repl-state-load (&optional FILE) validated read, nil on error - enkan-repl-state-tmux-reconcile (&optional FILE) classifies state vs live tmux server (A: both / B: state-only / C: tmux-only) and returns reattach plan governed by `enkan-repl-state-recovery-policy' (reattach / recreate / prompt / ignore). Configuration: - enkan-repl-state-file (default ~/.emacs.d/enkan-repl-state.eld) - enkan-repl-state-recovery-policy (default 'reattach) Integration: - enkan-repl.el requires enkan-repl-state at load time when available. - enkan-repl--save-workspace-state mirrors to disk on every save (best effort, ignored in noninteractive/batch mode so tests don't clobber the user's real state file). - kill-emacs-hook performs a final flush so even ungraceful exits leave the latest state on disk. Tests: - test/enkan-repl-state-test.el: 8 ERTs covering payload build / validate, save+load round-trip, missing-file and corrupt-file handling, atomic write (no .tmp leftover), and reconcile classification with mocked tmux session listing. Test suite: 179/179 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal/tmux): add mirror buffer + attach helper Phase 7 of the terminal-backend abstraction work. Two complementary ways to surface tmux sessions inside Emacs: 1. Mirror buffer (in-Emacs, read-only) - enkan-repl-tmux-mirror (defcustom, default t) - enkan-repl-tmux-mirror-interval idle refresh seconds (1.0) - enkan-repl-tmux-mirror-history-lines scrollback per refresh (500) - enkan-repl--terminal-tmux-display now creates / pops to a "*tmux <id>*" buffer driven by an idle timer that runs `tmux capture-pane -p -J -S -<lines> -t <id>'. - Buffer-local kill-buffer-hook cancels the timer so killing the mirror buffer cleanly stops polling. - When the underlying pane disappears, refresh stops itself and marks the buffer "[tmux pane closed]". 2. External attach - enkan-repl-tmux-attach (interactive) ws optional, defaults to current workspace. - enkan-repl-tmux-attach-command defcustom: nil (auto), or a string template with %s -> session, or a function of one arg. - Auto default per system-type: darwin -> osascript -> Terminal.app -> "tmux attach -t <session>" other -> "$SHELL -ic 'tmux attach -t <session>'" Tests: - test/enkan-repl-terminal-test.el: 2 new ERTs (mirror buffer naming, default attach command shape per OS). Test suite: 181/181 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(terminal): satisfy byte-compile/checkdoc - Add declare-function for enkan-repl--terminal-* and enkan-repl-state-* to silence "not known to be defined" errors when enkan-repl.el is byte-compiled with -Q (no autoloads loaded yet). - Add declare-function for enkan-repl--session-entry-* and enkan-repl--buffer-name->instance helpers introduced in earlier commits. - Update declare-function arity for enkan-repl--buffer-matches-directory to reflect optional INSTANCE argument. - Wrap a too-long docstring line in enkan-repl--register-session (>80 cols) to satisfy checkdoc. Test suite: 181/181 pass. byte-compile clean. checkdoc clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal/tmux): kill helpers + mirror-buffer name compat with eat User-facing additions: - enkan-repl-tmux-kill-session interactive: kill one enkan-* session (auto-pick if only one, completing-read otherwise) - enkan-repl-tmux-kill-all-enkan interactive: confirm + kill all enkan-* sessions Mirror buffer naming fix: - enkan-repl--terminal-tmux--mirror-buffer-name now returns the same *ws:NN enkan:/path/* form as the eat backend (with optional <N> multi-instance suffix) by querying the tmux pane's current dir via `tmux display-message #{pane_current_path}`. Falls back to "*tmux <id>*" when the path is unavailable. - Aligns implementation with docs/terminal-backend-abstraction.md and lets existing buffer-list-based layout / lookup code discover tmux mirror buffers transparently. Tests: - Updated mirror-buffer-name ERT to cover the new format and the fallback path. Test suite: 181/181 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal): make layout/lookup discover tmux mirror buffers Closes the gap that caused enkan-repl-setup to "do nothing visible" on tmux backend: tmux sessions were created externally but the in-Emacs layout / count code (operating on buffer-list) had no way to find them. Changes: - enkan-repl-start-eat: when backend is tmux, eagerly create the mirror buffer via enkan-repl--terminal-tmux--mirror-make so it appears in buffer-list before layout setup runs. No pop-up here; layout decides display. - enkan-repl--buffer-alive-as-terminal-p: new backend-agnostic alive predicate. Considers buffer "live as terminal" if any of: 1. buffer-local eat--process is a live process (kephale eat) 2. get-buffer-process returns a live process (akib eat / general) 3. buffer-local enkan-repl--tmux-mirror-id binds a live tmux pane - enkan-repl--get-workspace-buffer-count-pure (utils): delegates the per-buffer liveness check to the new predicate. - enkan-repl--get-available-buffers (enkan-repl): same. - declare-function / defvar additions for cross-file references so byte-compile -Q stays clean. Net effect: enkan-repl-setup-current-project-layout (C-M-l) and the send-line buffer resolution see tmux sessions as first-class siblings of eat sessions, while existing eat behavior is preserved (the predicate's first two branches cover the original eat criteria). Test suite: 181/181 pass. byte-compile clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(terminal/tmux): normalize mirror buffer path with trailing slash send-line returned "No active enkan sessions found" on tmux backend even after start, because the mirror buffer name and the resolve lookup used different path normalizations: - enkan-repl-target-directories entries usually carry a trailing "/", so `enkan-repl--path->buffer-name' yields ".../path/*" (with slash). - `tmux display-message #{pane_current_path}' returns the shell cwd without a trailing slash, yielding "...path*" (no slash). `enkan-repl--resolve-send-target' looked up the buffer via `get-buffer (enkan-repl--path->buffer-name path)', which produced the slash-form, while the mirror buffer's actual name was the no-slash form -- so the lookup missed every time. Fix in `enkan-repl--terminal-tmux--mirror-buffer-name': pass pane-cwd through `file-name-as-directory' so the buffer name always carries the trailing slash and matches what target-directories-derived lookups produce. Idempotent for paths that already end with "/". Test: - Updated mirror-buffer-name ERT to assert the trailing-slash form for both inputs (with and without trailing slash) and for the multi-instance suffix case. Test suite: 181/181 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(terminal): coerce buffer identifiers when dispatching to tmux backend Send/kill/etc. paths frequently obtain a target by buffer-list lookup (e.g. enkan-repl--resolve-send-target returns the mirror buffer). Passing that Emacs buffer to enkan-repl--terminal-tmux-* failed with "Wrong type argument: stringp #<buffer ...>" because the tmux helpers build shell command argv lists that require string ids. Introduce enkan-repl--terminal--coerce-id and apply it to the first positional argument inside enkan-repl--terminal--dispatch: - Under tmux backend, a buffer is reduced to its buffer-local enkan-repl--tmux-mirror-id (set when the mirror buffer was created), yielding the canonical "enkan-NN:window" form. - Under eat backend, no conversion happens (eat operates on buffers). - String ids are passed through unchanged in both backends. Result: callers can pass either a buffer (legacy / convenient) or a string id (precise) to any abstracted terminal op without caring. Tests: 4 new ERTs cover all four (backend x id-shape) combinations. Test suite: 185/185 pass. byte-compile clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(terminal)!: make tmux the default backend; doc + lint cleanup BREAKING: enkan-repl-terminal-backend now defaults to 'tmux instead of 'eat. Users who prefer the in-Emacs eat experience can opt in with (setq enkan-repl-terminal-backend 'eat). Existing eat-based workflows keep working unchanged when this opt-in is set; both backends share the same user-facing send / layout / lookup commands. Why tmux became the default: - The latest claude / codex CLI welcome screens emit thousands of cursor / color escape sequences that eat--t-write processes one character at a time in pure elisp, locking Emacs's main loop. Confirmed via SIGUSR2 backtrace from a frozen session. - Window geometry changes (narrow split, expanding minibuffer) trigger the same repaint storm. - Delegating the terminal to tmux moves all of this work outside Emacs. Sessions also survive Emacs restart. - The eat backend is preserved as a documented rollback path. Documentation: - README.org: new "Quick note on the terminal backend" section near the top, expanded "Configuration / Terminal Backend" section (custom-id terminal-backend) covering switch, tmux specifics, multi-instance naming, mirror buffer, attach helper, kill helpers, state persistence + recovery policy, and migration history. - README.org: "Dependency for a terminal emulator" rewritten to describe the abstraction with both backends. - AGENTS.md: added Terminal backend / Workspace / Sessions modules to the Project Structure listing. Tests: - Default-backend ERT now asserts 'tmux. - Eat-mocking ERTs now bind enkan-repl-terminal-backend to 'eat explicitly so they exercise the eat path regardless of the new default. Lint cleanup: - Capitalize first word of docstrings in eat / tmux backend dispatch helpers (checkdoc). - Restructure two defcustom docstrings whose first line previously wrapped (checkdoc "First line is not a complete sentence"). - Quote-escape '%s' in attach-command docstring (avoid byte-compile unescaped-quote error). - Remove unused lexical binding state-only in enkan-repl-state-tmux-reconcile. - Capitalize user-error / user-message strings (checkdoc "Messages should start with a capital letter"). - Add forward declare-function for enkan-repl-state--list-live-tmux-sessions. Verification: - make test: 185/185 pass - make compile: clean - make checkdoc: clean for enkan-repl.el / -terminal.el / -state.el Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Yoshitsugu Sekine <yoshitsugu.sekine@offisis.co.jp> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
🎉 This PR is included in version 0.18.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test plan
🤖 Generated with Claude Code