Xah Lee, 2009-02-20, 2010-07-17
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.
You want to add a keyboard shortcut set, and a menu, for a major mode you are writing.
Your shortcut set will become active, and menu become visible, only when user switches to your major mode.
The following shows a working template for defining a major mode with its own custom shortcuts 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 keyboard shortcut set 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 keyboard shortcuts and menu (when (not xlsl-mode-map) ; if it is not already defined ;; assign your keyboard shortcuts (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 use whatever shortcut is used ; for comment-dwim (user may have changed it) ;; 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 keyboard shortcuts, and the second group define menu.
The lines that define shortcuts are easy to understand. No need explanation here. However, you should know about the convention of key choices for keybinding. Basically, try to stick to 【Ctrl+c Ctrl+‹letter›】 space, and your letter should not be “h”. This is to make sure that your shortcuts 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 shortcuts and menu wil 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.
The “provide” function does just one thing. It puts your symbol to the “features” list if it is not already there. When a symbol is in the “features” list, that means that symbol's definition has been loaded.
The “provide” and “features”, are part of the emacs package/load
system. The purpose is to let emacs know which packages are already
loaded, so that emacs don't have to load them again. When you need a
package, you can call (require 'xlsl-mode), and emacs will check if
the symbol “xlsl-mode” is in “features”. Only if not, then it'll load
it.
For detail, see: Emacs Lisp's Library System.
2010-07-17 Thanks to Robbie Morrison for correcting a extraneously placed apostrophe in the menu code example.