Progress bars for apt in shell
24 Mar 2019Intro
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:
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:
You can find all of the above code in my config. Happy hacking!