0

I'm trying to use Hyperbole mode while also using Org mode. Org mode has a C-c / binding which is what I wish to do, but Hyperbole mode wants to redefine it. From reading of the hyperbole mode code, it seems there is an attempt by the author to avoid overriding pre-existing bindings in other modes, but that is not working. I've tried loading Hyperbole mode both before and after Org mode, but it still is stuck. So, short of hacking on Hyperbole's code, is there a way to force Hyperbole mode to not redefine bindings that are already defined in the mode it is used within?

Link to the source code: Hyperbole source code

1
  • AFAIK, not in general: I believe it's a minor mode, so its keymap has higher priority on the active keymap list than the major mode's keymap. You can of course redefine keys as you wish in the usual way: add a function to redefine the key(s) the way you want into the minor mode's hook. You can start with the one key you have identified and add more keys later.
    – NickD
    Dec 21, 2022 at 20:12

2 Answers 2

2

NickD's answer was very useful (and I could not have figured what I figured out below without his help!), but I found out that the hyperbole-mode-hook is ineffective. Instead, I had to resort to adding an after-init-hook as follows:

(defun my/hyperbole-disable-key-bindings-after-init-hook ()
  ;;
  ;; The table below is what I found defined by non-Hyperbole modes, which
  ;; Hyperbole's global minor mode overrides based upon the bindings listed by
  ;; https://emacs.stackexchange.com/a/75065/15483 :
  ;;
  ;; Binding         Hyperbole                             Without Hyperbole
  ;; --------------------------------------------------------------------------------
  ;; C-h A           hkey-help                             counsel-apropos
  ;; M-RET           hkey-either                           org-meta-return
  ;; ESC <return>    hkey-either                           org-meta-return
  ;; M-o             hkey-operate                          not defined
  ;; C-c RET         hui-select-thing                      org-ctrl-c-ret
  ;; C-c .           hui-select-goto-matching-delimiter    org-time-stamp
  ;; C-c /           hui-search-web                        org-sparse-tree
  ;; C-c @           hycontrol-windows-grid                org-mark-subtree
  ;; C-c \           hycontrol-enable-windows-mode         org-match-sparse-tree
  ;; C-x r s         hui-copy-to-register                  copy-to-register
  ;;
  ;; Thus as my usage of Hyperbole grows, I'll discover which ones
  ;; have to be undefined, as done below.
  ;;
  ;; Undefine the "C-c /" key in the hyperbole mode map, allowing
  ;; the existing binding in the Org mode map (C-c / runs the
  ;; command org-sparse-tree) to be active, but temporarily
  ;; orphaning `hui-search-web`:
  (define-key hyperbole-mode-map (kbd "C-c /") nil))

(use-package hyperbole
  :ensure t
  :config
  ;; The hyperbole-mode-hook is completely useless because of the
  ;; after-init-hook that hyperbole.el adds. So we have to add our own just to
  ;; tell it to stop defining keybindings we do not want (e.g., overrides Org
  ;; mode bindings):
  (add-hook 'after-init-hook #'my/hyperbole-disable-key-bindings-after-init-hook t))

(use-package is a tidier (IMO) wrapper around require for packages.)

The reason is due to this part of the last few lines of hyperbole.el:

;; This next expression initializes the Hyperbole keymap but does not
;; activate Hyperbole.  The only user-visible change it should make is
;; to globally bind {C-h h} to 'hyperbole' which when invoked will both
;; activate Hyperbole and show its minibuffer menu.
(if after-init-time
    ;; Initialize Hyperbole key bindings and hooks.
    (hyperb:init)
  ;; Initialize after other key bindings are loaded at startup.
  (add-hook 'after-init-hook #'hyperb:init t))

