(or emacs irrelevant)

Progress bars for apt in shell

Intro

For a couple years now, I use M-x shell as my main shell. Recently, I have fixed one of the minor annoyances that go along with using shell in Emacs. At least since Ubuntu 18.04, the terminal "progress bar" feature, displayed below is non-optional:

apt-install-progress-1

It uses terminal escape codes to display the progress bar, and shell-mode can't handle them well, so they clobber a lot of the output.

Initial work around

Previously, I was using this work around, since apt-get doesn't display the progress bar:

# sudo apt upgrade
sudo apt-get upgrade

Progress bar in the mode line

But typing 4 extra chars is hard. And apt-get will likely get these progress bars at some point as well. So I spent around an hour of my weekend hacking an Elisp solution. Here is the code:

(advice-add
 'ansi-color-apply-on-region
 :before 'ora-ansi-color-apply-on-region)

(defun ora-ansi-color-apply-on-region (begin end)
  "Fix progress bars for e.g. apt(8).
Display progress in the mode line instead."
  (let ((end-marker (copy-marker end))
        mb)
    (save-excursion
      (goto-char (copy-marker begin))
      (while (re-search-forward "\0337" end-marker t)
        (setq mb (match-beginning 0))
        (when (re-search-forward "\0338" end-marker t)
          (ora-apt-progress-message
           (substring-no-properties
            (delete-and-extract-region mb (point))
            2 -2)))))))

(defun ora-apt-progress-message (progress)
  (setq mode-line-process
        (if (string-match
             "Progress: \\[ *\\([0-9]+\\)%\\]" progress)
            (list
             (concat ":%s "
                     (match-string 1 progress)
                     "%%%% "))
          '(":%s")))
  (force-mode-line-update))

The solution will detect e.g. "\0337...Progress: [ 25%]...\0338", remove it from the shell buffer and display "25%" in the mode line instead.

Use the Echo Area instead of the mode line

The above is a good enough solution specifically for apt(8), but not for the generic case. Let's try to emulate how e.g. gnome-terminal handles these escape sequences. It takes sequences like "\0337.*\0338" and displays them in the bottom of the window. Kind of like the Emacs Echo Area. That's easy enough to do:

(defun ora-apt-progress-message (progress)
  (message
   (replace-regexp-in-string
    "%" "%%"
    (ansi-color-apply progress))))

Above, we use ansi-color-apply to get rid of any extra terminal escape codes. I decided to stay with the Echo Area version instead of the mode line version. Here's how it looks like:

apt-install-progress-2

You can find all of the above code in my config. Happy hacking!