Better Emacs shell: Part I

by oleksandrmanzyuk

Frankly, I am not particularly happy with Emacs shell (available via M-x shell; not to be confused with eshell, which I like even less). I invested some efforts into making it better, and in a series of blog posts I am going to share my small hacks that make Emacs shell more pleasant to use.

One of my complaints about Emacs shell has always been its poor handling of control sequences. ansi-color-for-comint-mode, which is enabled by default in recent versions of Emacs, translates SGR control sequences in the comint output into text-properties, but it does not handle the other escape characters. To see what I mean, try to do something like ls -la | grep --color=always bash. The matches are going to be highlighted in red, but they will also be surrounded by ^[[K. As another example, try to ssh to some host. The prompt is going to change to something like

^[]0;manzyuk@golconda: ~^Gmanzyuk@golconda:~$

These ^[]0; and ^G are escape sequences that are normally used to set the title of the terminal window (in my example, the title would be set to manzyuk@golconda: ~).

The following piece of Emacs Lisp enables filtering out of non-SGR escape sequences in the comint output.

(defun preamble-regexp-alternatives (regexps)
  "Return the alternation of a list of regexps."
  (mapconcat (lambda (regexp)
               (concat "\\(?:" regexp "\\)"))
             regexps "\\|"))

(defvar non-sgr-control-sequence-regexp nil
  "Regexp that matches non-SGR control sequences.")

(setq non-sgr-control-sequence-regexp
      (regexp-alternatives
       '(;; icon name escape sequences
         "\033\\][0-2];.*?\007"
         ;; non-SGR CSI escape sequences
         "\033\\[\\??[0-9;]*[^0-9;m]"
         ;; noop
         "\012\033\\[2K\033\\[1F"
         )))

(defun filter-non-sgr-control-sequences-in-region (begin end)
  (save-excursion
    (goto-char begin)
    (while (re-search-forward
            non-sgr-control-sequence-regexp end t)
      (replace-match ""))))

(defun filter-non-sgr-control-sequences-in-output (ignored)
  (let ((start-marker
         (or comint-last-output-start
             (point-min-marker)))
        (end-marker
         (process-mark
          (get-buffer-process (current-buffer)))))
    (filter-non-sgr-control-sequences-in-region
     start-marker
     end-marker)))

(add-hook 'comint-output-filter-functions
          'filter-non-sgr-control-sequences-in-output)

I am very pleased with the tidy look of my shell buffers, and should I encounter some control sequences that are not filtered out, they can easily be taken into account by adding a suitable regexp to the variable non-sgr-control-sequence-regexp.

Advertisements