Over the weekend I thought about this issue of configuring
`org-speed-commands' more easily, and I believe I have come up with a
simpler solution that I have provided in the attached patch.

This patch creates a new function `org-speed-command-add' that can be
used to add new speed command shortcuts to `org-speed-commands', as
well as modify existing shortcuts. This function takes as argument an
alist in the same format as `org-speed-commands'. For each command for
which the shortcut key is already present in `org-speed-commands', the
old command is replaced with the new command at the same position.
Commands with brand new shortcut keys are added to the user section of
`org-speed-commands'.

I believe this patch strikes a balance between power users, who can
still directly customize `org-speed-commands', and non-experts, who
may want to add/modify some speed commands without having to
understand the structure and organization of `org-speed-commands'.

Shankar

On Wed, Nov 24, 2021 at 10:36 AM Shankar Rao <shankar....@gmail.com> wrote:
>
> Hello all,
>
> I discovered that upgrading to 9.5 broke my configuration because the
> variable `org-speed-commands-user' was removed. I read the thread
> (https://list.orgmode.org/87v9hzzhrn....@gmail.com/) where this change
> was proposed and I completely agree that exposing the whole set of
> `org-speeds-commands' to the user for customization is an improvement
> over the previous state of affairs. However, I believe there were some
> unintended consequences of this change that can make it difficult to
> customize `org-speed-commands' for users that are not elisp gurus.
>
> The main problem is that `org-speed-commands' serves two purposes
> simultaneously:
>
> 1.) It contains all the mappings between speed keys and commands
> 2.) It contains headlines for command categories.
>
> Because of this second purpose, both the contents and order of entries
> in `org-speed-commands' are important. For example, suppose I want to
> replace the usual "n" command with my own. According to the usual
> conventions for alists
> (https://www.gnu.org/software/emacs/manual/html_node/elisp/Association-Lists.html),
> new associations such as this are added to the front of the list. But
> if I do so, by doing something like:
>
>   (setq org-speed-commands (cons '("n" . #'my-org-next-heading)
> org-speed-commands))
>
> Then the speed key "n" will show up twice when
> `org-speed-command-help' is invoked. I could first delete the old
> association by replacing `org-speed-commands' in the above with
> `(assoc-delete-all "n" org-speed-commands)', but then my modified
> command will no longer appear in the "Outline Navigation" section of
> the speed command help. Alternatively, I could replace the association
> for "n" using `alist-get':
>
>   (setf (alist-get "n" org-speed-commands nil nil #'equal)
> #'my-org-next-heading)
>
> However, this solution won't work for new speed commands (e.g., if I
> want to bind `my-org-next-heading' to "N" instead), because in that
> case `alist-get' will return `nil'.
>
> Below is the relevant portion of my config file where I customize
> `org-speed-commands':
> -----
>   (defun alist-set (key value alist-symbol &optional testfn)
>     "Set KEY to VALUE in alist referenced by ALIST-SYMBOL.
>
>   If KEY is not present in the alist, then add (KEY. VALUE) to the
>   front of the alist. Compare keys with TESTFN. Defaults to equal."
>     (if-let ((keyval (assoc key (eval alist-symbol) testfn)))
>         (setf (cdr keyval) value)
>       (set alist-symbol (cons (cons key value) (eval alist-symbol)))))
>
>
>   (defvar sbr-org-speed-commands-user '(("User Custom Speed Commands")
>                                         ("N" . 
> ded-org-show-next-heading-tidily)
>                                         ("P" .
> ded-org-show-previous-heading-tidily)
>                                         ("h" . 
> sbr-org-speed-insert-subheading)
>                                         ("u" . org-up-heading-or-item)
>                                         ("b" . org-backward-heading-or-item)
>                                         ("f" . org-forward-heading-or-item)
>                                         ("p" . org-prev-heading-or-item)
>                                         ("n" . org-next-heading-or-item))
>     "My custom Org speed commands")
>
>   (dolist (keyval (reverse sbr-org-speed-commands-user))
>     (alist-set (car keyval) (cdr keyval) 'org-speed-commands))
> -----
>
> As you can see, I defined my own function `alist-set', which modifies
> an association in an alist if the key is already present, or adds the
> new association to the front of the list otherwise. In my opinion,
> functionality like `alist-set' should be built into Emacs itself. My
> code then constructs my own list of custom speed commands with its own
> section header and uses `alist-set' to add/modify speed commands.
> While this code works, it's a bit unsatisfying because
>
> 1.) It relies on my custom `alist-set' function
> 2.) It relies on knowledge of the structure of `org-speed-commands'
>
> More specifically, it requires that my new speed commands need to be
> inserted in reverse order into `org-speed-commands' in order to be
> displayed properly in `org-speed-commands-help'.
>
> I don't know what is the best solution to enable Org users to add
> and/or modify speed commands while also keeping the display of
> `org-speed-commands-help' organized. Here is what I propose:
>
> 1.) Keep the whole set of `org-speed-commands' exposed to user
> customization for power users
> 2.) Bring back `org-speed-commands-user', but instead of just
> appending it to `org-speed-commands' as was done prior to Org 9.5, use
> something like my `alist-set' above to add/modify speed command
> associations as needed while preserving the display order in
> `org-speed-commands-help'.
>
> With my proposal, Org users wouldn't have to concern themselves with
> the section headers in `org-speed-commands', but they would still be
> able to add/modify/remove commands as they wish.
>
> Let me know if anyone has a simpler alternative to achieve these
> goals. If there is sufficient interest in my proposal, I would be
> happy to provide a patch.
>
> Thanks,
> Shankar
From 90684137340f3c60ed6a31815a1063a90079441e Mon Sep 17 00:00:00 2001
From: Shankar Rao <shankar.rao@gmail.com>
Date: Tue, 30 Nov 2021 14:30:51 +0100
Subject: [PATCH] Add function org-speed-command-add to add/modify speed
 commands
org-keys.el: Add function org-speed-command-add to add/modify speed commands

* lisp/org-keys.el (org-speed-command-add): Create a function that
adds new speed commands or modifies existing speed commands in
`org-speed-commands'. Speed commands whose shortcut key is already
present in `org-speed-commands' are replaced at the same position,
while speed commands with brand new shortcut keys are added to the
user section of `org-speed-commands'. 

