EmacsEasyMenuAndMenubarOrder

Created on November 12, 2023 at 11:38 am

These days DATE , Emacs ORG has a menu bar, theoretically in both graphical and text modes (I turn it off in terminals). One CARDINAL of the things that you (I) can want to do as part of customizing things like MH-E PRODUCT 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 PRODUCT is with Easy Menu’s ORG 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 CARDINAL 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 CARDINAL ways around this; the bad way of Emacs ORG 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 ORG ; 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 ORDINAL let’s do the two CARDINAL 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 ORDINAL element of its menu argument, and menu-items are the elements of the menu, everything except the first ORDINAL 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 ORDINAL entry" (message " First ORDINAL ")] [" Second ORDINAL entry" (message " Second WORK_OF_ART ")])))

(This is not proper Emacs ORG Lisp indentation.)

This will put your new ‘Example’ menu at the end of the mode specific menus in MH-E’s ORG 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 ORG 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 PRODUCT 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 CARDINAL 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 ORDINAL case and peels the two CARDINAL 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.)

Connecting to blog.lzomedia.com... Connected... Page load complete