Hi!

I'm making progress, macrostep is great, thanks:-) Here is a new patch with
my progress so far.

It adds om.el, which contains the start of a simple menu system, and uses
this to define org-cite-basic-follow.
Most of the work is done in org-menu-define.

Docstrings and the commit message are mostly placeholders, neither does it
so far support menu system other than transient.

I have a problem I do not understand: The definition of
org-cite-basic-follow using org-menu-define in oc-basic.el does not work
unless i evaluate it manually, otherwise org-cite-basic-follow is nil when
I try to follow citations.

Cheers
Tor-björn



Den lör 22 feb. 2025 kl 21:41 skrev Tor-björn Claesson <tclaes...@gmail.com
>:

> Thanks again, I'll be back :-)
>
> Cheers,
> Torbjörn
>
> Den lör 22 feb. 2025 16.18Ihor Radchenko <yanta...@posteo.net> skrev:
>
>> Tor-björn Claesson <tclaes...@gmail.com> writes:
>>
>> >> 1. I am looking at C-- C-u prefix, and I am not sure if it is ideal
>> >>    What I think might be better is:
>> >>    - make a short C-- ('- argument) the switch
>> >>    - make the switch customizable (if users do not like C--)
>> >>
>> >
>> > I tried simply changing the switch from "-4" to "'", but this does not
>> > work.
>> > My google skills fail me here, and I would be grateful for some advice
>> on
>> > how to proceed.
>>
>> Try this toy example:
>>
>> (defun yant/test (arg)
>>   (interactive "P")
>>   (if (eq arg '-)
>>       (message "Detected C-- !")
>>     (message "other arg: %S" arg)))
>>
>> >> 4. Moving towards my item (2) in the plan, we need to generalize
>> >>    (transient-define-prefix org-cite-basic-follow ...) into a macro
>> that
>> >>    can work for arbitrary command; not just for
>> `org-cite-basic-follow'.
>> >>    Let me know if you want my help on how to write such a macro.
>> >
>> > I'm trying to make this work. Is it OK to use (eval
>> > `(transient-define-prefix ...)) here?
>> > Also, I find it a bit easier to write this as a function rather than a
>> > macro, but this makes calling it a bit uglier.
>> > Should I try define this as a macro, or is a function acceptable?
>>
>> Macro will be better, but you can write is as a function first. We can
>> easily change it into a macro later.
>>
>> If you have troubles understanding macro expansion, I recommend playing
>> around with macrostep package.
>>
>> --
>> Ihor Radchenko // yantar92,
>> Org mode maintainer,
>> Learn more about Org mode at <https://orgmode.org/>.
>> Support Org development at <https://liberapay.com/org-mode>,
>> or support my work at <https://liberapay.com/yantar92>
>>
>
From 1d7610767a74fbc2a7c8673071ba10c16862700e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tor-bj=C3=B6ron?= <tclaes...@gmail.com>
Date: Thu, 27 Feb 2025 20:30:07 +0200
Subject: [PATCH] lisp/om.el: Org-menu, a simple menu system for org.

* 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--get-key): New function. Get citation key from
citation or citation reference.
(org-cite-basic-goto): Use org-cite-basic--get-key.
(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 |  77 ++++++++++++++++++++++---
 lisp/om.el       | 145 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 213 insertions(+), 9 deletions(-)
 create mode 100644 lisp/om.el

diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
index 32a99e987..95aa4c070 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))
 
@@ -140,6 +142,53 @@
   :type 'face
   :safe #'facep)
 
+(defcustom org-cite-basic-follow-actions
+  '[["Open"
+     ("b" "bibliography entry" (org-cite-basic-goto !object !prefix))]
+    ["Copy"
+     ("d" "DOI"
+      (let ((doi (org-cite-basic--get-field
+                  'doi
+                  (org-cite-basic--get-key !object))))
+        (if (not (s-blank? doi))
+            (kill-new doi)
+          (user-error "No DOI for `%s'" (org-cite-basic--get-key !object)))))
+     ("u" "URL"
+      (let ((url (org-cite-basic--get-field
+                  'url
+                  (org-cite-basic--get-key !object))))
+        (if (not (s-blank? url))
+            (kill-new url)
+          (user-error "No URL for `%s'" (org-cite-basic--get-key !object)))))]
+    ["Browse"
+     ("w" "Browse URL/DOI"
+      (let ((url (org-cite-basic--get-field
+                  'url
+                  (org-cite-basic--get-key !object)))
+            (doi (org-cite-basic--get-field
+                  'doi
+                  (org-cite-basic--get-key !object))))
+        (cond ((not (s-blank? url))
+               (browse-url url))
+              ((not (s-blank? 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 !object))))))]]
+  "Actions in the org-cite-basic-follow transient 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 !object (the citation
+object to be followed) and !prefix (any prefix argument to the follower) can be
+used to access those values. For example:
+(org-cite-basic-goto !object !prefix) or
+(lambda () (message (org-element-property :key !object)))"
+  :group 'org-cite
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
 
 ;;; Internal variables
 (defvar org-cite-basic--bibliography-cache nil
@@ -326,6 +375,16 @@ 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-KEY."
+  (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-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."
@@ -805,14 +864,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))
@@ -832,6 +884,13 @@ present in the citation."
        (bibtex-set-dialect)
        (bibtex-search-entry key)))))
 
