Hi!

Thank you Ihor for your careful review! I am really impressed by the amount of
work you put into org-mode, and thankful for the time you have given this 
little feature of ours=)
Please find attached version 8.

>
>> The small one: should we drop the description field and always use the
>> first line of the docstring for menu titles? This feels much smoother.
>
>This will be easier. And we can always add description field in the
>future if the need arises. 

Ok, thanks and fixed.

>> The big: should this be about choosing an "action" at point, or a
>> more general framework for presenting menus? If it remains small in
>> scope, should we find an other name than org-menu?
>
>org-menu is already pretty general name.
>Consider menus like org-capture that can be displayed from arbitrary
>buffers - querying Org parser would make no sense if we try to implement
>org-capture menu using your library.
>
>If you want to widen the scope even further, we will need to reach our
>to the emacs-devel, and there will be even more requirements.
>

I meant this the other way around - should we keep the scope limited?
We started out designing this to be a way to chose an action at point,
but now we are considering making it into a general menu system.

I think this is fine, in the way that we remove the forced
org-element-context, but at least in its current form I do not think
this system is a good fit for e.g. org export where we want to toggle async and 
such,
but rather transients are better suited for those dialogue style uses.
What do you think?

>> +(defun org-cite-basic--get-key (citation-or-citation-reference)
>> +  "Return citation key for CITATION-OR-CITATION-KEY."
>> +(defun org-cite-basic--get-url (citation-or-citation-reference)
>> +  "Return URL for CITATION-OR-CITATION-KEY."
>> +(defun org-cite-basic--get-doi (citation-or-citation-reference)
>> +  "Return DOI for CITATION-OR-CITATION-KEY."
>
>*CITATION-OR-CITATION-REFERENCE.
>

Thanks!

>> +(org-menu-define org-cite-basic-follow (citation-object &optional 
>> prefix-argument)
>> +  "Basic citation follower.
>> +
>> +Open citations by applying the function in 
>> +`org-cite-basic-follow-default-action'.  If `org-menu-mode' is active, 
>> display a
>> +menu specified in `org-cite-basic-follow-actions'.  This behaviour can be 
>> inverted
>> +by giving the prefix argument in `org-menu-switch'. See `org-menu-mode' for 
>> more information."
>
>I think that most of this docstring is pretty general. Maybe
>org-menu-define itself can auto-append it to the docstring?
>

Ok, the part staring with "If `org-menu-mode' is active, " is now added
automatically.

>> +    ("w" "Browse URL/DOI"
>> +     (let ((url (org-cite-basic--get-url !citation-object))
>> +           (doi (org-cite-basic--get-doi !citation-object)))
>> +       (cond ((org-string-nw-p url)
>> +              (browse-url url))
>> +             ((org-string-nw-p doi)
>> +              (if (string-match "^http" doi)
>> +                  (browse-url doi)
>> +                (browse-url (format "http://dx.doi.org/%s";; doi))))
>> +             (t (user-error "No URL or DOI for `%s'"
>> +                            (org-cite-basic--get-key !citation-object))))))]
>
>This can probably be a separate new function instead of adding inline lambda.
>

I added this as org-cite-basic--browse.

>> +(defun org-menu--specification-to-menu (description specification)
>> +  "Make a flattened menu keymap out of an org menu specification.
>> +
>> +SPECIFICATION should be of the form of `org-cite-basic-follow-actions'.
>> +
>> +The title of the menu keymap is DESCRIPTION."
>> +  (let ((new-map (make-sparse-keymap description)))
>> +  (named-let populate-menu-keymap
>> +      ;; First, make a flat reversed list of menu items. Items have the 
>> forms:
>> +      ;; ("title" MENU-HEADING) or
>> +      ;; (KEY DESCRIPTION BINDING)
>> +      ((posts (named-let build-list ((menu specification))
>
>This looks like tail recursion. Any chance you can write the same using
>some kind of loop/mapcar? (I admit that I do not 100% understand the logic)
>

We need to achieve two things:
All entries (including titles and separators) in the keymap need unique keys.
Entries must be added in reverse to the keymap.

This was the way that came to me first (probably because I have little 
programming experience, but have done a course in functional programming:-) ) I 
will describe my logic below, in the hope that this version is ok. If it still
seems too obscure, I agree to learn about elisp loops!

We add entries to the keymap in named-let populate-menu-keymap, which has 
arguments POSTS and ROW. ROW keeps track of how many entries there are in the 
menu, so that we can make unique "keys" for titles and separators. 
populate-menu-keymap simply matches on the menu entry type, adds it to the 
keymap and recurses on the rest of POSTS with ROW increased by one.
The initial value of POSTS is a reversed and flattened list of all menu 
entries, formatted for easy pattern matching,
which is build in the named-let build-list. build-list pattern matches on each 
entry in the specification vector:
vectors are flattened into the list (as (build-list first)), strings represent 
titles/separators,
and '(key desc function) lists are menu entries.

>> +(defun org-menu-popup (description specification)
>> +(defun org-menu-tmm-prompt (description specification)
>
>We should probably list these functions in defcustom specification for 
>org-menu-system.
>

I added a mention of these to the docstring.

>> +This function is a valid value for `org-menu-system', which takes two 
>> arguments:
>> +DESCRIPTION is the title for the menu, while SPECIFICATION is an org-menu
>> +specification as per `org-cite-basic-follow-actions'."
>
>> +(defun org-menu-tmm-prompt (description specification)
>> +  "Show an org-menu using a `tmm-prompt'.
>> +
>> +This function is a valid value for `org-menu-system', which takes two 
>> arguments:
>> +DESCRIPTION is the title for the menu, while SPECIFICATION is an org-menu
>> +specification as per `org-cite-basic-follow-actions'."
>
>", which takes two arguments" is redundant. After removing, the
>docstring will read just fine.
>

Thanks!

>> +(defmacro org-menu--defcustom-actions (menu-actions value menu-name)
>> +  "Define MENU-ACTIONS option for MENU-NAME with default VALUE."
>> +  `(defcustom ,menu-actions ,value
>> +     ,(concat "Actions in the `" (symbol-name menu-name) "' org menu.
>
>Since we are defining variables and functions dynamically, we should
>probably take care about helping Emacs finding their definition. See
>13.4.1 Finding Definitions section in Elisp manual.
>

Thanks. org-menu--defcustom-actions and org-menu--defcustom-default-action
now add the definition-name property to the defined variables. I made this point
to the overall menu definition, e.g. org-cite-basic-follow in our example. Is 
this
good, or should it point to the macros in om.el?

>> +(defun org-menu--strip-argument-decorators (arglist)
>> +  "Return a copy of ARGLIST without &optional, &body, &key, &aux, or &rest."
>
>You forgot to update the docstring. &key and &aux are no longer cleaned up.
>

Thanks, fixed.

>> +;;; Main macro definition
>> +(cl-defmacro org-menu-define
>> +    (name arglist docstring description contents default-action &body body)
>
>I am thinking if we should do (name arglist docstring &key contents 
>default-action &body body)
>That will make menu definition more readable, as CONTENTS and
>DEFAULT-ACTION will be clearly marked.
>

I changed this so that we have keyword arguments CONTENTS, DEFAULT_ACTION, and
INTERACTIVE-SPEC (defaulting to '(interactive), and removing the forced
org-element-context. 

>> +       (transient-define-prefix
>> +        ,name ,arglist
>
>I am looking at transient-define-prefix docs and the code and it looks
>like ARGLIST cannot be cl-style. If so, we can simplify
>`org-menu--strip-argument-decorators' even further, dropping '&body test.
>

Ok!

>> +        (let ((bound-arguments
>> +               (list ,@(cl-mapcar
>> +                        (lambda (param)
>> +                          `(list
>> +                            ',(intern (concat "!" (symbol-name param)))
>> +                            `',,param))
>> +                        (org-menu--strip-argument-decorators arglist)))))
>
>This is pretty similar to `org-menu--with-arguments'. Can we somehow
>reuse it?

I think they are different enough that at least trying to make a macro of the 
mapcar part would be more complicated than what we already have. It is very 
possible that I miss an obvious way to improve this,and will have to think 
about it for a while.

Cheers
Tor-björn
From 422e612ddb058f18d2838aba7611ba5863b03482 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tor-bj=C3=B6ron?= <[email protected]>
Date: Thu, 27 Feb 2025 20:30:07 +0200
Subject: [PATCH] lisp/om.el: Org-menu, a simple menu system for org.

* lisp/om.el: Add org-menu.
* lisp/oc-basic.el (require 'om): Pull in om.
(org-cite-basic-follow-actions): New customization option, that
specifies the contents of the transient menu.
(org-cite-basic-follow-default-action): New customization option,
the default action to be taken when following a citation object.
(org-cite-basic--get-key): New function. Get citation key from
citation or citation reference.
(org-cite-basic--get-url): New function. Get URL from citation or
citation reference.
(org-cite-basic--get-doi): New function. Get DOI from citation or
citation reference.
(org-cite-basic--browse): New function. Browse (using browse-url)
the URL or DOI-based URL of a citation or citation reference.
(org-cite-basic-goto): Use org-cite-basic--get-key.
(org-cite-basic-follow): Add a citation follower using org-menu.
(org-cite-register-processor 'basic): Update the basic citation
processor to follow citations using `org-cite-basic-follow'.

This change was co-authored with much support from Ihor Radchenko and
Jonas Bernoulli, thanks!
---
 lisp/oc-basic.el |  75 +++++++++--
 lisp/om.el       | 330 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 396 insertions(+), 9 deletions(-)
 create mode 100644 lisp/om.el

diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index fb6d9477a..f44d2bab3 100644
--- a/lisp/oc-basic.el
+++ b/lisp/oc-basic.el
@@ -74,10 +74,12 @@
 (require 'map)
 (require 'oc)
 (require 'seq)
+(require 'om)
 
 (declare-function org-open-at-point "org" (&optional arg))
 (declare-function org-open-file "org" (path &optional in-emacs line search))
 
+(declare-function org-element-context "org-element" (&optional element))
 (declare-function org-element-create "org-element-ast" (type &optional props &rest children))
 (declare-function org-element-set "org-element-ast" (old new &optional keep-props))
 
@@ -351,6 +353,41 @@ INFO is the export state, as a property list."
                 (map-keys entries))
               (org-cite-basic--parse-bibliography)))
 
+(defun org-cite-basic--get-key (citation-or-citation-reference)
+  "Return citation key for CITATION-OR-CITATION-REFERENCE."
+  (if (org-element-type-p citation-or-citation-reference 'citation-reference)
+      (org-element-property :key citation-or-citation-reference)
+    (pcase (org-cite-get-references citation-or-citation-reference t)
+      (`(,key) key)
+      (keys
+       (or (completing-read "Select citation key: " keys nil t)
+           (user-error "Aborted"))))))
+
+(defun org-cite-basic--get-url (citation-or-citation-reference)
+  "Return URL for CITATION-OR-CITATION-REFERENCE."
+  (org-cite-basic--get-field
+   'url
+   (org-cite-basic--get-key citation-or-citation-reference)))
+
+(defun org-cite-basic--get-doi (citation-or-citation-reference)
+  "Return DOI for CITATION-OR-CITATION-REFERENCE."
+  (org-cite-basic--get-field
+   'doi
+   (org-cite-basic--get-key citation-or-citation-reference)))
+
+(defun org-cite-basic--browse (citation-or-citation-reference)
+  "Browse (using `browse-url') to the URL or DOI of CITATION-OR-CITATION-REFERENCE."
+  (let ((url (org-cite-basic--get-url !citation-object))
+        (doi (org-cite-basic--get-doi !citation-object)))
+    (cond ((org-string-nw-p url)
+           (browse-url url))
+          ((org-string-nw-p doi)
+           (if (string-match "^http" doi)
+               (browse-url doi)
+             (browse-url (format "http://dx.doi.org/%s";; doi))))
+                                 (t (user-error "No URL or DOI for `%s'"
+                                                (org-cite-basic--get-key !citation-object))))))))))
+
 (defun org-cite-basic--get-entry (key &optional info)
   "Return BibTeX entry for KEY, as an association list.
 When non-nil, INFO is the export state, as a property list."
@@ -830,14 +867,7 @@ export state, as a property list."
 When DATUM is a citation reference, open bibliography entry referencing
 the citation key.  Otherwise, select which key to follow among all keys
 present in the citation."
-  (let* ((key
-          (if (org-element-type-p datum 'citation-reference)
-              (org-element-property :key datum)
-            (pcase (org-cite-get-references datum t)
-              (`(,key) key)
-              (keys
-               (or (completing-read "Select citation key: " keys nil t)
-                   (user-error "Aborted"))))))
+  (let* ((key (org-cite-basic--get-key datum))
          (file
           (pcase (seq-find (pcase-lambda (`(,_ . ,entries))
                              (gethash key entries))
@@ -857,6 +887,33 @@ present in the citation."
        (bibtex-set-dialect)
        (bibtex-search-entry key)))))
 
+(org-menu-define org-cite-basic-follow (citation-object &optional prefix-argument)
+  "Follow citation
+
+Open citations by applying the function in
+`org-cite-basic-follow-default-action'. "
+  :contents [["Open"
+              ("b" "Bibliography entry" (org-cite-basic-goto !citation-object !prefix-argument))
+              ("w" "Browse URL/DOI"
+               (org-cite-basic--browse !citation-object))]
+             ["Copy"
+              ("d" "DOI"
+               (let ((doi (org-cite-basic--get-doi !citation-object)))
+                 (if (org-string-nw-p doi)
+                     (kill-new doi)
+                   (user-error "No DOI for `%s'" (org-cite-basic--get-key !citation-object)))))
+              ("u" "URL"
+               (let ((url (org-cite-basic--get-url !citation-object)))
+                 (if (org-string-nw-p url)
+                     (kill-new url)
+                   (user-error "No URL for `%s'" (org-cite-basic--get-key !citation-object)))))]]
+  :default-action (org-cite-basic-goto !citation-object !prefix-argument)
+  :interactive-spec (interactive
+                     (list (let ((obj (org-element-context)))
+                             (pcase (org-element-type obj)
+                               ((or 'citation 'citation-reference) obj)
+                               (_ (user-error "Wrong object type")))))))
+
 
 ;;; "Insert" capability
 (defun org-cite-basic--complete-style (_)
@@ -1006,7 +1063,7 @@ Raise an error when no bibliography is set in the buffer."
   :activate #'org-cite-basic-activate
   :export-citation #'org-cite-basic-export-citation
   :export-bibliography #'org-cite-basic-export-bibliography
-  :follow #'org-cite-basic-goto
+  :follow #'org-cite-basic-follow
   :insert (org-cite-make-insert-processor #'org-cite-basic--complete-key
                                           #'org-cite-basic--complete-style)
   :cite-styles
diff --git a/lisp/om.el b/lisp/om.el
new file mode 100644
index 000000000..0d4f5f68f
--- /dev/null
+++ b/lisp/om.el
@@ -0,0 +1,330 @@
+;;; om.el --- Org Menu library                  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Free Software Foundation, Inc.
+
+;; Author: Tor-björn Claesson <[email protected]>
+
+;; 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:
+
+;; This library provides facilities for displaying menus in org mode.
+
+;;; Code:
+
+(require 'cl-macs)
+(require 'org-macs)
+(require 'transient)
+(require 'which-key)
+
+(org-assert-version)
+
+
+;;; Configuration variables
+(defgroup org-menu nil
+  "Options concerning menus in Org mode."
+  :group 'org
+  :tag "Org Menu")
+
+(defcustom org-menu-switch '-
+  "Prefix argument that inverts the behaviour of `org-menu-mode'."
+  :group 'org-menu
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
+(defcustom org-menu-system 'transient
+  "The menu system to use for displaying Org Menus.
+
+Unless equal to transient, it should be a function with the
+signature (specification), where SPECIFICATION is a menu definition as per
+`org-cite-basic-follow-actions'.
+
+org-menu includes the functions `org-menu-popup' and `org-menu-tmm-prompt',
+which are valid options for `org-menu-system'."
+  :group 'org-menu
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
+
+;;; Minor mode
+(define-minor-mode org-menu-mode
+  "Org menu mode.
+When Org menu mode is enabled, a menu prompting the user for an action
+will be presented upon activating certain objects.
+
+New menus can be defined using `org-menu-define'.
+
+The menu system used can be customized in `org-menu-system'.
+
+When `org-menu-mode' is active, it can be transiently deactivated by
+the prefix argument specified in `org-menu-switch', and vice verse
+transiently activated when inactive."
+  :init-value nil
+  :lighter " OM")
+
+;;; Helper functions
+
+(defmacro org-menu--bind-specification (bindings specification)
+  "Make BINDINGS visible to commands in SPECIFICATION.
+
+BINDINGS is a list of the form ((binding value) ...).
+SPECIFICATION is an org menu as per `org-cite-basic-follow-actions'.
+
+This macro returns SPECIFICATION, with each action wrapped in
+a let exposing BINDINGS."
+  `(cl-map
+    'vector
+    (lambda (group)
+      (cl-map
+       'vector
+       (lambda (spec)
+         (pcase spec
+           (`(,key ,desc (lambda ,args . ,body) . ,other)
+            `(,key ,desc
+                   (lambda ,args
+                     ,(unless (and (listp (car body))
+                                   (equal (caar body)
+                                          'interactive))
+                        '(interactive))
+                     (let ,,bindings
+                       ,@body))
+                   ,@other))
+           (`(,key ,desc (,fn . ,fn-args) . ,other)
+            `(,key ,desc
+                   (lambda ()
+	             (interactive)
+                     (let ,,bindings
+	               (,fn ,@fn-args)))
+                   ,@other))
+           (other other)))
+       group))
+    ,specification))
+
+(cl-defmacro org-menu--with-arguments (arg-list &body body)
+  "Make the arguments in ARG-LIST, prefixed with !, visible to BODY."
+  `(dlet ,(mapcar (lambda (arg)
+                    `(,(intern (concat "!" (symbol-name arg))) ,arg))
+                  arg-list)
+     ,@body))
+
+(defun org-menu--specification-to-menu (description specification)
+  "Make a flattened menu keymap out of an org menu specification.
+
+SPECIFICATION should be of the form of `org-cite-basic-follow-actions'.
+
+The title of the menu keymap is DESCRIPTION."
+  (let ((new-map (make-sparse-keymap description)))
+  (named-let populate-menu-keymap
+      ;; First, make a flat reversed list of menu items. Items have the forms:
+      ;; ("title" MENU-HEADING) or
+      ;; (KEY DESCRIPTION BINDING)
+      ((posts (named-let build-list ((menu specification))
+                (if (equal menu [])
+                    '()
+                  (let ((first (aref menu 0))
+                        (rest (seq-subseq menu 1)))
+                    (append (build-list rest)
+                            (pcase first
+                              ((pred vectorp)
+                               (build-list first))
+                              ((pred stringp)
+                               `(("title" ,first)))
+                              (`(,key ,desc ,function)
+                               `((,key ,desc ,function)))))))))
+       (row 0)) ;; Keep track of row number to give menu heading unique keys
+    (if (null posts)
+        new-map
+      (progn
+        (pcase (car posts)
+          (`("title" ,heading)
+           (define-key new-map `[,(make-symbol
+                                   (concat "header-"
+                                           (number-to-string row)))]
+                       `(menu-item ,heading))
+           (unless (null (cdr posts)) ;; No separator if first heading
+             (define-key new-map `[,(make-symbol
+                                     (concat "[separator-"
+                                             (number-to-string row)
+                                             "]"))]
+                         '(menu-item "--"))))
+          (`(,key ,desc ,function)
+           (define-key new-map key `(menu-item ,desc ,function))))
+        (populate-menu-keymap (cdr posts) (+ row 1)))))))
+
+(defun org-menu-popup (description specification)
+  "Show an org-menu using a `popup-menu'.
+
+This function is a valid value for `org-menu-system':
+DESCRIPTION is the title for the menu, while SPECIFICATION is an org-menu
+specification as per `org-cite-basic-follow-actions'."
+  (let ((menu-keymap (org-menu--specification-to-menu description specification)))
+    (popup-menu menu-keymap)))
+
+(defun org-menu-tmm-prompt (description specification)
+  "Show an org-menu using a `tmm-prompt'.
+
+This function is a valid value for `org-menu-system':
+DESCRIPTION is the title for the menu, while SPECIFICATION is an org-menu
+specification as per `org-cite-basic-follow-actions'."
+  (let ((menu-keymap (org-menu--specification-to-menu description specification)))
+    (tmm-prompt menu-keymap)))
+
+
+(defmacro org-menu--defcustom-actions (menu-actions value menu-name)
+  "Define MENU-ACTIONS option for MENU-NAME with default VALUE."
+  `(progn (defcustom ,menu-actions ,value
+            ,(concat "Actions in the `" (symbol-name menu-name) "' org menu.
+
+This option uses the same syntax as `transient-define-prefix', see
+Info node `(transient)Binding Suffix and Infix Commands'.  In
+addition, it is possible to specify a function call for the COMMAND
+part, where ARGUMENTS can be used to access those values.
+
+For example:
+
+[[\"Open\"
+  (\"b\" \"bibliography entry\"
+   (org-cite-basic-goto !citation-object !prefix-argument))]]
+
+will create an entry labeled \"bibliography entry\", activated with the
+b key, that calls `org-cite-basic-goto' with citation-object and
+prefix-argument as arguments.")
+            :group 'org-menu
+            :type 'sexp)
+          (put ',menu-actions 'definition-name ',menu-name)))
+
+(defmacro org-menu--defcustom-default-action
+    (default-action value menu-name arglist)
+  "Define DEFAULT-ACTION option for MENU-NAME with default VALUE.
+The action will accept ARGLIST arguments."
+  `(progn (defcustom ,default-action ,value
+            ,(concat "Default action for `" (symbol-name menu-name) "'.
+This should be a function accepting the arguments\n"
+                     (prin1-to-string arglist)
+                     ".")
+            :group 'org-menu
+            :type 'sexp)
+          (put ',default-action 'definition-name ',menu-name)))
+
+(defun org-menu--strip-argument-decorators (arglist)
+  "Return a copy of ARGLIST without &optional or &rest."
+  (seq-filter
+    (lambda (elt)
+      (not (or (eq elt '&optional)
+               (eq elt '&rest))))
+    arglist))
+
+;;; Main macro definition
+(cl-defmacro org-menu-define
+    (name arglist docstring &key contents default-action
+          (interactive-spec '(interactive)))
+  "Define an org menu NAME.
+
+A function called NAME will be created to activate the menu, as well as
+variables called NAME-actions and NAME-default-action.
+
+ARGLIST is the argument list of the function NAME.
+
+DOCSTRING is the docstring for NAME.  The first row is used as title for
+keymaps based on this menu.  The rest of the DOCSTRING should describe what
+the default action does.  A short description of `org-menu-mode' and the
+customization containing the menu definition will be appended.
+
+Following this, the function accepts the following keyword arguments:
+
+CONTENTS is used to populate the NAME-actions variable.  It follows the syntax
+decribed in `(transient)Binding Suffix and Infix Commands',
+with the addition that the arguments in ARGLIST are accessible
+prefixed with !.  For an example, see `org-cite-basic-follow-actions'.
+
+DEFAULT-ACTION specifies the action to be taken, if org-menu is
+inactive (as determined by `org-menu-mode' and modified by a
+prefix argument set in `org-menu-switch'.
+It has the form of a function call, where the arguments in
+ARGLIST are accessible prefixed by !.  For example, the default action
+of `org-cite-basic-follow', which is defined with n ARGLIST
+\\(citation-object prefix-argument), has the form
+\\(org-cite-basic-goto !citation-object !prefix-argument).
+
+INTERACTIVE-SPEC is optional and can be used to set up the interactive
+environment and validate arguments.  The body will be evaluated on activation
+of the menu, also when the default action is called."
+  (declare (indent defun))
+  (let ((menu-default-action
+         (intern (concat (symbol-name name) "-default-action")))
+        (menu-actions
+         (intern (concat (symbol-name name) "-actions"))))
+    `(progn
+       (org-menu--defcustom-actions
+        ,menu-actions ',contents ,name)
+       (org-menu--defcustom-default-action
+        ,menu-default-action
+        ',default-action
+        ,name
+        ',arglist)
+       (transient-define-prefix
+         ,name ,arglist
+         ,(concat  docstring
+                   "
+
+If `org-menu-mode' is active, display the menu specified in
+`"
+                   (symbol-name menu-actions)
+                   "'.
+
+This behaviour can be inverted by giving the prefix argument in
+`org-menu-switch'.  See `org-menu-mode' for more information.")
+         [:class
+          transient-columns
+          :setup-children
+          (lambda (_)
+            (transient-parse-suffixes
+             ',name
+             (org-menu--bind-specification
+              (transient-scope)
+              ,menu-actions)))
+          :pad-keys t]
+         ,interactive-spec
+         (let ((bound-arguments
+                (list ,@(mapcar
+                         (lambda (param)
+                           `(list
+                             ',(intern (concat "!" (symbol-name param)))
+                             `',,param))
+                         (org-menu--strip-argument-decorators arglist)))))
+           ;; Should we display a menu? If so, how?
+           (cond ((not (xor org-menu-mode
+                            (eq current-prefix-arg org-menu-switch)))
+                  ;; Call the default action
+                  (org-menu--with-arguments
+                   ,(org-menu--strip-argument-decorators arglist)
+                   (eval ,menu-default-action)))
+                 ((eq org-menu-system 'transient)
+                  ;; Activate transient
+                  (transient-setup
+                   (quote ,name) nil nil
+                   :scope bound-arguments))
+                 (t
+                  ;; Use the system specified in `org-menu-system'
+                  (funcall
+                   org-menu-system
+                   ,(car (split-string docstring "[\n]"))
+                   (org-menu--bind-specification
+                    bound-arguments
+                    ,menu-actions)))))))))
+
+(provide 'om)
+;;; om.el ends here
-- 
2.47.3

Reply via email to