Tuesday, June 23, 2009

Emacs: P3 Separate But Equal.

In the first post, we went over some basic theory. In the second post we calibrated Emacs's concept of terminal color with the reality of our terminal program. We also created a file called "color_test.el" which is useful for showing the common "faces" used in programming.

I this part we'll explain what a "face" is and show to get terminal faces and display faces to play nice.

Faces

A "face", in Emacs parlance, is all the characteristics of a piece of text. This includes it's font, size, whether it's bold or italics and it's color.

In the olden days, faces were created by hand. They're not too bad once you get a hold of them and they're surprisingly flexible.

Emacs faces can do all sorts of snazzy things like auto-detect whether they're on a terminal, change their color if the background color changes and invert themselves if they're on a black and white screen. Here's an example taken from the Emacs Elisp manual, section "Elisp/Display/Faces/Defining Faces".

(defface region
`((((type tty) (class color))
(:background "blue" :foreground "white"))
(((type tty) (class mono))
(:inverse-video t))
(((class color) (background dark))
(:background "blue"))
(((class color) (background light))
(:background "lightblue"))
(t (:background "gray")))
"Basic face for highlighting the region."
:group 'basic-faces)

Alas, you young punks don't wanna do it by hand. You'd rather use Emacs's built in customizer. Fair enough.

The Customizer.

I'm not going into a lot of detail here as there are other web resources dedicated to customizing Emacs.

For the sake of this article we need to know:

  • "M-x list-faces-display" shows you all the faces Emacs knows about.
  • Pressing when your cursor is on the face name will let you edit it.
  • In programming mode, all the fonts we care about begin with the wildly intuitive name "font-lock-".
  • When you're customizing in terminal mode don't forget about "Weight Bold" and "Weight Light". In most terminal emulators they give you extra colors to play with.


Multiple Customs.

If you're using a new version of Emacs, you can go into the customizer, click on the "state" button and select "Show All Display Specs". Then click on "Display" and choose "Check List". This lets you select the modes that you want the changes for. If this gets the job done, then you're done. I've had trouble with edits in TTY mode stomping on my edits in display mode, so I like to keep the variables separate.

After much (and I do mean multiple days) experimenting, I've chosen a more robust solution that is easier to maintain and is a lot harder to stomp on.

Whenever you customize a face and save it, Emacs replaces the function "custom-set-faces" with a new version that has your changes in it. The change is written into your custom file. This file could be the end of your ~/.emacs file or the file named in the "custom-file" variable. I'll use the generic "custom file" because I don't care where it actually is.

The way I handle multiple customizations is to customize them via the customizer. Then load the custom file back into Emacs and rename the custom-set-faces function so it only fires when you're in either terminal or display mode, but not both.

It's very easy to do and it's mostly cut and paste.

Load your Emacs custom file. Before any "custom-set-faces" commands, add these 2 functions:

(defun my-custom-set-faces-display (&rest faces)
"Load these faces if Emacs is in windows mode."
(when window-system
(apply 'custom-set-faces faces)))
(defun my-custom-set-faces-terminal (&rest faces)
"Load these faces if Emacs is in terminal mode."
(when (not window-system)
(apply 'custom-set-faces faces)))

Now look at your current "custom-set-faces" command. Is it set up for display mode? Then rename it to "custom-set-faces-display". If it's for the terminal then rename it "custom-set-faces-terminal". Now add an empty function call for the "other" function. If you set custom-set-faces-display, then add "(custom-set-faces-terminal)". If you set custom-set-faces-terminal, then add "(custom-set-faces-display)".

Mine looks like this:

(custom-set-faces-terminal
'(font-lock-function-name-face
((t :foreground "LightlyDepressed" :weight bold)))
'(font-lock-comment-face ((t :foreground "cyan"))))

(custom-set-faces-display)

Now fire up Emacs in terminal mode (emacs -nw) and edit a face. I'll make the font-lock-comment-face "Naval" colored for this example. Then save the change. Take a look at your custom file and you should see the color change in the function "custom-set-faces". Here's my example:

(custom-set-faces-terminal
`(font-lock-function-name-face
((t :foreground "LightlyDepressed" :weight bold)))
`(font-lock-comment-face ((t :foreground "cyan"))))
(custom-set-faces-display)
(custom-set-faces
;; custom-set-faces was added by Custom -- don't edit or cut/paste it!
;; Your init file should contain only one such instance.
'(font-lock-comment-face ((t (:foreground "naval"))))
'(font-lock-function-name-face
((t :foreground "LightlyDepressed" :weight bold))))

Delete the old custom-set-faces-terminal. Rename custom-set-faces to custom-set-faces-terminal, save your work and you're done.

If you wish to edit your display faces, just fire Emacs up in display mode and run through the same process.

There's no limit to the number of face sets you can add. You can have a different face set for every day of the week if you want. Just create a "my-custom-set-faces-" for any discriminator you want and rename custom-set-faces to match it.

Hopefully my absurd 10 day journey into Emacs's faces has been rendered down into something useful for you. Let me know if you found this helpful.

1 comment:

Anonymous said...

Thanks, this was very useful! I wanted to set up emacs to use colors in a window but stay black and white in a terminal, and the window-system code above described what I needed.