Oleksandr Manzyuk's Blog

Musings of a mathematician and aspiring programmer

Month: November, 2011

Better Emacs shell: Part II

Today I would like to talk about how to change colors in Emacs shell. The default color theme of Emacs shell is, if I am not mistaken, the default color theme of the rxvt terminal. It is, in my opinion, rather ugly and hard to read. I personally prefer Tango, the default color theme of gnome-terminal. You may have different preferences. Furthermore, I have observed that unlike genuine terminals, which display bold text in slightly brighter colors, Emacs shell uses the same color both for normal and bold text. Also, ansi-color doesn’t support high intensity colors corresponding to ESC [ <code> m escape sequence for the values of <code> between 90 and 109, which some console applications use.

Fortunately, as with pretty much everything in Emacs, it is possible to fix these problems. I am posting a snippet from my init.el with the hope that somebody may find it useful and so that I can point people to this blog entry when they ask me about my Emacs shell colors.

(setq color0  "#000000"
      color1  "#CC0000"
      color2  "#4E9A06"
      color3  "#C4A000"
      color4  "#3465A4"
      color5  "#75507B"
      color6  "#06989A"
      color7  "#D3D7CF"
      color8  "#555753"
      color9  "#ef2929"
      color10 "#8ae234"
      color11 "#fce94f"
      color12 "#729fcf"
      color13 "#ad7fa8"
      color14 "#34e2e2"
      color15 "#eeeeec")

(setq ansi-color-black        color0
      ansi-color-bold-black   color8
      ansi-color-red          color1
      ansi-color-bold-red     color9
      ansi-color-green        color2
      ansi-color-bold-green   color10
      ansi-color-yellow       color3
      ansi-color-bold-yellow  color11
      ansi-color-blue         color4
      ansi-color-bold-blue    color12
      ansi-color-magenta      color5
      ansi-color-bold-magenta color13
      ansi-color-cyan         color6
      ansi-color-bold-cyan    color14
      ansi-color-white        color7
      ansi-color-bold-white   color15)

(setq ansi-color-names-vector
      (vector ansi-color-black
              ansi-color-red
              ansi-color-green
              ansi-color-yellow
              ansi-color-blue
              ansi-color-magenta
              ansi-color-cyan
              ansi-color-white))

(setq ansi-color-bold-colors
      `((,ansi-color-black   . ,ansi-color-bold-black  )
        (,ansi-color-red     . ,ansi-color-bold-red    )
        (,ansi-color-green   . ,ansi-color-bold-green  )
        (,ansi-color-yellow  . ,ansi-color-bold-yellow )
        (,ansi-color-blue    . ,ansi-color-bold-blue   )
        (,ansi-color-magenta . ,ansi-color-bold-magenta)
        (,ansi-color-cyan    . ,ansi-color-bold-cyan   )
        (,ansi-color-white   . ,ansi-color-bold-white  )))

(defun ansi-color-get-bold-color (color)
  (or (cdr (assoc color ansi-color-bold-colors))
      color))

(defun ansi-color-boldify-face (face)
  (if (consp face)
      (let* ((property   (car face))
             (color      (cdr face))
             (bold-color (ansi-color-get-bold-color color)))
        (ansi-color-make-face property bold-color))
    face))

(eval-after-load "ansi-color"
  '(progn
     ;; Copied from `ansi-color.el' and modified to display
     ;; bold faces using slighly different, brigher colors.
     (defun ansi-color-get-face (escape-seq)
       (let ((i 0)
             f val)
         (while (string-match ansi-color-parameter-regexp
                              escape-seq i)
           (setq i   (match-end 0)
                 val (ansi-color-get-face-1
                      (string-to-number
                       (match-string 1 escape-seq) 10)))
           (cond ((not val))
                 ((eq val 'default)
                  (setq f (list val)))
                 (t
                  (unless (member val f)
                    (push val f)))))
         ;; Use brighter colors for bold faces.
         (when (member 'bold f)
           (setq f (mapcar 'ansi-color-boldify-face f)))
         f))
     ;; Copied from `ansi-color.el' and modified to support
     ;; so called high intensity colors.
     (defun ansi-color-make-color-map ()
       (let ((ansi-color-map (make-vector 110 nil))
             (index 0))
         ;; miscellaneous attributes
         (mapc
          (function (lambda (e)
                      (aset ansi-color-map index e)
                      (setq index (1+ index)) ))
          ansi-color-faces-vector)
         ;; foreground attributes
         (setq index 30)
         (mapc
          (function
           (lambda (e)
             (aset ansi-color-map index
                   (ansi-color-make-face 'foreground e))
             (setq index (1+ index)) ))
          ansi-color-names-vector)
         ;; background attributes
         (setq index 40)
         (mapc
          (function
           (lambda (e)
             (aset ansi-color-map index
                   (ansi-color-make-face 'background e))
             (setq index (1+ index)) ))
          ansi-color-names-vector)
         ;; foreground attributes -- high intensity
         (setq index 90)
         (mapc
          (function
           (lambda (e)
             (aset ansi-color-map index
                   (ansi-color-make-face 'foreground e))
             (setq index (1+ index)) ))
          ansi-color-names-vector)
         ;; background attributes -- high intensity
         (setq index 100)
         (mapc
          (function
           (lambda (e)
             (aset ansi-color-map index
                   (ansi-color-make-face 'background e))
             (setq index (1+ index)) ))
          ansi-color-names-vector)
         ansi-color-map))))

(defun ansi-color-generate-color-map ()
  (setq ansi-color-map (ansi-color-make-color-map)))

(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
(add-hook 'shell-mode-hook 'ansi-color-generate-color-map)
Advertisements

Better Emacs shell: Part I

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.