Single-use keybindings?

classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Single-use keybindings?

Eric Abrahamsen-2
A few times I've found myself wanting the ability to set a single-use
keybinding: the next time the user hits "q" (or what have you), run this
command, then reset "q" to whatever it was before.

This feels hacky, obviously, but more importantly it feels like I'm
not using the right tools, or misusing what I've got. Has anyone else
wanted to do this? Any better solutions? Maybe I should be using a minor
mode that turns itself off after use, or...

Anyway, any thoughts would be much appreciation. Here's the function I
just wrote -- it pops to a certain buffer from wherever you are, and
when it gets there it rebinds "q" to pop back where you came from,
resetting "q" to it's original binding as it goes. It works, but I still
feel bad about it.

Eric

(defun ebdb-mua-in-ebdb-buffer ()
  "From an MUA, temporarily move point to the corresponding EBDB buffer.

All further operations will take place within the EBDB buffer as
per normal, with the exception that \"q\" will return point to
where it was in the MUA, rather than quitting the EBDB buffer."
  (interactive)
  (let* ((buf (get-buffer (ebdb-make-buffer-name)))
         (w-conf (current-window-configuration))
         (w-win (selected-window))
         (w-point (window-point))
         (e-win (or (get-buffer-window buf)
                    (ebdb-pop-up-window buf t (ebdb-make-buffer-name))))
         q-bind)
    (select-window e-win t)
    (setq q-bind (key-binding "q"))
    (local-set-key
     "q"
     (lambda ()
       (interactive)
       (local-set-key (kbd "q") q-bind)
       (when (window-live-p w-win)
         (set-window-configuration w-conf)
         (goto-char w-point))))))


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RE: Single-use keybindings?

Drew Adams
> A few times I've found myself wanting the ability to set a single-use
> keybinding: the next time the user hits "q" (or what have you), run this
> command, then reset "q" to whatever it was before.
>
> This feels hacky, obviously, but more importantly it feels like I'm
> not using the right tools, or misusing what I've got. Has anyone else
> wanted to do this? Any better solutions? Maybe I should be using a minor
> mode that turns itself off after use, or...

See `set-transient-map'.  From (elisp) `Controlling Active Maps':

  This function adds KEYMAP as a “transient” keymap, which takes
  precedence over other keymaps for one (or more) subsequent keys.

  Normally, KEYMAP is used just once, to look up the very next key.
  If the optional argument KEEP-PRED is ‘t’, the map stays active as
  long as the user types keys defined in KEYMAP; when the user types
  a key that is not in KEYMAP, the transient keymap is deactivated
  and normal key lookup continues for that key.

  The KEEP-PRED argument can also be a function.  In that case, the
  function is called with no arguments, prior to running each
  command, while KEYMAP is active; it should return non-‘nil’ if
  KEYMAP should stay active.

  The optional argument ON-EXIT, if non-nil, specifies a function
  that is called, with no arguments, after KEYMAP is deactivated.

  This function works by adding and removing KEYMAP from the variable
  ‘overriding-terminal-local-map’, which takes precedence over all
  other active keymaps (see Searching Keymaps).

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
Drew Adams <[hidden email]> writes:

>> A few times I've found myself wanting the ability to set a single-use
>> keybinding: the next time the user hits "q" (or what have you), run this
>> command, then reset "q" to whatever it was before.
>>
>> This feels hacky, obviously, but more importantly it feels like I'm
>> not using the right tools, or misusing what I've got. Has anyone else
>> wanted to do this? Any better solutions? Maybe I should be using a minor
>> mode that turns itself off after use, or...
>
> See `set-transient-map'.  From (elisp) `Controlling Active Maps':

I suspected someone else would have wanted to do this, but I had no idea
it was that built-in! Thank you very much for the pointer.

E


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
In reply to this post by Drew Adams
Drew Adams <[hidden email]> writes:

>> A few times I've found myself wanting the ability to set a single-use
>> keybinding: the next time the user hits "q" (or what have you), run this
>> command, then reset "q" to whatever it was before.
>>
>> This feels hacky, obviously, but more importantly it feels like I'm
>> not using the right tools, or misusing what I've got. Has anyone else
>> wanted to do this? Any better solutions? Maybe I should be using a minor
>> mode that turns itself off after use, or...
>
> See `set-transient-map'.  From (elisp) `Controlling Active Maps':

