2011-08-25

customizing the mode-line

The mode-line is the emacs 'status bar', the bar just above the minibuffer that shows various pieces of information, such as the buffer name, the major mode, maybe the current line number, some indicators for active minor modes, and so on. As I'm looking at it, it starts with 1<U:**- (which is: input-method: latin-1-alt-postfix, buffer-coding-system: utf8-unix, line-ending: unix-style, buffer is writable and buffer is modified – the tooltips help).
As with just about anything in emacs, the mode-line can be customized just the way you like. I give some example below, not because I think it is necessarily the best way, but just to give you a bit of an example to start with when making your own best-mode-line-ever.
I'm not going through all the details of the example, but let me highlight a few things that make it a bit easier to understand.
First of all, the mode-line can be customized by setting the variable mode-line-format; this variable becomes buffer-local automatically when changed, so if you want to set it for all buffers, you'll need to use setq-default in your .emacs (or equivalent). The format is quite similar to the one for frame-title-format, which we discussed in setting the frame title a while back.
mode-line-format is a list of items which are evaluated, and put together as a string which then ends up as the mode-line contents. These properties can be any string. The following types of items can be used:
  • First, normal strings are just shown as-is;
  • Then, there are some special format parameters which will be replaced with their value in the mode-line, from the Emacs-documentation:
  %b -- print buffer name.      %f -- print visited file name.
  %F -- print frame name.
  %* -- print %, * or hyphen.   %+ -- print *, % or hyphen.
        %& is like %*, but ignore read-only-ness.
        % means buffer is read-only and * means it is modified.
        For a modified read-only buffer, %* gives % and %+ gives *.
  %s -- print process status.   %l -- print the current line number.
  %c -- print the current column number (this makes editing slower).
        To make the column number update correctly in all cases,
        `column-number-mode' must be non-nil.
  %i -- print the size of the buffer.
  %I -- like %i, but use k, M, G, etc., to abbreviate.
  %p -- print percent of buffer above top of window, or Top, Bot or All.
  %P -- print percent of buffer above bottom of window, perhaps plus Top,
        or print Bottom or All.
  %n -- print Narrow if appropriate.
  %t -- visited file is text or binary (if OS supports this distinction).
  %z -- print mnemonics of keyboard, terminal, and buffer coding systems.
  %Z -- like %z, but including the end-of-line format.
  %e -- print error message about full memory.
  %@ -- print @ or hyphen.  @ means that default-directory is on a
        remote machine.
  %[ -- print one [ for each recursive editing level.  %] similar.
  %% -- print %.   %- -- print infinitely many dashes.
Decimal digits after the % specify field width to which to pad.
  • Forms of the type (:eval ...) are evaluated each time the mode-line is drawn (just like the '%'-parameters) ; so, if you have a value that changes of the course your emacs session, you should use (:eval ...). For example, for your emacs-uptime you could use (:eval (emacs-uptime "%hh")); while the emacs-PID does not change, so simply you could simply use (format "PID:%d").
    The format parameter mentioned above are of evaluated each time as well. Note that you have to be a bit careful with evaluations - don't do too heavy operations there, and be careful the updates don't recurse.
  • There are many others which I won't go into now - please check the Elisp reference. It's a rather baroque format…
Now, let's put this all together in an example (tested with emacs 23 and 24). As I said, this is for demonstration purposes only; but hopefully it gives you some inspiration. A lot of the 'magic' (colors, tooltips, faces) happens with the propertize function; again, the Elisp documentation can tell you a lot more about that. I'm (ab)using the various font-lock-faces to have colors that blend in nicely with your current theme.
And it has a limitation still, namely that it does not react to mouse clicks; how to that, I will discuss in some future article.



;; use setq-default to set it for /all/ modes
(setq mode-line-format
  (list
    ;; the buffer name; the file name as a tool tip
    '(:eval (propertize "%b " 'face 'font-lock-keyword-face
        'help-echo (buffer-file-name)))

    ;; line and column
    "(" ;; '%02' to set to 2 chars at least; prevents flickering
      (propertize "%02l" 'face 'font-lock-type-face) ","
      (propertize "%02c" 'face 'font-lock-type-face) 
    ") "

    ;; relative position, size of file
    "["
    (propertize "%p" 'face 'font-lock-constant-face) ;; % above top
    "/"
    (propertize "%I" 'face 'font-lock-constant-face) ;; size
    "] "

    ;; the current major mode for the buffer.
    "["

    '(:eval (propertize "%m" 'face 'font-lock-string-face
              'help-echo buffer-file-coding-system))
    "] "


    "[" ;; insert vs overwrite mode, input-method in a tooltip
    '(:eval (propertize (if overwrite-mode "Ovr" "Ins")
              'face 'font-lock-preprocessor-face
              'help-echo (concat "Buffer is in "
                           (if overwrite-mode "overwrite" "insert") " mode")))

    ;; was this buffer modified since the last save?
    '(:eval (when (buffer-modified-p)
              (concat ","  (propertize "Mod"
                             'face 'font-lock-warning-face
                             'help-echo "Buffer has been modified"))))

    ;; is this buffer read-only?
    '(:eval (when buffer-read-only
              (concat ","  (propertize "RO"
                             'face 'font-lock-type-face
                             'help-echo "Buffer is read-only"))))  
    "] "

    ;; add the time, with the date and the emacs uptime in the tooltip
    '(:eval (propertize (format-time-string "%H:%M")
              'help-echo
              (concat (format-time-string "%c; ")
                      (emacs-uptime "Uptime:%hh"))))
    " --"
    ;; i don't want to see minor-modes; but if you want, uncomment this:
    ;; minor-mode-alist  ;; list of minor modes
    "%-" ;; fill with '-'
    ))
Have fun playing with this!

20 comments:

Tavis Rudd (openid only - no blog) said...

djcb, I found your previous tip about diminish.el very handy for keeping the modeline manageable (http://emacs-fu.blogspot.com/2010/05/cleaning-up-mode-line.html)

@tavisrudd

djcb said...

@Tavis Rudd: you can do things also in mode-line-format, by filtering the results from `minor-mode-alist'.

