a13 / reverse-im.el

116 stars 4 forks source link

Doesn't work with Hydra without modifier keys #17

Open a13 opened 4 years ago

a13 commented 4 years ago

doesn't work

(defhydra hydra-zoom (global-map "<f2>")
  "zoom"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

works

(defhydra hydra-zoom (global-map "<f2>")
  "zoom"
  ("C-g" text-scale-increase "in")
  ("C-l" text-scale-decrease "out"))
a13 commented 4 years ago

@jurta sorry for disturbing, as a person who knows about Emacs keymaps internals a /little/ bit more than me, do you have any ideas regarding this issue?

The thing is sometimes (like here or in org-agenda-mode-map) Emacs ignores function-key-map when the single key (without modifier keys) is pressed and runs self-insert-command, while in most cases it works as I expect :)

jurta commented 4 years ago

Sorry, I'm not an expert in hydra :) maybe hydra author can help to provide the results of processing by hydra internals as plain define-key calls, then I could help to see what is wrong.

In the example above with defhydra, what are the real calls of define-key by hydra?

a13 commented 4 years ago

it doesn't use define-key, but creates a keymap directly

(set
   (defvar hydra-zoom/keymap nil "Keymap for hydra-zoom.")
   '(keymap
     (12 . hydra-zoom/text-scale-decrease)
     (7 . hydra-zoom/text-scale-increase)
     (kp-subtract . hydra--negative-argument)
     (kp-9 . hydra--digit-argument)
     (kp-8 . hydra--digit-argument)
     (kp-7 . hydra--digit-argument)
     (kp-6 . hydra--digit-argument)
     (kp-5 . hydra--digit-argument)
     (kp-4 . hydra--digit-argument)
     (kp-3 . hydra--digit-argument)
     (kp-2 . hydra--digit-argument)
     (kp-1 . hydra--digit-argument)
     (kp-0 . hydra--digit-argument)
     (57 . hydra--digit-argument)
     (56 . hydra--digit-argument)
     (55 . hydra--digit-argument)
     (54 . hydra--digit-argument)
     (53 . hydra--digit-argument)
     (52 . hydra--digit-argument)
     (51 . hydra--digit-argument)
     (50 . hydra--digit-argument)
     (49 . hydra--digit-argument)
     (48 . hydra--digit-argument)
     (45 . hydra--negative-argument)
     (21 . hydra--universal-argument)))
a13 commented 4 years ago

org-agenda bindings (the same issue) use org-defkey wrapper instead, but since org-replace-disputed-keys is nil it should work like define-key

(defun org-key (key)
  "Select key according to `org-replace-disputed-keys' and `org-disputed-keys'.
Or return the original if not disputed."
  (when org-replace-disputed-keys
    (let* ((nkey (key-description key))
       (x (cl-find-if (lambda (x) (equal (key-description (car x)) nkey))
              org-disputed-keys)))
      (setq key (if x (cdr x) key))))
  key)

(defun org-defkey (keymap key def)
  "Define a key, possibly translated, as returned by `org-key'."
  (define-key keymap (org-key key) def))

https://github.com/emacs-mirror/emacs/blob/master/lisp/org/org-agenda.el#L2277-L2416

a13 commented 4 years ago

back to hydra, it works on the first keypress (the bindings are in global-map)

(define-key global-map
  [f2 103]
  'hydra-zoom/text-scale-increase)
(define-key global-map
  [f2 108]
  'hydra-zoom/text-scale-decrease)

But then it sets hydra-zoom/keymap as a transient one using

(internal-push-keymap keymap 'overriding-terminal-local-map)
(defun hydra-zoom/text-scale-increase nil "Call the head `text-scale-increase' in the \"hydra-zoom\" hydra.\n\nThe heads for the associated hydra are:\n\n\"g\":    `text-scale-increase',\n\"l\":    `text-scale-decrease'\n\nThe body can be accessed via `hydra-zoom/body', which is bound to \"<f2>\"."
         (interactive)
         (require 'hydra)
         (hydra-default-pre)
         (let
             ((hydra--ignore t))
           (hydra-keyboard-quit)
           (setq hydra-curr-body-fn 'hydra-zoom/body))
         (condition-case err
             (progn
               (setq this-command 'text-scale-increase)
               (hydra--call-interactively-remap-maybe #'text-scale-increase))
           ((quit error)
            (message
             (error-message-string err))))
         (hydra-show-hint hydra-zoom/hint 'hydra-zoom)
         (hydra-set-transient-map hydra-zoom/keymap
                                  (lambda nil
                                    (hydra-keyboard-quit)
                                    nil)
                                  nil))
jurta commented 4 years ago

Transient map is a real problem. What you could try to do is either to add around advice on hydra-set-transient-map and add your mappings to its arg keymap, or add after advice and do the same on the modified overriding-terminal-local-map.

ceed0 commented 1 year ago

Here is an advice function that does that. Though it uses keymap-set, which is only available starting with emacs 29. Edit2: NOW it works fine with define-key (probably without bugs this time). Edit3: fixed a recursion

  (defun +translate-keymap (map)
    (let ((tl-map (make-sparse-keymap)))
      (map-keymap
       (lambda (char cmd)
         (when (characterp char)
           (if-let* ((modifiers (event-modifiers char))
                     (event (event-basic-type char))
                     (tl-event (string (reverse-im--translate-char event)))
                     (key (vector `(,@modifiers ,tl-event))))
               (define-key tl-map key cmd)
             (define-key tl-map (string
                                 (reverse-im--translate-char char))
                         cmd))))
       map)
      tl-map))

  (defun +translate-keymap-a (fun map &rest args)
    (if-let* ((map? (keymapp map))
              (comp-map (make-composed-keymap map (+translate-keymap map))))
        (apply fun comp-map args)
      (apply fun map args)))

  (advice-add 'set-transient-map :around #'+translate-keymap-a)
  (advice-add 'hydra-set-transient-map :around #'+translate-keymap-a)

I've tested it a little with hydra and built-in repeat-mode, and it works without problems so far. I can create a pull request if you want.

ceed0 commented 1 year ago

By advising use-local-map it works with org agenda and other modes that use it, like mu4e-view, too.