Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added
- `ghostel-query-before-killing` defcustom controls whether Emacs
asks for confirmation before killing a live ghostel buffer or
exiting Emacs while one is running. Defaults to `auto`: quiet
at the shell prompt, queries while a command is running (via
OSC 133 C/D markers). Set to `t` for always-on confirmation,
`nil` to restore the previous never-query behavior.
Closes [#288](https://github.com/dakra/ghostel/issues/288).

## [0.26.0] — 2026-05-13

### Added
Expand Down
47 changes: 44 additions & 3 deletions lisp/ghostel.el
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,30 @@ The default, `ghostel--set-title-default', renames the buffer to
"Kill the buffer when the shell process exits."
:type 'boolean)

(defcustom ghostel-query-before-killing 'auto
"Whether to confirm before killing a live ghostel buffer or exiting Emacs.

The value controls the process query-on-exit flag (see
`set-process-query-on-exit-flag'), which Emacs honours both when
killing the buffer and when exiting Emacs while the buffer is live.

t Always query while the shell process is alive.
nil Never query.
auto Query only while a shell command is running. Requires OSC 133 shell
integration: at a prompt the flag is nil, and it flips to t between
the OSC 133 C (command start) and D (command finish) markers."
:type '(choice (const :tag "Always" t)
(const :tag "Never" nil)
(const :tag "While a command is running" auto)))

