Emacs: quickly killing processes


Every so often, I need to kill the odd unresponsive process. While I really like proced (check out Mickey Petersen’s article), I somehow find myself using macOS’s Activity Monitor to this purpose. Kinda odd, considering I prefer to do these kinds of things from Emacs.

What I’d really like is a way to quickly fuzzy search a list of active processes and choose the unresponsive culprid, using my preferred completion frontend (in my case ivy).

kill_x1.8.webp

Let’s implement an interactive function for it… While we could use ivy-read directly in our implementation, it’s best to use completing-read and remain compatible with other completion frameworks. I’m a big fan of the humble completing-read. You feed it a list of candidates and it prompts users to pick one. To build our process list, we can reuse proced‘s own source: proced-process-attributes. We transform its output to an alist, formatting the visible keys to contain the process id, owner, command name, and the command line which invoked the process. Once a process is chosen, we can send a kill signal using signal-process and our job is done. Read on for the full snippet.

(require 'map)
(require 'proced)
(require 'seq)

(defun ar/quick-kill-process ()
  (interactive)
  (let* ((pid-width 5)
         (comm-width 25)
         (user-width 10)
         (processes (proced-process-attributes))
         (candidates
          (mapcar (lambda (attributes)
                    (let* ((process (cdr attributes))
                           (pid (format (format "%%%ds" pid-width) (map-elt process 'pid)))
                           (user (format (format "%%-%ds" user-width)
                                         (truncate-string-to-width
                                          (map-elt process 'user) user-width nil nil t)))
                           (comm (format (format "%%-%ds" comm-width)
                                         (truncate-string-to-width
                                          (map-elt process 'comm) comm-width nil nil t)))
                           (args-width (- (window-width) (+ pid-width user-width comm-width 3)))
                           (args (map-elt process 'args)))
                      (cons (if args
                                (format "%s %s %s %s" pid user comm (truncate-string-to-width args args-width nil nil t))
                              (format "%s %s %s" pid user comm))
                            process)))
                  processes))
         (selection (map-elt candidates
                             (completing-read "kill process: "
                                              (seq-sort
                                               (lambda (p1 p2)
                                                 (string-lessp (nth 2 (split-string (string-trim (car p1))))
                                                               (nth 2 (split-string (string-trim (car p2))))))
                                               candidates) nil t)))
         (prompt-title (format "%s %s %s"
                               (map-elt selection 'pid)
                               (map-elt selection 'user)
                               (map-elt selection 'comm))))
    (when (y-or-n-p (format "Kill? %s" prompt-title))
      (if (eq (signal-process (map-elt selection 'pid) 9) 0)
          (message "killed: %s" prompt-title)
        (message "error: could not kill %s" prompt-title)))))

I’ve pushed ar/quick-kill-process to my config. Go suggestions? Alternatives? Lemme know.


Source link