Changing the menu bar order of Emacs easy-menu menus
These days, Emacs has a menu bar,
theoretically in both graphical and text modes (I turn it off
in terminals). One of the things that you (I) can want to do as
part of customizing things like MH-E is
to add additional menu-bar menus with your own convenient entries.
The easiest and most obvious way to define a new menu-bar menu in
Emacs Lisp is with Easy Menu's
easy-menu-define
. Easy-menu-define is both easy to use and powerful,
offering support for things like dynamic filtering and dynamic
enabling of entire menus. However, for my purposes it has one
limitation (I shouldn't call it a flaw), namely that easy-menu-define
adds new menus to the front of the menu bar (either a mode specific
menu bar or worse the global part of the menu bar). The cheerful
advice is to define your easy menus in reverse order, but you can't
really do this if you're extending an existing mode.
There are two ways around this; the bad way of Emacs crimes and the proper
way, which has worked for me so far. Both ways start with the fact
that menus are actually Emacs keymaps,
especially including menus in the menu bar, which is itself tied
up in keymaps; you add a menu to the menu bar by adding it to either
the global keymap or the current major mode keymap under a special
format of key names. The reason that easy-menu-define adds your
menu to the front of the menu bar is that it winds up using
define-key
,
and define-key
adds the new key binding to the front of the keymap.
If we want our new menu to be anywhere else in the menu bar, we
need to get the easy-menu system to use define-key-after
instead (either with or without an explicit thing to put our new
menu after).
If we ignore defining a function that you can use to make a pop-up
menu,
what easy-menu-define does (more or less) is it creates a menu with
easy-menu-create-menu, creates binding(s) from this menu with
easy-menu-binding, and then sets the binding(s) into the keymaps
you requested with define-key (making up a special 'key' name for
the binding
of the special form '[menu-bar <something>]
', which the menu bar
system will use to find all of the menu-bar menu entries). We can
do this ourselves. First let's do the two easy-menu steps:
(defun cks/easy-menu-setup (menu-name menu-items) (easy-menu-binding (easy-menu-create-menu menu-name menu-items) menu-name))
Here, menu-name
is the user-friendly name of your menu, which
with easy-menu-define you'd put as the first element of its menu
argument, and menu-items
are the elements of the menu, everything
except the first element of easy-menu-define's menu
argument.
This function does everything short of defining the menu-bar 'key'.
(easy-menu-create-menu will sometimes return a keymap and sometimes return something else I don't fully understand, depending on whether you gave the menu any properties. easy-menu-binding handles everything.)
Provided with this function, we can define menus and put them into keymaps to make them appear, like so:
(define-key-after mh-folder-mode-map [menu-bar my-example] (cks/easy-menu-setup "Example" '(["First entry" (message "First")] ["Second entry" (message "Second")])))
(This is not proper Emacs Lisp indentation.)
This will put your new 'Example' menu at the end of the mode specific
menus in MH-E's folder window. If you want, you can save the value
returned from cks/easy-menu-setup in a let
variable and use
define-key-after to set it in multiple modes, for example to also
set it in mh-show-mode-map (there are cautions here in the case of
MH-E that are outside the scope of this entry, and also general
cautions in that I'm not sure that reusing the same easy-menu-binding
in multiple keymaps is correct, although it works for me).
The normal easy-menu-define code will make up the special key name from the title text of your menu, which may not be what you want. Since we're doing this by hand, we can be different. Note that this may affect your ability to use other easy-menu functions to modify the menu later (for example, easy-menu-add-item). I haven't tested this.
The necessary disclaimer is that while this works for me so far, I'm not sure it's either completely correct or the best way to do this. And it would be nice if there were general functions to shuffle the order of menu bar entries.
PS: What definitely doesn't work, although you might innocently
think that it should, is extracting a menu-bar entry's keymap with
'(lookup-key map [menu-bar your-name])
', using define-key to
remove it from the keymap, and adding it back with define-key-after.
This will appear to work for simple easy-menu-define menus, but
won't for menus with things like a :filter; you appear to get the
post-filtered version of the menu and then things obviously go
wrong.
Sidebar: The Emacs crimes way
The Emacs crimes way is to use advice-add to temporarily and conditionally turn define-key into define-key-after, because easy-menu-define calls define-key only once (well, once per map). This allows us to keep using all of the features of easy-define-menu, and looks like this:
(defvar cks/define-key-to-after nil)(defun cks/define-key-to-after (oldfun keymap key def &optional remove) (if (and cks/define-key-to-after (not remove)) (define-key-after keymap key def) (apply oldfun keymap key def remove))) (advice-add 'define-key :around 'cks/define-key-to-after) (let ((cks/define-key-to-after t)) (easy-menu-define ... )) (advice-remove 'define-key 'cks/define-key-to-after)
My view is that this is definitely full bore Emacs crimes, but seasoned Emacs Lisp people may have different views.
A more elaborate version that allows you to optionally specify what to put the binding after (by setting a non-t value for cks/define-key-to-after) is left as an exercise to the reader.
Sidebar: What I think easy-menu-create-menu is returning
Since I started at Lisp code and the output of '(pp ...)
' for
long enough, what I think easy-menu-create-menu is doing is that
it returns either a keymap or a keymap and a set of properties. If
it only has a keymap to return, it returns just the keymap. If it
has two things to return, it returns an uninterned symbol that has
the keymap attached as the 'function' value of the symbol and the
menu properties attached as the 'menu-prop
' property of the symbol.
Easy-menu-binding detects if it's the second case and peels the two
parts apart again, then reassembles them differently into something
that can be passed to define-key to define a menu.
Easy-menu-create-menu uses extended menu item format, including especially for the top level item that represents your entire menu (and which may have, eg, your :filter on it).
(This entire sidebar may not make sense to future me, but at least I tried.)
|
|