6

In shell-mode when you exit the shell process (e.g. by typing exit) the buffer still hangs around (with the message "Process shell<1> finished").

How can I automatically kill the shell buffer when the shell process exits?

(1) Is there a package available for this?

(2) How do I write the correct piece of function advice for it? Noticing that 'shell-write-history-on-exit is called on exit I tried:

(defun leo-kill-shell-buffer-on-exit ()
  (kill-buffer))

(advice-add 'shell-write-history-on-exit :after #'leo-kill-shell-buffer-on-exit)

but nothing happens. What's going wrong?

halloleo
  • 1,215
  • 9
  • 23
  • Your advice function should take the arguments `&rest _`. – Tobias Mar 13 '19 at 07:07
  • Please have a look into `shell-mode`. You need to have `comint-input-ring-file-name` set if you want to use `shell-write-history-on-exit`. – Tobias Mar 13 '19 at 07:12
  • `shell-mode` tries to set `comint-input-ring-file-name` from the environment variable `HISTFILE` or if that does not exist it tries to set it to some defaults like `~/.bash_history`. The automagic setting did not work for me, but `(setenv "HISTFILE" "~/.bash-history")` before starting the shell did work. Nevertheless your add-on sentinel did not run yet. – Tobias Mar 13 '19 at 07:28
  • 1
    Just to check, did you *confirm* that `shell-write-history-on-exit` is called? That sentinel is set only for shells matching `shell-dumb-shell-regexp` – phils Mar 13 '19 at 07:29

1 Answers1

4

Use a process sentinel when you want to react to a process exiting. e.g.:

Refer to C-hig (elisp)Sentinels

In this case shell only calls shell-mode when (and after) starting the inferior process, so we can use shell-mode-hook to add the sentinel.

As @Tobias points out in the comments, set-process-sentinel will clobber any existing sentinel for that process. shell will always have a sentinel (exactly what it is can vary), and we can call it first.

(defun my-shell-mode-hook ()
  "Custom `shell-mode' behaviours."
  ;; Kill the buffer when the shell process exits.
  (let* ((proc (get-buffer-process (current-buffer)))
         (sentinel (process-sentinel proc)))
    (set-process-sentinel
     proc
     `(lambda (process signal)
        ;; Call the original process sentinel first.
        (funcall #',sentinel process signal)
        ;; Kill the buffer on an exit signal.
        (and (memq (process-status process) '(exit signal))
             (buffer-live-p (process-buffer process))
             (kill-buffer (process-buffer process)))))))

(add-hook 'shell-mode-hook 'my-shell-mode-hook)

Alternatively, @Tobias has provided an alternative using advice-add:

(defun add-process-sentinel (sentinel &optional process)
  "Add SENTINEL to PROCESS.
PROCESS defaults to the process of the current buffer.
Use this function with care.
If there is already a process sentinel SENTINEL is used as after-advice.
That can fail if the process sentinel is reset by some other function."
  (unless process
    (setq process (get-buffer-process (current-buffer))))
  (let ((old (process-sentinel process)))
    (cond
     ((symbolp old)
      (advice-add old :after sentinel))
     ((null old)
      (set-process-sentinel process sentinel))
     (t (warn "Cannot set sentinel %S for process %S." sentinel process)))))

(defun my-shell-mode-hook ()
  "Custom `shell-mode' behaviours."
  ;; Kill the buffer when the shell process exits.
  (add-process-sentinel
   (lambda (process signal)
     (and (memq (process-status process) '(exit signal))
          (buffer-live-p (process-buffer process))
          (kill-buffer (process-buffer process))))))

(add-hook 'shell-mode-hook 'my-shell-mode-hook)
phils
  • 48,657
  • 3
  • 76
  • 115
  • 1
    No good. This deactivates the process sentinel installed by `shell-mode` itself, e.g., `shell-write-history-on-exit`. If the OP set `comint-input-ring-file-name` the method of advising `shell-write-history-on-exit` is the right way of installing the additional sentinel. (AFAIK) – Tobias Mar 13 '19 at 07:09
  • Even if there is no `shell-write-history-on-exit` there is an `internal-default-process-sentinel`. But, if you just exit maybe you can override that one. – Tobias Mar 13 '19 at 07:45
  • I've added `add-process-sentinel` which is hopefully working under all present circumstances for this question. Roll back or edit if you see problems with this add-on. – Tobias Mar 13 '19 at 07:52
  • @Tobias, I suggest you write that as a separate answer? – phils Mar 13 '19 at 07:53
  • I consider `add-process-sentinel` really only as an add-on to your answer. I would only split it off if you insist. Note, that I know that the current version of `add-process-sentinel` is fragile. It does not work for the general case where there is already a lambda as a sentinel. One could take that lambda and call it from the new sentinel, but I did not want to go that far. – Tobias Mar 13 '19 at 07:54
  • I'll leave your code here if you prefer, but I think the mechanism for latching onto the existing sentinel is the actual interesting part, so I think a separate answer is warranted, as the two approaches are quite different. – phils Mar 13 '19 at 07:57
  • n.b. The problem with that `add-process-sentinel` approach as written is that `internal-default-process-sentinel` (most likely) now has that advice persistently, whereas that may not be desirable for non-`shell` use-cases. You want to be setting a new process sentinel and then adding (or rather prefixing) the original to *that*. – phils Mar 13 '19 at 08:32
  • `add-process-sentinel` is what its name says. If one wants to override the default process sentinel one can use `set-process-sentinel`. In our use-case the default does not hurt. – Tobias Mar 13 '19 at 08:35
  • Yes, I'm suggesting that you could `set-process-sentinel` with the new custom sentinel, and then use your `advice-add` approach to ensure the *original* sentinel runs `:before` the new sentinel. That way the only function which is being advised is the sentinel for the shell process (as opposed to advising `internal-default-process-sentinel` which may run for other processes too). i.e. an advice version of what I'd updated my code to do. – phils Mar 13 '19 at 08:43
  • Wow, @Tobias & phils, what a teamwork here! Thanks for all the thoughts. I will test it out tomorrow when I'm at my dev machine again. – halloleo Mar 13 '19 at 09:08
  • Both solutions work - I prefer somehow the second one. – halloleo Mar 14 '19 at 04:36
  • @halloleo As per my previous comment, I would suggest rewriting the second one so that advice is applied to the custom sentinel rather than the generic multi-purpose sentinel, unless you want "kill buffer upon process exit" to also happen in arbitrary other scenarios. – phils Mar 14 '19 at 08:07