This is a read-only archive!

Vim and Emacs modelines

Both Vim and Emacs have "windows", resizeable panes inside the main app "frame". Windows are a very useful feature (aside from the ill-chosen name; should've been "panes" or something). I find windows superior to tabs in my opinion; you can easily view more than one file at a time, view two files side-by-side for comparison, and such. Both Vim and Emacs use the border under the window to show helpful information like the filename and editing mode such; this is called the "status line" or "modeline". Again, very handy.

How do you set a nice custom modeline in Vim? Here's my config from my .vimrc:

set statusline=%f\ %2*%m\ %1*%h%r%=[%{&encoding}\ %{&fileformat}\ %{strlen(&ft)?&ft:'none'}\ %{getfperm(@%)}]\ 0x%B\ %12.(%c:%l/%L%)

Yikes! What a mess. It's just a big string with a bunch of special codes and literal characters all mixed up. If you do :h 'statusline' you can read about all the options. That string is ugly though.

Or so I thought, until I witnessed the trainwreck that is Emacs. The documentation for Emacs' modeline comprises a small encyclopedia. The configuration itself is spread across a couple dozen configuration variables. This is the default value for just one of them:

(#("%[" 0 2
   (help-echo "Recursive edit, type C-M-c to get out"))
 #("(" 0 1
   (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
 (:propertize
  ("" mode-name)
  help-echo "Major mode\nmouse-1: Display major mode menu\nmouse-2: Show help for major mode\nmouse-3: Toggle minor modes" mouse-face mode-line-highlight local-map
  (keymap
   (mode-line keymap
          (down-mouse-3 keymap
                (abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
                     (:toggle . abbrev-mode))
                (auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
                        (:toggle . auto-fill-function))
                (auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
                          (:toggle bound-and-true-p auto-revert-mode))
                (auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
                           (buffer-file-name)
                           :button
                           (:toggle bound-and-true-p auto-revert-tail-mode))
                (flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
                       (:toggle bound-and-true-p flyspell-mode))
                (font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
                        (:toggle . font-lock-mode))
                (glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
                      (:toggle bound-and-true-p glasses-mode))
                (hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
                         (:toggle bound-and-true-p hide-ifdef-mode))
                (highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
                            (:toggle bound-and-true-p highlight-changes-mode))
                (outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
                        (:toggle bound-and-true-p outline-minor-mode))
                (overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
                        (:toggle . overwrite-mode))
                "Minor Modes")
          (mouse-2 . describe-mode)
          (down-mouse-1 menu-item "Menu Bar" ignore :filter
                (lambda
                  (_)
                  (mouse-menu-major-mode-map))))))
 ("" mode-line-process)
 (:propertize
  ("" minor-mode-alist)
  mouse-face mode-line-highlight help-echo "Minor mode\nmouse-1: Display minor mode menu\nmouse-2: Show help for minor mode\nmouse-3: Toggle minor modes" local-map
  (keymap
   (header-line keymap
        (down-mouse-3 keymap
                  (abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
                       (:toggle . abbrev-mode))
                  (auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
                          (:toggle . auto-fill-function))
                  (auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
                        (:toggle bound-and-true-p auto-revert-mode))
                  (auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
                             (buffer-file-name)
                             :button
                             (:toggle bound-and-true-p auto-revert-tail-mode))
                  (flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
                         (:toggle bound-and-true-p flyspell-mode))
                  (font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
                          (:toggle . font-lock-mode))
                  (glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
                        (:toggle bound-and-true-p glasses-mode))
                  (hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
                           (:toggle bound-and-true-p hide-ifdef-mode))
                  (highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
                              (:toggle bound-and-true-p highlight-changes-mode))
                  (outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
                          (:toggle bound-and-true-p outline-minor-mode))
                  (overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
                          (:toggle . overwrite-mode))
                  "Minor Modes"))
   (mode-line keymap
          (down-mouse-3 keymap
                (abbrev-mode menu-item "Abbrev (Abbrev)" abbrev-mode :help "Automatically expand abbreviations" :button
                     (:toggle . abbrev-mode))
                (auto-fill-mode menu-item "Auto fill (Fill)" auto-fill-mode :help "Automatically insert new lines" :button
                        (:toggle . auto-fill-function))
                (auto-revert-mode menu-item "Auto revert (ARev)" auto-revert-mode :help "Revert the buffer when the file on disk changes" :button
                          (:toggle bound-and-true-p auto-revert-mode))
                (auto-revert-tail-mode menu-item "Auto revert tail (Tail)" auto-revert-tail-mode :help "Revert the tail of the buffer when buffer grows" :enable
                           (buffer-file-name)
                           :button
                           (:toggle bound-and-true-p auto-revert-tail-mode))
                (flyspell-mode menu-item "Flyspell (Fly)" flyspell-mode :help "Spell checking on the fly" :button
                       (:toggle bound-and-true-p flyspell-mode))
                (font-lock-mode menu-item "Font Lock" font-lock-mode :help "Syntax coloring" :button
                        (:toggle . font-lock-mode))
                (glasses-mode menu-item "Glasses (o^o)" glasses-mode :help "Insert virtual separators to make long identifiers easy to read" :button
                      (:toggle bound-and-true-p glasses-mode))
                (hide-ifdef-mode menu-item "Hide ifdef (Ifdef)" hide-ifdef-mode :help "Show/Hide code within #ifdef constructs" :button
                         (:toggle bound-and-true-p hide-ifdef-mode))
                (highlight-changes-mode menu-item "Highlight changes (Chg)" highlight-changes-mode :help "Show changes in the buffer in a distinctive color" :button
                            (:toggle bound-and-true-p highlight-changes-mode))
                (outline-minor-mode menu-item "Outline (Outl)" outline-minor-mode :help "" :button
                        (:toggle bound-and-true-p outline-minor-mode))
                (overwrite-mode menu-item "Overwrite (Ovwrt)" overwrite-mode :help "Overwrite mode: typed characters replace existing text" :button
                        (:toggle . overwrite-mode))
                "Minor Modes")
          (mouse-2 . mode-line-minor-mode-help)
          (down-mouse-1 . mouse-minor-mode-menu))))
 #("%n" 0 2
   (local-map
    (keymap
     (mode-line keymap
        (mouse-2 . mode-line-widen)))
    mouse-face mode-line-highlight help-echo "mouse-2: Remove narrowing from the current buffer"))
 #(")" 0 1
   (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display"))
 #("%]" 0 2
   (help-echo "Recursive edit, type C-M-c to get out"))
 #("--" 0 2
   (help-echo "mouse-1: Select (drag to resize)\nmouse-2: Make current window occupy the whole frame\nmouse-3: Remove current window from display")))

