Modify repeat keys?
Hi, I'm using this function to exit insert state with jk (use `(define-key evil-insert-state-map (cofi/maybe-exit ?j ?k))')
Sorry I f*cked the mime types up. here is my message again: Hi, I'm using this function to exit insert state with jk (use `(define-key evil-insert-state-map (cofi/maybe-exit ?j ?k))') #+begin_src emacs-lisp (defun cofi/maybe-exit (entry-char exit-char) "Return a a function that maybe inserts or calls `evil-normal-state'. Insert `ENTRY-CHAR', if it is followed by a `EXIT-CHAR' in the next half second, delete `ENTRY-CHAR' and call `evil-normal-state''." `(lambda () (interactive) (let ((modified (buffer-modified-p))) (insert ,entry-char) (let ((evt (read-event (format "Insert %c to exit insert state" ,exit-char) nil 0.5))) (cond ((null evt) (message "")) ((and (integerp evt) (char-equal evt ,exit-char)) (delete-char -1) (set-buffer-modified-p modified) (evil-normal-state)) (t (setq unread-command-events (append unread-command-events (list evt))))))))) #+end_src emacs-lisp It works fine except for repeating insertions. I tried to modify `evil-repeat-keys' before calling `evil-normal-state' — something along the lines of (setq evil-repeat-keys (append (butlast evil-repeat-keys) [escape])) — but it didn't work and was greeted a `characterp' error. I also tried to access the `evil-repeat-ring' after the call to `evil-normal-state' but it was still empty. So please help me out here: How can I modify the repeat keys correctly? Thanks, Michael
On Thu, Aug 11, 2011 at 01:56:05AM +0200, Michael Markert wrote:
Hi,
I'm using this function to exit insert state with jk
You found a point where the evil already has an answer ;) The following code should work (I removed the lambda-trick because you need 'evil-define-command' so this is easier). #+begin_src emacs-lisp (define-key evil-insert-state-map "j" #'cofi/maybe-exit) (evil-define-command cofi/maybe-exit () :repeat change (interactive) (let ((modified (buffer-modified-p))) (insert "j") (let ((evt (read-event (format "Insert %c to exit insert state" ?k) nil 0.5))) (cond ((null evt) (message "")) ((and (integerp evt) (char-equal evt ?k)) (delete-char -1) (set-buffer-modified-p modified) (push 'escape unread-command-events)) (t (setq unread-command-events (append unread-command-events (list evt)))))))) #+end_src emacs-lisp The only difference is the keyword ':repeat change' and instead of switching back to normal mode the 'escape event resend (given that this event still exits insert-mode, otherwise you need more work). Let me explain how this works. Evil knows two different ways of recording changes. The first simpler way is to record the key-strokes, which is the default for (almost) all commands. This is usually what you want and this approach handles some cases like automatic identation very well. But your function is ... evil. Its behaviour depends on time and therefore the function is not recordable as key-strokes. Even if the switch back to normal-state would work, it would be impossible to repeat the two characters "jk" in some word, because when repeated those two commands would always follow very quickly and therefore exit insert-state, no matter how big the pause between the two original key-strokes has been. The same problem arises, e.g., for some auto-completion packages which popup possible completions after some time. For these situations Evil has a second way to record repeation, namely by buffer changes. Instead of recording the key-sequences the buffer changes itself are recorded (and the key-strokes are dropped). The keyword ':repeat change' makes Evil repeat your function by change instead of by key-strokes. This means when "jk" is repeated the function cofi/maybe-exit is never called again. Instead the buffer change itself is replayed, which is either the insertion of "jk" or the insertion and deletion of "j". Furthermore, if "jk" is used to exit insert-state, the extra event 'escape is appended to the repeat-information (i.e., the whole repeat-information for the insertion consists of the command to start insert-state, some keys that are inserted literally, some buffer changes caused by "jk" and finally a key-stroke sequence [escape]) which is the same as pressing escape. I hope this helps. Frank
On 11 Aug 2011, Frank Fischer wrote:
On Thu, Aug 11, 2011 at 01:56:05AM +0200, Michael Markert wrote: You found a point where the evil already has an answer ;)
I hoped for this :)
The following code should work (I removed the lambda-trick because you need 'evil-define-command' so this is easier).
Thanks, it works great! But is there a way to produce a parameterized command like my function did? Short of extra variables/constants or a lexical-let on binding?
Let me explain how this works.
Thanks for the great explanation!
... and finally a key-stroke sequence [escape]) which is the same as pressing escape.
Oh yes, that would have been too easy. But it's better I didn't think of this and asked :) Thanks, Michael
On Thu, Aug 11, 2011 at 12:40:20PM +0200, Michael Markert wrote:
On 11 Aug 2011, Frank Fischer wrote:
On Thu, Aug 11, 2011 at 01:56:05AM +0200, Michael Markert wrote: You found a point where the evil already has an answer ;)
I hoped for this :)
The following code should work (I removed the lambda-trick because you need 'evil-define-command' so this is easier).
Thanks, it works great! But is there a way to produce a parameterized command like my function did? Short of extra variables/constants or a lexical-let on binding?
Currently the command-properties are stored in an alist that requires the key to be a symbol (the function name), therefore a command with a property like :repeat must be bound to a symbol, i.e., must be an ordinary function and not a lambda expression. The only idea I have is to use a macro: (defmacro cofi/define-maybe-exit (entry-char exit-char) (let ((name (intern (concat "cofi/maybe-exit-" (char-to-string entry-char) (char-to-string exit-char))))) `(progn (define-key evil-insert-state-map (char-to-string ,entry-char) #',name) (evil-define-command ,name () :repeat change (interactive) (let ((modified (buffer-modified-p))) (insert ,entry-char) (let ((evt (read-event (format "Insert %c to exit insert state" ,exit-char) nil 0.5))) (cond ((null evt) (message "")) ((and (integerp evt) (char-equal evt ,exit-char)) (delete-char -1) (set-buffer-modified-p modified) (push 'escape unread-command-events)) (t (setq unread-command-events (append unread-command-events (list evt))))))))))) and then (cofi/define-maybe-exit ?j ?k). Alternatively one would have to modify the functions `evil-put-property' and `evil-get-property' so they can handle lambda-expressions as well. Frank
On 11 Aug 2011, Frank Fischer wrote:
On Thu, Aug 11, 2011 at 12:40:20PM +0200, Michael Markert wrote:
Thanks, it works great! But is there a way to produce a parameterized command like my function did? Short of extra variables/constants or a lexical-let on binding?
Currently the command-properties are stored in an alist that requires the key to be a symbol (the function name), therefore a command with a property like :repeat must be bound to a symbol, i.e., must be an ordinary function and not a lambda expression. The only idea I have is to use a macro:
Thanks, I was just curious if there was something I overlooked. The only reason why I want this is the ease of adjustment. But defvars would do a better job there. Michael
On 2011-08-11 14:42 +0200, Frank Fischer wrote:
Alternatively one would have to modify the functions `evil-put-property' and `evil-get-property' so they can handle lambda-expressions as well.
Actually, it is possible to assign command properties to lambda functions already. I added this some time ago because I wanted to be able to define throwaway commands for testing purposes. There are two ways to set command properties for lambda functions: (1) Define the lambda function first and then pass it to `evil-add-command-properties'. (2) Use the macro `evil-define-command', specify the properties as keyword arguments, and pass nil for the command's name. The return value is a lambda function. (This behavior also extends to other definition macros, like `evil-define-motion' and `evil-define-operator'.) Command properties are stored in the association list `evil-command-properties', which accepts lambda functions for keys just as well as function symbols. (That is, the list contains a pointer to your lambda function.) -- Vegard
On Thu, Aug 11, 2011 at 03:35:31PM +0200, Vegard Øye wrote:
On 2011-08-11 14:42 +0200, Frank Fischer wrote:
Alternatively one would have to modify the functions `evil-put-property' and `evil-get-property' so they can handle lambda-expressions as well.
Actually, it is possible to assign command properties to lambda functions already. I added this some time ago because I wanted to be able to define throwaway commands for testing purposes.
There are two ways to set command properties for lambda functions:
(1) Define the lambda function first and then pass it to `evil-add-command-properties'.
Of course, you're right. The tricky thing was that I tested the following code: (let ((f '(lambda (x)))) (evil-add-command-properties f :repeat t) (evil-get-command-property f :repeat)) This code returns t the first time it is called but nil all other times, which confused me. I assume the reason is that add-to-list, which is used by `evil-put-property', uses equal internally instead of eq, and so the second time the lambda f is not added to evil-command-properties (there is already an equal lambda in there). Perhaps it's better either not to use `add-to-list' (so that non-eq lambdas are always considered different, also on put, otherwise it would be impossible to store two different lambdas that happen to be equal) or to use `assoc'. I think the first one is better. Frank
On 2011-08-11 16:02 +0200, Frank Fischer wrote:
The tricky thing was that I tested the following code:
(let ((f '(lambda (x)))) (evil-add-command-properties f :repeat t) (evil-get-command-property f :repeat))
This code returns t the first time it is called but nil all other times, which confused me.
My bad. `evil-put-property' and `evil-get-property' (and their derivations) should of course be fully symmetric -- if one uses `eq', so should the other.
Perhaps it's better either not to use `add-to-list' (so that non-eq lambdas are always considered different, also on put, otherwise it would be impossible to store two different lambdas that happen to be equal) or to use `assoc'. I think the first one is better.
I agree. Fixed in commit 5b5c0a7. -- Vegard
participants (3)
-
Frank Fischer -
Michael Markert -
Vegard Øye