There's the new version, which I'm feeling much better about. Thanks
again.

(defun ebdb-mua-in-ebdb-buffer ()
  "From an MUA, temporarily move point to the corresponding EBDB buffer.

All further operations will take place within the EBDB buffer as
per normal, with the exception that \"q\" will return point to
where it was in the MUA, rather than quitting the EBDB buffer."
  (interactive)
  (let* ((buf (get-buffer (ebdb-make-buffer-name)))
         (w-conf (current-window-configuration))
         (w-win (selected-window))
         (w-point (window-point))
         (e-win (or (window-live-p (get-buffer-window buf))
                    (ebdb-pop-up-window buf t (ebdb-popup-window))))
         (key-m (make-sparse-keymap)))
    (define-key key-m (kbd "q")
      (lambda ()
        (interactive)
        (when (window-live-p w-win)
          (set-window-configuration w-conf)
          (goto-char w-point))))
    (select-window e-win t)
    (set-transient-map key-m)))


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Emanuel Berg-4
In reply to this post by Eric Abrahamsen-2
Eric Abrahamsen wrote:

> Maybe I should be using a minor mode that
> turns itself off after use

You can, I've done exactly that! Create a copy
of the global-map and then
`substitute-key-definition' for a function
that, as you say, turns itself off after doing
its special thing.

But when I did that, I didn't know of that
other thing mentioned already, which is
probably the right way to do it - only it seems
a bit complicated...

If you succeed with a small snippet, feel free
to post it here!

--
underground experts united
http://user.it.uu.se/~embe8573


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
Emanuel Berg <[hidden email]> writes:

> Eric Abrahamsen wrote:
>
>> Maybe I should be using a minor mode that
>> turns itself off after use
>
> You can, I've done exactly that! Create a copy
> of the global-map and then
> `substitute-key-definition' for a function
> that, as you say, turns itself off after doing
> its special thing.
>
> But when I did that, I didn't know of that
> other thing mentioned already, which is
> probably the right way to do it - only it seems
> a bit complicated...

If I was trying to do something more complicated, it might be worth
looking into a minor mode. As it is, the transient key map is exactly
what I was wanting.

> If you succeed with a small snippet, feel free
> to post it here!

My previous message had it -- in fact, the revised version of the
function didn't look a whole lot different from my original version! But
it feels less fragile, and I'm more confident I'm DTRT.

Eric


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
Eric Abrahamsen <[hidden email]> writes:

> Emanuel Berg <[hidden email]> writes:
>
>> Eric Abrahamsen wrote:
>>
>>> Maybe I should be using a minor mode that
>>> turns itself off after use
>>
>> You can, I've done exactly that! Create a copy
>> of the global-map and then
>> `substitute-key-definition' for a function
>> that, as you say, turns itself off after doing
>> its special thing.
>>
>> But when I did that, I didn't know of that
>> other thing mentioned already, which is
>> probably the right way to do it - only it seems
>> a bit complicated...
>
> If I was trying to do something more complicated, it might be worth
> looking into a minor mode. As it is, the transient key map is exactly
> what I was wanting.

Actually, it wasn't! The transient map disappears the first time any key
is hit that isn't in the map itself. What I wanted was to rewire "q",
and nothing but "q", until the first time that "q" is hit. I can
probably still get what I want by fooling with the KEEP-PRED argument to
`set-transient-map', but it's not the no-brainer I thought it was.


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Emanuel Berg-4
Eric Abrahamsen wrote:

