Ghostel 0.59 runs Emacs async shell commands in a real terminal

1 hour ago

Ghostel 0.59 is out, and the headline is a new command, ghostel-shell-command. It's Emacs's async shell command, except the program gets a real terminal, so programs that misbehave under a dumb terminal get a proper one. This release also adds commands that always open a fresh terminal, and it fixes exit hooks on the native PTY.

Ask about this presentation

Chapters

  1. 0:00Ghostel 0.59
  2. 0:23ghostel-shell-command
  3. 0:44When the buffer is busy
  4. 1:06Route every async command
  5. 1:29ghostel-new
  6. 1:47Real exit status
  7. 2:11Project commands moved
  8. 2:32Upgrade
Show transcript
  1. Ghostel 0.59 is out, and the headline is a new command, ghostel-shell-command. It's Emacs's async shell command, except the program gets a real terminal, so programs that misbehave under a dumb terminal get a proper one. This release also adds commands that always open a fresh terminal, and it fixes exit hooks on the native PTY.

    Slide text
    *ghostel*
    dakra/ghostel · v0.59.0 · 2026-10-19

    Async shell commands, in a real terminal

    + ghostel-new
    + real exit codes on the native PTY
    btop running in a Ghostel buffer
    btop in a Ghostel buffer
    U:@**- *ghostel* All (Ghostel+)
    M-xghostel-shell-command
  2. The command runs through your shell-file-name and shell-command-switch, so a remote TRAMP directory uses the remote host's shell. The output lands in a buffer called Ghostel Shell Command, which stays open after the command exits. And unlike ghostel-compile, it's a plain terminal, with no header, footer, or error parsing.

    Slide text
    *Ghostel Shell Command*

    ghostel-shell-command is async-shell-command with a real TTY

    1 Runs via shell-file-name and shell-command-switch the remote host's, for a TRAMP directory
    2 The buffer stays after the command exits
    3 A plain terminal: no header, footer or error parsing
    U:@**- *Ghostel Shell Command* All (Ghostel+)
    M-xghostel-shell-command
  3. If that buffer is still running something, Ghostel follows your async-shell-command-buffer setting, the same variable M-& reads. It can kill the running command, use a new buffer, or rename the old one, and the confirm variants ask you first. A trailing ampersand is dropped, so the job stays in the foreground and its output stays in the terminal.

    Slide text
    *Ghostel Shell Command*

    A busy buffer follows your async-shell-command-buffer

    confirm-kill-process kill the running command
    new-buffer, confirm-new-buffer use a new buffer
    rename-buffer, confirm-rename-buffer rename the old one
    A trailing & is dropped, so the command stays in the foreground.
    U:%*- *Ghostel Shell Command* run (Ghostel+)
    A command is running in *Ghostel Shell Command*. Use a new buffer? (yes or no)
  4. To use it everywhere, turn on ghostel-shell-command-global-mode. It advises shell-command, so M-&, Dired's ampersand, project-async-shell-command, and Lisp callers all land in a Ghostel terminal. Synchronous shell commands are left alone. If you only want M-& to change, remap that one command instead and leave the rest stock.

    Slide text
    init.el

    One mode sends every async shell command to Ghostel

    (use-package ghostel-shell-command
      :hook (after-init . ghostel-shell-command-global-mode))
    Only M-&, nothing else:
    (keymap-global-set "<remap> <async-shell-command>"
                       #'ghostel-shell-command)
    Covered by the mode
    +M-&
    +Dired's &
    +project-async-
    shell-command
    +Lisp callers
    U:--- init.el All (ELisp/d)
    M-xghostel-shell-command-global-mode
  5. Two smaller additions open terminals. ghostel-new always creates a new terminal, the same as running ghostel with a prefix argument, and ghostel-project-new does the same in the project root. The terminal menu now calls them, so its entries read New Terminal and New Project Terminal.

    Slide text
    *ghostel*<2>

    ghostel-new always opens a fresh terminal

    C-u M-x ghostel → M-x ghostel-new
    C-u M-x ghostel-project → M-x ghostel-project-new
    Terminal menu: New Terminal, New Project Terminal
    U:@**- *ghostel*<2> All (Ghostel+)
    M-xghostel-new
  6. On the native PTY, ghostel-exit-functions always received the word finished, whatever the child process returned. Now the hook gets the real status, like exited abnormally with code 3, which matches the Emacs PTY backend. The child's final output is also rendered before the hooks run, so a hook, or ghostel-kill-buffer-on-exit, sees a buffer with the last lines in it.

    Slide text
    *ghostel*

    ghostel-exit-functions now get the real exit status

    native PTY, 0.58.0
    $ sh -c 'exit 3'
    "finished"
    native PTY, 0.59.0
    $ sh -c 'exit 3'
    "exited abnormally with code 3"
    The last lines of output are in the buffer before the hooks run.
    U:%*- *ghostel* Bot (Ghostel+)
    [Process exited]
  7. One thing moved. The project commands, and the ghostel-project-buffer-scope option, now live in ghostel-project.el. If you install Ghostel as a package, the commands are autoloaded, so there is nothing to add. If you load it from a bare load-path without package autoloads, add one line that requires ghostel-project.

    Slide text
    ghostel-project.el

    The project commands moved to their own file

    ghostel.el
    →
    ghostel-project.el
    ghostel-project · ghostel-project-next · ghostel-project-previous
    ghostel-project-list-buffers · ghostel-project-buffer-list
    ghostel-project-buffer-scope
    Installed as a package: commands are autoloaded
    Bare load-path, no autoloads: add (require 'ghostel-project)
    U:--- ghostel-project.el Top (ELisp/d)
    (require 'ghostel-project)
  8. When you upgrade, move the native module too. The Elisp now requires module version 0.59.0, because the module changed how it reports a child's exit status. The exit-status fix only changes the native PTY, since the Emacs PTY backend already reported it. That's Ghostel 0.59: async shell commands, in a real terminal.

    Slide text
    *ghostel*
    0.59.0
    The Elisp now requires native module 0.59.0 or newer.
    ghostel--minimum-module-version
    The exit-status fix is for the native PTY. The Emacs PTY backend already reported it.
    github.com/dakra/ghostel/releases/tag/v0.59.0
    U:@**- *ghostel* All (Ghostel+)
    M-xghostel-shell-command

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️