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?.

Tianxiang Xiong
  • 3,878
  • 18
  • 28
  • 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 04: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 06: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

4 Answers4

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"))))))
clemera
  • 3,451
  • 14
  • 40
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.

Tianxiang Xiong
  • 3,878
  • 18
  • 28
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.

phils
  • 50,977
  • 3
  • 79
  • 122
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.

Stefan
  • 26,404
  • 3
  • 48
  • 85