0

I want to make a function that stops the ESS inferior process (e.g. an R or Julia shell), kills its buffer and deletes its window. ess-inf.el provides the function ess-quit to exit the inferior process, so my first attempt was

(defun kill-ess-inf-buffer-and-window ()
  "Exit the inferior ESS process, kill its buffer and delete its
window."
  (interactive)
  (let ((ess-process-buffer (ess-get-process-buffer)))
    (with-current-buffer ess-process-buffer ; Otherwise ESS inserts the command for quitting in the source code buffer, if this function is invoked from there.
      (ess-quit)
      (delete-window (get-buffer-window ess-process-buffer))
      (kill-buffer ess-process-buffer))))

but Emacs asks me about killing the buffer because it has a running process. Apparently kill-buffer runs before ess-quit has finished its job, so I tried adding a (sleep-for 0.1) before (kill-buffer ess-process-buffer) and Emacs didn't ask anymore, but I guess there is a more robust way of doing it.

gnuplot.el has a function that does for gnuplot shells exactly what I'm trying to do for ESS shells, gnuplot-kill-gnuplot-buffer:

(defun gnuplot-kill-gnuplot-buffer ()
  "Kill the gnuplot process and its display buffers."
  (interactive)
  (if (and gnuplot-process
           (eq (process-status gnuplot-process) 'run))  ;; <SE>
      (kill-process gnuplot-process))
  (if (and gnuplot-buffer (get-buffer gnuplot-buffer))
      (progn
        (if (one-window-p) ()
          (delete-window (get-buffer-window gnuplot-buffer)))
        (kill-buffer gnuplot-buffer)))
  (setq gnuplot-process nil
        gnuplot-buffer nil))

It uses kill-process, but I don't know how that function works. Is it a request for a graceful shutdown or a SIGDIEMOTHERF**ER? I don't want to brutally murder my inferior ESS processes.

6
  • Somewhat related: stackoverflow.com/questions/20339604/… Commented Mar 30, 2023 at 10:47
  • You could use a process sentinel to kill the associated buffer as soon as the process reports that it has exited. Or you could simply assume that the process will exit and wait for that to happen, something like (when-let ((proc (get-buffer-process (current-buffer)))) (while (buffer-live-p proc) (sit-for 0.1)))
    – phils
    Commented Mar 30, 2023 at 11:01
  • Hi @phils :-) > You could use a process sentinel to kill the associated buffer as soon as the process reports that it has exited. I was thinking about that so I read about process sentinels in the manual, but I can't figure out how to do it. Can you tell me, please? Commented Mar 30, 2023 at 11:07
  • C-h i g (elisp)Signals to Processes regarding kill-process and friends.
    – phils
    Commented Mar 30, 2023 at 11:07
  • I'll leave you to research that; no time to follow up right now. It's more complex than the other option.
    – phils
    Commented Mar 30, 2023 at 11:08

1 Answer 1

1

This is a template for a function that kills the process and buffer of a shell SHELL and deletes its window.

(defun kill-SHELL-inf-buffer-and-window ()
  "Exit the inferior <SHELL> process, kill its buffer and delete its
window."
  (interactive)
  (set-process-sentinel
   (get-process SHELL-PROCESS-NAME)
   (lambda (process event)
     (ORIGNAL-SENTINEL-FUNCTION-IF-ANY process event)
     (and (memq (process-status process) '(exit signal))
          (buffer-live-p (process-buffer process))
          (kill-buffer (process-buffer process)))))
  (delete-window (get-buffer-window (process-buffer (get-process SHELL-PROCESS-NAME))))
  (FUNCTION-THAT-QUITS-SHELL))

The function below applies it to shells launched by ESS.


As suggested by phils in the comments, here I use a process sentinel to kill the buffer as soon as the process reports it has exited. Or at least that's the idea. From the first tests it seems like it works.

Edit updated according to phils' suggestions.

(defun kill-ess-inf-buffer-and-window ()
  "Exit the inferior ESS process, kill its buffer and delete its
window."
  (interactive)
  (with-current-buffer (ess-get-process-buffer)
    (set-process-sentinel
     (ess-get-process ess-local-process-name)
     (lambda (process event)
       (ess-process-sentinel process event) ; Run ESS’s own sentinel.
       (and (memq (process-status process) '(exit signal))
            (buffer-live-p (process-buffer process))
            (kill-buffer (process-buffer process)))))
    (ess-quit)
    (delete-window (get-buffer-window (ess-get-process-buffer)))))

Notes:

  • ess-get-process is ESS's counterpart of get-process. It takes a process name as argument and returns the corresponding process object.
  • ess-get-process-buffer is ESS's counterpart of process-buffer. It retrieves the the buffer associated with the process.
  • ess-local-process-name is where the name of the process is stored.
  • I use (with-current-buffer (ess-get-process-buffer) …) because otherwise ESS inserts the command for quitting in the source code buffer, if the function is invoked from there.
  • (delete-window) alone causes the screen to flicker when the window is deleted, while (delete-window (get-buffer-window (ess-get-process-buffer))) prevents the flickering but it works only if called after ess-quit. I don't know why, I think it’s due to something that ess-quit does.
8
  • 1
    Nice one. A couple of things... 1. You should set the sentinel before calling ess-quit -- it may well never make a difference in practice, but in principle if the process died instantly your sentinel might never have a chance to run if you don't try to set it until after you've asked it to exit. And 2, always check the signal in a sentinel: (lambda (process signal) (and (memq (process-status process) '(exit signal)) (buffer-live-p (process-buffer process)) (kill-buffer (process-buffer process))))
    – phils
    Commented Mar 31, 2023 at 8:57
  • n.b. I would usually be dealing with (process-buffer process) within a sentinel function, so I've shown that, but I don't know what (ess-get-process-buffer) actually does.
    – phils
    Commented Mar 31, 2023 at 8:59
  • 1
    That's fair enough -- your process-live-p approach was probably fine. I'd be inclined to use explicit status tests as a general rule in sentinels, though -- sentinels can be called for a number of reasons, so I think it makes the purpose clearer. Regarding the and form, that's just personal preference -- (and x y z) is simpler than (when (and x y) z). The byte compiler will optimise the latter to the former, though, so it makes no real difference.
    – phils
    Commented Mar 31, 2023 at 11:36
  • 1
    My other tip about sentinels is to remember that a process has only one of them -- if you set a sentinel, you're replacing any pre-existing sentinel. As such, it can be a good idea to figure out if it's just using internal-default-process-sentinel (the default), or if there's something else already configured. If necessary you could call he original function explicitly in your sentinel, or else use something along the lines of (add-function :after (process-sentinel proc) #'my-sentinel) (which I haven't tested, but (elisp)Advising Functions provides a similar example for filters).
    – phils
    Commented Mar 31, 2023 at 11:51
  • 1
    Case-by-case -- it 100% depends on what the original sentinel does. In that example I see there's another buffer (:accum_buffer ?) which the default sentinel kills at the beginning, so maybe you do want that to run first, to ensure it cleans up after itself? You can call ess-process-sentinel in your function as an alternative to advice, which is quite likely to be equivalent in practice (i.e. so long as nothing else is messing with the sentinel for this).
    – phils
    Commented Mar 31, 2023 at 22:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.