Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upReplace key-chord mode with hydra #129
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 22, 2015
You can achieve that modifying the behaviour using :post, something like this:
(defhydra hydra-change-mode (:color blue
:post (when (eq evil-state 'insert)
"If you exit the hydra without change mode, insert `j`"
(insert "j"))
:idle 1)
"change mode"
("j" (insert "j") "insert j")
("k" evil-normal-state "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)You have to use the j head to avoid to call another hydra when you want to type jj (you never know). And the idle 1 is to avoid to show the hydra when you are typing (makes it slower, because it stops you every time it shows) but at the same time you could see it if you wait 1 second.
I could suggest you this head too:
("?" (insert "k") "insert k")The ? is a suggestion of another improbable combination, j? (I think than more than jk) to insert jk as needed.
joedicastro
commented
May 22, 2015
|
You can achieve that modifying the behaviour using (defhydra hydra-change-mode (:color blue
:post (when (eq evil-state 'insert)
"If you exit the hydra without change mode, insert `j`"
(insert "j"))
:idle 1)
"change mode"
("j" (insert "j") "insert j")
("k" evil-normal-state "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)You have to use the I could suggest you this head too: ("?" (insert "k") "insert k")The |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
legendre6891
May 22, 2015
Thanks @joedicastro! Your solution works great, but it relies on knowing what k changed the mode. If I had another head that did something else (and stayined in insert mode, say), then the solution would not work.
I suppose a more general solution is to:
- Using
:pre, set somevariableequal tonil. - In each head, set
variabletonon-nil. - In post, check whether
variableisnilto see whether a head was pressed.
Is this possible, or is there a better general solution? (It would be nice if hydra-mode had some sort of builtin
mechanism for this.)
I actually don't know enough elisp to actually implement step 1 and 2, so any help there is appreciated to
P.S. It is possibel to insert jj and jk without setting up another head by doing C-q j j and C-q j k, respectively (assuming you didn't map C-q to something else). With the ("j" (insert "j") "insert j") head posted above, it is actually impossible to type an odd number of consecutive js -- but I suppose that case never comes up anyhow.
legendre6891
commented
May 22, 2015
|
Thanks @joedicastro! Your solution works great, but it relies on knowing what I suppose a more general solution is to:
Is this possible, or is there a better general solution? (It would be nice if I actually don't know enough elisp to actually implement step 1 and 2, so any help there is appreciated to P.S. It is possibel to insert |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 22, 2015
@legendre6891 Oh, I didn't know that you expected a more generic case
Well, you proposed solution can be implemented as following:
(defvar my-temporal-var nil)
(defhydra hydra-change-mode (:color blue
:pre (setq my-temporal-var nil)
:post (when (eq my-temporal-var nil)
"If you exit the hydra without change mode, insert `j`"
(insert "j"))
:idle 1)
"change mode"
("k" (progn (evil-normal-state) (setq my-temporal-var t)) "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)I don't know to much elisp either, so sure that code could be improved.
P.S. It is possibel to insert jj and jk without setting up another head by doing C-q j j and C-q j k, respectively (assuming you didn't map C-q to something else).
Oh, thanks! I didn't know that.
joedicastro
commented
May 22, 2015
|
@legendre6891 Oh, I didn't know that you expected a more generic case Well, you proposed solution can be implemented as following: (defvar my-temporal-var nil)
(defhydra hydra-change-mode (:color blue
:pre (setq my-temporal-var nil)
:post (when (eq my-temporal-var nil)
"If you exit the hydra without change mode, insert `j`"
(insert "j"))
:idle 1)
"change mode"
("k" (progn (evil-normal-state) (setq my-temporal-var t)) "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)I don't know to much elisp either, so sure that code could be improved.
Oh, thanks! I didn't know that. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
legendre6891
May 22, 2015
Alright thanks @joedicastro! The solution is good for now, but it could be improved in the following way:
- We have to set up a
my-temporal-varfor each hydra for which we want to detect exiting with a foreign key. - This breaks down if we want to nest hydras.
At a source code level, I think it would be enough if each hydra-*/body simply returned a value that indicated how it exited (i.e. give the value of the foriegn-key that caused the hydra to exit). I digged into the source code, but it wasn't clear how to do this; perhaps @abo-abo would be kind enough to give some pointers?
legendre6891
commented
May 22, 2015
|
Alright thanks @joedicastro! The solution is good for now, but it could be improved in the following way:
At a source code level, I think it would be enough if each |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 23, 2015
@legendre6891 Nesting hydras? more hydras? Oh, I see, so the initial problem was only an example... It could be done, but I'm curious, what do you pretend to do with this? cross-calling another hydras, perhaps?
I have a pair of a ideas of how to do this, but maybe @abo-abo could help you with this better.
joedicastro
commented
May 23, 2015
|
@legendre6891 Nesting hydras? more hydras? Oh, I see, so the initial problem was only an example... It could be done, but I'm curious, what do you pretend to do with this? cross-calling another hydras, perhaps? I have a pair of a ideas of how to do this, but maybe @abo-abo could help you with this better. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
legendre6891
May 23, 2015
@joedicastro Ah I just want to replace key-chord mode with hydra (the latter is more robust, it seems); the replacement needs the feature(s) above
I'm open to hearing your ideas, as well as @abo-abo's.
legendre6891
commented
May 23, 2015
|
@joedicastro Ah I just want to replace I'm open to hearing your ideas, as well as @abo-abo's. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 23, 2015
@joedicastro Ah I just want to replace key-chord mode with hydra (the latter is more robust, it seems); the replacement needs the feature(s) above
😄
You don't need to nest hydras to replace key-chord with hydra key-chord with hydra makes no sense.
Well, this solution maybe could fit you:
(defhydra hydra-change-mode (:color blue
:post (when
(not
(lookup-key hydra-curr-map (this-single-command-keys)))
"Check if the pressed key is in the hydra keymap, if not, it's a foreign key"
(insert "j"))
:idle 1)
"change mode"
("k" evil-normal-state "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)It's a solution that works without change anything in hydra
joedicastro
commented
May 23, 2015
You don't need to nest hydras to replace Well, this solution maybe could fit you: (defhydra hydra-change-mode (:color blue
:post (when
(not
(lookup-key hydra-curr-map (this-single-command-keys)))
"Check if the pressed key is in the hydra keymap, if not, it's a foreign key"
(insert "j"))
:idle 1)
"change mode"
("k" evil-normal-state "change to normal state"))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)It's a solution that works without change anything in hydra |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
legendre6891
May 23, 2015
Ah that is a good solution this-single-command-key.
My justification for replacing key-chord mode with hydra is that
- hydra mode is more powerful.
- More importantly, with
key-chordmode I couldn't define key chords that activated only when
both of the conditions are metevilis ininsert-mode- the current buffer is
python-mode(i.e. is using thepython-mode-keymap)
Or more generally, I can't mix n' match specifying which mode (within evil) that key chord should trigger while additionally specifying which key map is active. But this is easily accomplished with hydra via evil-define-key. (Perhaps there is a way to accomplish this in key-chord, but I am not aware of one.)
Nesting hydras may be necessary for key chords involving more than 2 keys, I think.
Anyway, thanks so much for your help. Would still be interested if @abo-abo chimes in, so leaving this open at the moment.
legendre6891
commented
May 23, 2015
|
Ah that is a good solution My justification for replacing
Or more generally, I can't mix n' match specifying which mode (within evil) that key chord should trigger while additionally specifying which key map is active. But this is easily accomplished with hydra via Nesting hydras may be necessary for key chords involving more than 2 keys, I think. Anyway, thanks so much for your help. Would still be interested if @abo-abo chimes in, so leaving this open at the moment. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 23, 2015
@legendre6891 My pleasure
I still think that you are over-thinking this, introducing too much overhead.
You could do something more simple like this, activating key-chord by mode:
(add-hook 'python-mode-hook (key-chord-define evil-insert-state-map "jk" 'evil-normal-state))
joedicastro
commented
May 23, 2015
|
@legendre6891 My pleasure I still think that you are over-thinking this, introducing too much overhead. You could do something more simple like this, activating (add-hook 'python-mode-hook (key-chord-define evil-insert-state-map "jk" 'evil-normal-state)) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
abo-abo
May 23, 2015
Owner
Thanks @joedicastro, you're better at solving this problem, since I don't have experience with key chords or evil.
It's important to completely understand what the problem is, in order to solve it.
Otherwise, we'd be just re-implementing (a possibly worse) key-chord-mode in hydra.
My current opinion is that hydra shouldn't replace key-chord for general Emacs. The reason is that it would add more buggy edge cases, and when hydra has a bug, it can become unpleasant: sometimes up to the point all bindings stop working and Emacs has to be killed from outside. I don't recall a bug report like this, but it happens sometimes when I'm experimenting with the code.
But for evil-mode it should be possible, since you can just use this:
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)
No bug-prone timers or post-command-hooks: just a plain define-key.
Here's a simple hydra:
(defhydra hydra-change-mode (:color blue
:body-pre (insert "j")
:idle 1.0)
("k" (progn
(delete-char -1)
(message "ohai"))))It will actually insert "j" just as you type it, and then delete it only if you press k afterwards.
Here's one modified slightly to account for prefix arguments, e.g. C-u jk should work, although it's an unlikely situation:
(defhydra hydra-change-mode (:color blue
:body-pre
(progn
(let ((pt (point)))
(let ((buffer-undo-list t))
(call-interactively 'self-insert-command))
(push (cons pt (point))
buffer-undo-list)))
:idle 1.0)
("k" (progn
(primitive-undo
(if (null (car buffer-undo-list)) 2 1)
buffer-undo-list)
(message "ohai"))))|
Thanks @joedicastro, you're better at solving this problem, since I don't have experience with key chords or evil. It's important to completely understand what the problem is, in order to solve it. My current opinion is that hydra shouldn't replace key-chord for general Emacs. The reason is that it would add more buggy edge cases, and when hydra has a bug, it can become unpleasant: sometimes up to the point all bindings stop working and Emacs has to be killed from outside. I don't recall a bug report like this, but it happens sometimes when I'm experimenting with the code. But for evil-mode it should be possible, since you can just use this:
No bug-prone timers or post-command-hooks: just a plain Here's a simple hydra: (defhydra hydra-change-mode (:color blue
:body-pre (insert "j")
:idle 1.0)
("k" (progn
(delete-char -1)
(message "ohai"))))It will actually insert "j" just as you type it, and then delete it only if you press k afterwards. Here's one modified slightly to account for prefix arguments, e.g. C-u jk should work, although it's an unlikely situation: (defhydra hydra-change-mode (:color blue
:body-pre
(progn
(let ((pt (point)))
(let ((buffer-undo-list t))
(call-interactively 'self-insert-command))
(push (cons pt (point))
buffer-undo-list)))
:idle 1.0)
("k" (progn
(primitive-undo
(if (null (car buffer-undo-list)) 2 1)
buffer-undo-list)
(message "ohai")))) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
joedicastro
May 23, 2015
@abo-abo The simple hydra is a very clever solution, if we change the message by what @legendre6891 wanted, changing the mode to come back to the normal mode, you have an amazing light solution:
(defhydra hydra-change-mode (:color blue
:body-pre (insert "j")
:idle 1.0)
("k" (progn
(delete-char -1)
(evil-normal-state))))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)Works perfectly fine. Thanks Oleh!
The other hydra, is less useful because the prefix arguments works in other way in Evil/Vim and AFAIK almost nobody that uses Evil uses the standard Emacs C-u prefix. Also the Evil prefix works perfectly fine with the simple solution. For example if you wanted to write jim 5 times you could do it in this way: 5ijimESC. What @legendre6891 pretends is to use the chord jk to avoid to press ESC to come back to the normal mode from the insert mode (I personally prefer to map the Caps Lock key as an additional Ctrl key and the use the xcape tool to use that key as an ESC key when is pressed alone). The Vimmers and formal Vimmers (like me) are kinda obsessed with try to keep the fingers the most time in the main row and avoid to use the "suburban" keys.
joedicastro
commented
May 23, 2015
|
@abo-abo The simple hydra is a very clever solution, if we change the message by what @legendre6891 wanted, changing the mode to come back to the normal mode, you have an amazing light solution: (defhydra hydra-change-mode (:color blue
:body-pre (insert "j")
:idle 1.0)
("k" (progn
(delete-char -1)
(evil-normal-state))))
(define-key evil-insert-state-map (kbd "j") 'hydra-change-mode/body)Works perfectly fine. Thanks Oleh! The other hydra, is less useful because the prefix arguments works in other way in Evil/Vim and AFAIK almost nobody that uses Evil uses the standard Emacs C-u prefix. Also the Evil prefix works perfectly fine with the simple solution. For example if you wanted to write |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
legendre6891
May 24, 2015
Ah @abo-abo's solutions above are actually quite nice. The use of :body-pre solves the problem.
And thanks again, @joedicastro; however
(add-hook 'python-mode-hook (key-chord-define evil-insert-state-map "jk" 'evil-normal-state))
doesn't quite work, because this puts jk into the evil-insert-state-map once I visit any python file (e.g. if I visit a .pl file afterwards, the binding jk is still present).
Anyway, thanks everyone!
P.S. The feature :body-pre is only documented on @abo-abo's blog posts -- it does not have documention here on github. Are there plans to update the documentation?
legendre6891
commented
May 24, 2015
|
Ah @abo-abo's solutions above are actually quite nice. The use of
doesn't quite work, because this puts Anyway, thanks everyone! P.S. The feature |
legendre6891 commentedMay 22, 2015
Is it possible to replace
key-chordmode with hydra.For example, I am trying to replicate the following behavior:
with
but the problem is that if I take
j*, where*is any character not equal tok, then the the head exits without insertingj*in the buffer.Is there a way to attach test whether a hydra exits via a blue keypress vs. a foreign key?