+(org-menu-define org-cite-basic-follow
+                 "Follow citation"
+                 org-cite-basic-follow-actions
+                 :display t
+                 :default-action 'org-cite-basic-goto
+                 :parameter-types ('citation 'citation-reference))
+
 
 ;;; "Insert" capability
 (defun org-cite-basic--complete-style (_)
@@ -920,7 +979,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..558d55167
--- /dev/null
+++ b/lisp/om.el
@@ -0,0 +1,145 @@
+;;; om.el --- Org Menu library                  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Free Software Foundation, Inc.
+
+;; Author: Tor-björn Claesson <tclaes...@gmail.com>
+
+;; 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-lib)
+(require 'org-macs)
+(require 'transient)
+
+(org-assert-version)
+
+
+;;; Configuration variables
+(defgroup org-menu nil
+  "Options concerning menus in Org mode."
+  :group 'org
+  :tag "Org Menu")
+
+(defcustom org-menu-display '()
+  "Should a specific menu be displayed?
+An association list, where the key is a menu name. When the value is nil,
+the menu will not be displayed and the default action in 
+`org-menu-default-actions' taken."
+  :group 'org-menu
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
+(defcustom org-menu-default-actions '()
+  "The default action to take.
+An association list, where the key is a menu name, and the value the action
+to take for this menu when it is not displayed."
+  :group 'org-menu
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
+(defcustom org-menu-switch "-4"
+  "Prefix argument for inverting the behaviour of Org menus with regards to
+`org-menu-display'."
+  :group 'org-menu
+  :package-version '(Org . "9.8")
+  :type 'sexp)
+
+(defcustom org-menu-system 'transient
+  ""
+  :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, and depending on the settings for the specific
+menu in `org-menu-ask', a menu propting the user for an action to will be 
+presented upon activating certain objects."
+  :init-value nil
+  :lighter " OM")
+
+
+;;; Helper functions
+(defun org-menu--parse-suffix-specification (specification)
+  "Handle special syntax for `org-cite-basic-follow-actions'."
+  (pcase specification
+    (`(,key ,desc (lambda ,args . ,fn-args) . ,other)
+     `(,key ,desc
+            (lambda ,args
+              ,(unless (and (listp (car fn-args))
+                            (equal (caar fn-args)
+                                   'interactive))
+                 '(interactive))
+              (let ((!object (plist-get (transient-scope) :object))
+                    (!prefix (plist-get (transient-scope) :prefix)))
+                ,@fn-args))
+            ,@other))
+    (`(,key ,desc (,fn . ,fn-args) . ,other)
+     `(,key ,desc
+            (lambda ()
+	      (interactive)
+              (let ((!object (plist-get (transient-scope) :object))
+                    (!prefix (plist-get (transient-scope) :prefix)))
+	        (,fn ,@fn-args)))
+            ,@other))
+    (other other)))
+
+
+;;; Main macro definition
+(cl-defmacro org-menu-define (name
+                              docstring
+                              contents
+                              &key ((:display display) nil)
+                              &key ((:default-action default-action) nil)
+                              &key ((:parameter-types types) nil))
+  "Define an org menu."
+  (setq org-menu-display (cons `(,name . ,display) org-menu-display))
+  (setq org-menu-default-actions (cons `(,name . ,default-action) org-menu-default-actions))
+
+  `(transient-define-prefix ,name (object &optional prefix)
+     ,docstring
+     [:class transient-columns
+             :setup-children
+             (lambda (_)
+               (transient-parse-suffixes
+                (quote ,name)
+                (cl-map 'vector
+                        (lambda (group)
+                          (cl-map 'vector
+                                  #'org-menu--parse-suffix-specification
+                                  group))
+                        ,contents)))
+             :pad-keys t]
+     (interactive
+      (list (let ((obj (org-element-context)))
+              (pcase (org-element-type obj)
+                ((or ,@types) obj)
+                (_ (user-error "No citation at point"))))))
+     (if (xor (cdr (assoc (quote ,name) org-menu-display))
+              (equal prefix (list org-menu-switch)))
+         (transient-setup (quote ,name) nil nil
+                          :scope (list :object object :prefix prefix))
+       (funcall (cdr (assq (quote ,name) org-menu-default-actions)) object prefix))))
+
+(provide 'om)
+;;; om.el ends here
-- 
2.47.2

Reply via email to