From: Ihor Radchenko <yantar92@posteo.net>
To: "Tor-björn Claesson" <tclaesson@gmail.com>, emacs-devel@gnu.org
Cc: emacs-orgmode@gnu.org, "Philip Kaludercic" <philipk@posteo.net>,
"Omar Antolín Camarena" <omar@matem.unam.mx>,
"Jonas Bernoulli" <emacs.transient@jonas.bernoulli.dev>,
"Juri Linkov" <juri@linkov.net>,
karthikchikmagalur@gmail.com, Visuwesh <visuweshm@gmail.com>,
charles.choi@yummymelon.com, "Justin Burkett" <justin@burkett.cc>
Subject: [RFC] The best way to choose an "action" at point: context-menu-mode, transient, which-key or embark? (was: Fwd: Org-cite: Replace basic follow-processor with transient menu?)
Date: Fri, 13 Dec 2024 18:41:14 +0000 [thread overview]
Message-ID: <87msgzh1dh.fsf@localhost> (raw)
In-Reply-To: <87wmg6edr0.fsf@gmail.com>
TL;DR: We are in the process of designing a more unified selection
interface for Org mode and want to see if there is some way to unify
context-menu-mode, transient, which-key and embark together. The idea is
to (1) avoid too many customizations; (2) allow users to decide how to
choose between multiple options - by mouse, keyboard, and using
customizable UIs.
Dear all,
I have raised the topic of refactoring Org mode menu systems during
EmacsConf in https://emacsconf.org/2024/talks/org-update/
The initial idea was replacing the self-written menu code in Org with
built-in transient.el.
Later, during OrgMeetup a number of people raised concerns that
transient may sometimes be an overkill, and that some people may prefer
alternative UIs. In particular, embark and context-menu-mode were
mentioned.
(I am CCing the discussion participants and potentially interested
maintainers)
In Org mode (although not only in Org mode, looking at the success of
embark.el), we often have a UI model where users call an "action"
command (like org-ctrl-c-ctrl-c or org-open-at-point) followed by
interactive selection among multiple actions.
For example, org-open-at-point on a heading with multiple links inside
will raise a section buffer listing all the links - pressing a number
will open the corresponding link.
Another example (see the example patch below), which is a
work-in-progress patch for Org citation system, is "following" a
citation. To "follow" citation may mean multiple things including but
not limited to: (1) going to citation record in the bibliography file;
(2) following URL; (3) downloading .pdf file for a citation; etc.
**The list of "follow" actions may be customized by users**
The general UI flow in these scenarios will be:
1. User calls "action" with cursor at certain syntax element
2. Action menu is displayed, showing the available actions/targets (dynamically built)
3. User selects the action/target
This UI flow can be implemented using context menus, which-key popups,
transient menus, and also using embark (where the way menu is displayed
can be customized).
All the 4 approaches represent different UI models with various
strengths and weaknesses:
- transient has a very flexible layout builder where the menu items can
be arranged granularly, but intercepts the main loop disrupting
certain keyboard-based workflows
- which-key does not stand on the way and integrates well into Emacs'
key binding model, but provides little flexibility for menu layout
- embark stays in the middle between which-key and transient, making use
of transient keymaps and allowing a custom menu renderer
- context-menu-mode provides mouse experience
I am wondering if we can work out some universal API to plug the
described action->menu->selection model into the UI that user prefers.
Tentatively, I am thinking about the following:
For a given Emacs "prefix" command (e.g. org-open-at-point), we define a
set of customizations:
1. List of possible actions: ((name1 . action1 props) (name2 . action2 ...) ...)
PROPS is a plist defining extra properties like key-binding, display
string, maybe something else to be used in the future.
2. Menu interface to use (transient, context-menu, embark, which-key)
3. Layout settings for the specific interfaces. For example, transient
layout definition.
WDYT?
Best,
Ihor
Tor-björn Claesson <tclaesson@gmail.com> writes:
> From 7e9e0c64fbda2dcb67d8c8421d1c9923ca93e8b4 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Tor-bj=C3=B6rn=20Claesson?= <torb@barbar.claesson.fi>
> Date: Tue, 12 Nov 2024 11:09:16 +0200
> Subject: [PATCH] lisp/oc-basic.el: Transient menu for following citations
>
> * lisp/oc-basic.el (require 'transient): Pull in transient.
> (require 'org-element): Pull in org-element.
> (org-cite-basic-follow-ask): New customization option. should
> `org-cite-basic-follow' prompt the user for an action?
> (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-follow): New function. Displays a menu asking how to
> follow a citation if `org-cite-basic-follow-ask' is
> non-nil. Otherwise, it retains the default behaviour of opening the
> bibliography entry. This can be inversed with a negative prefix argument.
> (org-cite-basic-follow--parse-suffix-specification and
> org-cite-basic-follow--setup): Helper functions for
> `org-cite-basic-follow'.
> (org-cite-register-processor 'basic): Update the basic citation
> processor to follow citations using `org-cite-basic-follow'.
>
> * etc/ORG_NEWS (Menu for choosing how to follow citations): Describe
> the new feature
> (New option ~org-cite-basic-follow-ask~): Describe this new
> customization option.
> (New option ~org-cite-basic-follow-actions~): Describe this new
> customization option, which specifies the layout of the
> `org-cite-basic-follow' transient menu.
>
> This change was co-authored with much support from Ihor Radchenko and
> Jonas Bernoulli, thanks!
> ---
> etc/ORG-NEWS | 22 +++++++++
> lisp/oc-basic.el | 115 +++++++++++++++++++++++++++++++++++++++++++----
> 2 files changed, 128 insertions(+), 9 deletions(-)
>
> diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
> index de4f11b25..bacc38be2 100644
> --- a/etc/ORG-NEWS
> +++ b/etc/ORG-NEWS
> @@ -114,6 +114,15 @@ The keybindings in the repeat-maps can be changed by customizing
>
> See the new [[info:org#Repeating commands]["Repeating commands"]] section in Org mode manual.
>
> +*** New transient menu when following citations
> +
> +Following citations with the org-cite-basic citation backend can now present a
> +transient menu. To show this menu, set ~org-cite-basic-follow-ask~ to non-nil.
> +This behaviour can be reversed with a -4 prefix.
> +
> +The contents of this menu can be customized in
> +~org-cite-basic-follow-actions~.
> +
> ** New and changed options
>
> # Chanes deadling with changing default values of customizations,
> @@ -158,6 +167,19 @@ English. The default value is ~t~ as the CSL standard assumes that
> English titles are specified in sentence-case but the bibtex
> bibliography format requires them to be written in title-case.
>
> +*** New option ~org-cite-basic-follow-ask~
> +
> +When this option is non-nil, following a citation with the basic citation
> +backend will present a transient menu with choices for how to follow the
> +citation.
> +If nil, following a citation will open its bibliography entry.
> +
> +This behaviour can be reversed with a -4 prefix argument.
> +
> +*** New option ~org-cite-basic-follow-actions~
> +
> +This option specifies the options presented by ~org-cite-basic-follow~.
> +
> ** New functions and changes in function arguments
>
> # This also includes changes in function behavior from Elisp perspective.
> diff --git a/lisp/oc-basic.el b/lisp/oc-basic.el
> index e207a1997..fc55d3a32 100644
> --- a/lisp/oc-basic.el
> +++ b/lisp/oc-basic.el
> @@ -74,6 +74,8 @@
> (require 'map)
> (require 'oc)
> (require 'seq)
> +(require 'transient)
> +(require 'org-element)
>
> (declare-function org-open-at-point "org" (&optional arg))
> (declare-function org-open-file "org" (path &optional in-emacs line search))
> @@ -140,6 +142,39 @@
> :type 'face
> :safe #'facep)
>
> +(defcustom org-cite-basic-follow-ask nil
> + "Should `org-cite-basic' ask how to follow citations?
> +
> +When this option is nil, `org-cite-basic-follow' opens the bibliography entry.
> +Otherwise, `org-cite-basic-follow' will display a transient menu prompting the
> +user for an action. The contents of this menu can be customized in
> +`org-cite-basic-follow-actions'."
> + :group 'org-cite
> + :package-version '(Org . "9.8")
> + :type 'boolean)
> +
> +(defcustom org-cite-basic-follow-actions
> + '[["Open"
> + ("b" "bibliography entry" (org-cite-basic-goto !citation !prefix))]
> + ["Copy"
> + ("d" "DOI"
> + (kill-new
> + (org-cite-basic--get-field
> + 'doi
> + (org-cite-basic--get-key !citation))))]]
> + "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 !citation (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 !citation !prefix) or
> +(lambda () (message (org-element-property :key !citation)))"
> + :group 'org-cite
> + :package-version '(Org . "9.8")
> + :type 'sexp)
> +
> \f
> ;;; Internal variables
> (defvar org-cite-basic--bibliography-cache nil
> @@ -326,6 +361,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."
> + (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 +850,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 +870,65 @@ present in the citation."
> (bibtex-set-dialect)
> (bibtex-search-entry key)))))
>
> +(transient-define-prefix org-cite-basic-follow (citation-object &optional prefix)
> + "Follow citation.
> +
> +If `org-cite-basic-follow-ask' is non-nil, this transient will present
> +a menu prompting the user for an action.
> +Otherwise, it will open the bibliography entry for the citation at point.
> +This behaviour is inverted when the transient is called with a -4 prefix
> +argument.
> +
> +The contents of the menu are defined in the variable
> +`org-cite-basic-follow-actions'."
> + [:class transient-columns
> + :setup-children org-cite-basic-follow--setup
> + :pad-keys t]
> + (interactive
> + (list (let ((obj (org-element-context)))
> + (pcase (org-element-type obj)
> + ((or 'citation 'citation-reference) obj)
> + (_ (user-error "No citation at point"))))))
> + (if (xor org-cite-basic-follow-ask
> + (equal prefix '(-4)))
> + (transient-setup 'org-cite-basic-follow nil nil
> + :scope (list :citation citation-object :prefix prefix))
> + (org-cite-basic-goto citation-object prefix)))
> +
> +(defun org-cite-basic-follow--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 ((!citation (plist-get (transient-scope) :citation))
> + (!prefix (plist-get (transient-scope) :prefix)))
> + ,@fn-args))
> + ,@other))
> + (`(,key ,desc (,fn . ,fn-args) . ,other)
> + `(,key ,desc
> + (lambda ()
> + (interactive)
> + (let ((!citation (plist-get (transient-scope) :citation))
> + (!prefix (plist-get (transient-scope) :prefix)))
> + (,fn ,@fn-args)))
> + ,@other))
> + (other other)))
> +
> +(defun org-cite-basic-follow--setup (_)
> + "Update `org-cite-basic-follow' when `org-cite-basic-follow-actions' changes."
> + (transient-parse-suffixes
> + 'org-cite-basic-follow
> + (cl-map 'vector
> + (lambda (group)
> + (cl-map 'vector #'org-cite-basic-follow--parse-suffix-specification
> + group))
> + org-cite-basic-follow-actions)))
> +
> \f
> ;;; "Insert" capability
> (defun org-cite-basic--complete-style (_)
> @@ -920,7 +1017,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
> --
> 2.46.0
>
--
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>
next prev parent reply other threads:[~2024-12-13 18:41 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <8734m28l9a.fsf@gmail.com>
[not found] ` <874j6h3rw8.fsf@localhost>
[not found] ` <877cbamq2q.fsf@gmail.com>
[not found] ` <87cykvrgwr.fsf@localhost>
[not found] ` <8734lpmkjn.fsf@gmail.com>
[not found] ` <87r08lqlbu.fsf@localhost>
[not found] ` <87a5ewfvo1.fsf@gmail.com>
2024-10-22 17:58 ` Org-cite: Replace basic follow-processor with transient menu? Ihor Radchenko
[not found] ` <87wmhlmp83.fsf@gmail.com>
[not found] ` <871pzte929.fsf@localhost>
[not found] ` <87v7x548ri.fsf@gmail.com>
[not found] ` <87y120daue.fsf@localhost>
[not found] ` <874j4m9ep6.fsf@gmail.com>
[not found] ` <87h68gfqj1.fsf@localhost>
[not found] ` <CAO0k701CGFnQwCCuODjTFuf=OTsj9Vdqg+COP8nkpJg0wL_hQg@mail.gmail.com>
[not found] ` <87pln3f3cc.fsf@localhost>
[not found] ` <CAO0k7006goK-AfhG+3PVwhz=4QU_DMm+5edmATZpjdRHkj61Bg@mail.gmail.com>
[not found] ` <87jzd9ojj0.fsf@localhost>
[not found] ` <87cyj0ajm9.fsf@gmail.com>
[not found] ` <87zfm4s50x.fsf@localhost>
[not found] ` <CAO0k703a5SCv4Eaogjs-14zgmTi-pK5qqG=8VzB8+7h-kcC8yg@mail.gmail.com>
[not found] ` <87wmh8s358.fsf@localhost>
[not found] ` <87y11nwp9z.fsf@gmail.com>
[not found] ` <CAO0k702GsRi-h8BEY08kpf5FzMxi_MvRygNXJCyFnbtaC-a59w@mail.gmail.com>
[not found] ` <87v7wd9a2h.fsf@localhost>
[not found] ` <878qt7fbki.fsf@gmail.com>
[not found] ` <87o71jwdxz.fsf@localhost>
[not found] ` <87wmg6edr0.fsf@gmail.com>
2024-12-13 18:41 ` Ihor Radchenko [this message]
2024-12-13 22:09 ` [RFC] The best way to choose an "action" at point: context-menu-mode, transient, which-key or embark? Gabriel Santos
2024-12-14 9:57 ` Ihor Radchenko
2024-12-14 10:59 ` Gabriel Santos
2024-12-13 22:57 ` Suhail Singh
2024-12-14 9:59 ` Ihor Radchenko
2024-12-14 1:16 ` [RFC] The best way to choose an "action" at point: context-menu-mode, transient, which-key or embark? (was: Fwd: Org-cite: Replace basic follow-processor with transient menu?) Panayotis Manganaris
2024-12-14 10:08 ` Ihor Radchenko
2024-12-14 10:50 ` [RFC] The best way to choose an "action" at point: context-menu-mode, transient, which-key or embark? indieterminacy
2024-12-14 0:37 [RFC] The best way to choose an "action" at point: context-menu-mode, transient, which-key or embark? (was: Fwd: Org-cite: Replace basic follow-processor with transient menu?) Psionic K
2024-12-14 9:48 ` Ihor Radchenko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87msgzh1dh.fsf@localhost \
--to=yantar92@posteo.net \
--cc=charles.choi@yummymelon.com \
--cc=emacs-devel@gnu.org \
--cc=emacs-orgmode@gnu.org \
--cc=emacs.transient@jonas.bernoulli.dev \
--cc=juri@linkov.net \
--cc=justin@burkett.cc \
--cc=karthikchikmagalur@gmail.com \
--cc=omar@matem.unam.mx \
--cc=philipk@posteo.net \
--cc=tclaesson@gmail.com \
--cc=visuweshm@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).