> Actually, it wasn't! The transient map
> disappears the first time any key is hit that
> isn't in the map itself. What I wanted was to
> rewire "q", and nothing but "q", until the
> first time that "q" is hit. I can probably
> still get what I want by fooling with the
> KEEP-PRED argument to `set-transient-map',
> but it's not the no-brainer I thought it was.

Here is the minor-mode-that-shuts-off-itself
idea. It has served me well. I wrote it in 2014
but it feels like 10 years ago. Man...

    http://user.it.uu.se/~embe8573/emacs-init/caps-back.el

--
underground experts united
http://user.it.uu.se/~embe8573


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Michael Heerdegen
In reply to this post by Eric Abrahamsen-2
Eric Abrahamsen <[hidden email]> writes:

> A few times I've found myself wanting the ability to set a single-use
> keybinding: the next time the user hits "q" (or what have you), run this
> command, then reset "q" to whatever it was before.
>
> This feels hacky, obviously, but more importantly it feels like I'm
> not using the right tools, or misusing what I've got. Has anyone else
> wanted to do this? Any better solutions? Maybe I should be using a minor
> mode that turns itself off after use, or...

After thinking about it: I implemented something like this myself.
Because resetting is tricky - and sometimes even something that you
don't want! - I just use a key binding that was unbound before.  So I
simply don't use any kind of resetting.  Hitting the key (again) when it
makes no sense may result in an error.  That's the case for other keys,
too, so...


Michael.

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
In reply to this post by Emanuel Berg-4
Emanuel Berg <[hidden email]> writes:

> Eric Abrahamsen wrote:
>
>> Actually, it wasn't! The transient map
>> disappears the first time any key is hit that
>> isn't in the map itself. What I wanted was to
>> rewire "q", and nothing but "q", until the
>> first time that "q" is hit. I can probably
>> still get what I want by fooling with the
>> KEEP-PRED argument to `set-transient-map',
>> but it's not the no-brainer I thought it was.
>
> Here is the minor-mode-that-shuts-off-itself
> idea. It has served me well. I wrote it in 2014
> but it feels like 10 years ago. Man...
>
>     http://user.it.uu.se/~embe8573/emacs-init/caps-back.el

I got this working, and it seems to do the right thing. It did end up
being a lot of code, though. Note the second argument to
`set-transient-map', that's pretty much what makes it all work.

I'm kind of ambivalent about this now, but I guess it's still the best
solution.

  (let* ((buf (get-buffer (ebdb-make-buffer-name)))
         (w-conf (current-window-configuration))
         (w-win (selected-window))
         (w-point (window-point))
         (e-win (if (window-live-p (get-buffer-window buf))
                    (get-buffer-window buf)
                  (ebdb-pop-up-window buf t (ebdb-popup-window))))
         (key-m (make-sparse-keymap)))
    (define-key key-m (kbd "q")
      (lambda ()
        (interactive)
        (when (window-live-p w-win)
          (set-window-configuration w-conf)
          (goto-char w-point))))
    (select-window e-win t)
    (set-transient-map
     key-m
     (lambda ()
       ;; Keep the transient map active until the user hits "q".
       (null
        (equal (this-command-keys-vector)
               [?q])))))


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Emanuel Berg-4
In reply to this post by Michael Heerdegen
Michael Heerdegen wrote:

> After thinking about it: I implemented
> something like this myself. Because resetting
> is tricky - and sometimes even something that
> you don't want! - I just use a key binding
> that was unbound before. So I simply don't
> use any kind of resetting. Hitting the key
> (again) when it makes no sense may result in
> an error. That's the case for other keys,
> too, so...

For sure, should be used only for the really
odd case. Nothing for the entire keyboard.
Acid programming that would be...

--
underground experts united
http://user.it.uu.se/~embe8573


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Single-use keybindings?

Eric Abrahamsen-2
In reply to this post by Michael Heerdegen
Michael Heerdegen <[hidden email]> writes:

> Eric Abrahamsen <[hidden email]> writes:
>
>> A few times I've found myself wanting the ability to set a single-use
>> keybinding: the next time the user hits "q" (or what have you), run this
>> command, then reset "q" to whatever it was before.
>>
>> This feels hacky, obviously, but more importantly it feels like I'm
>> not using the right tools, or misusing what I've got. Has anyone else
>> wanted to do this? Any better solutions? Maybe I should be using a minor
>> mode that turns itself off after use, or...
>
> After thinking about it: I implemented something like this myself.
> Because resetting is tricky - and sometimes even something that you
> don't want! - I just use a key binding that was unbound before.  So I
> simply don't use any kind of resetting.  Hitting the key (again) when it
> makes no sense may result in an error.  That's the case for other keys,
> too, so...
>
>
> Michael.

This would have the added bonus that you have more freedom to make that
new keybinding do "more stuff". The only downside is that the user has
to learn and remember a new key. My use-case is still bare-bones enough
that I'll stick with the transient key map, but your approach makes
plenty of sense.


Loading...