;; !! FIXME: This next expression activates Hyperbole for compatibility
;; with prior releases where (require 'hyperbole) was enough to
;; activate its key bindings.  However, loading a file should not
;; change Emacs's behavior, so after educating users to add this
;; next line to their Emacs initializations, it should be removed.
(hyperbole-mode 1)

(makunbound 'hyperbole-loading)

(provide 'hyperbole)

The use-package call does a require of hyperbole, which installs an after-init-hook. Notice the logic using after-init-time which apparently must be nil at the time that hyperbole.el is evaluated. If I used this instead:

(add-hook 'hyperbole-mode-hook #'my/hyperbole-disable-key-bindings-after-init-hook)

It would execute, but then undone again due to the call to hyperb:init which then re-establishes the definition of the key binding.

I had to instrument defun hkey-initialize with calls to (backtrace) to find out exactly where and when it was happening.

2
  • 1
    This sounds right: in my experiments, I loaded hyperbole by hand, so I probably missed the whole after-init-time/ after-init-hook stuff.
    – NickD
    Dec 23, 2022 at 11:06
  • My answer is still questionable. Your answer seems closer to what I would have hoped should be workable. But I'm a new user of Hyperbole mode so I'm still figuring things out at the moment. Perhaps others can chime in to correct what I've written and if I need to, I can change my answer accordingly.
    – bgoodr
    Dec 23, 2022 at 21:13
0

There are several such keybindings that you might want to "fix". Doing C-h m in an Org mode buffer with hyperbole enabled, I see:

C-h A       hkey-help

M-RET       hkey-either
M-o     hkey-operate
ESC <return>    hkey-either

C-c RET     hui-select-thing
C-c .       hui-select-goto-matching-delimiter
C-c /       hui-search-web
C-c @       hycontrol-windows-grid
C-c \       hycontrol-enable-windows-mode

C-x r s     hui-copy-to-register

I will illustrate "fixing" one of these (the one you flagged), but the rest can be similarly treated; the main stumbling block is finding other keys to bind the hyperbole commands to, keys that are not defined in Org mode. One possibility is the combinations C-c <letter>. Emacs conventions require that these should not be used by packages, so that users can bind them as they wish. In my case, I tested C-c w, C-c x, C-c y and C-c z and they are all unused, but YMMV: you should do your own testing and find unused key combos like this.

The idea is to write the function that resets the problematic binding in the hyperbole map and then adds a different binding for the hyperbole command that was bound to that key. E.g. the key C-c / is bound to org-sparse-tree in the Org mode map; hyperbole binds it to hui-search-web in its own map (which has higher priority in the list of active keymaps since it's a minor mode); so our function might look like this:

(defun my/fix-hyperbole-bindings ()
   ;; Undefine the "C-c /" key in the hyperbole mode map,
   ;; allowing the existing binding in the Org mode map to be active,
   ;; but temporarily orphaning `hui-search-web`. 
   (define-key hyperbole-mode-map (kbd "C-c /") nil)
   ;; Bind the unused "C-c z" to `hui-search-web` so that you can
   ;; invoke it with a key combo (though a *different* key combo).
   (define-key hyperbole-mode-map (kbd "C-c w") #'hui-search-web))

There is plenty of room for additional work in the function, but first, let's add the function to the mode hook, so that it (the function) will run when the mode is enabled:

(add-hook 'hyperbole-mode-hook #'my/fix-hyperbole-bindings)

Once the hook has been modified (e.g. by adding the code above to your init file - but see below for a possible gotcha), when you enable hyperbole in an Org mode buffer, the hook will run the function, which will modify the hyperbole mode map to undefine C-c / and bind hui-search-web to a different key combo. When you press C-c /, the hyperbole mode map will be searched but no binding will be found there since we undefined it; it will then continue with the other active maps, in particular the Org mode map since the buffer's major mode is org-mode, and there it will find a binding to org-sparse-tree.

It should be clear how to deal with the other problematic keybindings in the hyperbole mode map (C-c . and C-c RET are even worse, because they are very commonly used in Org mode buffers).

Note also that even though we have been talking about an Org mode buffer in which you enable the hyperbole minor mode, hyperbole is a global minor mode, so it will be enabled in every buffer. In a different buffer, the major mode map might not have a binding for C-c / at all, and after the hook is run, neither does the hyperbole mode map. But that's OK: C-c / will be undefined in such a buffer, as it was before hyperbole was enabled. And hui-search-web is still available, albeit on a different combination of keys (but the same combination in every buffer).

One common gotcha is that the add-hook call modifies the variable hyperbole-mode-hook which means that the variable has to have been defined already. But that may not be true: your init file might try to modify the hook before hyperbole is loaded and, therefore, before the hook variable has been defined. That will lead to an error. There are two ways to deal with that problem: either load the package that defined the variable before you modify it, or arrange for the modification to happen after the package is loaded, using eval-after-load. This answer describes the two methods and is worth keeping in mind: it is a common problem.

1
  • Thanks. That was quite helpful. But, alas, I could not get it to work and was thwarted by hyperbole's use of after-init-hook. See my answer.
    – bgoodr
    Dec 23, 2022 at 6:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.