Emacs: How to Add a Menu

By Xah Lee. Date:

This page shows you how to modify or add your own menus to emacs.

Problem

You want to add a menu to emacs.

Most people use keyboard shortcuts to execute commands, or call command name directly by typing Alt+x first. However, sometimes it is convenient to have them on the graphical menu. For example, you might have created many personal commands over the years and you don't remember what are their names or shortcuts. A menu can collect them, and serve as a overview or reminder.

emacs language mode menu
A example of user added menu for language modes.

Solution

Adding Your Own Menu

Here's a example of how to add your own menu. Suppose you want a menu named “MyMenu” that comes after the “Tools” menu. Suppose your menu will have 2 menu items: “Next Line” and “Previous Line”, each should call the emacs command next-line and previous-line. Here's the elisp code to do it:

;; Creating a new menu pane in the menu bar to the right of “Tools” menu
(define-key-after
  global-map
  [menu-bar mymenu]
  (cons "MyMenu" (make-sparse-keymap "hoot hoot"))
  'tools )

;; Creating a menu item, under the menu by the id “[menu-bar mymenu]”
(define-key
  global-map
  [menu-bar mymenu nl]
  '("Next Line" . next-line))

;; creating another menu item
(define-key
  global-map
  [menu-bar mymenu pl]
  '("Previous Line" . previous-line))

;; code to remove the whole menu panel
;; (global-unset-key [menu-bar mymenu])

In above, you see these lines:

 [menu-bar mymenu]
 [menu-bar mymenu nl]
 [menu-bar mymenu pl]

These can be thought of as the ID for each menu item. Thus, in elisp code, the [menu-bar mymenu] uniquely identifies the menu panel menu [MyMenu]. The [menu-bar mymenu nl] uniquely identifies the menu [MyMenu ▸ Next Line], and same for other menu panel or items. (emacs call these IDs “fake keys”.)

You can find out the ID of a menu by Alt+x describe-key. For example, if you Alt+x describe-key then pull the menu [File ▸ Open File…], then you'll see this in the first line: <menu-bar> <file> <open-file>. This means, the menu's ID in elisp code would be [menu-bar file open-file].

In our sample code, the strings {mymenu, nl, pl} are chosen arbitrarily. They should be similar or identical to the menu item's display string, because it shows up when user calls describe-function. You could use [menu-bar a b], but then describe-function will say “<menu-bar> <a> <b> runs the command next-line …” but user would be confused where the “a b” came from.

Also keep in mind that the menu IDs form a tree. So, if you create a menu item whose parent does not exist, that item won't show up. Here's a illustration:

Menu IDs forms a tree.

 [menu-bar]            ← id of top menu
 [menu-bar mymenu]     ← id of a item under “[menu-bar]”
 [menu-bar mymenu nl]  ← id of a item under “[menu-bar mymenu]”
 [menu-bar a b]        ← won't show because “[menu-bar a]” doesn't exist

How Keybinding Works

Emacs' menu system is based on its keybinding system. So, you must understand a bit about emacs keybinding system before understanding how menu works. To define a key, you do:

(define-key destinationKeymap keySyntax commandName)

The destinationKeymap is a keymap you want to assign the key to. The keySyntax is the keystroke you want. The reason you need a destination keymap above is that each mode has its own keymap. A keymap is a set of keybindings.

In a simplified explanation, a “keymap” is a list, each item in keymap is a pair (keystroke . command). When a user presses a key combination, emacs search thru the current active keymaps (with certain predefined order) and find a element that matches the keystroke, then invoke the associated command.

However, the command part can also be another keymap. For example, if you have a element (myKeyStroke . myMapX), that means when user presses myKeyStroke, emacs waits for another keystroke and will search that key's definition in the keymap myMapX. This process repeats until a actual command is found (or if the last keymap doesn't contain the last keystroke, in that case, emacs reports the key is undefined).

The keySyntax specifies a keystroke (or mouse action). (For example, [f1], [(meta b)], (kbd "M-b"). (there are a lot syntax variations. [see Emacs Key Syntax Explained])

In summary, emacs uses keymaps to find the command for a keystroke. Keymap definition can contain other keymaps. Thus, a keymap is a tree. (they don't all have a single root. So, all keymaps are a set of trees) Keymaps containing keymaps, are used for keyboard shortcuts that are a sequence of keystrokes, such as the keystroke sequence Ctrl+x Ctrl+c.

Also, when you call define-key and your keystroke is a sequence such as Ctrl+c Ctrl+a, and if the keymap for Ctrl+c doesn't exist, it is automatically created and added to the keymap. For example, if you define:

(define-key global-map [(meta f1) (a)] 'forward-word)
;; make the keystrok sequence Alt+F1 followed by a, for forward-word.

Emacs automatically creates a keymap with a single element ("a" . 'forward-word) in it, and this keymap is added as a element to the keymap stored in the var “global-map”. The element would look something like ([meta f1] . metaf1map), where metaf1map is a keymap with a entry like ("a" . 'forward-word).

So, when user presses Alt+F1, emacs looks in the keymap stored in “global-map” (if minor mode's keymaps didn't result anything), then emacs will find a element whose keystroke def matches M-<f1>, but emacs find that its command is another keymap, then emacs waits for user. When user now presses a, emacs find that element, then calls forward-word.

Summary: Emacs has many keymaps, typically one for each minor mode, major mode, and a “global-map”, and is searched in that order. The top level keymaps are assigned to variables. A keymap may contain elements that's again a keymap. When define-key is called, emacs adds a entry to a keymap. If the keystroke is a sequence of keys, emacs will create a new keymap and add it to a keymap's branch if that branch doesn't already exist. Emacs will create multiple level of non-existent parent if necessary. (For example, (define-key global-map [(meta f1) (meta a) (meta b) (c)] 'backward-word))

Keymaps (ELISP Manual)

How Menu Works

Emacs's menu system is based on its keybinding system. So, for example, here are 2 lines for comparison:

;; define a shortcut
(define-key global-map [(meta f1) (a)] 'next-line)

;; define a menu
(define-key global-map [menu-bar mymenu nl] '("Next Line" . next-line))

The first defines a keyboard shortcut, the other adds a menu item.

The second argument to define-key is a key syntax. Notice the key syntax for the menu item is [mebu-bar mymenu nl]. This is called a “fake key”. It serves the purpose of ID for the menu item. Almost all things said about keymaps applies to the menu; however, parent menu must exist otherwise your menu item won't show up. For example, if you define a menu with id [menu-bar mymenu nl], and if the menu [menu-bar mymenu] doesn't exist, it won't show.

Example Of Language Mode Menu

Here's a sample code. It adds a language mode menu under the File menu.

(define-key-after global-map [menu-bar file lang-modes]
  (cons "Language Modes" (make-sparse-keymap "major modes")) 'kill-buffer )

(define-key global-map [menu-bar file lang-modes bash] '("Bash" . sh-mode))
(define-key global-map [menu-bar file lang-modes tcl] '("TCL" . tcl-mode))
(define-key global-map [menu-bar file lang-modes ruby] '("Ruby" . ruby-mode))
(define-key global-map [menu-bar file lang-modes python] '("Python" . python-mode))
(define-key global-map [menu-bar file lang-modes php] '("PHP" . php-mode))
(define-key global-map [menu-bar file lang-modes perl] '("Perl" . cperl-mode))
(define-key global-map [menu-bar file lang-modes separator1] '("--"))
(define-key global-map [menu-bar file lang-modes haskell] '("Haskell" . haskell-mode))
(define-key global-map [menu-bar file lang-modes ocaml] '("OCaml" . tuareg-mode))
(define-key global-map [menu-bar file lang-modes elisp] '("Emacs Lisp" . emacs-lisp-mode))
(define-key global-map [menu-bar file lang-modes separator2] '("--"))
(define-key global-map [menu-bar file lang-modes latex] '("LaTeX" . latex-mode))
(define-key global-map [menu-bar file lang-modes js] '("Javascript" . js2-mode))
(define-key global-map [menu-bar file lang-modes xml] '("XML (xml-mode)" . xml-mode))
(define-key global-map [menu-bar file lang-modes nxml] '("XML (nxml-mode)" . nxml-mode))
(define-key global-map [menu-bar file lang-modes html] '("HTML" . html-mode))
(define-key global-map [menu-bar file lang-modes htmlhelper] '("HTML (html-helper-mode)" . html-helper-mode))
(define-key global-map [menu-bar file lang-modes css] '("CSS" . css-mode))
(define-key global-map [menu-bar file lang-modes separator3] '("--"))
(define-key global-map [menu-bar file lang-modes java] '("Java" . java-mode))
(define-key global-map [menu-bar file lang-modes c++] '("C++" . c++-mode))
(define-key global-map [menu-bar file lang-modes c] '("C" . c-mode))