Duncan MacGregor said...

I think the way you've defined your mode-line is excellent for readability in the case of this tutorial, and makes for something much more understandable than the traditional list of #( forms and (var var) pairs for choosing a mode-line, but for actual use I'd strongly recommend defining each part separately and then using those to define your final mode line.

So my mode line definition for your example would be roughly

(setq mode-line-format
(list
my-mode-line-buffer-name
my-mode-line-position
my-mode-line-relative-position
my-mode-line-major-mode
"["
my-mode-line-insert-indicator
my-mode-line-modified-indicator
"] "
my-mode-line-read-only-indicator
my-mode-line-time
" --"
;; i don't want to see minor-modes; but if you want, uncomment this:
;; minor-mode-alist ;; list of minor modes
my-mode-line-padding ))

With individual parts defined like so

(setq my-mode-line-position
;; line and column
(list
"(" ;; '%02' to set to 2 chars at least; prevents flickering
(propertize "%02l" 'face 'font-lock-type-face) ","
(propertize "%02c" 'face 'font-lock-type-face)
") " ))

I find this makes life much easier both for the initial fiddling (do I really want those things in that order?) and when six months later I realise I actually want to add some more indicators or remove others.

Greg said...

As always, an excellent set of ideas and very well described.

Someday I hope you set your sights lower. I'd like to know about customizing the messages buffer at the very bottom. I find that it does not get my attention as much as I'd like it to. I'd like to fix up the type face, font size, and even font color if possible.

Duncan MacGregor said...

@Greg. Those are actually pretty easy to change as they're controlled by faces just like pretty much everything else. There are a few different faces to customise (echo-area, minibuffer, the various completion faces, etc.) but that's all pretty straight forward.

I think there is room for a good article on how to use a dedicated frame for the mini-buffer, and if anybody could find a way to not only auto-raise it but place it close to the point then that would be even better.

djcb said...

@Duncan MacGregor: actually, I find it easier to play with when it's a self-contained blob that I can evaluate at once.

In, say, C, I usually have many little functions, but in elisp it seems it's more convenient to combine things.

Anyway, I guess this is all a matter of taste.

Anonymous said...

It looks nice, but one thing I miss: the major (and minor) mode (mouseclick) Menu.

Any suggestions how to fix it?

Greg said...

Aha!

I did this:

(custom-set-faces

;;; stuff

'(minibuffer-prompt ((default (:foreground "red")) (nil (:foreground "red"))))
)

and got a red prompt. Just what I needed!

Anonymous said...

Thanks for this post, but I am missing notifications like the twittering or erc ones. Any idea how to add this parts?

djcb said...

@Anonymous: to see the minor modes, there's a line to uncomment in the last lines of the example.

adben said...

Great stuff thank you, btw what is the theme that you are using?

djcb said...

@Adben: zenburn

Unknown said...

Uncommenting the minor mode part shows the modes (though it doesn't seem to always show the full list that the regular non customized version does) and it doesn't show "notifications" from things like ERC that the previous commenter asked about.

It's things like activity from channels Etc. Any idea how to fix that?

Anonymous said...

@Unknown
I think you must add: %s -- print process status.

cb said...

To show status messages from ErcTrack use:

'(:eval global-mode-string)

jpc said...

for anything that gets piped into the global-mode-string which includes erc tracking, emacs jabber and display-time/battery just add %M to your custom modeline

Anonymous said...

As @cb and @jpc suggested, global-mode-string has to be added; otherwise, things like the output of org-timer doesn't show on the mode-line. But does anyone know how to change the color of global-mode-string?

Anonymous said...

Answer to anonymous:
The Elisp info-page "Mode Line format" states:

2. Put a text property on a mode line %-construct such as `%12b'; then
the expansion of the %-construct will have that same text property.

So what you should do is putting a text property on the added "%M". How to do that is also in the elisp manual (just C-s for it)

Anonymous said...

How do we add evil modes to this
modeline ?
Like insert, normal, motion, emacs,
visual etc.

lawlist said...

Thank you for the format-time-string example -- I am now going to use: `(:eval (propertize (format-time-string "%A, %B %d, %Y -- %1I:%M %p")))` -- greatly appreciated.