ChatGPT Shell: Confirm Before Closing and Split Compose Buffer
I admit: I’ve been relying heavily on ChatGPT to get to grips with some PHP things. Asking for interpretation, alternatives, and PHP 8-specific stuff was a lot of help.
I’ve been using this in a separate floating window (aka ‘frame’) in Emacs next to my editing context, and that was great.
Until I accidentally closed the buffer and lost the history.
Twice.
Álvaro Ramírez, author of chatgpt-shell
, kindly suggested I could ask for confirmation before closing ChatGPT buffers. Haven’t thought of that, obviously, so I was very grateful.
Update 2024-01-16: All of this is now part of shell-maker.el
which is used by chatgpt-shell
(and other interactive shells, like kagi), so if you install the bleeding edge version from MELPA or source now, you get a confirmation dialog out of the box. I’m leaving this post for history.
A web search later, I found out that kill-buffer-query-functions
exists and is triggered as a sort of filter that intercepts buffer closing actions. The documentation says:
List of functions called with no args to query before killing a buffer.
The buffer being killed will be current while the functions are running.
See ‘kill-buffer’.If any of them returns nil, the buffer is not killed. Functions run by
this hook are supposed to not change the current buffer.
Confusingly, though, you can use yes-or-no-p
to ask the user for confirmation, and confirming returns t
for “yes” and nil
for “no”, but the returned valued just works in a kill-buffer-query-functions
function …?! Also, returning t
actually skips the function and is the neutral element here.
That doesn’t sound right but StackOverflow agrees, so I’m keeping it.
I don’t know.
Anyway, here’s the config:
(use-package chatgpt-shell
:commands (chatgpt-shell
chatgpt-shell-prompt-compose)
:bind (("C-c C-e" . chatgpt-shell-prompt-compose))
:config
(defun ct/confirm-before-killing-chatgpt ()
(let ((buf (current-buffer)))
(if (and (buffer-match-p "^\\*chatgpt\\*" buf)
(not (buffer-match-p " compose$" buf)))
(yes-or-no-p "ChatGPT Buffer! Kill anyway? ")
t)))
(add-to-list 'kill-buffer-query-functions #'ct/confirm-before-killing-chatgpt)
(add-to-list 'display-buffer-alist
'("\\*chatgpt\\*.*compose"
(display-buffer-reuse-window
display-buffer-in-side-window)
(reusable-frames . visible)
(side . bottom)
(window-height . 0.3))))
;; Later:
(with-eval-after-load 'chatgpt-shell
(with-eval-after-load 'xah-fly-keys
(define-key xah-fly-leader-key-map (kbd "i k") #'chatgpt-shell-prompt-compose)))
I bound the composition window to SPC i k, the space key being the leader key in command-mode. That was free and is pretty convenient to pop up a composition buffer. I like the dedicated side window split for this.
Since I’m using the composition function, the buffer closing interception checks for “starts with *chatgpt*
” and “does not end with ` compose`” so that the pop-up composition buffers can come and go, only a real history buffer is protected.
Maybe I should add autosaving of the transcript next, deleting transcripts that are 5 days old or older automatically.