Wednesday, March 4, 2009

Color testrun output (I mean it this time!)

So, the last code I posted did yield colorful test output, but it broke the next-error and goto-error functions, which are incredibly useful. goto-error is the function that lets you hit enter on a line from a stack trace and wind up looking at that line in that file. That's far more useful than just colorful stuff.

goto-error uses text properties to determine whether there's an error on that line or not, so turning off font-lock-mode turns off goto-error. Thus, font-lock-mode must stay on. However, ansi-color-apply gives you text with its 'face' property set, and font-lock-mode overwrites the 'face' property when it fontifies the buffer.

There's a way out, though: font-lock-mode leaves the 'font-lock-face' property alone, and if your text has 'font-lock-face' set, then that acts like its 'face' property.

So all we have to do is take the output of ansi-color-apply, turn its 'face' property into 'font-lock-face', and make *that* the string that compilation-filter gets.

(defadvice compilation-filter (before ansify-compilation-output activate)
(with-current-buffer (process-buffer (ad-get-arg 0))
(let ((colorstr (ansi-color-apply (ad-get-arg 1))))
(ad-set-arg 1 (font-lock-proof colorstr 0)))))

(defun font-lock-proof (string start)
((>= start (length string)) "")
(let* ((end (next-property-change start string (length string)))
(s (substring string start end)))
(set-text-properties 0
(length s)
(substitute 'font-lock-face 'face (text-properties-at 0 s))
(concat s (font-lock-proof string end))))))

(fancy highlighted code courtesy of htmlize.el)

Saturday, February 21, 2009

Addendum and bugfix

In my last post, I had this code that would give colorful output in compilation mode. Well, it turns out that only works if your test output is very short and comes very quickly. For the toy Ruby spec I was using to develop it, that's true. For real specs, it's very much not.

Here's the original code:

(defadvice compilation-filter (before ansify-compilation-output activate)
(with-current-buffer (process-buffer (ad-get-arg 0))
(font-lock-mode -1)
(ad-set-arg 1 (ansi-color-apply (ad-get-arg 1))))

The problem is that turning off font-lock-mode strips all the colors from the current buffer's text. If your process produces multiple pieces of output, you get this effect sort of like a line of sparks. The rightmost few are bright and colorful, but they quickly fade out and become dull gray.

The fix is to turn off font-lock-mode only when it's on. That is, like so:

(defadvice compilation-filter (before ansify-compilation-output activate)
(with-current-buffer (process-buffer (ad-get-arg 0))
(if font-lock-mode (font-lock-mode -1))
(ad-set-arg 1 (ansi-color-apply (ad-get-arg 1)))))

Now the compilation process's output starts out and stays colorful.

Sunday, February 15, 2009

Getting colorful testrun output in Emacs

When I run my RSpec tests from the command line, the output is colorful, but from within Emacs, it's all just plain white text. Something must be done!

The first and most-obvious problem: spec (the testrunner) needs the -c flag before it produces colorful output. Easy fix! Update my test-running function to throw a -c in there, and presto! Beautiful output:

Compilation started at Sun Feb 15 16:35:33

spec spec/thingy.rb -c

Great. spec emitted a bunch of ANSI color escape sequences (yay!), and Emacs failed to interpret them (boo!).

Googling revealed that compilation-mode is derived from comint-mode, so I should just set up comint-mode to have color output. It's well-documented and easy to do:

(setq ansi-color-for-comint-mode t)
(add-hook 'comint-output-filter-functions 'ansi-color-process-output)

This let me run a shell from within Emacs and have ls --color show color in it. Unfortunately, this did absolutely nothing for my test output, which remained full of raw escape sequences.

The problem is in compile.el. It calls comint-exec, which sets the process's output filter to comint-output-filter, which in turn exposes a variety of useful hooks, including the aforementioned comint-output-filter-functions. Unfortunately, after calling comint-exec, compile sets the output filter to compilation-filter, which only exposes one hook, and that one gets called *after* the string's been inserted, with no arguments. It's not very useful.

Fortunately, Emacs's advice system makes it easy to modify the arguments that compilation-filter gets, so it's easy to turn those escape sequences into colors.

(defadvice compilation-filter (before ansify-compilation-output activate)
(ad-set-arg 1 (ansi-color-apply (ad-get-arg 1))))

Now I run my tests, and I get... no escape sequences or colors? Huh?

It turns out that you can't insert text with color properties (or something) into a buffer with font-lock-mode on. Since I have that on everywhere, I just need to turn off font-lock-mode in the compilation buffer. Fortunately, a process filter's first argument is the process.

(defadvice compilation-filter (before ansify-compilation-output activate)
(with-current-buffer (process-buffer (ad-get-arg 0))
(font-lock-mode -1)
(ad-set-arg 1 (ansi-color-apply (ad-get-arg 1)))))

Now I've got colorful test output in Emacs.

(Why not add something to compilation-mode-hook? It's because compilation-mode unconditionally turns on font-lock-mode after running compilation-mode-hook.)