What the hell?

Instead of a string, it's an s-expression, which is better, right? Well no, it's still just a big construct with arbitrary meanings assigned to its contents. Lists mean one thing, strings mean another thing (and those strings, like Vim's, can contain special escape sequences). Symbols mean something else, symbols that are keywords mean something else, numbers mean something else, and so on.

To decipher this I had to learn this mini-language. And also learn about "text properties" and a bunch of elisp stuff. It also required knowledge about a bunch of minor modes and how they tie into the modeline, all of which is essentially a bowl of spaghetti code. And keymaps, and maps to control mouse click events and such. Eventually I figured out that most of that crap is controlling tooltip text.

If you do M-x customize-apropos in Emacs and search for "mode-line", you'll get a helpful list of all of the configuration values and their values. (The default values contain literal tab characters, which you can't even type into the customize text fields without C-qing, because tab jumps you between fields. Ughhhhhhh.)

I gave up even trying to get Emacs to have all the helpful information my Vim modeline has. Even deleting the default crap to pare this down to something readable took some effort. This what I ended up with:

(mode-line-format (quote ("%e--[" mode-line-buffer-identification "]" (vc-mode vc-mode) "  " mode-line-modes global-mode-string " %-")))
(mode-line-in-non-selected-windows t)
(mode-line-modes (quote ("%[" "(" (:propertize ("" mode-name)) ("" mode-line-process) (:propertize ("" minor-mode-alist)) "%n" ")" "%]")))
(mode-line ((((class color) (min-colors 88)) (:background "#333333" :foreground "#bcbcbc" :box (:line-width -1 :color "#333333")))))
(mode-line-highlight ((((class color) (min-colors 88)) nil)))
(mode-line-inactive ((default (:inherit mode-line)) (((class color) (min-colors 88) (background dark)) (:foreground "#8b8b8b" :weight light))))

What does that mean? Don't ask me, I can no longer read it. If customize hadn't produced a lot of that for me, I probably wouldn't have managed. My favorite part is the four-deep nested list of lists of lists of lists for the colors.

Verdict?

  1. Vim's modeline is less powerful than Emacs. But who cares if you can't even read it to edit it? Most of Emacs' modeline features are annoying. (My motivation for editing this to begin with was to turn off all the mouse buttons and mini-menus and crap.)

    Vim's status line is exactly configurable enough. I don't want to build a small GUI app in my modeline. I want it to show certain bits of information about the buffer, that's it. Vim does this.

  2. Vim script is less elegant than elisp. Or is it? Vim's modeline is a custom DSL for formatting modelines. It's hard to think of anything more concise. Concision is a very good thing. Emacs' version is more general, at the expense of horrid verbosity and unreadability.
  3. Vim's modeline is a string, which means you either write it literally, or you construct it by concatenating lots of other strings. This is a faux pas, right? It's like using eval in Ruby or Perl. It's fragile and error-prone. Emacs uses a Lisp, with its macros and quoted lists and code is data and so on.

    But who cares? In this case, a simple string is powerful enough. I don't need a whole Turing-complete programming language to configure a modeline. It's massive overkill and you pay a price for it. The minute a human being is supposed to be keeping track that the second element in the 5-deep nested list means "x", something has gone horribly wrong.