---
 doc/org-manual.org |  7 ++++---
 etc/ORG-NEWS       |  9 ++++++++-
 lisp/org-keys.el   | 22 ++++++++++++++++++++++
 3 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 19f42fc77..f8b13973d 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -18941,9 +18941,10 @@ headline, before any of the stars.
 #+vindex: org-speed-commands
 #+findex: org-speed-command-help
 Org comes with a pre-defined list of Speed Keys.  To add or modify
-Speed Keys, customize the option ~org-speed-commands~.  For more
-details, see the variable's docstring.  With Speed Keys activated,
-{{{kbd(M-x org-speed-command-help)}}}, or {{{kbd(?)}}} when point is at the
+Speed Keys, one can either use the function ~org-speed-command-add~ or
+directly customize the option ~org-speed-commands~.  For more details,
+see the variable's docstring.  With Speed Keys activated, {{{kbd(M-x
+org-speed-command-help)}}}, or {{{kbd(?)}}} when point is at the
 beginning of an Org headline, shows currently active Speed Keys,
 including the user-defined ones.
 
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index e618feb9a..c39af5226 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -94,7 +94,14 @@ argument.
 
 ~org-get-tags~ now accepts Org element or buffer position as first
 argument.
-
+*** New function ~org-speed-command-add~ to add/modify Org speed commands
+
+This function takes an alist of speed commands in the same format as
+~org-speed-commands~ and adds these speed commands to
+~org-speed-commands~. If the letter for a speed command is already
+present in ~org-speed-commands~, then the new speed command overwrites
+the previous command at the same position. Otherwise, the new command
+is added to the user section of ~org-speed-commands~.
 ** Miscellaneous
 
 *** New =transparent-image-converter= property for =dvipng=
diff --git a/lisp/org-keys.el b/lisp/org-keys.el
index 103456526..f5d69701b 100644
--- a/lisp/org-keys.el
+++ b/lisp/org-keys.el
@@ -819,6 +819,28 @@ command."
   (with-current-buffer "*Help*"
     (setq truncate-lines t)))
 
+(defun org-speed-command-add (command-list)
+  "Add speed commands in COMMAND-LIST to ``org-speed-commands''.
+
+COMMAND-LIST is an alist of speed commands with the same format
+as ``org-speed-commands''. If a command in COMMAND-LIST is
+already present in ``org-speed-commands'', then the new command
+replaces the old command at the same position. Otherwise, the new
+command is added to the user section of
+``org-speed-commands''."
+  (let* ((title "User Speed Commands")
+         (has-user-section (assoc title org-speed-commands))
+         (new-commands nil))
+    (dolist (command command-list)
+      (if-let ((keyval (assoc (car command) org-speed-commands)))
+          (setf (cdr keyval) (cdr command))
+        (push command new-commands)))
+    (when new-commands
+      (setq org-speed-commands (append (unless has-user-section
+                                         (list (list title)))
+                                       (nreverse new-commands)
+                                       org-speed-commands)))))
+
 (defun org-speed-move-safe (cmd)
   "Execute CMD, but make sure that the cursor always ends up in a headline.
 If not, return to the original position and throw an error."
-- 
2.25.1

Reply via email to