2

I'm trying to create a transient keymap that calls calls quick-peek-hide on any key except mouse scrolling, which should function normally.

I've got the following:

(let ((map (make-sparse-keymap))) 
  (define-key map [mouse-4] #'mwheel-scroll)
  (define-key map [mouse-5] #'mwheel-scroll) 
  (define-key map [t] #'quick-peek-hide)
  (set-transient-map map <keep-pred> <on-exit>))

And don't know what should be the values of <keep-pred> and <on-exit> should be.

If keep-pred is t, wouldn't the transient map remain active forever because we define the default key [t]? If on-exit is #'quick-peek-hide, scrolling would invoke quick-peek-hide instead of simply doing mwheel-scroll.

Update:

A few observations:

  • The default keybinding [t] does not keep the transient map active.
(let ((map (make-sparse-keymap)))
  (define-key map [t] (lambda ()
                        (interactive)
                        (message "default!"))) 
  (set-transient-map map t))

Activating the above transient map causes the next key to message default!, but exits the transient map thereafter.

  • Mouse events are preceded by down- mouse events, so it is not sufficient to bind [mouse-4], [mouse-5], etc.

For example, describe-key for a mouse scroll down gives:

<mouse-5> (translated from <down-mouse-5> <mouse-5>) at that spot runs the command mwheel-scroll

So scrolling down with the following:

(let ((map (make-sparse-keymap)))
  (dolist (scroll-key (where-is-internal #'mwheel-scroll))
    (define-key map scroll-key (lambda ()
                                 (interactive)
                                 (message "scroll-key!")))) 
  (define-key map [t] (lambda ()
                        (interactive)
                        (message "default!"))) 
  (set-transient-map map t))

Does not message scroll-key!, but default!.

  • Hacking the following together:
(let ((map (make-sparse-keymap)))
  (dolist (scroll-key (where-is-internal #'mwheel-scroll))
    (define-key map scroll-key (lambda ()
                                 (interactive)
                                 (message "scroll-key!")))) 
  (define-key map [t] (lambda ()
                        (interactive)
                        (message "default!")))

  (define-key map (kbd "<down-mouse-5>") (lambda ()
                                           (interactive)
                                           (message "down-mouse-5!"))) 
  (set-transient-map map t))

The first scroll down message down-mouse-5! and scroll-key!, but the next one messages default and exits the transient map.

So the question is: what keys/events need to be bound to prevent scrolling from exiting the transient map?.

  • set-transient-map was invented specifically to avoid the need to use [t] bindings (since these are always problematic: they prevent input-decode-map and friends from doing their job and there is no 100% reliable way to unread an event). – Stefan May 15 '17 at 4:13
  • The [t] bindings allow a keypress to quick-peek-hide, but not the execute the key's usual function. I want every key (except scrolling) after quick-peek-show to quick-peek-hide, not do what they'd normally do (insert char, etc.). Is there a way to achieve that without binding [t]? – Tianxiang Xiong May 15 '17 at 6:36
  • You can use the on-exit argument where you can set this-command to ignore in order to cause the exiting key to "do nothing". – Stefan May 15 '17 at 12:09
  • Hmm, ok. That actually seems less straightforward than binding [t]. Is the issue w/ binding [t] that the specific key is not recorded? – Tianxiang Xiong May 15 '17 at 16:25
1

You can provide a function as the KEEP-PRED argument. If it returns non-nil the map stays active. With the following only C-g will exit the map.

(let ((map (make-sparse-keymap))) 
  (define-key map [mouse-4] 'mwheel-scroll)
  (define-key map [mouse-5] 'mwheel-scroll)
  (define-key map (kbd "C-g") 'ignore) 
  (define-key map [t] (lambda ()
                        (interactive)
                        (message "Don't leave that map!!!")))
  (set-transient-map map (lambda ()
                           (not (equal (this-command-keys) (kbd "C-g"))))))
1

Ah, I think I've got it!

@compunaut set me on the right track. No need to screw with bindings; KEEP-PRED and ON-EXIT are enough.

(set-transient-map map
                   (lambda ()
                     (eq #'mwheel-scroll this-command))
                   (lambda ()
                     (quick-peek-hide)
                     (setq this-command #'ignore)))

The trick is to deal with commands rather than events, because it's hard to figure out which sequences of events are translated into which commands.

0

If keep-pred is t, wouldn't the transient map remain active forever because we define the default key [t]?

Perhaps. It might be a special case. Seems simple enough to test, but in any case, keep-pred can be a custom function, so you can always use that facility. I'd guess you'd be checking last-command-event against the anticipated values.

If on-exit is #'quick-peek-hide, scrolling would invoke quick-peek-hide instead of simply doing mwheel-scroll.

Why would you set that value? Your default key binding already takes care of calling quick-peek-hide for the situations you want it to be called in.

0

My crystal ball is pretty clouded but I get the impression it wants to tell me that you should print the "current event" (i.e. (this-command-keys) when you exit the transient-map, and that will help you figure out which bindings are missing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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