summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/easymenu.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/easymenu.el')
-rw-r--r--lisp/emacs-lisp/easymenu.el662
1 files changed, 662 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/easymenu.el b/lisp/emacs-lisp/easymenu.el
new file mode 100644
index 00000000000..41e3a197af4
--- /dev/null
+++ b/lisp/emacs-lisp/easymenu.el
@@ -0,0 +1,662 @@
+;;; easymenu.el --- support the easymenu interface for defining a menu -*- lexical-binding:t -*-
+
+;; Copyright (C) 1994, 1996, 1998-2022 Free Software Foundation, Inc.
+
+;; Keywords: emulations
+;; Author: Richard Stallman <rms@gnu.org>
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The `easy-menu-define' macro provides a convenient way to define
+;; pop-up menus and/or menu bar menus.
+;;
+;; This is compatible with easymenu.el by Per Abrahamsen
+;; but it is much simpler as it doesn't try to support other Emacs versions.
+;; The code was mostly derived from lmenu.el.
+
+;;; Code:
+
+(defsubst easy-menu-intern (s)
+ (if (stringp s) (intern s) s))
+
+(defmacro easy-menu-define (symbol maps doc menu)
+ "Define a pop-up menu and/or menu bar menu specified by MENU.
+If SYMBOL is non-nil, define SYMBOL as a function to pop up the
+submenu defined by MENU, with DOC as its doc string.
+
+MAPS, if non-nil, should be a keymap or a list of keymaps; add
+the submenu defined by MENU to the keymap or each of the keymaps,
+as a top-level menu bar item.
+
+The first element of MENU must be a string. It is the menu bar
+item name. It may be followed by the following keyword argument
+pairs:
+
+ :filter FUNCTION
+ FUNCTION must be a function which, if called with one
+ argument---the list of the other menu items---returns the
+ items to actually display.
+
+ :visible INCLUDE
+ INCLUDE is an expression. The menu is visible if the
+ expression evaluates to a non-nil value. `:included' is an
+ alias for `:visible'.
+
+ :active ENABLE
+ ENABLE is an expression. The menu is enabled for selection
+ if the expression evaluates to a non-nil value. `:enable' is
+ an alias for `:active'.
+
+ :label FORM
+ FORM is an expression that is dynamically evaluated and whose
+ value serves as the menu's label (the default is the first
+ element of MENU).
+
+ :help HELP
+ HELP is a string, the help to display for the menu.
+ In a GUI this is a \"tooltip\" on the menu button. (Though
+ in Lucid :help is not shown for the top-level menu bar, only
+ for sub-menus.)
+
+The rest of the elements in MENU are menu items.
+A menu item can be a vector of three elements:
+
+ [NAME CALLBACK ENABLE]
+
+NAME is a string--the menu item name.
+
+CALLBACK is a command to run when the item is chosen, or an
+expression to evaluate when the item is chosen.
+
+ENABLE is an expression; the item is enabled for selection if the
+expression evaluates to a non-nil value.
+
+Alternatively, a menu item may have the form:
+
+ [ NAME CALLBACK [ KEYWORD ARG ]... ]
+
+where NAME and CALLBACK have the same meanings as above, and each
+optional KEYWORD and ARG pair should be one of the following:
+
+ :keys KEYS
+ KEYS is a string; a keyboard equivalent to the menu item.
+ This is normally not needed because keyboard equivalents are
+ usually computed automatically. KEYS is expanded with
+ `substitute-command-keys' before it is used.
+
+ :key-sequence KEYS
+ KEYS is a hint for speeding up Emacs's first display of the
+ menu. It should be nil if you know that the menu item has no
+ keyboard equivalent; otherwise it should be a string or
+ vector specifying a keyboard equivalent for the menu item.
+
+ :active ENABLE
+ ENABLE is an expression; the item is enabled for selection
+ whenever this expression's value is non-nil. `:enable' is an
+ alias for `:active'.
+
+ :visible INCLUDE
+ INCLUDE is an expression; this item is only visible if this
+ expression has a non-nil value. `:included' is an alias for
+ `:visible'.
+
+ :label FORM
+ FORM is an expression that is dynamically evaluated and whose
+ value serves as the menu item's label (the default is NAME).
+
+ :suffix FORM
+ FORM is an expression that is dynamically evaluated and whose
+ value is concatenated with the menu entry's label.
+
+ :style STYLE
+ STYLE is a symbol describing the type of menu item; it should
+ be `toggle' (a checkbox), or `radio' (a radio button), or any
+ other value (meaning an ordinary menu item).
+
+ :selected SELECTED
+ SELECTED is an expression; the checkbox or radio button is
+ selected whenever the expression's value is non-nil.
+
+ :help HELP
+ HELP is a string, the help to display for the menu item.
+
+Alternatively, a menu item can be a string. Then that string
+appears in the menu as unselectable text. A string consisting
+solely of dashes is displayed as a menu separator.
+
+Alternatively, a menu item can be a list with the same format as
+MENU. This is a submenu."
+ (declare (indent defun) (debug (symbolp body)) (doc-string 3))
+ `(progn
+ ,(if symbol `(defvar ,symbol nil ,doc))
+ (easy-menu-do-define (quote ,symbol) ,maps ,doc ,menu)))
+
+(defun easy-menu-binding (menu &optional item-name)
+ "Return a binding suitable to pass to `define-key'.
+This is expected to be bound to a mouse event."
+ ;; Under Emacs this is almost trivial, whereas under XEmacs this may
+ ;; involve defining a function that calls popup-menu.
+ (let ((props (if (symbolp menu)
+ (prog1 (get menu 'menu-prop)
+ (setq menu (symbol-function menu))))))
+ (cons 'menu-item
+ (cons (if (eq :label (car props))
+ (prog1 (cadr props)
+ (setq props (cddr props)))
+ (or item-name
+ (if (keymapp menu)
+ (keymap-prompt menu))
+ ""))
+ (cons menu props)))))
+
+(defun easy-menu-do-define (symbol maps doc menu)
+ ;; We can't do anything that might differ between Emacs dialects in
+ ;; `easy-menu-define' in order to make byte compiled files
+ ;; compatible. Therefore everything interesting is done in this
+ ;; function.
+ (let ((keymap (easy-menu-create-menu (car menu) (cdr menu))))
+ (when symbol
+ (set symbol keymap)
+ (defalias symbol
+ (lambda (event) (:documentation doc) (interactive "@e")
+ (x-popup-menu event
+ (or (and (symbolp keymap)
+ (funcall
+ (or (plist-get (get keymap 'menu-prop)
+ :filter)
+ #'identity)
+ (symbol-function keymap)))
+ keymap))))
+ ;; These symbols are commands, but not interesting for users
+ ;; to `M-x TAB'.
+ (function-put symbol 'completion-predicate #'ignore))
+ (dolist (map (if (keymapp maps) (list maps) maps))
+ (define-key map
+ (vector 'menu-bar (if (symbolp (car menu))
+ (car menu)
+ ;; If a string, then use the downcased
+ ;; version for greater backwards compatibility.
+ (intern (downcase (car menu)))))
+ (easy-menu-binding keymap (car menu))))))
+
+(defun easy-menu-filter-return (menu &optional name)
+ "Convert MENU to the right thing to return from a menu filter.
+MENU is a menu as computed by `easy-menu-define' or `easy-menu-create-menu' or
+a symbol whose value is such a menu.
+In Emacs a menu filter must return a menu (a keymap), in XEmacs a filter must
+return a menu items list (without menu name and keywords).
+This function returns the right thing in the two cases.
+If NAME is provided, it is used for the keymap."
+ (cond
+ ((and (not (keymapp menu)) (consp menu))
+ ;; If it's a cons but not a keymap, then it can't be right
+ ;; unless it's an XEmacs menu.
+ (setq menu (easy-menu-create-menu (or name "") menu)))
+ ((vectorp menu)
+ ;; It's just a menu entry.
+ (setq menu (cdr (easy-menu-convert-item menu)))))
+ menu)
+
+(defvar easy-menu-avoid-duplicate-keys t
+ "Dynamically scoped var to register already used keys in a menu.
+If it holds a list, this is expected to be a list of keys already seen in the
+menu we're processing. Else it means we're not processing a menu.")
+
+(defun easy-menu-create-menu (menu-name menu-items)
+ "Create a menu called MENU-NAME with items described in MENU-ITEMS.
+MENU-NAME is a string, the name of the menu. MENU-ITEMS is a list of items
+possibly preceded by keyword pairs as described in `easy-menu-define'."
+ (let ((menu (make-sparse-keymap menu-name))
+ (easy-menu-avoid-duplicate-keys nil)
+ prop keyword label enable filter visible help)
+ ;; Look for keywords.
+ (while (and menu-items
+ (cdr menu-items)
+ (keywordp (setq keyword (car menu-items))))
+ (let ((arg (cadr menu-items)))
+ (setq menu-items (cddr menu-items))
+ (pcase keyword
+ (:filter
+ (setq filter (lambda (menu)
+ (easy-menu-filter-return (funcall arg menu)
+ menu-name))))
+ ((or :enable :active) (setq enable (or arg ''nil)))
+ (:label (setq label arg))
+ (:help (setq help arg))
+ ((or :included :visible) (setq visible (or arg ''nil))))))
+ (if (equal visible ''nil)
+ nil ; Invisible menu entry, return nil.
+ (if (and visible (not (easy-menu-always-true-p visible)))
+ (setq prop (cons :visible (cons visible prop))))
+ (if (and enable (not (easy-menu-always-true-p enable)))
+ (setq prop (cons :enable (cons enable prop))))
+ (if filter (setq prop (cons :filter (cons filter prop))))
+ (if help (setq prop (cons :help (cons help prop))))
+ (if label (setq prop (cons :label (cons label prop))))
+ (setq menu (if filter
+ ;; The filter expects the menu in its XEmacs form and the
+ ;; pre-filter form will only be passed to the filter
+ ;; anyway, so we'd better not convert it at all (it will
+ ;; be converted on the fly by easy-menu-filter-return).
+ menu-items
+ (append menu (mapcar #'easy-menu-convert-item menu-items))))
+ (when prop
+ (setq menu (easy-menu-make-symbol menu 'noexp))
+ (put menu 'menu-prop prop))
+ menu)))
+
+
+;; Known button types.
+(defvar easy-menu-button-prefix
+ '((radio . :radio) (toggle . :toggle)))
+
+(defvar easy-menu-converted-items-table (make-hash-table :test 'equal))
+
+(defun easy-menu-convert-item (item)
+ "Memoize the value returned by `easy-menu-convert-item-1' called on ITEM.
+This makes key-shortcut-caching work a *lot* better when this
+conversion is done from within a filter.
+This also helps when the NAME of the entry is recreated each time:
+since the menu is built and traversed separately, the lookup
+would always fail because the key is `equal' but not `eq'."
+ (let* ((cache (gethash item easy-menu-converted-items-table))
+ (result (or cache (easy-menu-convert-item-1 item)))
+ (key (car-safe result)))
+ (when (and (listp easy-menu-avoid-duplicate-keys) (symbolp key))
+ ;; Merging multiple entries with the same name is sometimes what we
+ ;; want, but not when the entries are actually different (e.g. same
+ ;; name but different :suffix as seen in cal-menu.el) and appear in
+ ;; the same menu. So we try to detect and resolve conflicts.
+ (while (memq key easy-menu-avoid-duplicate-keys)
+ ;; We need to use some distinct object, ideally a symbol, ideally
+ ;; related to the `name'. Uninterned symbols do not work (they
+ ;; are apparently turned into strings and re-interned later on).
+ (setq key (intern (format "%s-%d" (symbol-name key)
+ (length easy-menu-avoid-duplicate-keys))))
+ (setq result (cons key (cdr result))))
+ (push key easy-menu-avoid-duplicate-keys))
+
+ (unless cache (puthash item result easy-menu-converted-items-table))
+ result))
+
+(defun easy-menu-convert-item-1 (item)
+ "Parse an item description and convert it to a menu keymap element.
+ITEM defines an item as in `easy-menu-define'."
+ (let (name command label prop remove)
+ (cond
+ ((stringp item) ; An item or separator.
+ (setq label item))
+ ((consp item) ; A sub-menu
+ (setq label (setq name (car item)))
+ (setq command (cdr item))
+ (if (not (keymapp command))
+ (setq command (easy-menu-create-menu name command)))
+ (if (null command)
+ ;; Invisible menu item. Don't insert into keymap.
+ (setq remove t)
+ (when (and (symbolp command) (setq prop (get command 'menu-prop)))
+ (when (eq :label (car prop))
+ (setq label (cadr prop))
+ (setq prop (cddr prop)))
+ (setq command (symbol-function command)))))
+ ((vectorp item) ; An item.
+ (let* ((ilen (length item))
+ (active (if (> ilen 2) (or (aref item 2) ''nil) t))
+ (no-name (not (symbolp (setq command (aref item 1)))))
+ cache cache-specified)
+ (setq label (setq name (aref item 0)))
+ (if no-name (setq command (easy-menu-make-symbol command)))
+ (if (keywordp active)
+ (let ((count 2)
+ keyword arg suffix visible style selected keys)
+ (setq active nil)
+ (while (> ilen count)
+ (setq keyword (aref item count))
+ (setq arg (aref item (1+ count)))
+ (setq count (+ 2 count))
+ (pcase keyword
+ ((or :included :visible) (setq visible (or arg ''nil)))
+ (:key-sequence (setq cache arg cache-specified t))
+ (:keys (setq keys arg no-name nil))
+ (:label (setq label arg))
+ ((or :active :enable) (setq active (or arg ''nil)))
+ (:help (setq prop (cons :help (cons arg prop))))
+ (:suffix (setq suffix arg))
+ (:style (setq style arg))
+ (:selected (setq selected (or arg ''nil)))))
+ (if suffix
+ (setq label
+ (if (stringp suffix)
+ (if (stringp label) (concat label " " suffix)
+ `(concat ,label ,(concat " " suffix)))
+ (if (stringp label)
+ `(concat ,(concat label " ") ,suffix)
+ `(concat ,label " " ,suffix)))))
+ (cond
+ ((eq style 'button)
+ (setq label (if (stringp label) (concat "[" label "]")
+ `(concat "[" ,label "]"))))
+ ((and selected
+ (setq style (assq style easy-menu-button-prefix)))
+ (setq prop (cons :button
+ (cons (cons (cdr style) selected) prop)))))
+ (when (stringp keys)
+ (if (string-match "^[^\\]*\\(\\\\\\[\\([^]]+\\)]\\)[^\\]*$"
+ keys)
+ (let ((prefix
+ (if (< (match-beginning 0) (match-beginning 1))
+ (substring keys 0 (match-beginning 1))))
+ (postfix
+ (if (< (match-end 1) (match-end 0))
+ (substring keys (match-end 1))))
+ (cmd (intern (match-string 2 keys))))
+ (setq keys (and (or prefix postfix)
+ (cons prefix postfix)))
+ (setq keys
+ (and (or keys (not (eq command cmd)))
+ (cons cmd keys))))
+ (setq cache-specified nil))
+ (if keys (setq prop (cons :keys (cons keys prop)))))
+ (if (and visible (not (easy-menu-always-true-p visible)))
+ (if (equal visible ''nil)
+ ;; Invisible menu item. Don't insert into keymap.
+ (setq remove t)
+ (setq prop (cons :visible (cons visible prop)))))))
+ (if (and active (not (easy-menu-always-true-p active)))
+ (setq prop (cons :enable (cons active prop))))
+ (if (and (or no-name cache-specified)
+ (or (null cache) (stringp cache) (vectorp cache)))
+ (setq prop (cons :key-sequence (cons cache prop))))))
+ (t (error "Invalid menu item in easymenu")))
+ ;; `intern' the name so as to merge multiple entries with the same name.
+ ;; It also makes it easier/possible to lookup/change menu bindings
+ ;; via keymap functions.
+ (let ((key (easy-menu-intern name)))
+ (cons key
+ (and (not remove)
+ (cons 'menu-item
+ (cons label
+ (and name
+ (cons command prop)))))))))
+
+(defun easy-menu-define-key (menu key item &optional before)
+ "Add binding in MENU for KEY => ITEM. Similar to `define-key-after'.
+If KEY is not nil then delete any duplications.
+If ITEM is nil, then delete the definition of KEY.
+
+Optional argument BEFORE is nil or a key in MENU. If BEFORE is not nil,
+put binding before the item in MENU named BEFORE; otherwise,
+if a binding for KEY is already present in MENU, just change it;
+otherwise put the new binding last in MENU.
+BEFORE can be either a string (menu item name) or a symbol
+\(the fake function key for the menu item).
+KEY does not have to be a symbol, and comparison is done with equal."
+ (if (symbolp menu) (setq menu (indirect-function menu)))
+ (let ((inserted (null item)) ; Fake already inserted.
+ tail done)
+ (while (not done)
+ (cond
+ ((or (setq done (or (null (cdr menu)) (keymapp (cdr menu))))
+ (and before (easy-menu-name-match before (cadr menu))))
+ ;; If key is nil, stop here, otherwise keep going past the
+ ;; inserted element so we can delete any duplications that come
+ ;; later.
+ (if (null key) (setq done t))
+ (unless inserted ; Don't insert more than once.
+ (setcdr menu (cons (cons key item) (cdr menu)))
+ (setq inserted t)
+ (setq menu (cdr menu)))
+ (setq menu (cdr menu)))
+ ((and key (equal (car-safe (cadr menu)) key))
+ (if (or inserted ; Already inserted or
+ (and before ; wanted elsewhere and
+ (setq tail (cddr menu)) ; not last item and not
+ (not (keymapp tail))
+ (not (easy-menu-name-match
+ before (car tail))))) ; in position
+ (setcdr menu (cddr menu)) ; Remove item.
+ (setcdr (cadr menu) item) ; Change item.
+ (setq inserted t)
+ (setq menu (cdr menu))))
+ (t (setq menu (cdr menu)))))))
+
+(defun easy-menu-name-match (name item)
+ "Return t if NAME is the name of menu item ITEM.
+NAME can be either a string, or a symbol.
+ITEM should be a keymap binding of the form (KEY . MENU-ITEM)."
+ (if (consp item)
+ (if (symbolp name)
+ (eq (car-safe item) name)
+ (if (stringp name)
+ ;; Match against the text that is displayed to the user.
+ (or (condition-case nil (member-ignore-case name item)
+ (error nil)) ;`item' might not be a proper list.
+ ;; Also check the string version of the symbol name,
+ ;; for backwards compatibility.
+ (eq (car-safe item) (intern name)))))))
+
+(defun easy-menu-always-true-p (x)
+ "Return true if form X never evaluates to nil."
+ (if (consp x) (and (eq (car x) 'quote) (cadr x))
+ (or (eq x t) (not (symbolp x)))))
+
+(defvar easy-menu-item-count 0)
+
+(defun easy-menu-make-symbol (callback &optional noexp)
+ "Return a unique symbol with CALLBACK as function value.
+When non-nil, NOEXP indicates that CALLBACK cannot be an expression
+\(i.e. does not need to be turned into a function)."
+ (let ((command
+ (make-symbol (format "menu-function-%d" easy-menu-item-count))))
+ (setq easy-menu-item-count (1+ easy-menu-item-count))
+ (fset command
+ (if (or (keymapp callback) (commandp callback)
+ ;; `functionp' is probably not needed.
+ (functionp callback) noexp)
+ callback
+ (eval `(lambda () (interactive) ,callback) t)))
+ command))
+
+(defun easy-menu-change (path name items &optional before map)
+ "Change menu found at PATH as item NAME to contain ITEMS.
+PATH is a list of strings for locating the menu that
+should contain a submenu named NAME.
+ITEMS is a list of menu items, as in `easy-menu-define'.
+These items entirely replace the previous items in that submenu.
+
+If MAP is specified, it should normally be a keymap; nil stands for the local
+menu-bar keymap. It can also be a symbol, which has earlier been used as the
+first argument in a call to `easy-menu-define', or the value of such a symbol.
+
+If the menu located by PATH has no submenu named NAME, add one.
+If the optional argument BEFORE is present, add it just before
+the submenu named BEFORE, otherwise add it at the end of the menu.
+
+To implement dynamic menus, either call this from
+`menu-bar-update-hook' or use a menu filter."
+ (easy-menu-add-item map path (easy-menu-create-menu name items) before))
+
+(defalias 'easy-menu-remove #'ignore)
+(make-obsolete 'easy-menu-remove "this was always a no-op in Emacs \
+and can be safely removed." "28.1")
+
+(defalias 'easy-menu-add #'ignore)
+(make-obsolete 'easy-menu-add "this was always a no-op in Emacs \
+and can be safely removed." "28.1")
+
+(defun add-submenu (menu-path submenu &optional before in-menu)
+ "Add submenu SUBMENU in the menu at MENU-PATH.
+If BEFORE is non-nil, add before the item named BEFORE.
+If IN-MENU is non-nil, follow MENU-PATH in IN-MENU.
+This is a compatibility function; use `easy-menu-add-item'."
+ (declare (obsolete easy-menu-add-item "28.1"))
+ (easy-menu-add-item (or in-menu (current-global-map))
+ (cons "menu-bar" menu-path)
+ submenu before))
+
+(defun easy-menu-add-item (map path item &optional before)
+ "To the submenu of MAP with path PATH, add ITEM.
+
+If an item with the same name is already present in this submenu,
+then ITEM replaces it. Otherwise, ITEM is added to this submenu.
+In the latter case, ITEM is normally added at the end of the submenu.
+However, if BEFORE is a string and there is an item in the submenu
+with that name, then ITEM is added before that item.
+
+MAP should normally be a keymap; nil stands for the local menu-bar keymap.
+It can also be a symbol, which has earlier been used as the first
+argument in a call to `easy-menu-define', or the value of such a symbol.
+
+PATH is a list of strings for locating the submenu where ITEM is to be
+added. If PATH is nil, MAP itself is used. Otherwise, the first
+element should be the name of a submenu directly under MAP. This
+submenu is then traversed recursively with the remaining elements of PATH.
+
+ITEM is either defined as in `easy-menu-define' or a non-nil value returned
+by `easy-menu-item-present-p' or `easy-menu-remove-item' or a menu defined
+earlier by `easy-menu-define' or `easy-menu-create-menu'."
+ (setq map (easy-menu-get-map map path
+ (and (null map) (null path)
+ (stringp (car-safe item))
+ (car item))))
+ (if (and (consp item) (consp (cdr item)) (eq (cadr item) 'menu-item))
+ ;; This is a value returned by `easy-menu-item-present-p' or
+ ;; `easy-menu-remove-item'.
+ (easy-menu-define-key map (easy-menu-intern (car item))
+ (cdr item) before)
+ (if (or (keymapp item)
+ (and (symbolp item) (keymapp (symbol-value item))
+ (setq item (symbol-value item))))
+ ;; Item is a keymap, find the prompt string and use as item name.
+ (setq item (cons (keymap-prompt item) item)))
+ (setq item (easy-menu-convert-item item))
+ (easy-menu-define-key map (easy-menu-intern (car item)) (cdr item) before)))
+
+(defun easy-menu-item-present-p (map path name)
+ "In submenu of MAP with path PATH, return non-nil if item NAME is present.
+MAP and PATH are defined as in `easy-menu-add-item'.
+NAME should be a string, the name of the element to be looked for."
+ (easy-menu-return-item (easy-menu-get-map map path) name))
+
+(defun easy-menu-remove-item (map path name)
+ "From submenu of MAP with path PATH remove item NAME.
+MAP and PATH are defined as in `easy-menu-add-item'.
+NAME should be a string, the name of the element to be removed."
+ (setq map (easy-menu-get-map map path))
+ (let ((ret (easy-menu-return-item map name)))
+ (if ret (easy-menu-define-key map (easy-menu-intern name) nil))
+ ret))
+
+(defun easy-menu-return-item (menu name)
+ "In menu MENU try to look for menu item with name NAME.
+If a menu item is found, return (NAME . item), otherwise return nil.
+If item is an old format item, a new format item is returned."
+ ;; The call to `lookup-key' also calls the C function `get_keyelt' which
+ ;; looks inside a menu-item to only return the actual command. This is
+ ;; not what we want here. We should either add an arg to lookup-key to be
+ ;; able to turn off this "feature", or else we could use map-keymap here.
+ ;; In the mean time, I just use `assq' which is an OK approximation since
+ ;; menus are rarely built from vectors or char-tables.
+ (let ((item (or (cdr (assq name menu))
+ (lookup-key menu (vector (easy-menu-intern name)))))
+ ret enable cache label)
+ (cond
+ ((stringp (car-safe item))
+ ;; This is the old menu format. Convert it to new format.
+ (setq label (car item))
+ (when (stringp (car (setq item (cdr item)))) ; Got help string
+ (setq ret (list :help (car item)))
+ (setq item (cdr item)))
+ (when (and (consp item) (consp (car item))
+ (or (null (caar item)) (numberp (caar item))))
+ (setq cache (car item)) ; Got cache
+ (setq item (cdr item)))
+ (and (symbolp item) (setq enable (get item 'menu-enable)) ; Got enable
+ (setq ret (cons :enable (cons enable ret))))
+ (if cache (setq ret (cons cache ret)))
+ (cons name (cons 'menu-enable (cons label (cons item ret)))))
+ (item ; (or (symbolp item) (keymapp item) (eq (car-safe item) 'menu-item))
+ (cons name item)) ; Keymap or new menu format
+ )))
+
+(defun easy-menu-lookup-name (map name)
+ "Lookup menu item NAME in keymap MAP.
+Like `lookup-key' except that NAME is not an array but just a single key
+and that NAME can be a string representing the menu item's name."
+ (or (lookup-key map (vector (easy-menu-intern name)))
+ (when (stringp name)
+ ;; `lookup-key' failed and we have a menu item name: look at the
+ ;; actual menu entries's names.
+ (catch 'found
+ (map-keymap (lambda (key item)
+ (if (condition-case nil (member name item)
+ (error nil))
+ ;; Found it!! Look for it again with
+ ;; `lookup-key' so as to handle inheritance and
+ ;; to extract the actual command/keymap bound to
+ ;; `name' from the item (via get_keyelt).
+ (throw 'found (lookup-key map (vector key)))))
+ map)))))
+
+(defun easy-menu-get-map (map path &optional to-modify)
+ "Return a sparse keymap in which to add or remove an item.
+MAP and PATH are as defined in `easy-menu-add-item'.
+
+TO-MODIFY, if non-nil, is the name of the item the caller
+wants to modify in the map that we return.
+In some cases we use that to select between the local and global maps."
+ (setq map
+ (catch 'found
+ (if (and map (symbolp map) (not (keymapp map)))
+ (setq map (symbol-value map)))
+ (let ((maps (if map (if (keymapp map) (list map) map)
+ (current-active-maps))))
+ ;; Look for PATH in each map.
+ (unless map (push 'menu-bar path))
+ (dolist (name path)
+ (setq maps
+ (delq nil (mapcar (lambda (map)
+ (setq map (easy-menu-lookup-name
+ map name))
+ (and (keymapp map) map))
+ maps))))
+
+ ;; Prefer a map that already contains the to-be-modified entry.
+ (when to-modify
+ (dolist (map maps)
+ (when (easy-menu-lookup-name map to-modify)
+ (throw 'found map))))
+ ;; Use the first valid map.
+ (when maps (throw 'found (car maps)))
+
+ ;; Otherwise, make one up.
+ ;; Hardcoding current-local-map is lame, but it's difficult
+ ;; to know what the caller intended for us to do ;-(
+ (let* ((name (if path (format "%s" (car (reverse path)))))
+ (newmap (make-sparse-keymap name)))
+ (define-key (or map (current-local-map))
+ (apply #'vector (mapcar #'easy-menu-intern path))
+ (if name (cons name newmap) newmap))
+ newmap))))
+ (or (keymapp map) (error "Malformed menu in easy-menu: (%s)" map))
+ map)
+
+(provide 'easymenu)
+
+;;; easymenu.el ends here