Vim wins this round.

May 05, 2009 @ 11:22 AM PDT
Cateogory: Programming
Tags: Lisp, Emacs, Vim

8 Comments

Dion Moult
Quoth Dion Moult on May 05, 2009 @ 7:36 PM PDT

Agreed - again another excuse for not bothering to try Emacs in detail ;)

Steve Purcell
Quoth Steve Purcell on May 06, 2009 @ 3:39 AM PDT

Perhaps you should stick with Vim. The extra complexity in this area of Emacs is precisely because its modeline DSL supports "all the mouse buttons and mini-menus and crap" you wanted to turn off.

Most willing Emacs users would simply accept that it's not Vim, and get on with things. They would certainly rarely even consider tweaking the modeline sexp directly when a simple "column-number-mode" or similar will usually add the information they want.

On the other hand, if you're using Emacs because it supports some features you need/want for your work (e.g. Slime+Swank for Clojure), then you've already bought into the same "deep programmability" that, in the modeline case, appears to you to be overkill.

(For the record, I switched from Vim to Emacs about 10 years ago, but still heretically use viper-mode to emulate vi's modal editing style.)

rzezeski
Quoth rzezeski on May 07, 2009 @ 1:00 AM PDT

You make some good points Brian. Especially the point about concision and having just enough to get the job done but no more. However, I imagine it would be trivial for the Emacs maintainers to create a modeline DSL that hides some of the verbosity through some higher-order macros and functions. It is a Lisp after all :). Maybe the reason for it's current state is because people don't customize their editor as you do, and there just hasn't been enough noise to warrant an update.

I will concur though, Vim wins this round.

FWIW, I was a Vimmer for the last 2+ years (no Jedi, but decent) and recently switched back to Emacs for reasons I wont get into here. I'm just glad we have two great choices!

sdkfjkdsajf
Quoth sdkfjkdsajf on May 28, 2009 @ 4:54 PM PDT

Try this vim status line:

set statusline=%F%m%r%h%w\ [FORMAT=%{&ff}]\ [TYPE=%Y]\ [ASCII=\%03.3b]\ [HEX=\%02.2B]\ [POS=%04l,%04v][%p%%]\ [LEN=%L]

Andrew
Quoth Andrew on October 22, 2009 @ 3:03 AM PDT

Most of the .vimrc's of Programmers I Like® define their status line's piecemeal like this:

"statusline setup
set statusline=%f     "tail of the filename
set statusline+=%h    "help file flag
set statusline+=%y    "filetype
set statusline+=%r    "read only flag

and for fancy stuff wrap it in a function and call it:

set statusline+=%{StatuslineTrailingSpaceWarning()}
set statusline+=%{StatuslineLongLineWarning()}

with the above functions provided by plugins or defined elsewhere in the vimrc.

The above was excerpted from Scrooloose's (author of NERDTree) public vim files: http://github.com/scrooloose

Dan Lewis
Quoth Dan Lewis on October 22, 2009 @ 3:32 AM PDT

Who needs to learn to remember how to set modeline options?

As you said, you didn't learn the modeline language for Emacs. You just used customize and it all worked like magic.

Now you are complaining that you know how sausage is made.

Kennu
Quoth Kennu on October 22, 2009 @ 6:40 AM PDT

In vim "modeline" actually means something completely different. It is the magic line in the beginning of a file that specifies autoindent options etc.

Taz
Quoth Taz on April 05, 2012 @ 9:31 AM PDT

In the interest of fair comparison, I'll point out a couple of things.

First of all, emacs does support basic modeline formatting as well, if you don't care about the extra features like menus and tooltips and fancy text styling. Here is the equivalent format setting as yours as a one-liner:

'("%e--[%b]" (vc-mode vc-mode) "  %[(" mode-name mode-line-process minor-mode-alist "%n)%]" global-mode-string " %-")

(And some of those elements are invisible by default and could be removed if you never actually use them.)

Secondly, your example showing the complexity of the default modeline values was basically the equivalent of opening the HTML source of this page to comment on the difficulty of creating it. That output is not meant to be edited directly and the corresponding code that generated it is much shorter.