All notable changes to this project will be documented in this file.
ghostel-projectno longer conflates same-named projects in different directories. Buffers now carry a structured identity (the new buffer-localghostel-identityalist: a mandatorykindplus scope keys likeproject-rootand aninstancenumber for reusable slots) instead of being matched by buffer name, so two projects that share a directory basename get separate terminals, and theidentityscope ofghostel-project-next/-previous/-list-buffersincludes numbered instances and excludes non-terminal buffers (e.g. a runningghostel-compile). Third parties can tag their own buffers (e.g.(kind . my-repl)) via the new optional IDENTITY argument ofghostel-execand filter them withghostel-identity-match-p. Bookmarks store the structured identity; bookmarks saved by older versions still restore but spawn a fresh terminal instead of reusing a live one. A numeric prefix of 1 now selects the default terminal (formerly a separate*ghostel*<1>buffer).ghostel-execand eshell visual-command buffers record their program and arguments in the identity'scommandkey; jumping to their bookmark reattaches to a live buffer running the recorded command, or respawns it instead of a plain shell. Restored buffers are plainghostel-execbuffers without kind-specific wiring (such as eshell's visual exit behavior), and the argv is saved to the bookmark file in plaintext.
- Terminal buffers keep a stable name; the terminal title moves to the
mode line.
ghostel-buffer-name-functionnow defaults to nil, so OSC 2 titles and OSC 7cds no longer rename the buffer — names like*ghostel*stay valid forswitch-to-bufferhistory,display-buffer-alistrules, bookmarks, and desktop restore, and buffer lists still disambiguate terminals by directory (buffer-menu, ibuffer, and marginalia show each terminal's cwd). The title is instead shown inmode-line-buffer-identification, controlled by the newghostel-buffer-identification-format— aformat-specstring (default"%b (%.30t)") with%b= buffer name,%t= title (capped at 30 columns by default, full title in the tooltip), and%d= abbreviateddefault-directory; when a format uses%tand the terminal has no title, only the buffer name is shown, and nil leaves the mode line untouched. To restore the previous behavior:(setq ghostel-buffer-name-function #'ghostel-buffer-name-by-title). ghostel-compilenow honourscompilation-scroll-outputinstead of always scrolling. With the default nil, compilation-style runs keep the window at the top of the output likeM-x compile; any non-nil value follows the output as it arrives, andfirst-erroradditionally leaves point on the first error message in the compile buffer when the command finishes. Interactive runs (C-u M-x ghostel-compile) always follow the live cursor. To keep the previous always-scroll behavior, setcompilation-scroll-outputto t. Fixes #599.
- An empty OSC 0/2 title (e.g.
printf '\e]2;\a') now clears the terminal title instead of being ignored, matching how Ghostty and iTerm2 treat it (reset to as if no title was ever set): the mode line falls back to the plain buffer name, and a buffer renamed by title tracking reverts to its original name (manual renames are still respected). Fixes #619. - evil-ghostel: typing the first key of an
evil-escapechord (e.g.jk) no longer doubles the character in the shell, and completing the chord no longer leaks it. evil-escape previews the first key with a speculative insert it reverts moments later; since terminal buffers became writable (0.47.0) that preview was forwarded to the PTY where it could not be reverted. The preview is now skipped in ghostel buffers — the chord itself keeps working. Fixes #597.
- Plain-text file/URL link detection now scans the region the renderer
actually repainted instead of the last screenful. A flood of output
(a compiler, a test runner) materializes far more rows than one
viewport between scans, and everything above the final screen was
never scanned: soft-wrapped paths there carried no highlight, were
not clickable, and were invisible to
ghostel-previous-hyperlink. Large backlogs drain in chunks from the newest end so on-screen rows are linkified first and a multi-megabyte redraw cannot block Emacs, and the row the cursor leaves is rescanned, so the last line of a command with no trailing newline gets its link once the prompt returns. Soft-wrap joining also restarts its row limit at every hard newline; regions holding more than 50 wrapped lines used to stop joining at each 50th, losing that line's link. Fixes #582. - Redraws that rebuild the whole buffer — changing
ghostel-bold-color, the foreign-insert repair, line-mode teardown — no longer strip detected file/URL links for good; they queue a rescan of what they rebuilt.ghostel-compiledrains detection synchronously before finalizing, so a compile buffer keeps the links in the tail of its output. buffer-face-mode,buffer-face-set, andvariable-pitch-modenow take effect in ghostel buffers and resize the terminal grid to the new font. Theghostel-defaultremapping used to mask any face-name remapping added after it, and the font change never triggered a window-size recalculation, so the shell kept the old row/column count. Fixes #588.- The cell pixel dimensions reported to programs (XTWINOPS CSI 14/16 t)
and used for kitty graphics sizing now come from the buffer's actual
font rather than the frame's, so
text-scale-mode,buffer-face-mode, or aghostel-default:heightis reflected in what programs see, and image placements scale with the text. ghostel-compileno longer runs the compilation with a 1×1 px cell size: reconciling the VT to the output window dropped the cell dimensions, corrupting CSI 14/16 t replies and kitty graphics geometry until the first manual window resize.- Interactive
ghostel-compileruns now echo typed input. The PTY spawns with echo enabled for interactive runs, and the line-mode toggle flips it on the live PTY mid-run — except while a full-screen program owns the display or the cursor row matchesghostel-password-prompt-regex. Compilation-style runs keep echo off so terminal query replies don't render as garbage in the log.
thing-at-pointintegration: in ghostel buffers,filename,existing-filename, andurlthings resolve to the whole target even when the terminal soft-wrapped it across rows, instead of the fragment on one row. On a detected link the answer comes from the link's target (soexisting-filenameis the absolute path detection already validated); elsewhere the logical line is joined before extraction. Consumers that askthing-at-point—browse-url-at-point, embark's URL target — pick this up automatically; on Emacs 30bounds-of-thing-at-pointspans the wrap on detected links too.- The find-file prompt's
M-ndefault now names the detected file at point viafile-name-at-point-functions. - New command
ghostel-find-file-at-point, bound via<remap> <find-file-at-point>: opens the link at point at its recorded line and column, falling back tofind-file-at-point. Plainffapreads raw buffer text, so on a soft-wrapped path it stops at the row break and visits only the directory prefix.
- Prompt navigation (
C-c M-n/C-c M-pand imenu jumps) now honorsghostel-readonly-default-modeinstead of always entering Emacs mode, matching hyperlink navigation and the other read-only triggers — with everything at defaults it now enters copy mode. The new optionghostel-prompt-navigation-input-mode(default,copy, oremacs) overrides the choice for prompt navigation only; set it toemacsto keep the previous behavior.
- Modified character keys are now encoded through the kitty keyboard
protocol when the running program has enabled it:
M-<letter>went out as legacyESC <letter>even under kitty flags, andC-M-<letter>lost the meta modifier entirely, sending the bare control byte.C-<letter>chords sent programmatically and uppercase chords likeM-Tnow also report correctly under kitty flags; legacy byte sequences are unchanged.ghostel-send-C-c/-C-z/-C-dremain raw bytes so SIGINT/SIGTSTP/EOF reach a hung program that holds kitty mode. One API note:ghostel-send-keywith"m"/"i"/"["plus ctrl now yields the fixterms CSI-u sequence (matching ghostty) instead of a C0 byte; live keystrokes are unaffected. Fixes #579. - The copy-mode entry message now describes the actual exit: with
ghostel-readonly-fast-exitoff it names the copy-mode toggle and the semi-char switch (reflecting user rebindings) instead of the incorrect "Press any key to exit". Fixes #580.
- The hyperlink subsystem (link keymap and commands, plain-text URL and
file detection, deferred detection, thing-at-point/ffap integration)
moved from
ghostel.elinto the newlisp/ghostel-links.el; no behavior change.
- New option
ghostel-readonly-default-mode(copyoremacs): the read-only mode entered byC-x C-q(new commandghostel-readonly-enter; pressed again inside copy/Emacs mode it exits), by hyperlink navigation, and byghostel-mouse-drag-input-mode,ghostel-mark-activation-input-modeandghostel-point-leave-input-modeat their newdefaultsetting — one knob for users who prefer Emacs mode over copy mode everywhere. Explicitcopy/emacs/nilsettings on the trigger options behave as before.ghostel-modeis also marked mode-classspecial(liketerm-mode), soview-read-onlycan no longer pullview-modeinto a terminal buffer. - Android/Termux support: releases now ship an
aarch64-androidmodule built against bionic at API level 24, and the installer picks it for Emacs builds with an*-linux-androidconfiguration. Cross-compiling needs the Android NDK, located throughANDROID_NDK_HOME,ANDROID_HOME, orANDROID_SDK_ROOT; on-device compilation in Termux needs onlypkg install zig ndk-sysroot.
- Live terminal-input modes (semi-char, char, interactive
ghostel-compile) now clearbuffer-read-onlyinstead of setting a buffer-localinhibit-read-only. The buffer-local binding broke(let ((inhibit-read-only t)) ...)code entered with a ghostel buffer current that then writes to another read-only buffer — e.g.C-x C-fcompletion display under mct erroring with "Buffer is read-only: Completions". Foreign edits are still intercepted and forwarded to the PTY as before; copy/Emacs modes, compilation-style runs, and dead terminals stay read-only. Packages gating onbuffer-read-only(e.g. meow) now see a writable buffer in the live terminal-input modes. Fixes #570. - A finished
ghostel-compilebuffer joins the rows the terminal wrapped, socompilation-modeerror parsing,next-error,ffapand saving the buffer see whole lines. A path straddling the wrap column used to be recorded as the fragment after the break. - A finished
ghostel-compilebuffer follows the user'struncate-linesinstead of keeping the terminal's. The variable ispermanent-local, so the terminal's setting survived the mode switch and the joined rows were truncated at the window edge. - Emacs mode now follows live output while point sits on the terminal
cursor; moving point off the cursor (or activating the region) stops the
scrolling, and returning point to the cursor resumes it (
M->in the read-only modes lands on the terminal cursor when the cursor sits past the last content, so in Emacs mode an end-of-buffer jump resumes the follow). - Line mode now stops following live output the moment point leaves the
live edge (the input region, or the end of output while a command runs)
instead of drifting until the reading position hits the top of the
window;
M->, clicking the prompt, or typing resumes the follow. This applies to the selected window; other windows showing the terminal keep following as before. While reading scrollback with pending typed input, async output no longer teleports point back to the input line. ghostel-debug-infonow reports the buffer, window, and rendering sections for any buffer (with the major-mode parent chain and read-only state) instead of printing only a "not in a ghostel buffer" note, and adds Compile run and Compile routing sections forghostel-compile: command, directories, launch mode, timings, exit status, parsed-error count, and global-mode/advice wiring.
- Kitty graphics placements are sized with the correct cell geometry: per-cell pixel dimensions are passed to libghostty's resize API instead of being multiplied into the terminal dimensions, which inflated image placements.
- URLs and
file:linereferences that the terminal wrapped across rows are now detected as one link: every row carries the whole target, and link navigation treats the rows as a single link. They also survive the reflow a window resize causes. Fixes #566. ghostel-compileno longer loses output from a command that finishes while its buffer is displayed in no window, or while copy mode holds the terminal frozen; a finished buffer is no longer repainted from the terminal grid on a window resize (which discarded the footer).- A saved
ghostel-compilelog reopens inghostel-compile-view-mode. Its header named a mode that does not exist, so the file landed infundamental-modewithout error navigation. - Pre-built Linux modules now target glibc 2.35 so they load on Ubuntu 22.04.
- Byte compilation sets
load-prefer-newer, so recompiling after a source change no longer loads stale.elcfiles and warns "Source file newer than byte-compiled file".
- Programmatic insertion commands such as
insert-charandemoji-insertnow forward inserted text to live terminals instead of failing against the read-only buffer. Multi-line insertions use bracketed paste.
- Building the native module now requires exactly Zig 0.16.0.
- Added the public
ghostel-alt-screen-pAPI for detecting alternate screens entered through DEC modes 47, 1047, or 1049. - evil-ghostel now shows a one-time hint when
ESCis routed to an alternate-screen application, withC-c C-rto toggle routing andC-c <escape>to enter Evil normal state without changing it. Closes #539.
- Building the native module now requires exactly Zig 0.15.2 and reports an explicit error for unsupported versions.
ghostel-yank-popnow usesread-from-kill-ring, preserving newest-first order and refreshing interprogram clipboard contents.- Live Ghostel buffers now reject major-mode changes that would destroy their terminal state; buffers whose process has exited remain changeable.
- Bash integration now supports scalar and array-valued
PROMPT_COMMAND, including hooks installed by systemd. Fixes #540. - Bash integration now works under
set -u, tolerates a user-definedtypefunction, and avoids emitting command-start markers during shell startup. Fixes #557. ghostel-othernow creates a fresh terminal when invoked from the only existing Ghostel buffer instead of reselecting that buffer.- Window anchoring no longer scrolls the live cursor out of view when another window shrinks the terminal, preventing unintended entry into copy mode.
- Alternate screens entered through DEC modes 47 or 1047 are now detected by line mode and evil-ghostel, not only those using mode 1049.
- Native module builds on musl now consistently use Ghostel's vendored
emacs-module.h, including when invoked outside the project root. Fixes #551. - Pasting kills containing undecoded or invalid byte sequences now repairs UTF-8 and substitutes invalid characters instead of signaling an error.
- Operations racing with terminal exit, including input, resize, cleanup, and evil-ghostel key routing, now safely become no-ops or fall back to normal Evil behavior. Related to #555.
- Windows native process spawning now decodes unibyte arguments, environment variables, and working directories using the system ANSI code page while preserving UTF-8 multibyte strings.
- Extension byte compilation now depends on the core byte-compiled files, preventing parallel builds from compiling extensions against stale APIs.
- New
ghostel-line-spacingcustomization pads terminal rows with extra vertical space (integer pixels or a float fraction of the line height). The terminal grid and reported cell pixel sizes follow the taller rows; changing it live resizes open terminals. Note that Emacs draws no content into the spacing strip, so non-zero values leave gaps in box-drawing borders, block characters, and between kitty-graphics image rows.
- Point placed elsewhere on the cursor's row (for example after evil's normal-state end-of-line adjustment at a prompt) now follows the cursor row-wise when async output arrives, restoring the user's column instead of stranding point in scrollback.
- evil-ghostel: point parked on the cursor in normal/motion state now stays on the live cursor during streaming output instead of stranding in scrollback (regression of the original #228 fix).
- Hidden terminals now keep pending output until the buffer is actually redisplayed and only clear their pending state after a successful render, so revealing a hidden terminal no longer shows stale content.
- Ghostel buffers now override
default-text-propertieslocally, so globally configuredline-spacing/line-heighttext properties no longer inflate rendered rows and scroll the top of the terminal out of view. - ghostel-kitty: images keep their requested scale so they are no longer cropped.
- Quitting with
C-gduring terminal creation (for example at a dir-locals prompt) now kills the partially created buffer instead of leaving an empty ghostel buffer with no terminal behind.
- The ReadTheOrg theme for the HTML docs export is now vendored under
docs/, so the docs build no longer depends on upstream URLs or network access.
- Native Windows support for x86_64 and ARM64 Emacs through a ConPTY backend with process spawning, resizing, environment propagation, and terminal I/O.
- Windows releases now bundle the native module and Microsoft's redistributable ConPTY runtime. Automatic downloads and local builds install the matching support files, with the system ConPTY as a fallback.
- Windows
sshxTRAMP sessions now resolve the remote user's login shell, and remote password prompts are detected without probing the local PTY. - Updated libghostty so OSC color-query replies and related terminal parsing stay aligned with current Ghostty behavior.
- Full-viewport terminal rotations now redraw rows marked dirty at page level, including alternate-screen and primary-screen configurations without scrollback.
- Window resizing now resolves terminal state from the resized window's buffer, preventing split or programmatic resizes from leaving the terminal grid stale.
- Focus-only clicks now preserve the existing scrollback position instead of snapping the window back to the live cursor.
- Input-mode commands now reject non-Ghostel buffers instead of replacing their local keymaps or leaking mode state.
- Bash shell integration can now be sourced from POSIX-mode shells, including the Bash 3.2 shipped with macOS.
- Native PTY teardown now drains final output, safely interrupts blocked I/O, keeps writes serialized and quit-aware, and terminates native children during Emacs shutdown.
- Native children now fail for invalid working directories, preserve full Windows exit codes, and report POSIX signal exits consistently.
- Split native PTY handling into shared lifecycle code with platform-specific POSIX and ConPTY backends and common cancellation and write contracts.
- Expanded portable native PTY, Windows asset, TRAMP, lifecycle, OSC, renderer, and path-resolution regression coverage.
- Hidden terminal cursors now keep their logical cursor position updated, so
focus clicks and input anchoring no longer jump to
point-maxor expose the trailing empty row when terminal applications hide the cursor. - Batched renderer redraws now preserve saved markers and window positions across adjacent changed-length rows.
- Copy, Emacs, and line modes now keep normal Emacs mouse selection even when the terminal application has DEC mouse tracking enabled, so copy-mode selection works while TUIs are capturing mouse input.
ghostel-module-recompilenow installs the freshly compiled native module after a successful rebuild.ghostel-compilenow runs live buffer-local finish hooks before switching to the final compilation mode and standard finish hooks after, matchingcompilation-startsemantics for callers such as Dape.- Lisp input methods that check
buffer-read-only(for example Korean Hangul methods) now compose in read-only ghostel buffers before forwarding committed text. - Generic copy commands such as
kill-ring-savenow filter terminal soft-wrap newlines and trailing padding the same wayghostel-readonly-copydoes. - Terminal color parsing now uses libghostty's parser, keeping OSC color handling aligned with Ghostty.
- Renderer redraws batch buffer insertions, read styles lazily, render directly from terminal state, and defer GC during redraws, reducing redraw overhead.
- Updated the libghostty pin.
- Added render regression coverage for full resets, alt-screen partial redraws, cursor-only updates, and property runs across row boundaries.
- Added
GHOSTEL_DEBUG_TESTSfor printing tests as they start and expanded Dape/xctrace benchmark debugging and profiling helpers.
- Ghostel buffers substituted into the Emacs daemon's invisible 80x24 initial frame no longer clamp the terminal to that dummy window (79x22): such windows are filtered out of the window-size hook and their PTYs opt out of core's process-window-size adjustment. Fixes #504.
- A local and a remote (TRAMP)
ghostel-projectsharing a name no longer collide on the same*NAME-ghostel*buffer; remote roots fold theirmethod:user@hostprefix into the buffer identity. Fixes #344. ghostel-prompt-regexpnow matches prompts padded with a no-break space (U+00A0) — such as Claude Code's>prompt — so column-aware motions no longer land on the padding.- Text scaling now preserves window anchoring, and global text-scale changes
(
global-text-scale-adjust) resize and re-anchor the terminal like localtext-scale-mode. - Leaving copy mode now forces a terminal resize, so size changes made while in copy mode take effect on return.
- The native PTY now flushes pending terminal updates before the child process exits, so the last output before exit is no longer lost.
ghostel-debug-infono longer truncates the report to the key-encoding section: the probe terminal (ghostel--newis buffer-affine and erases the current buffer since 0.40.0) is now created in a temp buffer instead of*ghostel-debug*.- The key-encoding probe and
ghostel-debug-keypressreport encoder bytes again:ghostel--encode-keynow returns the encoded bytes (the native PTY path no longer routes them through the elispghostel--write-pty).
- The native
ghostel--encode-keynow returns the encoded bytes instead of a boolean; the minimum required native module version is bumped to 0.41.0. - The native module build now respects the Zig install prefix (
zig build --prefix .). - Added Ghostel window lookup helpers and store the allocator on the renderer.
- Added an elate scenario suite for evil-ghostel shell editing and an agent-output anchor test script.
- Shell integration now supports nushell alongside bash, zsh, and fish.
Directory tracking (OSC 7), prompt navigation (OSC 133), and title tracking
(OSC 2) come from nushell's built-in
$env.config.shell_integration; ghostel addsghostel_cmdand the outboundsshterminfo-install wrapper. Works with auto-injection (ghostel-shell-integration), manualsource, and remote TRAMP sessions (ghostel-tramp-shell-integration). ghostel-inhibit-anchor-functionslets integrations veto per-window redraw anchoring.evil-ghosteluses it so normal, visual, operator, and motion state navigation can roam away from the live cursor while terminal output continues.
- Removed the
ghostel-full-redrawuser option.
- Clearing scrollback no longer leaves stale pre-clear content visible in the Emacs buffer in some cases.
- Oversized single-width glyphs now borrow an adjacent cell only when they are
standalone, keeping adjacent icon runs such as
⏵⏵aligned. evil-ghostel-goto-cursoris now a linewise Evil motion, soVGextends the selection to the terminal cursor instead of snapping back. Fixes #485.- Rightward cursor moves in
evil-ghostelnow clamp before trailing shell autosuggestions, so motions such as$no longer accept zsh/fish suggestions and then undo unrelated readline state. Fixes #493. p/Pinevil-ghostelnow paste into read-only ghostel buffers; the live terminal path writes through the PTY instead of editing the buffer.evil-ghostelno longer yanks point back to the live cursor on insert-state redraws while the window is scrolled into scrollback.ghostel-keymap-exceptionsnow honorsC-g: adding"C-g"to the list leaves it unbound in ghostel so it falls through to your global binding (e.g.keyboard-quit) instead of forwardingBELto the terminal. Fixes #489.
- Renderer invalidation now validates scrollback clears before evicting old pages, and the renderer owns font-query cache lifetime during redraws.
- Added a GUI xctrace benchmark runner and removed obsolete full-redraw bench cases.
- Updated the Ghostty dependency.
- Expanded Hypothesis terminal-sequence coverage and stabilized native PTY, compile, and CI setup tests.
ghostel-debug-describe-glyph-scalingreports how the renderer scales glyphs into terminal cells, making emoji and font-size issues easier to diagnose.
- Ghostel buffers are now read-only, preventing editing commands such as
transpose-linesfrom modifying terminal contents. - Default terminal foreground/background colors now come from buffer-local face
remapping of
ghostel-defaultinstead of per-cell face properties, while OSC color queries continue to report the terminal protocol defaults.
- Hidden buffers now force a redraw when they reappear, so terminal contents no longer remain blank or stale if synchronized output is active at that moment. Fixes #456.
C-a/ghostel-beginning-of-input-or-lineno longer jumps to a stray prompt character inside command output such as progress percentages. Fixes #430.- Native child processes now get the same
DISPLAYfallback used by Elisp process spawns. - Glyph scaling now quantizes to the font pixel size, keeping scaled glyphs aligned with terminal cells.
- Remote bash shell integration now passes
--rcfile/--posixbefore login/interactive flags, avoiding bash option parsing failures.
- Native process-lifecycle tests use readiness handshakes and CI-scaled polling timeouts to avoid slow-runner races.
- Emacs bookmark support for ghostel buffers:
bookmark-set(C-x r m) records a terminal's working directory and name, andbookmark-jump(C-x r b) reopens it — reusing a live ghostel buffer of that name or starting a fresh shell in the bookmarked directory. The newghostel-bookmark-check-dir(defaultt) controls whether the directory is restored (cd'd into a reused buffer that has since moved elsewhere). Fixes #447.
- Remote (TRAMP) shells now start as login+interactive shells by default for
recognized shells (bash, zsh, fish), so they reliably source the user's
rc/profile files (
.zshrc,.zprofile, …) like an interactivessh hostlogin. Previously remote shells were spawned with no flags: they were never login shells (so.zprofile/.zloginwere skipped), and interactivity was left to the shell's own tty auto-detection — which doesn't hold on every TRAMP method, so.zshrccould be skipped too. Passing-l -iexplicitly restores login semantics and makes interactivity reliable. This also applies when remote shell integration (ghostel-tramp-shell-integration) is enabled, so the user's own config (prompt, aliases, …) loads alongside the integration; bash uses-ionly there, since a login bash would ignore the--rcfilethe integration relies on. Unrecognized shells (e.g./bin/sh) are left unchanged.ghostel-tramp-shellsentries gained an optional trailing args slot —(METHOD SHELL [FALLBACK [ARG...]])— to override the default per method. Fixes #444.
- Line mode now keeps point in the user's in-progress input when viewport anchoring scrolls the window back to the live terminal output. Fixes #436.
- Remote shell integration: the per-session temp files (zsh
ZDOTDIR, bash--rcfile, fish init script, and the pushed terminfo dir) were deleted immediately after the asynchronous spawn, racing the remote shell that had not yet read them. The shell would then start withZDOTDIRpointing at a deleted directory, so neither the integration nor the user's own config loaded. Cleanup is now deferred to buffer-kill, past the shell's startup read and the session-long lifetime of the terminfo dir. - Scrollback row accounting now stays correct when a rendered viewport spans multiple libghostty pages.
- Emoji and other glyphs scaled into the terminal cell now follow buffer-local text scaling.
- Split native-module provisioning, face/color definitions, and OSC 133 prompt navigation/imenu support into dedicated Elisp files.
- Native VT writes now defer callbacks through a shared Elisp helper that preserves the originating terminal buffer.
- Reentrant renderer redraws now fail loudly instead of continuing through an unsupported nested render.
- Removed native terminal accessor functions that duplicated Elisp state.
- Expanded OSC 2 test coverage across parser/terminator variants.
ghostel-initial-input-modelets newghostelterminals start in semi-char, char, or line mode; line mode is entered automatically after the first prompt is detected.
- Pasting now uses libghostty's paste encoder, matching terminal state for bracketed paste, newline normalization, and filtering unsafe control bytes.
- Resizing a terminal while the cursor is not on the bottom row no longer leaves the rendered buffer out of sync.
- Line-mode-specific code was split into
lisp/ghostel-line-mode.el. - Renderer resize invalidation was simplified, and the renderer now owns native terminal row/column bookkeeping when resizes are committed.
- Hypothesis driver coverage was fixed.
isearchand minibuffer navigation (consult-line, etc.) in semi-char mode now switch to a read-only mode when they leave you parked in the scrollback, mirroring a mouse click — so the next redraw no longer yanks point back to the prompt. The target mode is picked by the newghostel-point-leave-input-modedefcustom (copydefault,emacs, or nil to disable). Wired intoisearch-mode-end-hookandminibuffer-exit-hook; for other jump packages (avy, flash, …) add the new commandghostel-maybe-leave-inputto their after-jump hook or as:afteradvice. Mouse selection and region activation keep their independentghostel-mouse-drag-input-mode/ghostel-mark-activation-input-modeknobs.- Blinking cursor support. A terminal app that requests a blinking cursor
(DECSCUSR
CSI 5/3/1 SP q, i.e. mode 12) now blinks the cursor in the Emacs buffer instead of rendering it steady. Works for the bar, block, and underline shapes. Yields to the globalblink-cursor-modewhen that is enabled (no double-blink); graphical frames only.
- Programs launched by Ghostel are now resolved through Emacs's
exec-path(bare names) anddefault-directory(names with a directory component) before being spawned, instead of relying on the child inheritingPATH. The resolved binary now matches what other Emacs commands (executable-find,M-x compile, …) would find, and remote TRAMP buffers are unaffected. - Internal libghostty VT-parser warnings no longer leak to the module's
stderr in release builds. In a terminal Emacs (
emacs -nw) that stderr is the controlling tty, so the warnings were painted onto the screen outside Emacs's redisplay and lingered — e.g.unimplemented mode: 9001left a stale line near the mode line after running lazygit (#425). Such logs are now available only viaM-x ghostel-debug-start(*ghostel-debug*); debug builds keep console output as before.
- Cursor style (shape) is now driven from Elisp rather than the Zig renderer, unifying cursor control with the new blink support.
- The forked child now writes via
posix.writeinstead of a Zig buffered writer, keeping the post-fork / pre-exec path async-signal-safe.
- Native PTY startup now opens the replica with
NOCTTY, avoiding crashes when launching terminals from Emacs daemon mode.
- Documentation now has a short README landing page plus a full README.org manual, with generated Info/HTML targets and GitHub Pages publishing.
- Native PTY processing now tolerates benign re-entrant calls back into the module while calling Emacs, avoiding crashes from incidental module access.
- Native PTY startup now configures the terminal directly instead of shelling out
through
stty, avoidingghostel-execresize issues while preserving UTF-8 erase handling and directC-q/C-sdelivery to the child process.
- FreeBSD native PTY builds now include
<sys/wait.h>, exposingWEXITSTATUSduring Zig C translation so thex86_64-freebsdrelease artifact builds.
- The native CI build matrix now includes
x86_64-freebsd, matching the release artifact matrix.
- Local Ghostel buffers now use the native PTY backend by default via
ghostel-use-native-pty. The native reader consumes PTY output outside Emacs's process filter path while remote TRAMP buffers continue to use Emacs processes. - Hyperlinks now support Eldoc, showing link metadata at point.
- Activating the mark in semi-char mode (
C-SPC/set-mark-command, expand-region variants,C-x h, or any other region-activating command) now switches to a read-only mode, mirroring mouse selection, so streaming output cannot clobber a keyboard-driven region. Implemented via a buffer-localactivate-mark-hook, so it works for whatever command the user has bound — no specific keys are intercepted. The target mode is picked by the newghostel-mark-activation-input-modedefcustom (copydefault,emacs, or nil to keep the old stay-in-semi-char behavior). Mouse selection still followsghostel-mouse-drag-input-modeindependently.
evil-ghostelnow uses Evil operators, motions, and command remaps instead of broad advice around core Evil commands. Normal/visual editing keeps its PTY-driven behavior while avoiding the previous advice lifecycle hazards, and Evil buffers opt out of Ghostel's keyboard-mark-to-copy-mode switch so visual state owns selection.
- ZSH shell integration now reports gethostname(2) in its OSC 7 directory update
(matching the bash and fish integrations and Emacs
system-name), instead of zsh's$HOST, which is canonicalized to the fully qualified domain name when a DNS search domain is configured. Previously the FQDN disagreed withsystem-name(the short hostname), so the local shell was misclassified as remote and switched on TRAMP (#417). - Fish shell integration now caches the hostname once at load instead of forking
the
hostnamecommand on every prompt, matching the bash and zsh integrations. - Char mode now captures GUI
C-SPCand forwards it to the terminal as NUL; previously only the TTYC-@representation was bound, so a GUI Ctrl+Space in char mode fell through to the globalset-mark-command. - CJK and other fallback glyphs now claim cell space correctly, and fallback glyph scaling is clamped independently for ascent and descent. This prevents rows from growing or shrinking when tall glyphs (for example status-line symbols) blink in and out.
evil-ghostelnow forwards<delete>correctly inside alt-screen TUIs; previously it could edit the rendered buffer locally and then reappear on the next redraw.- Read-only/copy-mode exits and minibuffer-close reanchoring no longer try to anchor stale windows or non-live/frozen Ghostel terminals.
- A left-click that only focuses a Ghostel window — bringing the frame to the
foreground from another application or Emacs frame, or selecting an unfocused
window in the current frame — is now treated as a pure focus click and never
enters copy mode (#403). It snaps point to the live cursor and stays in
semi-char. This closes every path that previously froze such a click:
- The refocus click is detected from the focus-in event itself rather than a tracked focus transition, so it keeps working when the window system's focus state is stale — notably on macOS (NS), where a dropped focus-out can leave `frame-focus-state' stuck reporting focus and previously made every refocus click enter copy mode after a while.
- A tiny involuntary pointer movement that makes Emacs report the click as
drag-mouse-1' instead ofmouse-1' selects nothing, so the micro-drag is handled like a plain single click rather than a region drag. - Selecting a previously-unselected window leaves an empty region active, whose
activation the command loop finalized after the press handler returned;
ghostel--mark-activated' now ignores mark activations that originate from the mouse handlers (gating onthis-command'), so that deferred activation no longer freezes the buffer. Mouse selection stays governed by `ghostel-mouse-drag-input-mode' alone.
- Benchmark and dape helper tooling were updated, and PTY lifecycle coverage now exercises both Emacs and native backends.
ghostel-ime-mode, an opt-in buffer-local minor mode that forwards text committed by Emacs Lisp input methods — Quail-style methods such as Korean Hangul, and Japanese/Chinese — to the PTY, so characters committed by a directself-insert-commandreach the shell instead of being erased on the next redraw. Enable with:hook (ghostel-mode . ghostel-ime-mode). Core gains a genericghostel-inhibit-redraw-functionshook to defer redraws mid-composition.- Undo now works on the input line during line-mode; renderer and internal line-mode buffer mutations are shielded from the undo list.
- A plain single left-click in an already-selected window now switches input
mode per
ghostel-mouse-drag-input-mode(defaultcopy), restoring click-to-copy. A click that focuses a previously-unselected window stays a pure focus click and snaps point to the live cursor. Setghostel-mouse-drag-input-modeto nil for standard click-sets-point. - Line-mode can now be entered at an OSC 133 shell prompt on the alternate
screen (for example a prompt inside tmux/screen);
C-u C-c C-lforces entry, otherwise line-mode arms and advertises the override. C-],C-/, and theirC-M-variants are now forwarded to the shell in semi-char/char mode (readline character-search and undo) instead of being captured by Emacs (#380).- Read-only mode now exits immediately on explicit input.
- Bare LF on the primary screen now preserves the cursor column instead of
being normalized to CRLF. The emulator no longer synthesizes a carriage
return: cooked-mode
\nis turned into CRLF by the PTY's ONLCR (enabled bystty sanein the spawn wrapper), and raw-mode applications that emit a bare LF mean a column-preserving linefeed. This fixes inline TUIs (bubbletea / lipgloss apps such as the AntigravityagyCLI) whose banners position with column-preserving LF plus relative cursor-back, which previously collapsed to the left margin (#388). - ghostel buffers now redraw immediately when shown, avoiding a stale display.
- Viewport anchoring no longer breaks when the mode-line is disabled in a GUI buffer; the "still anchored" threshold is now measured from the window body instead of the whole window (#373).
- Window pixel anchoring falls back to line-count anchoring on GUI Emacs 28,
where the Emacs 29 cons
FROMargument towindow-text-pixel-sizeis unsupported and previously broke the anchor. qno longer leakskill-current-bufferinto the shared semi-char keymap after an eshell visual command exits (#372).compilation-finish-functionsnow run before ghostel's final mode switch, so buffer-local continuation state installed by those hooks survives.- Corrected the
ghostel-eval-cmdsdefcustom type.
- Added glyph metrics caching and switched window pixel anchoring to accurate text metrics.
- OSC 7 directory reports now only update the buffer directory when the reported directory actually changes, avoiding redundant directory-change work.
- Updated libghostty, fixing rendering of emoji such as the pilot emoji.
- Immediate redraws now clear pending redraw timer bookkeeping, avoiding stale delayed-redraw state.
- Kitty graphics Elisp was split into
ghostel-kitty.el. - Native module internals were simplified: CRLF patching moved into the handler
path, Emacs API helper/registration code was refactored, and
sys.zigwas removed. - Flaky compile and shell tests were hardened.
ARCHITECTURE.mdnow documents Ghostel's design and its architectural trade-offs against other Emacs terminal packages.
- Automatic buffer renaming now goes through
ghostel-buffer-name-function, a side-effect-free function called for both OSC 2 title changes and OSC 7 directory reports. It receives the terminal title and returns the buffer name to use, or nil to leave it unchanged; Ghostel performs the rename, buffer-name uniquification, and the "don't clobber a manually renamed buffer" guard.ghostel-set-title-functionremains as an obsolete alias. ghostel-modeword boundaries now match Ghostty's terminal selection defaults, so double-click selection, word motion, Evil text objects, and word search treat paths, hostnames, and similar terminal text as single words. The newghostel-word-boundary-stringcustom option can be changed live.- Rendering now writes directly to the terminal buffer instead of maintaining an Elisp side buffer, substantially improving throughput.
- Mouse drags in alt-screen applications with mouse tracking enabled (for
example vim with
:set mouse=a) now stream motion events live instead of updating only on release. The final drag event is sent as a release, which also fixes button-release reporting for basic mouse tracking. Fixes #349. - Multi-codepoint/composed characters now select the correct glyph when computing display metrics.
- Point, mark, and window-start/scroll positions are preserved by the native renderer across redraws and resizes.
- OSC 8 link handling now stores URIs directly in text properties and compares link IDs in the renderer, reducing duplicated state between libghostty and the materialized buffer.
- Renderer state restoration moved into Zig and the Elisp redraw path was simplified around the new invariants.
- Link, glyph, scroll, shell-integration, and buffer-renaming tests were expanded/reorganized.
make package-lintnow provisions its own dependencies in an isolated package directory, so it can run standalone outside CI.- Development tooling now uses
lldb-dapfor dape debugging.
- The
:Char/:Line/:Copy/:Emacsinput-mode lighter inmode-line-processnow has ahelp-echotooltip (reflecting the user's bindings and the liveghostel-readonly-fast-exitsetting), amode-line-highlightmouse-face, and amouse-1binding that exits the mode (back to semi-char for char/line; back to the pre-readonly mode for copy/emacs). Closes #329.
- Copy and Emacs modes are now symmetric mode toggles. Under the
default
ghostel-readonly-fast-exit,C-c C-eandC-c C-twere repurposed as exit shortcuts, so the eat-style mode-switch matrix did not work from those modes (e.g.C-c C-ein copy mode exited to semi-char instead of switching to Emacs mode).C-c C-enow toggles Emacs mode andC-c C-ttoggles copy mode from every read-only mode; the fast-exit affordances (q,C-g, self-insert) are unchanged. Fixes #342. ghostel-keymap-exceptionsnow takes effect when customized after the package has loaded. The semi-char keymap was populated once at load time, souse-package :config+add-to-listhad no effect; a:sethandler now rebuilds the keymap in place (preserving the keymap object's identity so buffer-local references stay valid). Related #327.
- Evil insert-state Ctrl passthrough stays active in alt-screen apps
(vim, less, htop). It used
evil-ghostel--active-p, which intentionally returns nil under DECSET 1049, soC-ufell back to Evil's insert binding and deleted buffer text instead of reaching the terminal. A narrower passthrough predicate remains active in semi-char mode during alt-screen sessions. - Reading large output (e.g.
rgover a big tree inemacs -nw) no longer pops the debugger for users withdebug-on-errorset.extractStringAllocnow probes the required size with a NULL buffer before copying instead of optimistically reusing the existing buffer, which raisedmemory-buffer-too-smallon every chunk past the previous high-water mark. Fixes #338. - Mouse double/triple-click now selects the word/line and the
selection is protected.
ghostel-mouse-release-or-set-pointforwards PROMOTE-TO-REGION tomouse-set-pointso the word/line selection from the down-event survives, and a multi-click in semi-char mode switches to the input mode configured byghostel-mouse-drag-input-modeso a redraw can't extend the highlight to the cursor. Fixes #337. - Alt-screen rendering corrected: the alt screen is now treated separately from the primary screen (it has scrollback that must not be rendered), and a resize on a no-scrollback screen forces a full rebuild.
- Cursor-line padding is now rendered precisely.
M->in emacs and copy modes stays in isearch during motion.ghostel-readonly-end-of-bufferlacked anisearch-motionproperty, so withisearch-allow-motionon it exited search and jumped to point-max instead of moving to the last match like the globalend-of-buffer. Fixes #327.
- Buffer terminal initialization centralized;
ghostel--init-bufferno longer clears title/identity bookkeeping, so OSC 2 sequences cannot auto-rename a user-renamed buffer on re-init. - Property-based renderer testing expanded: resizing operations and a
dape test-running helper for the Hypothesis suite, alt-screen
regression tests, more tests routed through
ghostel-test--with-terminal-buffer, and redraw invalidation logic factored out into its own path. extractString/extractStringAllocnow return errors instead of null, and avoid a debug-mode free of a wrong-length slice.
- Breaking:
ghostel_cmd(shell helper) now emitsOSC 52;einstead ofOSC 51;E. Libghostty parses OSC 51 into an action the standard handler ignores, so the elisp-eval extension never reached ghostel's callback through normal action dispatch — the old code worked around this with a post-write byte scanner. The extension now rides on OSC 52 with a reservedkindbyte (e), which libghostty dispatches directly to ghostel's handler. Scripts that hand-roll the legacy\e]51;E…\e\\sequence will silently no-op; update to\e]52;e;…\e\\or call the bundledghostel_cmdhelper. The post-write byte scanner is gone; the OSC 52 path is parsed by libghostty and now reassembles payloads split across read boundaries (slow producers, SSH). - Rendering now tracks scroll position and row eviction via libghostty's pin/page primitives instead of the previous heuristic. The old method had corner cases that triggered unnecessary full rebuilds; the new path keeps incremental updates correct across scrollback growth and eviction.
ghostel-next-hyperlink/ghostel-previous-hyperlinknow dedupe by OSC 8id=, so a single logical URL split across multiple cell runs (e.g. a URL wrapped across rows inside an ASCII box) is visited once instead of once per chunk. As a side effect, two adjacent cells pointing to different OSC 8 hyperlinks no longer merge into a single style run that clobbered the second link's text properties. Fixes #125.M-<punct>andM-<digit>keys (e.g.M-.,M-/,M-;,M-1) no longer drop silently when libghostty's encoder returns no output. The raw fallback inghostel--raw-key-sequencepreviously only covered meta + lowercase a–z; it now emits ESC + char for the full printable ASCII range, matching legacy alt encoding (zsh/readlineM-.→ insert-last-word, etc.).- Scrollback eviction no longer drops rows incorrectly when the active pin lands at the top of the screen — found via property-based testing. Related viewport rendering paths were hardened against over/underflow.
- Property-based renderer testing via Hypothesis
(
test/hypothesis/), with CI replay of captured failure cases as regression tests. - Renderer hardening: debug assertions on
rows_in_buffer, over/underflow guards, consolidated renderer tests, CSI 3J same-count refill regression test, fix for the debug finalizer setup. - Emacs function registration refactored to be declarative with
the function body next to its metadata, and
link_idfolded intoCellPropsso OSC 8 metadata lives in one place. EMACSFLAGSnow forwarded into the Hypothesis driver and small development utilities added undertools/.
ghostel-comint-mode(andglobal-ghostel-comint-mode) replacesansi-color-process-outputwith a full libghostty VT parser as a comint preoutput filter. Handles truecolor / 256-color SGR, italic / faint / strikethrough / overline, curly / dotted / dashed / double underline (including colon-separator forms), underline colour, OSC 8 hyperlinks (reusingghostel-link-mapso clicks dispatch by URI scheme), OSC 7 working-directory reports, and silently consumes DCS / APC / SS3 without leaking bytes. Unstyled runs inherit the comint buffer's default face; inverse is emitted as:inverse-video t. Whenfont-lock-modeis on, the filter swapsfaceforfont-lock-faceto survive font-lock's unfontify pass. Ref #278.ghostel-glyph-scale-floorbuffer-local defcustom (number, 0.0–1.0, default 0.0) clamps the computed glyph scale from below. At 0.0 the existing strict-grid behaviour is unchanged; at 1.0 CJK and other fallback glyphs render at natural font size at the cost of slightly taller rows. Being buffer-local, different ghostel buffers can use different settings. Closes #298.- Semi-char mode now forwards
M-<digit>,M-<punct>, andM-<uppercase>(plusM-SPC) to the terminal. Previously theM-loop only covered?a..?z, soM-.,M-1,M-/,M-;, etc. fell through to Emacs global commands instead of reaching the shell. CSI/SS3 escape-sequence prefixes (M-[,M-O) are still left to TTY input decoding. Fixes #314.
ghostel--filter-soft-wrapswas O(n²) due to one-character-at- a-time string concatenation, causing multi-second freezes when copying large selections (e.g. full scrollback) withM-win copy mode. Replaced with a chunk-collection approach.- Skip rows-only resize while a minibuffer is active. Fish (and
other shells with
fish_handle_reflowon) repaints its prompt on every SIGWINCH; aconsult-buffer/M-xcycle grew then shrank the body of the window showing the ghostel buffer, producing two prompt repaints in quick succession. The deferral hits the no-change branch after the minibuffer closes. Col changes and non-minibuffer vertical resizes still propagate so$LINESstays accurate. Fixes #268. - CRLF normalization is now skipped on the alternate screen. Apps
like tmux, vim, and less emit VT-correct sequences where a bare
\nmeans LF; normalizing them to\r\ncorrupted their layout. Detection covers all three alt-screen entry modes (DECSET 47 / 1047 / 1049). - Default-styled text is no longer invisible on the Linux
framebuffer TTY.
ghostel--face-hex-coloronly recognised the literal"unspecified"string as unset, so on a TTY frame where the default face reports"unspecified-fg"/"unspecified-bg"it fell through to a"#000000"fallback for both attributes, collapsing fg and bg to black-on-black. All three sentinel strings are now recognised; the last-resort fallback splits into white for:foregroundand black for:background. Fixes #297.
- Removed
ghostel-readonly-recenter— no longer needed now that scrollback is materialized into the buffer. Fixes #310.
- Switched the Zig module from libghostty-vt's C API to the Zig
API. OSC actions are now routed through a custom
GhostelHandlerthat wraps libghostty's standard terminal handler and overrides the OSC arms (semantic_prompt, color_operation, report_pwd, clipboard_contents, show_desktop_notification, progress_report). This deletes ~395 lines of parallel byte-scanning code inmodule.zigin favour of reading ghostty's parsed Command values directly. Only OSC 51 still uses a bespoke scan since it is ghostel's elisp-eval extension. - Style-to-face translation extracted into
src/style_face.zigand shared between the renderer and theghostel-comintstream filter so they stay in sync. - Debug builds use
DebugAllocatorfor corruption and leak detection; allocator is deinit'd on atexit and the false-positive leak check was tightened. Allocators are now injected via Zig's best-practice pattern: terminal storesallocat init,Renderer/RowContentacceptallocparameters (unmanaged pattern), andmodule.zighas a single top-levelallocbinding instead of scatteredc_allocatorreferences. - Zig refactors:
Terminalrenamed toGhostelTermto avoid confusion with ghostty'sTerminaltype;Rendererstoresgt.Terminal; theforce_fullargument is replaced by setting the render state dirty; debug-mode userptr freeing is generalized beyondGhostelTerm; Zig log output also goes to stderr.
- Multi-terminal navigation commands:
ghostel-next,ghostel-previous,ghostel-list-buffers, plus project-scoped variantsghostel-project-next,ghostel-project-previous, andghostel-project-list-buffersfor cycling and picking among ghostel buffers. Project membership is configurable via the newghostel-project-buffer-scopedefcustom.
- Font metrics are now cached during redraw, avoiding repeated font-attribute lookups per frame.
- Tests run in parallel via per-file Make targets, with stamps
under
.build/tests/. Recommended invocation:make -j$(nproc) all(or-j$(sysctl -n hw.ncpu)on macOS). Wall-clock improvements on a warm cache:test12.5s → 6.6s,test-native11.2s → 7.8s,all10.4s → 8.4s. NewEMACSFLAGSvariable lets callers inject load paths. - The monolithic
ghostel-test.elwas split into 16 per-topicghostel-*-test.elfiles plus a sharedghostel-test-helpers.el. Native-only tests carry:tags '(native); the runner selects via(tag native)/(not (tag native))instead of a hand-maintained whitelist. - A review pass over the split fixed ~30 tests that were passing for the wrong reasons (tautological assertions, mocks of the function under test, references to symbols that no longer exist).
ghostel-query-before-killingdefcustom controls whether Emacs asks for confirmation before killing a live ghostel buffer or exiting Emacs while one is running. Defaults toauto: quiet at the shell prompt, queries while a command is running (via OSC 133 C/D markers). Set totfor always-on confirmation,nilto restore the previous never-query behavior. Closes #288.ghostel-macos-login-shelldefcustom (defaultton Darwin) wraps the shell via/usr/bin/login -flp $USERso~/.zprofileand~/.bash_profileare sourced, matching Terminal.app and Ghostty. Skipped for TRAMP, non-Darwin, andghostel-exec.ghostel-shellnow also accepts a list form (program + args) so users can pass extra flags like'("/bin/zsh" "--login")on any platform. Fixes #285.ghostel-prompt-regexpdefcustom provides a prompt-detection fallback forghostel-input-start-pointandghostel-beginning-of-input-or-linewhen OSC 133 shell integration isn't available (raw zsh, Python REPL, sqlite3, ssh into unprovisioned hosts). Default recognizes$ # % > >>>and themed prompts (λ ❯ ➜ →). OSC 133 still wins when present.- Public cursor-state API for integrations:
ghostel-input-start-point,ghostel-cursor-point, andghostel-point-on-cursor-row-p. Pure state reads — safe to call from any input mode.
- Bash's OSC 7 cwd report now uses the real kernel hostname via
${var@P}on\H(bash 4.4+) instead of$HOSTNAME. Toolbox and container runtimes export$HOSTNAMEwith a value that disagrees withgethostname(2), so Emacs's(system-name)saw a mismatch and TRAMP fired on everycd. Falls back to$HOSTNAMEon bash <4.4. Fixes #276. - OSC 7 is now emitted after any user-supplied
PROMPT_COMMAND/precmd_functionsentries, so competing emitters (e.g. Fedora's/etc/profile.d/vte.sh) can no longer overwrite ghostel's cwd report and cause buffers to be misclassified as remote. - Auto-composition is now disabled on TTY frames to fix the
off-by-one column drift caused by emoji + VS-16 grapheme
clusters (e.g. 🗂️) — Emacs's
char-widthreports 1 while VS-16-honoring terminals paint 2 columns, desyncing the TTY screen cache. GUI frames are untouched. Fixes #274. - Glyphs that occupy a narrow cell but have an empty cell after them are now promoted to wide on render, so emoji and other ambiguous-width sequences land in the right column even when upstream width tables disagree with the terminal.
- Internal Zig refactor of
adjustGlyphsandputTextProperty; no user-visible change beyond the width promotion above. - Removed dead debug entry points (
fnDebugState,fnDebugFeed) and unused render state fields.
ghostel-bold-colordefcustom mirrors Ghostty 1.2.0'sbold-color: bold text with palette colors 0-7 is mapped to the 8-15 bright slot when set tobright(the default), or rendered in a fixed face/color when set to a color spec. Existing terminals pick up the change live.
S-<insert>is bound toghostel-yank, matching the conventional alternative-paste binding in browsers, terminal emulators, and file managers. In the same pass, semi-char and read-only maps layer<remap> <yank>so any key Emacs binds to yank globally —s-von macOS plus user rebinds — routes throughghostel-yankwithout per-key entries.C-ystays explicitly bound so user rebinds of the global yank key cannot break ghostel paste; line mode keeps Emacs's regular yank so paste lands in the input region. Closes #263.ghostel-readonly-RET-or-exit-and-sendnow honoursghostel-readonly-fast-exitwhen point is on a hyperlink: read-only mode exits first (capturing the URL beforehand, since exit moves point andfile:///fileref:links switch buffers), then the link is opened. Previously RET on a hyperlink left the buffer in copy mode.
- Exiting read-only modes (copy, Emacs) no longer flickers.
The viewport snap-back to the live terminal cursor used to
schedule an async redraw; in the gap, Emacs would observe
point at
point-maxand recenter, only for the timer to anchor the window back a few ms later. The redraw is now synchronous, so the window is anchored to the active viewport before redisplay runs. Fixes #269. - Upgrading the elisp package ahead of the native module no
longer requires two restarts. build.zig writes a
ghostel-module.versionsidecar next to the binary, andghostel--load-moduleconsults it beforemodule-loadand refuses to map a stale.so— so a fresh install loads in the same Emacs process. The interactive version check also runs unconditionally on existing installs without a sidecar, soM-x ghostelsurfaces the install prompt instead of only warning. Fixes #256.
ghostel-mouse-drag-input-modedefcustom: a bare left click in semi-char mode no longer freezes the buffer into copy mode on press — it only focuses the window and sets point, matching standard Emacs behavior. A drag switches input mode on release so streaming output cannot clobber the selection; the target mode iscopy(default; freezes redraws, selection is rock-solid),emacs(terminal keeps streaming, buffer becomes read-only), ornil(stay in semi-char; scrollback selections still survive, selections over live-redrawn rows can be lost). Closes #257.ghostel-module-directorydefcustom selects where the compiled native module lives. Defaults to the package directory (current behavior). Set it to a path outside the package manager's tree (e.g. elpaca'srepos/dir, which gets deleted on rebuild) to keep the artifact stable across rebuilds.M-x ghostel-module-compilebuilds in the resource root and moves the artifact into the configured directory.ghostel-password-prompt-debouncedefcustom (default 0.2s). Ghostel's canonical+!echo heuristic is checked on every redraw (sub-100ms), so short-lived termios flips that ghostty's 200ms poller silently misses became user-visibleread-passwdpopups in ghostel. The rising edge is now debounced: the source chain only runs if the heuristic still reports password mode at the deadline. Sub-debounce flickers leave nothing behind except a brief mode-line indicator flash, matching ghostty's transient lock-icon UX.
ghostel-download-moduleandM-x ghostel-module-compilenow write to a sibling temp file andrename-fileit into place. Renaming swaps the directory entry to a fresh inode, so any Emacs still holding the previous file mmap'd retains a valid mapping — updating the native module is safe under a running Emacs and no longer risks crashes on the next code-resolution. Fixes #247.- Native module internals: function-registration table reworked
for readability and alphabetical ordering; error/logging
helpers unified; ergonomic variadic
Env.f("fn", .{…})replaces the per-aritycallNfamily; remaining legacy interop usage removed. No user-visible API changes, but the Elisp/native pairing changes — the bundledghostel-module-versionis bumped andghostel--minimum-module-versionis now0.25.0. UseM-x ghostel-download-moduleafter updating ghostel.el.
- An open
read-passwdminibuffer that ghostel opened auto-cancels on the falling edge of the heuristic. When the foreground program exits (e.g. the user kills sudo withC-c C-csending Ctrl+C), the prompt now disappears with it instead of waiting for the user to dismiss it. Three gates ensure ghostel only aborts its own minibuffer (active-flag, minibuffer depth, and the minibuffer-buffer identity captured at open time), so unrelated concurrent minibuffers are never closed.
- OSC 133 imenu integration: each shell prompt with OSC 133
A/B/Cmarkers becomes an imenu entry of the form<cwd> <command>, with the target landing on the prompt prefix's start. Composes withconsult-imenu,imenu-list, evil's]m/[m, etc. The cwd is captured at command-start (OSC 133C) and tracked chronologically, so prompts issued before acdkeep the correct directory in their imenu label. Selecting an entry leaves line and copy modes untouched; only semi-char and char modes switch to Emacs mode so point can move. ghostel-readonly-fake-cursor: shows a thin hint cursor at the live terminal-cursor position when point has moved away in copy or Emacs mode, so the next-output spot stays visible while the user reads scrollback. Facesghostel-fake-cursor(hollow) andghostel-fake-cursor-box(solid) are user-customisable; the hollow box uses:line-width (-1 . -1)to stay inside the character cell and not reflow the line. Updates are driven bypre-redisplay-functionsinstalled buffer-locally on read-only entry — nopost-command-hookoverhead.- Click-to-focus in semi-char mode no longer drops the press when
no app is tracking the mouse. Left mouse-1 falls back to
mouse-drag-region/mouse-set-point/mouse-set-regionwhen no DEC mouse-tracking mode (1000/1002/1003) is active, so click-set-point and drag-to-select work in any input mode. (Refined further in 0.25.0 byghostel-mouse-drag-input-mode.) ghostel-detect-password-prompts— defcustom (default t) gating the entire password-prompt detector.ghostel-compilebinds it to nil buffer-locally because compile buffers run withstty -echoto avoid double-echoing the command, which puts the pty into the exactcanonical+!echostate the libghostty heuristic matches — leaving detection on would pop aread-passwdminibuffer at the start of every compile.M-x ghostel-debug-password-events-show— view the last 32 password-prompt rising edges with the detection arm that fired (zigorregex), the cursor row text, and the buffer's remote-shell state. Capture is enabled byM-x ghostel-debug-start; the events buffer survivesghostel-debug-stop.
ghostel-password-prompt-regexnow defaults tocomint-password-prompt-regexp(the same regexM-x shell,M-x term, and eshell use). The previous default ([Pp]ass\(?:word\|phrase\)[^:]*:[ \t]*\') was too permissive and caused the false-positive class fixed below. Users who want to extend the regex should prefer customizingcomint-password-prompt-regexpitself (BEFORE loading ghostel) so other Emacs facilities benefit from the same change.ghostel--password-prompt-detected-pnow returns nil or a symbol (zigorregex) identifying which arm fired. Truthiness is unchanged for callers that treat the return value as a boolean.- Wide-char / emoji rendering is now handled in the native module
rather than by an elisp pixel-compensation pass. The renderer
reports glyph adjustments directly, so emoji-heavy output no
longer trips the
string-widthvs. pixel-width divergence that used to overflow the window. Eliminates theghostel--has-wide-chars/ghostel--compensate-wide-charsround-trip on every redraw. - Native module switched to strict Zig error handling: bare
catch {}swallow sites in color, PWD-update, and render-state paths now log to*Messages*(or signal) with the unifiedghostel: [function] failed: [error]pattern. Cursor tracking no longer goes through accessor methods that clobbered render state; the renderer publishes cursor position as buffer-local variables matching what was rendered, which removes a class of subtle bugs.
- Spurious
read-passwdminibuffer prompts when the terminal cursor row happened to end inPassword:/passphrase:— typingecho Password:at a local shell prompt would pop a password prompt because the regex fallback ran unconditionally whenever the libghostty heuristic returned nil, and the regex itself was anchored only at end-of-line. The fallback regex now defaults tocomint-password-prompt-regexp(structurally anchored at start-of-line or after curated trigger words), and the fallback only runs whenghostel--remote-shell-pindicates a remote shell — local raw-mode TUIs (vim, less, htop) don't risk false positives from coincidental cursor-row content. Fixes #244. consult-line,consult-imenu, and othergoto-charjumps in line mode no longer snap back to the live cursor. The minibuffer that consult opens resizes the ghostel window twice (open + close); each resize forced a redraw whose anchored-window predicate ignoredwindow-point, so the closing resize re-anchored the window and yanked point back to the live cursor. The predicate now also checkswindow-pointagainst the anchor.- Several evil-ghostel operator and motion bugs in multi-line TUI
input and single-line shell prompts:
dd/ccon a non-cursor row of a multi-line input (pi, ipython, prompt_toolkit) now deletes the line at point instead of the last line;cwlands at the start of the deleted range, not at column 0;dwon a single trailing space treats it as content in single-line ranges;0/^skip the shell prompt prefix on prompt rows and fall through to the original motion on scrollback / output rows;^/$/0followed byipreserves the navigated column across scrollback; column navigation in normal state survives idle redraws on the cursor's line;evil-replace's paste count matches its delete count when trailing whitespace is stripped. Underneath: cursor delta math now subtracts scrollback before comparing buffer to viewport rows, and a newevil-ghostel--shadow-cursormodels the pending terminal cursor between PTY-bound key emits and their echo back through the redraw. Fixes #218. <mouse-2>in semi-char or copy mode no longer drops the click when no app is tracking the mouse. Middle-click now feedsgui-get-primary-selectiontoghostel--paste-text(bracketed paste at the live prompt) when no tracking mode is on, matching the standard X primary-selection paste on Linux. In copy / Emacs mode withghostel-readonly-fast-exiton, the click exits to the prior input mode first so the paste lands at the prompt; with fast-exit off it pastes in place, mirroringghostel-yank/C-y.- RET in copy mode no longer dies in
text-read-onlyafterghostel-mouse-press-or-copy-modeflipped the buffer into copy mode on a focus click. RET now exits read-only mode (whenghostel-readonly-fast-exitis on) and forwards a CR via the encoder; hyperlinks under point still callghostel-open-link-at-pointas before.C-c C-lwas renamed toC-c M-lso the parent map'sghostel-line-modebinding is no longer shadowed. Fixes #251.
- Eat-style input modes. Five modes replace the old semi-char /
copy duality:
semi-char(terminal-focused default),char(send-everything, including most Emacs prefixes),emacs(live but read-only — buffer keeps streaming whileisearch,occur,M-x, mark +M-wall work over the same vocabulary that reads any Emacs buffer),copy(frozen, point-based navigation), andline(shell-style local edit, send whole lines on RET, with filename / env / executable / pcomplete /bash-completion-backed TAB completion, history ring onM-p/M-n,C-c C-cinterrupt, andC-dEOF on empty input). Mode switches live in a slim base map so they work from every live mode:C-c C-jsemi-char,C-c M-dchar,C-c C-eemacs,C-c C-tcopy,C-c C-lline,M-RETescape from char mode. Prompt navigation (C-c M-n/C-c M-p) auto-enters Emacs mode so the terminal keeps running while the user jumps between prompts. Line mode discovers the prompt via the terminal cursor (newghostel--cursor-row-char-offsetZig binding that walks the cursor row's cells so wide / box-drawing glyphs map correctly), refined by OSC 133 markers when present, so it works in REPLs and shells without integration loaded. README gains an "Input modes" section andevil-ghostelnow gates on semi-char specifically. Closes #40. ghostel-compileis now read-only by default, mirroringM-x compile:greruns,n/pwalk errors, RET jumps,C-c C-cjumps,C-c C-kinterrupts.C-u M-x ghostel-compileopens a writable ghostel terminal (the previous default); useful forhtop,less,read -p, test prompts. Two new commands flip a buffer between states mid-run without restarting:ghostel-compile-switch-to-interactive(C-c C-j) andghostel-compile-switch-to-readonly(C-c C-e).mode-line-processshows:run/:run/iso the current state is visible at a glance.ghostel-recompileandM-x revert-bufferboth preserve the launch mode of the source buffer;next-error/M-g n/M-g pwork mid-run in either variant.ghostel-compile-global-modeadvice now routescompilation-start CMD tto a writable ghostel terminal — callers asking for the comint variant still get a real TTY, just rendered by ghostel.- Password prompt detection. When
sudo,ssh,gpg,passwd, etc. ask for a password ghostel pops upread-passwdand sends the answer through the PTY — the keystrokes never flow through Emacs's normal key pipeline, so the password does not land inview-lossage, the recent-keys ring, or any keyboard-macro recording. Detection mirrors libghostty's heuristic (the slave tty is in canonical mode with echo off) via a tinytcgetattrZig binding, with a regex fallback on the cursor row when the local tty's echo state can't be observed (remote ssh, programs that don't toggle echo). Mode-line shows🔒Passwordwhile a prompt is open, the wire copy of the password isclear-string'd immediately after the send, and wrong-password retries auto-detect via cursor movement. Customizeghostel-password-prompt-functions— a chain of(ROW) -> string-or-nilsources tried in order — to plug inauth-source(or Keepass / pass / etc); the defcustom docstring includes a TRAMP-awareauth-source-pick-first-passwordexample. PR #241. ghostel-debug-ghostel, a wrapper aroundM-x ghostelthat installs self-removing advice onghostel--spawn-ptyandghostel--start-processto capture program / args / geometry / stty-flags / extra-env / process-environment, the wrapper command list passed tomake-process(caught viacl-letf*onmake-processso TRAMP's file handler can't rewrite it before capture), per-phase spawn timings, the first ~16 KB of PTY output as a(timestamp . chunk)event log, and the first ~64 sends.ghostel-debug-inforenders the capture on a single chronological timeline (sends and PTY output interleaved) and grows a TRAMP section withtramp-version,tramp-terminal-type, the resolvedtramp-direct-async-process, multi-hop length, and the local-vs-toplevel TERM divergence diagnostic that surfaces whentramp-local-environment-variable-pwould silently strip the pushed TERM.C-u M-x ghostel-debug-infoon a remote buffer additionally runs a single-round-trip remote probe (infocmp,xterm-ghostty/xterm-256colorterminfo,~/.local/share/ghostel/terminfopaths,/bin/shidentity, remotebash --version,$INPUTRC, and the first 80 lines of~/.inputrc). All capture lives inghostel-debug.el— plainM-x ghostelsessions are unaffected.- Local "Key encoding (legacy mode)" section in
ghostel-debug-infothat probes a fresh terminal for the chords commonly cited in inputrc reports (C-Backspace,M-f,C-M-f,C-M-v, …) and shows the resulting bytes, mirroringghostel--send-encoded's encoder + raw fallback so the output matches what a real keystroke produces. repeat-modesupport for prompt and link navigation:C-c C-n C-n C-ncycles hyperlinks,C-c M-n M-n M-ncycles prompts (baren/pwork after releasing the modifier). Internally, eight keymaps inghostel.elare converted fromlet/define-keyblocks todefvar-keymap;compat 30.1.0.1is added toPackage-Requiressodefvar-keymapis available on Emacs 28.
C-M-<letter>chords (C-M-f,C-M-v, …) are now bound inghostel-mode-mapand routed through libghostty's encoder so readline.inputrcrules like"\e\C-f": dump-functionsactually fire. Without the binding, Emacs'sforward-sexp/scroll-down-commandran instead of reaching the shell. Fixes #239.- Remote spawns now apply
stty saneas a baseline (matching vterm) instead of the prior per-spawn flag list that omittedechoon the remote path withoutghostel-tramp-shell-integration. Any upstream that left the PTY with echo cleared (TRAMP env stripping, custom remote/etc/bashrc, old bash readline) no longer produces silent input on the remote shell. All five spawn paths share a singleghostel--default-sttyconstant. Fixes #224 (third iteration). ghostel--set-buffer-facenow caches the last(FG . BG)pair and short-circuits theface-remapround-trip when the colors haven't changed. The render path called this on every dirty redraw, andface-remap-remove-relative/face-remap-add-relativeeach callforce-mode-line-updateinternally — measured at ~960 FMLU per 5s with two visible buffers running spinners, dropping to ~50 after the cache. The visible symptom was the minibuffer flickering andC-g/ RET taking several attempts to land while output was streaming.
ghostel-next-prompt/ghostel-previous-promptnow land correctly with the realistic two-propertyghostel-prompt/ghostel-inputrow layout.ghostel--prompt-input-startusedskip-charsheuristics that jumped past the last char + newline for any input ending in a single-char arg (e.g.echo b) and landed on the last word for multi-word commands; previous-prompt failed to recognize point inside aghostel-inputregion or at the start of input as "still on the current prompt" and snapped back to the same prompt instead of the prior one.C-gin semi-char and char input modes now reachesghostel-send-C-grather than the raw^Glambda installed byghostel--define-terminal-keys. The terminal-key loop ran on the per-mode child maps and shadowed the parent'sC-gbinding, re-introducing #200 —deactivate-markand thequit-flagclear were skipped. The loop now skips?g, and char mode binds it explicitly.ghostel-readonly-copy(M-w/C-w) and the copy-mode branch ofghostel-xterm-pastenow honourghostel-readonly-fast-exit nilinstead of unconditionally exiting copy/Emacs mode on those keys.ghostel-readonly-copydeactivates the mark likekill-ring-savedoes, soM-w/C-wno longer leaves the region highlighted whenghostel-readonly-fast-exitis nil. Closes #238.- RET on a
ghostel--detect-urls-linkified cell no longer hijacks the keystroke away from the PTY in the default terminal mode. RET is dropped fromghostel-link-map(text-property keymaps outrank evenemulation-mode-map-alists) and bound onghostel-copy-mode-mapinstead, so click is still a real click in any mode but RET routes to the local map. Link detection on the cursor's own row is now skipped regardless of OSC 133 state, so REPLs and shells without integration loaded (Gemini CLI, raw bash, …) no longer linkify the typed command. - In evil normal state, output that grew scrollback no longer leaves point above the new prompt. The around-redraw advice used to restore point by buffer position; it now tracks the buffer line where the previous redraw placed point at the terminal cursor and lets the renderer's new placement stand when the user has not navigated. Closes #228.
ghostel-debug-infono longer raisesvoid-variableon Emacs 28/29.tramp-direct-async-processwas added in Emacs 30.1's bundled TRAMP; the variable read is guarded withboundpand a forwarddefvarquietsbyte-compile-error-on-warnin CI.
-
Loading
ghostel.elno longer prompts to download or compile the native module under any circumstances. Previouslyghostel.elconsultedghostel-module-auto-installat load time and could open an interactiveread-char-choiceprompt — this hung Emacs 31user-lisp/auto-byte-compile and similar harnesses where a user(setq ghostel-module-auto-install nil)had not yet been evaluated. Module installation now happens only on an explicit user action:M-x ghostel,M-x ghostel-download-module, orM-x ghostel-module-compile. When the module is missing at load time the package issues adisplay-warninginstead. Closes #231. -
No-echo on remote shells launched from a TRAMP
default-directory(ghostel-tramp-shell-integration nil). The previous fix reboundtramp-terminal-type, which only takes effect on the generictramp-handle-make-processpath; the ssh-method path (tramp-sh-handle-make-process) ignores it. In addition, when the local default-toplevelprocess-environmentalready hasTERM=xterm-ghostty(e.g. Emacs launched from ghostty),tramp-local-environment-variable-pstrips ghostel's pushed TERM as "ambient", and the remote shell inherits TERM=dumb from TRAMP's connection shell — disabling readline/ZLE/fish line editing.The per-spawn
/bin/sh -cwrapper now sets TERM itself on the remote, after probing forxterm-ghosttyterminfo viainfocmp. Single path covers auto-integration (TERMINFO pushed), manual install (system or~/.terminfo), and the bare case (fall back toxterm-256colorso echo works). The bogus local TERMINFO path is no longer pushed to the remote. Closes #224 again.
- Manual remote-integration setups can now drop the bundled
xterm-ghosttyterminfo at~/.local/share/ghostel/terminfo/alongside the shell scripts (scp -r etc/terminfo/{x,78}from the local package). TRAMP-spawned remote shells detect it and prepend the directory toTERMINFO_DIRSautomatically — notic, no touching~/.terminfo. README "Option 2: Manual setup" updated with the one-shot install recipe.
ghostel-pre-spawn-hook, run insideghostel--spawn-ptyjust beforemake-processwithprocess-environmentdynamically bound to the about-to-be-spawned env. Hook functions cansetenvto inject entries the child inherits. Intended for integrations like with-editor — with awith-editor-setup-environmentexposed upstream, users can wire Magit'sEDITORplumbing into ghostel buffers via(add-hook 'ghostel-pre-spawn-hook #'with-editor-setup-environment). Fires for bothghostel/ghostel-projectandghostel-execspawns;ghostel-compilehas its ownmake-processand is not covered.evil-ghostel-escapecontrols how ESC is routed in evil insert state:auto(default) inspects DECSET 1049 to send ESC to the terminal in alt-screen apps (vim, less, htop, …) and otherwise fall back toevil-normal-state; explicitterminalandevilvalues force one or the other. A toggle command with numeric prefix support is also bound. The terminal-bound ESC snaps to the live viewport like every other typed key; the evil-bound fallback lands onevil-force-normal-statewhen the user's<escape>binding is missing or a chord prefix (#215).make bench-e2e(and a--e2eflag onrun-bench.sh) measures whole-pipeline throughput by installing each backend's production filter+sentinel on acatsubprocess and waiting for full quiescence, so the wall clock reflects what users actually feel (including ghostel'sdelayed-redrawlink detection / anchoring, vterm's regex split + decode loop, and eat's deferred queue). Composes with--quick,--size,--iterations, and the backend-skip flags.
- The Zig native module has been broadly refactored ("Zigify
everything"): libghostty calls are wrapped to be return-value
oriented rather than out-pointer oriented, errors propagate via
Zig's
try/catchand error unions instead of C error codes, optional absence is distinguished from real errors, and per-error logging/handling is consistent across the module (#217). This is internal — no user-visible API changes — but bumps the minimum required native module version, so update both ghostel.el and the prebuilt.so/.dylibtogether. - Render code is reorganised around a single property-run
abstraction (prompt cells, input cells, and ordinary content all
flow through the same path), and uses a new internal
FixedArrayListto cut per-row allocation overhead. Adds an Emacs↔Zig logging/debugging layer used during the refactor and available for future native-module work.
- Launching
M-x ghostelfrom a TRAMPdefault-directory(e.g. afterfind-file /ssh:host:) now produces a usable remote shell. Previously TRAMP'smake-process' handler reset TERM totramp-terminal-type' (default"dumb"'), which caused bash/readline, zsh/ZLE, and fish to disable interactive line editing on the remote: typed characters didn't echo, although Enter still submitted the line. Ghostel now rebindstramp-terminal-type' for its remote spawns toxterm-256color' (orxterm-ghostty' when `ghostel-tramp-shell-integration' has pushed the bundled terminfo), restoring echo and line editing (#224). - Shell integration survives prompt themes and rcfile assignments
that overwrite
PROMPT/PS1/fish_promptafter ghostel sourced its bootstrap. The OSC 133 A/B markers are now (re)installed every prompt cycle — modeled on ghostty's own zsh/bash/fish integrations — so powerlevel10k, agnoster, Pure, oh-my-zsh / prezto add-zle-hook chains, bashPROMPT_COMMANDreassignments, and fish themes loaded viaconf.d/no longer strip the prompt-range markers. Without those markers, the file-path link detector linkified user-typed cells and the link keymap's RET binding shadowed the normal terminal RET in tty Emacs — pressing RET on a typedcd some/pathopened the link instead of executing the command (#199). - New terminals no longer briefly flash with libghostty's default colors before ghostel applies the Emacs theme. A regression test guards against the flicker reappearing (#219).
ghostel-keymap-exceptionsnow also excludes special keys (<return>,<tab>,<f1>, …, including theirS-/C-/M-/C-S-/M-S-/C-M-variants). The special-keys binding loop inghostel-mode-mapwas missing the exceptions check that theC-<letter>andM-<letter>loops had, so users could not exclude e.g.C-<return>orC-M-<down>(#210).ghostel-download-moduleno longer segfaults when the module is already loaded. Callingmodule-loadon a path whose shared library is mapped into the running Emacs makes dyld/ld.so return the existing handle and resolveemacs_module_initviadlsymon the stale image. Ghostel now skips the secondmodule-loadwhen the module is alreadyfeaturep'd and tells the user to restart Emacs to pick up the new version (#78).
ghostel-spinner-progress, a built-in handler forghostel-progress-functionthat animatesmode-line-processvia spinner.el during indeterminate progress (e.g. while Claude Code is working) and shows percentage text for determinate states. spinner.el is a soft dependency: when it is on theload-pathat ghostel load time,ghostel-progress-functiondefaults to this handler; otherwise the existingghostel-default-progresstext indicator is used. Newghostel-spinner-typedefcustom (defaultprogress-bar) selects the spinner style.
- Resize redraw work now scales with the resized axis. Column changes still trigger a full scrollback rebuild (cell wrapping depends on width), but row-only changes only re-render the visible area and defer until after the next redraw, eliminating noticeable lag when only the height changes (24f6653).
- Claude Code's progress reports now update
mode-line-processin ghostel buffers. Claude Code gates OSC 9;4 progress emission onTERM_PROGRAM_VERSIONparsing as semver>= 1.2.0; ghostel advertisedTERM_PROGRAM=ghosttywithout the version, so the gate failed and progress was silently dropped. Ghostel now also exportsTERM_PROGRAM_VERSIONmatching the vendored libghostty pin, satisfying Claude Code's check and any other consumer that applies the same probe. - Plain-text URL/file detection no longer linkifies the cell the
user is typing into. In tty Emacs,
RETon a linkified cell resolved toghostel-open-link-at-point(text-property keymap overridesghostel-mode-map), so pressing return at a path the shell echoed — e.g.cd src/main.rs— opened the file instead of running the command. The renderer now marks OSC 133 B..C cells asghostel-inputandghostel--detect-urlsskips the prompt prefix unconditionally and the active input line. The bundled bash/zsh/fish integrations are updated to emit 133;A and 133;B from inside the prompt itself rather than back-to-back before prompt expansion, so libghostty sees a non-empty PROMPT range (c145c5e, closes #199). - OSC 51;E callbacks dispatch synchronously from the process
filter rather than waiting for the next redraw timer tick.
Callers like
b4 prep --edit-coverwrite the OSC and continue, cleaning up their tempdir before deferred elisp couldfind-filethe path; deferred dispatch producedSetting current directory: No such file or directory. Matches vterm's behavior. OSC 51;A (directory tracking) is left deferred since it has no such race (3f8846c, fixes #209).
- Style-run break detection during render uses a cheap
CellStyleKeyrather than a fullCellStylecomparison, cutting per-cell overhead on large dirty regions (81f1258). - libghostty can now be built with optimization settings independent from the ghostel module itself, so debug ghostel builds no longer drag libghostty into debug mode (5baea2d).
- Kitty graphics protocol support — render images inline in the
ghostel buffer for both traditional non-virtual placements
(timg, kitty +kitten icat, applications using direct kitty
graphics) and unicode-placeholder placements (yazi, modern image
previewers). All decoding, storage, and protocol parsing flow
through libghostty's kitty graphics C API; ghostel queries the
placement iterator each redraw and applies image overlays in
Emacs. Non-PNG pixel data (RGBA / RGB / GrayAlpha / Gray) is
converted to PPM (P6) for Emacs's built-in image renderer; PNGs
go through libghostty's PNG-decode hook backed by vendored
stb_image. Per-row slicing (
:ascent 'centerplus aline-heightclamp on the trailing newline) keeps image rows flush even when the placeholder line'sline-pixel-heightis pulled aboveframe-char-heightby a fallback font or nerd-font icon on the same line (57ef5a7). ghostel-cell-pixel-scalecontrols the physical:logical pixel ratio reported to apps that probe XTWINOPS CSI 14/16/18 t. Apps like timg and yazi expect cell dimensions in physical pixels (what standalone Ghostty advertises via the OS window server's backing scale factor), but Emacs only exposes logical pixels — reporting them unscaled makes apps either fall back to half-blocks (timg) or fill many more cells than expected with upscaled, blocky output (yazi). Theautodefault derives a float scale from display DPI (display-pixel-width/display-mm-widthcompared to the 96 DPI reference); a numeric override is available for pixel-perfect parity with standalone Ghostty (57ef5a7).- File detection recognises tilde-prefixed paths
(
~/file.el:42) —~is added to the leading character class and leading anchor ofghostel-file-detection-path-regex(abae518).
OPT_SIZE(XTWINOPS CSI 14/16/18 t) is now answered by ghostel, alongside the existingOPT_DEVICE_ATTRIBUTESreply. Image- rendering tools probe these queries to detect kitty graphics support and pick image dimensions; without a response timg fell back to half-block rendering even whenTERM_PROGRAM=ghostty. Cell pixel dimensions are stored on the Terminal struct and updated on every resize, andghostel--set-sizeis seeded once betweenghostel--newand the process spawn so the very first output (e.g. timg's transmit-and-place) reports authoritative values rather than zero (57ef5a7).
ghostel-execuses the universal 80×24 default when BUFFER is not displayed in any window, instead of sizing the PTY from(selected-window). The selected window had nothing to do with where the agent buffer would eventually be shown — programs ending up in a different window had to rely on SIGWINCH to recover, and TUIs that latch initial dimensions at startup rendered against the wrong size. Matches eat's behaviour; the displayed-buffer path is unchanged (a8bf9ae).
0.19.0 — 2026-04-29
ghostel-debug-keypressarms a one-shot capture of the next keystroke in the current ghostel buffer and renders a paste-ready diagnostic (raw event, resolved keymap binding, every byte sent, active DEC mode flags, coalesce-buffer state) into*ghostel-debug-keypress*— enough to distinguish kitty CSI-u from legacy encoding without any new native binding (07b8438).ghostel-debug-infogains anEnvironmentblock (env vars passed to the spawned shell —TERM,COLORTERM,TERMINFO,TERM_PROGRAM,INSIDE_EMACS,ghostel-environmentoverrides, and pass-throughLANG/LC_*), aSize syncblock (Emacs window body rows vsghostel--term-rowsvs Emacs's recordedold-body-pixel-height, with explicit verdicts for in-sync / chrome-absorbed-but-unreconciled / pending-redisplay), and aRenderingblock (default face, resolved font with fallback/remap detection,line-spacingbroken into buffer-local / default-value / frame-parameter,face-remapping-alist). The buffer is now read-only (special-mode,qto quit), module-file lookup works in the dev /package-vclayout, and customized defcustoms are auto-detected by comparing againststandard-value(0b19011, d717732, ea594fc, e8eb2f8).- FreeBSD release artifact built via Zig cross-compile from the
Linux CI runner, using the same matrix-driven
-Dtarget=...pattern asaarch64-linux-gnu(81abb18). list-buffers-directoryis set to the buffer'sdefault-directoryinghostel-modeandghostel-compile-view-mode, and is updated by OSC 7 directory tracking — sobuffer-menu/ibuffer(and any consumer that reads the variable) can categorise ghostel terminals by working directory. Mirrorsshell-mode's convention (75fe69f).tui-partialbenchmark renders the static screen once, then updates only the bottom row per iteration — the workload that status bars and prompt redraws actually produce. Exposes that ghostel's per-row dirty-bit branch is 8–13× faster than full mode at 24×80 and 40×120 (6a4a069).
- Native rendering rewritten. Each pass parks the libghostty
terminal at
max_offset - 1, which lets us track scrollback correctly across the libghostty cap by detecting eviction from the parked offset, identify scrollback-clear (CSI 3Jet al.) reliably via theoffset+len==totalsnap-back signal without per-byte VT scanning, use libghostty dirty flags directly to identify stale lines (fixes the case where promoted scrollback rows could carry outdated content), and evict old scrollback rows from the Emacs buffer in lockstep with libghostty's ring-buffer wrap. OSC 8 hyperlink handling switches from storing URI strings on every run to a lazyhelp-echolookup viaghostel--native-uri-at. The Emacs buffer now always carries a trailing newline so line-math is uniform across the codebase (8e3135f). - PTY size is now captured against the buffer's window via
window-screen-lines, not the selected window viawindow-body-height. When a theme remaps the buffer's default face — e.g.nano-light/nano-darkbumping it ~7% — the two metrics disagree and the previous capture spawned the PTY too tall, then issued a startup SIGWINCH that some TUIs (Claude Code's/tuifullscreen) mishandle. Updates every spawn / commit / reconcile site (ghostel--init-buffer,ghostel-exec,ghostel--commit-cropped-size,ghostel-compile). Closes #192 (23cdc7c, ce966eb). - Terminal size is reconciled via
window-buffer-change-functions(event-driven) instead of a 50 ms idle timer. When a ghostel buffer migrates to a window of a different size (popup dismissed,+popup/raise, etc.) no Emacs-visible window-size-change event fires — only the buffer-to-window mapping changed — so the existingadjust-window-size-functionmachinery did not run. The hook covers the buffer's whole lifetime cleanly (387c275, b9c12ca). - README's manual
~/.bashrc/~/.zshrc/~/.config/fish/config.fishsource gate now uses a prefix match (${INSIDE_EMACS%%,*}, fish-anchored regex) so TRAMP-rewrittenINSIDE_EMACS=ghostel,tramp:VERmatches. The remote-host gate adds aTERM=xterm-ghosttyfallback because plainsshcannot propagateINSIDE_EMACSwithoutAcceptEnvconfigured server-side. Also updates the source-comment header inetc/shell/ghostel.{bash,zsh,fish}and adds a TRAMP canary test ontramp-inside-emacs(d3ecac0). package-lint,checkdoc, anddocquotes(a regex check for back/front-quoted non-symbols, widened from melpazoid's[A-Z]+to[A-Z_]+so identifiers likeINSIDE_EMACSaren't skipped) now run in CI as a single Emacs 29.4 lint job;evil-ghostel.elis byte-compiled and linted alongsidelisp/(5d73106, 5321d1b).
- Coalesced single-byte input is drained before every direct PTY write from Zig (key encoder, mouse encoder, OSC 4/10/11 replies, focus events, VT write-back). Encoded keystrokes could otherwise overtake preceding self-insert bytes (0b37dae).
C-gin a ghostel buffer now also deactivates the active region, matching the side effect users expect fromkeyboard-quit(theinhibit-quitbinding routesC-gthrough the keymap toghostel-send-C-g, which previously only sent SIGINT). Closes #200 (14b4e85, 7631ea9).- IME preedit anchor stays stable during redraw — the cursor row no longer drifts while typing into an active preedit window (90f1f71).
- Evil state transitions no longer wipe + rebuild the buffer once
any scrollback exists. A
deferinfnCursorPosition/fnDebugState/fnDebugFeedwas scoped to an innerif (term.getScrollbar()) |sb| { … }block, so the viewport-restore fired before theSCROLL_BOTTOMcall below it. Each call left libghostty parked at the bottom (offset+len==total), which the next redraw mistook for a scrollback-clear signal. Hoisted the defer to function scope. Folded in: a heap-fallbackdefer ... .free(buf)infnUriAtwas scoped to the inner alloc block and freed beforemakeStringread through the alias — hoisted, plus aSUCCESScheck onghostty_grid_ref_hyperlink_uriso error returns no longer stringify uninitialised data (8e3135f).
0.18.1 — 2026-04-25
ghostel-plain-link-detection-delayuser option (default 0.1s) controls how long ghostel waits after a redraw before scanning for plain-text URLs and file paths. Set to 0 to restore the previous synchronous behavior (671d3ee).
- Plain-text link detection is now deferred off the redraw path and coalesced via a single timer, so bursts of redraws collapse into one scan instead of running detection on every dirty redraw. Native OSC-8 hyperlink spans continue to be handled inside the renderer. The process sentinel cancels the pending detection timer so it cannot fire against a buffer that is about to be killed (671d3ee).
- Scrollback rotation detection now snapshots the first scrollback
row directly (
std.mem.eqlover allterm.colscells) instead of hashing the first 16 cells with FNV-1a. Removes a small collision probability and the arbitrary 16-cell sample that could miss rotation when two rows shared the same opening cells; the cached-read optimisation that skips the end-of-redraw round trip is preserved (4b1a0ba).
0.18.0 — 2026-04-24
- Repository layout reorganized. Elisp sources now live under
lisp/(theghostelpackage) andextensions/(independentevil-ghostelpackage); vendored headers moved frominclude/tovendor/; the bundled compiled terminfo moved fromterminfo/toetc/terminfo/; shell-integration assets restructured intoetc/shell/ghostel.{bash, fish,zsh}(user-sourced rc files) andetc/shell/bootstrap/(env- hook shims for local auto-injection) (266e3e9). - Users who source ghostel's shell rc files manually from their own
shell configuration must update the path:
etc/ghostel.{bash,zsh, fish}→etc/shell/ghostel.{bash,zsh,fish}. evil-ghostelis now published as a separate MELPA package. Users who relied on installingghostelalone and getting evil integration for free must now installevil-ghostelseparately. In return,package-vc-install ghostelno longer pullsevilin as a transitive dependency of the single-repo scan.- Removed the
ghostel-evilcompatibility shim that was deprecated in 0.13.0. Replace any(require 'ghostel-evil)with(require 'evil-ghostel)and anyghostel-evil-modecalls withevil-ghostel-mode.
ghostel-environmentuser option (mirrorsvterm-environment): list ofKEY=VALUEstrings prepended toprocess-environmentbefore spawning the shell. Honors.dir-locals.elviahack-dir-local-variables, propagates to TRAMP remote shells, and applies to both shell spawns andghostel-compilespawns. User entries take precedence over ghostel's ownTERM/INSIDE_EMACS. Closes #176 (87c99e5).ghostel-defaultface (inheritsdefault) as the per-buffer customization point for terminal foreground/background, allowing e.g. a dark terminal inside a light Emacs without resorting todefadvice. Closes #178 (7c3fa5b).
ghostelandghostel-projectnow explicitly return the buffer they create or switch to, so callers can use the buffer programmatically without relying onpop-to-bufferside effects. Closes #185 (fdfb68f).- ANSI color faces now inherit from
ansi-color-*instead ofterm-color-*. Themes (notably modus) deliberately remapterm-color-black/term-color-whiteto bright palette entries to keep them distinct fromterm.el's buffer face — that accommodation made e.g. htop's status bar render gray-on-green and unreadable.ansi-color-*is the canonical ANSI face family since Emacs 28.1 and themes customize it to the proper palette. Closes #175 (a27f2fa).
- Scrollback no longer leaves stale rows after
CSI 3J(clear-scrollback) followed by enough new output to restore the same scrollback depth. A unifiedrebuild_pendingflag now tracks all scrollback-validity signals (resize, CSI 3J, rotation hash mismatch); the surgical-trim fallback that misbehaved on reflow is replaced with a single full-erase path. Closes #160 (f5524ef). - A ghostel buffer that received output while hidden no longer
shows a stale pre-hide screen on re-show. A per-window snap list
populated via
window-buffer-change-functionsforces the next redraw to anchor to the latest output. Closes #177 (63e008f). - The first ghostel buffer in a session now respects
display-buffer-alist. Fixes #179 (d33052d). ghostelandghostel-projectreuse an existing terminal buffer even afterghostel--set-title-defaulthas renamed it. Buffers now carry a stickyghostel--buffer-identityset at creation time, and lookup matches on identity rather than current buffer name. Fixes #168 (465030e).- Bind
[xterm-paste]to a ghostel-aware handler so clipboard pastes delivered by the host terminal (TTY Emacs with bracketed paste) reach the inferior shell instead of being inserted into the renderer-owned buffer and wiped on the next redraw. Fixes #172 (5546b97). - Meta-modified keys (
M-x,M-DEL, …) now reach the terminal in TTY Emacs. TTY Emacs deliversM-<key>as an ESC prefix that consumes the meta modifier before the binding fires; the dispatch path now detects theesc-maplookup viathis-command-keys-vectorand re-injects meta. Follow-up to 43220db; fixes #48 (c42451e). - Fish auto-inject now installs
xterm-ghosttyterminfo on remote hosts via thesshwrapper (parity with bash/zsh), and no longer leaks fish's internal vendor-confxdg_data_dirs(with/fishappended) intoXDG_DATA_DIRSfor every spawned subprocess. The vendor-conf shim now chains toetc/ghostel.fishinstead of carrying a drifting inline copy (d9fd009). package-vc-installon Emacs 30.x no longer fails byte-compilingtest/,bench/, andextensions/. A.elpaignorescopes recompilation to the package's lisp directory viabyte-compile-ignore-files. Emacs 31 fixed this upstream (573acd97); the.elpaignorecovers the un-backported case (bcba725).
0.17.0 — 2026-04-21
evil-ghostel-initial-statedefcustom controls the initial evil state in ghostel buffers (defaultinsert). Replaces a hard-codedevil-set-initial-statecall that fired on every ghostel buffer creation and silently clobbered user overrides.:setre-applies the value on change, and thesetq-before-requirepath is honoured on load (5fcbb19).
- Replaced
ghostel-enable-title-tracking(boolean) withghostel-set-title-function. The new option holds the function invoked on OSC 2 title changes; set to nil to disable title tracking, or to a custom function to fully override the rename behaviour (5bd67f1).
marknow survives native redraws. The full-redraw path (eraseBuffer) previously snapped every marker topoint-min, and the partial-redraw path drifted markers asymmetrically by insertion-type — soC-SPC-set marks or normal-state region commands lost their anchor on every frame (4816ece).- Evil visual selections no longer stretch to a multi-row phantom
region in a buffer that is streaming output. The
around-redrawadvice now saves and restoresevil-visual-beginning/evil-visual-endwhile in visual state, in addition topoint(606ec4d). - Removed the
evil-ghostelnormal-state-entry hook that corrupted point after operator commands —yy,v..y, andv..<escape>could discard the motion and land point on the TUI cursor row. Evil's own operator/visual machinery places point correctly without the extra snap (b955dbb).
0.16.3 — 2026-04-20
- Block cursor no longer drifts up a row when a TUI parks it on an
empty last row via absolute positioning (CUP). The
window-pointclamp from 0.16.1 is broadened via a newghostel--cursor-on-empty-row-pnative predicate so the clamp fires on both pending-wrap and empty-trailing-row conditions. Closes #157 (d4fdc8e). - The bundled
sshwrapper inghostel.bash/ghostel.zshno longer fails with a parse error when the user hasalias ssh=…set before sourcing the integration. Usesfunction ssh { … }form to sidestep alias expansion. Fixes #155 (44aaf67).
0.16.2 — 2026-04-20
- Bundled
xterm-ghosttyterminfo underterminfo/(both Linux and macOS hashed-dir layouts). Terminal sessions now setTERM=xterm-ghostty+TERMINFO=<bundled>+TERM_PROGRAM=ghosttyso TUI apps that consult terminfo see ghostel's real capabilities — most notably DEC 2026 (Sync), which Claude Code needs to avoid cascading unsynchronised redraws onM-xwith large scrollback. TRAMP pushes terminfo to a remote temp dir over the existing connection; outboundsshfrom a local buffer is shadowed with a wrapper that installs terminfo on the remote viaticon first use (cached per-host under$XDG_CACHE_HOME/ghostel/, invalidated on libghostty bumps). New options:ghostel-term,ghostel-ssh-install-terminfo,M-x ghostel-ssh-clear-terminfo-cache(2c92f68).
- Minibuffer activation (M-x, vertico, consult) no longer repaints the shell prompt or forces full TUI redraws. Shrinks caused by the minibuffer stealing window space are treated as viewport crops instead of real resizes, suppressing the spurious SIGWINCH. Apps on the alternate screen (vim, htop, less, Claude Code) still receive SIGWINCH because they own the full viewport; selecting the ghostel window while the minibuffer is open commits the cropped size (3e8d9c7).
ghostel-compileheader and early output no longer wrap at the wrong column when the compile buffer lands in a smaller window than the selected one. The VT is now reconciled to the output window's dimensions before rendering the header and before spawning the process (dcbbf1d).M-x kill-compilationnow finds and terminates a liveghostel-compilerun.compilation-locsis declared buffer-locally during the run socompilation-buffer-internal-precognises the buffer (dcbbf1d).
0.16.1 — 2026-04-20
- Block cursor no longer draws on top of the last character while the
user is typing at a shell prompt. The
window-pointclamp introduced in 0.16.0 is narrowed to fire only when libghostty reports the cursor in pending-wrap state, exposed via a newghostel--cursor-pending-wrap-pnative function. Fixes #146 (ad8536e).
0.16.0 — 2026-04-19
- Desktop notifications via OSC 9 (iTerm2) and OSC 777 (rxvt
notify), plus ConEmu OSC 9;4 progress reports. Notifications route throughghostel-notification-function(default usesnotifications-notifywith amessagefallback, dispatched viarun-at-timeso a slow DBus broker can't stall the VT parser); progress routes throughghostel-progress-function(default shows[42%]/[...]/[err]/[paused]in the mode line). OSC 9;9 CWD reports are handled the same way as OSC 7. Closes #141 (4f7b1cd). ghostel-compile-global-mode: opt-in global minor mode that advisescompilation-startso every caller (compile,recompile,project-compile, ...) automatically runs in a ghostel buffer. Falls through to the stock implementation forgrep-mode, comint, andcontinue=non-nil; excluded set is customisable viaghostel-compile-global-mode-excluded-modes(e7164ec).ghostel-send-stringandghostel-send-keypublic API for external packages (agent integrations, custom keymaps) to drive a ghostel buffer without reaching intoghostel--internals. The old internalghostel--send-keyis kept as an obsolete alias; the raw-byte primitive is nowghostel--send-string(5453c22).<XF86Paste>and<XF86Copy>media keys are now bound toghostel-yankandkill-ring-save. Previously they fell through to the global commands and got overpainted by the next redraw (65932e6).
ghostel-compileno longer types its command into an interactive shell. Each invocation spawnsshell-file-name -c COMMANDdirectly viamake-processthrough a PTY owned by the ghostel renderer. Multi-line scripts with embedded newlines now pass through verbatim (the old type-into-shell path interpreted each newline as RET), exit status comes from the process sentinel, and shell integration is no longer required. The banner is written to the VT before spawn so it appears live; interactive programs likehtop,less, andreadprompts keep working because the buffer stays inghostel-modeduring the run (e7164ec).ghostel-recompilenow re-runs into the current buffer when it holds a localghostel-compile--command, so pressinggin a*compilation*buffer produced byghostel-compile-global-modereuses the buffer and window instead of opening a second one (e7164ec).ghostel-compileopens its buffer in a non-selected window, matchingM-x compileexactly. Respectsdisplay-buffer-alist, keeps focus on the caller, andquit-windowdisposes of the window the way users expect. Closes #122 (9846c64).evil-ghostelpoint now tracks the terminal cursor inevil-emacs-state, not justinsert-state— emacs-state is evil's vanilla-Emacs escape hatch and should behave like a normal terminal (f05e0db).- Large TUI redraws (Claude Code, post-resize frames) now stream in a
single filter call.
process-adaptive-read-bufferingis disabled andread-process-output-maxraised to at least 1 MB for ghostel PTYs; pre-Emacs 31 this collapses a 570 KB post-resize frame from ~9 filter calls to 1 — a ~15-second cascading repaint becomes instant. Mirrors what vterm does for the same reason. Fixes #85 (bcf2f0c).
- Child programs that enable focus reporting (Claude Code, btop, vim)
now see focus-out when the user selects a different window inside
Emacs, not only when the whole frame blurs. Adds hooks on
window-selection-change-functionsandwindow-buffer-change-functionsin addition to frame focus. Closes #140 (ddaefbc). - Process sentinel no longer removes the focus-reporting hook globally on exit, which had broken focus reports for every other live ghostel buffer (ddaefbc).
- TUI cursor no longer disappears on the last viewport row when it
lands in pending-wrap state. Clamps
window-pointback by one whenptequalspoint-maxso Emacs redisplay stops shiftingwindow-startup by a row to "make it visible." Closes #138 (17fc791). - Viewport no longer snaps to the prompt when the minibuffer opens (and the ghostel window shrinks) in a scrolled-up TUI. During a resize-triggered redraw, windows that were auto-following before the resize are treated as still anchored rather than as a user scroll. Closes #127 (aa4912d).
- Per-cell face properties (colours from SGR sequences) now survive
when
font-lock-modeis force-enabled in a ghostel buffer — e.g. Doom Emacs setsfont-lock-defaultsglobally, which reactivates font-lock afterghostel-mode's(font-lock-mode -1). A buffer-localfont-lock-unfontify-region-functionneutralises the unfontify pass in bothghostel-modeandghostel-compile-view-mode(28f5071). evil-ghostel: entering normal state in a buffer with any scrollback no longer snaps point to row N of the scrollback region instead of row N of the visible viewport — the row offset now accounts for scrollback line count (69d4b0d).
0.15.0 — 2026-04-17
ghostel-compileandghostel-recompile:M-x compile-style workflow backed by a real PTY, so commands that need a terminal (colour output, progress bars, curses tools) work normally. Finished buffers supportnext-errornavigation and sharecompile-command/compile-historywithM-x compile;grecompiles in the original directory,C-u gprompts to edit the command (5280db2, d72751e).ghostel-eshell-visual-command-mode: overrideseshell-exec-visualso TUI programs invoked from eshell (vim, htop, less, top) run in a dedicated ghostel buffer instead of the defaultterm-modefallback. Addsghostel-execas the public primitive for running an arbitrary program in a ghostel buffer and aneshell/ghostelbuiltin (8df9fc7).ghostel-next-hyperlink/ghostel-previous-hyperlinknavigate OSC 8 hyperlinks, auto-detected URLs, and file:line references viaC-c C-n/C-c C-p; prompt navigation moves toC-c M-n/C-c M-p(895e55b).ghostel-debug-infocommand collects Emacs version, system info, native module version (with mismatch warning), terminal state, and settings into*ghostel-debug*for pasting into bug reports. Resize and redraw events are now logged whenghostel-debug-startis active (b5d7b4d).ghostel-ignore-cursor-changeoption ignores terminal requests that change cursor shape or visibility; useful when editor-owned cursor behaviour should take precedence (c901c02).M-ywith no preceding yank now opens acompleting-readbrowser over the kill ring (works with consult/vertico) instead of signalling an error (e1e1896).
C-gis now sent to the terminal instead of triggeringkeyboard-quit; in copy mode it still exits copy mode (057fb1f).- Linkified file paths in terminal output now also match bare relative
paths (e.g.
src/foo.rs:43:4), paths wrapped in punctuation (Python tracebacks, backticks, brackets), and an optional:columnafter the line number. Configurable viaghostel-file-detection-regex. Closes #107 (ed17efb). - Module auto-download now works on systems that report
amd64/arm64insystem-configuration(27dcec0). - OSC dispatch rewritten to scan each PTY write once instead of five
times. A single
OscIteratoryields(code, payload, terminator)and onedispatchPostWriteOscshandles codes 7/51/52/133 in document order. Engine micro-benchmarks improve ~20–28% on bulk input (819098f, 1729f24). - CRLF normalisation is now zero-allocation and zero-copy. The old
path allocated up to 131 KB of scratch (with heap fallback and a
silent-truncation failure mode) and walked the input twice; the new
path streams raw segments into libghostty's VT parser and emits
\r\ninline at each bare\n. State is persisted across calls so a CRLF pair split between two writes isn't double-normalised (42092e7, 1729f24). - Module loader unified into a single helper. Load-time and interactive-command paths no longer diverge in guard checks, directory resolution, or failure mode (bbe1c41).
evil-ghostelnow included inmake checkdoc(0a9faa1).
- Top line no longer renders clipped after a terminal redraw when
pixel-scroll-precision-modehad left a partial pixel offset. Closes #105 (bfb6e7c). - Scroll position preserved across window resizes (M-x, vertico
open/close, window splits). A pre-redraw classifier tags windows as
auto-follow vs. user-scrolled via multi-line content keys that
survive scrollback eviction, full-redraw erase, and viewport
rewrite — so scrolling up to read history and pressing
M-xno longer yanks the view back to the prompt. Also eliminates a 1-row per-keystroke flicker seen in Claude Code's TUI. Closes #115 (2efecf2). - Backspace now works in terminal mode (
emacs -nw). The event arrives as integer 127 and is now normalised to"backspace"at the Emacs-event boundary before key-name dispatch. Fixes #114 (c5b38d5). - Typing or pasting while point is in scrollback (after mouse wheel, M-v, pixel-scroll) now jumps the viewport to the terminal prompt as intended. Fixes #113 (31bdc9c).
- Wheel events on an unselected ghostel window no longer hang Emacs.
The scroll intercept was running in the selected window's buffer
instead of the event window's, so the re-dispatched event hit the
intercept again — infinite loop, recoverable only via
C-g. Fixes #119 (305eacd). ghostel-compileno longer leaves ~24 blank rows between the output and the footer on short commands. Trailing blank grid rows from the VT render are trimmed on finalise. Fixes #111 (60ab84f).- OSC iterator no longer cannibalises the next OSC's bytes when a
preceding OSC is missing its terminator — a new
\e]introducer now ends the current payload (1729f24).
0.14.0 — 2026-04-13
C-c C-lbinding in copy mode (156a714).
- Decouple module downloads from package version (36a1ad5).
- Disable XON/XOFF flow control so
C-qandC-sreach the shell (a8a3034). - Speed up test suite with early-return polling and parallel execution (2d3bda7).
- Wheel events now fall through to third-party scroll packages
(ultra-scroll,
pixel-scroll-precision-mode,mwheel) when terminal mouse tracking is inactive. Fixes #97 (3b6c980).
- Dead scroll commands (156a714).
- Blank first page of scrollback after initial output burst (f01de74).
- Keystrokes are now visible from the first character in bash sessions
(previously invisible on old bash, notably macOS
/bin/bash3.2). Fixes #101 (51705bd).
0.13.0 — 2026-04-13
- VT log callback (a3d043a).
- Build with Zig and vendored Emacs header (4ca5770).
- Use env vars for Emacs header override (cdcfa76).
- Replace ghostty git submodule with Zig URL dependency (b32308c).
- Use
_get_multifor render state queries (a3d043a). - Remove
zig build checkstep and clean up stale references (f19409e).
- musl cross-compilation; release builds now pass
-Dcpu=baseline(3e0776d).
0.12.2 — 2026-04-12
- Rename
ghostel-eviltoevil-ghostel(1c37fef).
- Blank screen after idle when buffer gets out of sync (0f60388).
0.12.1 — 2026-04-12
- Cursor lands on the correct character for box-drawing and other glyphs where Emacs' width calculation disagrees with the terminal (seen on CJK/pgtk). Fixes #86 (fcb8d3b).
0.12.0 — 2026-04-12
ghostel-enable-title-trackingdefcustom (0102ad9).
- Defer buffer erasure on resize to eliminate blank flash (5966043).
- Redraw synchronously on resize and anchor
window-start(6728ffc).
- Stale horizontal scroll after window resize (cc48ae3).
0.11.0 — 2026-04-11
- Materialize libghostty scrollback into the Emacs buffer (vterm parity) (34645e2).
- Detect cap rotation via first-row hash to keep scrollback fresh (9ed2a76).
- Always insert trailing newline in
insertScrollbackRange(d3acea1). - Trim trailing blank cells when rendering rows (b3e86b5).
- Update benchmark numbers after trailing-whitespace trim (1a31d37).
- OSC 51;E eval no longer crashes the process filter when the executed command switches buffers, signals an error, or deselects the ghostel window. Fixes #82 (20cce42).
0.10.1 — 2026-04-11
- Configurable TRAMP method for OSC 7 directory tracking (1159a5b).
- Pass
-Dcpu=baselinefor native x86_64 builds (11df11b). - Harden Claude review workflow for fork PRs (f16d7b8).
0.10.0 — 2026-04-11
evil-modeintegration: normal-mode navigation works in terminal buffers with the cursor kept in sync on state transitions. Closes #52 (21d8439).- OSC 4/10/11 color query responses (c57f281, fixes #75).
- SIGWINCH delivery tests for PTY resize (500d978).
- Use per-process property for window resize handler (dc102eb).
- Track
.elcfiles as proper Make targets (144c9ba). - Use
executable-findto locate bash in SIGWINCH tests (ae06f8e).
- Remote zsh temp directory leak during session startup (519f063).
- ncurses apps (htop, etc.) now redraw at the correct size after a window resize instead of being frozen at their start-up dimensions. Fixes #67 (83d90f7).
- SIGWINCH baseline tests on Linux by using bash explicitly (e5582d5).
wrong-number-of-argumentsinghostel-evil--around-delete(79a6b86).
0.9.0 — 2026-04-09
- TRAMP integration for remote shell spawning and directory tracking (512a4db).
- Scroll wheel inside TUI apps with mouse tracking (htop, less, etc.) is now forwarded to the application; it still scrolls the viewport when mouse tracking is off. Fixes #60 (a46c784).
ghostel-send-next-keynow works with prefix keys (C-x,C-h) and Meta-modified keys (M-x). Fixes #62 (f9e7fc0).claude-code-reviewworkflow write permissions (63f5550).- Wide-char pixel overflow compensation for emoji (4c191c3).
- Cursor visibility preserved in copy mode during redraws (5d7be51).
0.8.0 — 2026-04-08
ghostel-projectfunction (560776f).ghostel-scroll-on-inputto jump to bottom on typing (cabb939).ghostel--cursor-positionto query terminal cursor location (e3852d8).ghostel-copy-mode-recenter(C-l) for copy mode (6075b64).- Full scrollback copy mode and copy-all command (d07f509).
ghostel-copy-mode-auto-load-scrollbackoption (fc7fc94).
- Bump minimum Emacs version for CI test to 28.2 (cd3031d).
- Preserve manual ghostel buffer renames (c6eb801).
- Rework how ghostel buffers are created (cd7c043).
- Ignore byte-compiled elisp files (cfb0112).
- Move
ghostel--suppress-interfering-modescall insideghostel-mode(59b6928). - Display lint errors when checking locally (628ecae).
- Terminal buffers now respect
display-buffer-alistrules (e.g.(derived-mode . ghostel-mode)). Fixes #56 (85b3e5f). - Preserve column position when scrolling in copy mode (0e7f904).
0.7.1 — 2026-04-06
- Prebuilt binaries for x86_64-macos and aarch64-linux (in addition to the existing x86_64-linux and aarch64-macos). Closes #43 (d04afa6).
- MELPA installation instructions and source build notes (c1d0daf).
- Address MELPA review feedback (416cf7a).
- Build on musl-based distros (Alpine Linux) (204164f).
- Scrollback defaults treated as bytes and not lines (21abb3d).
- Module download URL when installed from MELPA (cb74461).
- Mouse scroll when
pixel-scroll-precision-modeis enabled (067af25). ghostel-test-package-versionfailure with stale.elc(beb72d5).
0.7.0 — 2026-04-05
- Use
grid_refAPI for hyperlink detection instead of HTML formatter (23aa22a). - Optimize release binaries: strip symbols and enable dead-code elimination (f6f3ba3).
0.6.0 — 2026-04-05
- Change ghostty submodule from ssh to https (607beae).
C-tand other control keys not being sent to the terminal (d4ac858).
0.5 — 2026-04-05
- Bind
s-v(Cmd-V) toghostel-yankon macOS (4e43d38).
- Improve typing responsiveness with immediate redraw and input coalescing (a30b53a).
- Clicking ghostel buffer not switching window focus (d030cbb).
struct_timespecopaque type error on some Linux systems (5e1660b).- Dim/faint text (SGR 2) is now rendered by dimming the foreground
colour (previously used
:weight light, which most monospace fonts ignore). Closes #27 (a644834). - Backspace not working in fish shell (36433fd).
0.4 — 2026-04-04
- Claude Code GitHub Actions workflows (23b7caa).
- Prompt to install native module when
ghostelcommand is called (57c6352).
- Set default terminal fg/bg from Emacs theme colors (e66a57d).
0.3 — 2026-04-04
- Module version check to detect stale native modules (be5c399).
- Shrink terminal when tall glyphs push content off-screen (d913076).
- Compensate for wide-char pixel overflow by hiding trailing spaces (e87d820).
- Skip wide-character spacer cells to fix emoji line overflow (40b23e1).
- Skip wide-char compensation when no wide characters are present (691a752).
- Revert overflow detection — keep only viewport pinning (d335346).
- Fix melpazoid warning (ff6dc1b).
ghostel--pin-window-start(caused emoji clipping) (f029fbf).
- Drag-and-drop by extracting drop data from correct event position (794b5c8).
0.2 — 2026-04-02
- Automatic theme color synchronization (eb545fa).
- ERT test for
ghostel-sync-theme(9668724). - Performance benchmark suite comparing ghostel, vterm, and eat (a184e34).
- Emacs built-in
termadded to benchmark suite; README performance section (2c86fb5). - Ghostel vs. vterm comparison section in README (3c71314).
- OSC 51 elisp eval from shell (b3094b7).
- Table of contents in README (ece4a52).
ghostel-full-redrawoption and forcewindow-startpin (1f299df).- Multi-version byte-compile job; warnings treated as errors (97258ef).
- Makefile (835d878).
- Migrate test suite from custom framework to ERT (bb986a2).
- Build with
ReleaseFastfor production performance (7393e64). - Replace manual lint CI with melpazoid (ede8f76).
- Remove
Package-Requiresfrom secondary file (c47662c). - Fix melpazoid lint warnings (9e0f076).
- Filter libghostty info log spam from benchmark output (ae0e9b9).
- Overhaul README: installation, features, configuration (1649771).
- Exit copy mode on normal key press (2ee9d3b).
- Show cursor in copy-mode even when terminal app hid it (c78b290).
- Suppress
hl-line-modein terminal buffer to prevent prompt flicker (a9a07f1). - Byte-compile warnings treated as errors in CI (56ef155).
- Bottom lines cut off when TUI apps fill the screen (f276f2d).
extractStringsilently dropping data >= 64KB (1f88bed).- Missing
errdeferformouse_encoderinTerminal.init(04ca152). - Heap fallback for HTML formatter buffer in
scanHyperlinks(08e2649). ghostel-dirfalling back todefault-directoryinstart-process(6d45a0d).- Missing double-quote escaping in zsh
ghostel_cmd(d398fec). - Copy-mode
M->landing at bottom-right and exit not scrolling back (4a9eb59). ghostel-clearandghostel-clear-scrollback(6ac0ba2).
0.1 — 2026-03-31
Initial tagged release.
- Initial skeleton: Emacs terminal module powered by libghostty-vt (d0e0ee3).
- Styled rendering with colors, bold, italic, underline, etc. (150e9e2).
- Key encoding via
GhosttyKeyEncoder(a8ad51b). - Scrollback, cursor style, and resize improvements (f23290e).
- Mouse input, paste, copy mode, directory tracking (de8d2c7).
- Test suite (61 tests) (6be06f7).
- Incremental redraw using
DIRTY_PARTIAL(c8024ed). - Focus event support gated by DEC mode 1004 (3855df9).
- ANSI 16-color palette customization (b42321f).
- Use face inheritance for ANSI color palette (e75f897).
INSIDE_EMACS=ghostelin shell environment (a496249).ghostel-kill-buffer-on-exitoption (6d31a99).- Shell integration scripts for bash, zsh, and fish (eddc7d8).
- Auto-inject shell integration without requiring .bashrc changes (593af8e).
- Clear scrollback and clear screen commands (f8c9a80).
ghostel-send-next-keyescape hatch (aa9207b).ghostel-yankandghostel-yank-popfor kill-ring cycling (6eaa60b).ghostel-exit-functionshook (0b5de44).- OSC 52 clipboard support (a7eb78a).
- OSC 8 hyperlink support with click-to-open (58f92e3).
- OSC 133 semantic prompt markers for prompt navigation (052c3d7).
- URL auto-detection and
file://link support (b0d143c). - Detect file:line references and open them in Emacs on click (454b794).
- Separate defcustom for file:line detection (457977b).
- Bracketed paste conditional on terminal mode 2004 (e209445).
- Cache frequently-used Emacs symbols as global refs (7103a56).
- Synchronized output support and debounced resize (0a2eb85).
- Keyboard scrolling in copy mode (83b7f44).
M-</M->to jump to top/bottom of scrollback in copy mode (ea4353f).C-ein copy mode to stop at last non-whitespace character (c2328dc).- Preserve column position during
C-n/C-pin copy mode (7a04588). - Strip trailing whitespace from copied text in copy mode (7d0de0f).
- Filter soft-wrapped newlines in copy mode (f384746).
ghostel-module-compilecommand (d75d9d1).- Cross-platform build support and module auto-download (a734e8e).
- Rework module installation with interactive choice and defcustom (f95fd8a).
- Full native build in CI and add release workflow (20bf1f6).
- GitHub Actions CI with linting and tests (3a190e3).
- Improve code quality, adaptive redraw, and CI coverage (ebe6f8b).
- GPL3 license and expanded commentary section (1d676df).
- README with build instructions, features, and configuration (c43bf6a).