How to Define Keymap and Menu in Your Emacs Major Mode

, ,

This page shows you how to define a keyboard shortcut set, and graphical menu, for people who want to write a emacs major mode. You should know the basics of writing a major mode. If not, see: How to Write a Emacs Major Mode for Syntax Coloring.

Problem Description

You want to add a keyboard shortcut set (aka keybinding), and a menu, for a major mode you are writing.

Your key set will become active, and menu become visible, only when user switches to your major mode.

Solution

The following shows a working template for defining a major mode with its own custom keys and menu.

;; define a var for your keymap, so that you can set it as local map
;; (meaning, active only when your mode is on)
(defvar xlsl-mode-map nil "Keymap for xlsl-mode")

Both keybindings and graphical menu, are defined in emacs using a keymap. A keymap is basically a elisp datatype. It is a list with a particular structure, and can contain keymaps in its elements. (info "(elisp) Format of Keymaps")

In the above code, we declare a variable “xlsl-mode-map” using defvar. This variable will hold the keymap.

;; definition for your keybinding and menu
(when (not xlsl-mode-map) ; if it is not already defined

  ;; assign command to keys
  (setq xlsl-mode-map (make-sparse-keymap))
  (define-key xlsl-mode-map (kbd "C-c C-c") 'xlsl-copy-all)
  (define-key xlsl-mode-map (kbd "C-c C-l") 'xlsl-syntax-check)
  (define-key xlsl-mode-map (kbd "C-c C-r") 'xlsl-lookup-lsl-ref)
  (define-key xlsl-mode-map (kbd "C-c C-g") 'xlsl-convert-rgb)
  ;; … more here …

  (define-key xlsl-mode-map [remap comment-dwim] 'xlsl-comment-dwim)
   ; above: make your comment command “xlsl-comment-dwim” use the current key for “comment-dwim” (because user may have changed the key for “comment-dwim”)

  ;; define your menu
  (define-key xlsl-mode-map [menu-bar] (make-sparse-keymap))

  (let ((menuMap (make-sparse-keymap "LSL")))
    (define-key xlsl-mode-map [menu-bar xlsl] (cons "LSL" menuMap))

    (define-key menuMap [about]
      '("About xlsl-mode" . xlsl-about))
    (define-key menuMap [customize]
      '("Customize xlsl-mode" . xlsl-customize))
    (define-key menuMap [separator]
      '("--"))
    (define-key menuMap [convert-rgb]
      '("Convert #rrggbb under cursor" . xlsl-convert-rgb))
    (define-key menuMap [copy-all]
      '("Copy whole buffer content" . xlsl-copy-all))
    (define-key menuMap [syntax-check]
      '("Check syntax" . xlsl-syntax-check))
    (define-key menuMap [lookup-onlne-doc]
      '("Lookup ref of word under cursor" . xlsl-lookup-lsl-ref))))

In the above, we create a keymap datatype using make-sparse-keymap, and set it to the variable. This is done by first checking if the var is defined, because otherwise loading your package twice might screw up the keymap (when the map is complex involving inherited keymap etc.). Even if you can't imagine how your package may be loaded twice, but someone probably will. It is a good practice to make sure your package works fine even if loaded multiple times.

In the above code, the definition are grouped in 2 parts. The first group of lines define the keys, and the second group define menu.

The lines that define keys are easy to understand. No need explanation here. However, you should know about the convention of key choices for major modes. Basically, try to stick to 【Ctrl+c Ctrl+‹letter›】 space, and your letter should not be any of {h, g, ?}. This is to make sure that your keys are compatible with the rest of emacs. (info "(elisp) Key Binding Conventions")

For the menu part, it is a bit more complex to explain. Just make sure your own code follows the structure exactly.

Basically, we create another keymap for the menu and attach it to the “xlsl-mode-map” keymap, as a branch. This is done by the line

(define-key xlsl-mode-map [menu-bar] (make-sparse-keymap))

Now, any branch of “[menu-bar]” will become a menu item.

Then, we define a keymap for the menu panel named “LSL”, and attach it to the “[menu-bar]” keymap. This is done in the line:

(define-key xlsl-mode-map [menu-bar xlsl] (cons "LSL" menuMap))

Now, anything attached to “[menu-bar xlsl]” will be a menu item in that panel.

After the above, things are easy to understand. We simply add each menu item to that menu panel. For example, a line typically look like this: (define-key menuMap [about] '("About xlsl-mode" . xlsl-about)).

If you want a bit more detailed explanation on how emacs's keymap and menu system works, see: How to Add Menu in Emacs.

(defun xlsl-mode ()
  "Major mode for editing LSL (Linden Scripting Language).

…"
  (interactive)
  (kill-all-local-variables)

  (setq major-mode 'xlsl-mode)
  (setq mode-name "LSL") ; for display purposes in mode line
  (use-local-map xlsl-mode-map)

  ;; … other code here

  (run-hooks 'xlsl-mode-hook))

In the above, we define the major mode function itself. The interesting line here is the (use-local-map xlsl-mode-map). This makes sure that your keybindings and menu will be active only when user is in your mode.

;; put your mode symbol into the list “features”, so that user can
;; call (require 'xlsl-mode), which will load your code only when
;; needed
(provide 'xlsl-mode)

Finally, we call (provide 'xlsl-mode) to put the symbol on the “features” variable that holds a list of symbols. For detail of exactly what {provide, require, feature} do, see: Emacs Lisp's Library System: What's require, load, load-file, autoload, feature?.

Thanks to Robbie Morrison for correcting a extraneously placed apostrophe in the menu code example.

blog comments powered by Disqus