(defcustom ghostel-exit-functions nil
"Hook run when the terminal process exits.
Each function is called with two arguments: the buffer and the
exit event string."
:type 'hook)

(defcustom ghostel-command-finish-functions nil
(defcustom ghostel-command-finish-functions
'(ghostel--query-before-killing-on-cmd-finish)
"Hook run when a shell command finishes (OSC 133 D marker).
Each function is called with two arguments: the buffer and the
exit status (an integer, or nil if the shell did not report one).
Expand All @@ -388,7 +405,8 @@ non-nil, in which case the error is re-signalled so the debugger
can fire (standard `with-demoted-errors' semantics)."
:type 'hook)

(defcustom ghostel-command-start-functions nil
(defcustom ghostel-command-start-functions
'(ghostel--query-before-killing-on-cmd-start)
"Hook run when a shell command starts running (OSC 133 C marker).
Each function is called with one argument: the buffer.

Expand Down Expand Up @@ -4510,6 +4528,24 @@ is non-nil so the debugger fires for hook authors who want it."
(apply fn args))
nil)))

(defun ghostel--query-before-killing-on-cmd-start (buf)
"Flip the process query-on-exit flag on for BUF while a command runs.
Active only when `ghostel-query-before-killing' is `auto'.
Hung off `ghostel-command-start-functions'."
(when (eq ghostel-query-before-killing 'auto)
(let ((proc (buffer-local-value 'ghostel--process buf)))
(when (process-live-p proc)
(set-process-query-on-exit-flag proc t)))))

(defun ghostel--query-before-killing-on-cmd-finish (buf _exit)
"Clear the process query-on-exit flag on BUF when the command finishes.
Active only when `ghostel-query-before-killing' is `auto'.
Hung off `ghostel-command-finish-functions'."
(when (eq ghostel-query-before-killing 'auto)
(let ((proc (buffer-local-value 'ghostel--process buf)))
(when (process-live-p proc)
(set-process-query-on-exit-flag proc nil)))))

(defun ghostel--prompt-input-start ()
"From the start of a `ghostel-prompt' region, move past the prefix.
If `ghostel-input' begins on the same line, point lands at its
Expand Down Expand Up @@ -5969,7 +6005,12 @@ matches the PTY window size, and stores the process in
;; Set the PTY's actual window size (ioctl TIOCSWINSZ) so that
;; the program's line editor (readline/ZLE) can render properly.
(set-process-window-size proc height width)
(set-process-query-on-exit-flag proc nil)
;; For `auto', start nil — we spawn at a fresh prompt. The
;; OSC 133 C/D handlers flip the flag while a command runs.
(set-process-query-on-exit-flag
proc (if (eq ghostel-query-before-killing 'auto)
nil
ghostel-query-before-killing))
(process-put proc 'adjust-window-size-function
#'ghostel--window-adjust-process-window-size)
proc)))
Expand Down
77 changes: 77 additions & 0 deletions test/ghostel-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -4660,6 +4660,79 @@ bind `debug-on-error' to nil."
(ghostel--osc133-marker "D" "0")
(should later-ran))))) ; second hook still fired

;; -----------------------------------------------------------------------
;; Test: ghostel-query-before-killing
;; -----------------------------------------------------------------------

(defmacro ghostel-test--with-cat-process (var &rest body)
"Spawn a long-lived `cat' process bound to VAR, run BODY, then clean up.
The process is killed and the temp buffer destroyed on exit so the
flag-flip tests don't leak processes between runs."
(declare (indent 1))
`(let* ((buf (generate-new-buffer " *ghostel-test-query-cat*"))
(,var (make-process :name "ghostel-test-cat"
:buffer buf
:command '("cat")
:connection-type 'pipe
:noquery nil)))
(unwind-protect (progn ,@body)
(when (process-live-p ,var)
(delete-process ,var))
(kill-buffer buf))))

(ert-deftest ghostel-test-query-before-killing-auto-toggles ()
"`auto' flips the query-on-exit flag around OSC 133 C/D markers."
(ghostel-test--with-cat-process proc
(with-current-buffer (process-buffer proc)
(setq ghostel--process proc)
(let ((ghostel-query-before-killing 'auto))
(set-process-query-on-exit-flag proc nil) ; baseline
(ghostel--query-before-killing-on-cmd-start (current-buffer))
(should (process-query-on-exit-flag proc)) ; command running
(ghostel--query-before-killing-on-cmd-finish (current-buffer) 0)
(should-not (process-query-on-exit-flag proc)))))) ; back at prompt

(ert-deftest ghostel-test-query-before-killing-nil-is-noop ()
"When set to nil, the OSC 133 handlers must not touch the flag."
(ghostel-test--with-cat-process proc
(with-current-buffer (process-buffer proc)
(setq ghostel--process proc)
(let ((ghostel-query-before-killing nil))
(set-process-query-on-exit-flag proc nil)
(ghostel--query-before-killing-on-cmd-start (current-buffer))
(should-not (process-query-on-exit-flag proc)) ; unchanged
(ghostel--query-before-killing-on-cmd-finish (current-buffer) 0)
(should-not (process-query-on-exit-flag proc))))))

(ert-deftest ghostel-test-query-before-killing-t-is-noop ()
"When set to t, the OSC 133 handlers must not touch the flag.
The flag is already t from spawn time, and `auto'-only toggling
would defeat the user's request to always be asked."
(ghostel-test--with-cat-process proc
(with-current-buffer (process-buffer proc)
(setq ghostel--process proc)
(let ((ghostel-query-before-killing t))
(set-process-query-on-exit-flag proc t)
(ghostel--query-before-killing-on-cmd-start (current-buffer))
(should (process-query-on-exit-flag proc)) ; still t
(ghostel--query-before-killing-on-cmd-finish (current-buffer) 0)
(should (process-query-on-exit-flag proc)))))) ; still t after D

(ert-deftest ghostel-test-query-before-killing-handles-dead-process ()
"Handlers must not raise if the process has already exited."
(ghostel-test--with-cat-process proc
(with-current-buffer (process-buffer proc)
(setq ghostel--process proc)
(delete-process proc)
(let ((ghostel-query-before-killing 'auto))
(should-not (condition-case _
(progn (ghostel--query-before-killing-on-cmd-start
(current-buffer))
(ghostel--query-before-killing-on-cmd-finish
(current-buffer) 0)
nil)
(error t)))))))

;; -----------------------------------------------------------------------
;; Test: ghostel-compile--finalize
;; -----------------------------------------------------------------------
Expand Down Expand Up @@ -15553,6 +15626,10 @@ slip past the unit tests."
ghostel-test-command-finish-hook-error-isolated
ghostel-test-command-finish-hook-runs-synchronously
ghostel-test-command-start-hook-runs-synchronously
ghostel-test-query-before-killing-auto-toggles
ghostel-test-query-before-killing-nil-is-noop
ghostel-test-query-before-killing-t-is-noop
ghostel-test-query-before-killing-handles-dead-process
ghostel-test-compile-finalize-scans-errors
ghostel-test-compile-finalize-appends-footer
ghostel-test-compile-finalize-footer-on-failure
Expand Down
Loading