Combining two operators in Evil-mode Emacs

7

3

In vim I've remapped > and < when in visual mode to >gv and <gv respectively, like so:

vnoremap > >gv
vnoremap < <gv

Since my target for this question are folks experienced with emacs and not vim, what > and < do is indent/dedent visually selected text. What gv does is reselect the previously selected text. These maps cause > and < to indent/dedent and then reselect the previously selected text.

I'm trying out emacs with evil-mode and I'd like to do the same, but I'm having some difficulty figuring out how, exactly, to accomplish the automatic reselection.

It looks like I need to somehow call evil-shift-right and evil-visual-restore sequentially, but I don't know how to create a map that will do both, so I tried creating my own function which would call both sequentially and map that instead, but it didn't work, possibly due to the fact that both of them are defined, not as functions with defun but instead as operators with evil-define-operator.

I tried creating my own operators:

(evil-define-operator shift-left-reselect (beg end)
    (evil-shift-left beg end)
    (evil-visual-restore))

(evil-define-operator shift-right-reselect (beg end)
    (evil-shift-right beg end)
    (evil-visual-restore))

but that doesn't restore visual as expected. A stab in the dark gave me this:

(evil-define-operator shift-left-reselect (beg end)
    (evil-shift-left beg end)
    ('evil-visual-restore))

(evil-define-operator shift-right-reselect (beg end)
    (evil-shift-right beg end)
    ('evil-visual-restore))

but that selects one additional line whenever it is supposed to reselect.

For now I've been using the following, which only has the problem where it reselects an additional line in the < operator.

(evil-define-operator shift-right-reselect (beg end)
  (evil-shift-right beg end)
  (evil-visual-make-selection beg end))

(evil-define-operator shift-left-reselect (beg end)
  (evil-shift-left beg end)
  (evil-visual-make-selection beg end))

and I've mapped them:

(define-key evil-visual-state-map ">" 'shift-right-reselect)
(define-key evil-visual-state-map "<" 'shift-left-reselect)

any help / pointers / tips would be greatly appreciated.

Thanks in advance.

edit:

I'd like to be able to essentially do this:

(define-key evil-visual-state-map ">" (kbd ">gv"))
(define-key evil-visual-state-map "<" (kbd "<gv"))

But since there's no concept of a default mapping for > or < the idea of a recursive mapping doesn't make any sense, and this has all the problems you'd expect from such a map. The following works, however, but it's ugly since it requires the addition of two throw away maps. Is there any way to avoid doing this?

(define-key evil-visual-state-map "g>" 'evil-shift-right)
(define-key evil-visual-state-map "g<" 'evil-shift-left)
(define-key evil-visual-state-map ">" (kbd "g>gv"))
(define-key evil-visual-state-map "<" (kbd "g<gv"))

If enough time goes by I'll take this, create an answer, and accept it so that others who find this question can easily find/use it.

mkomitee

Posted 2012-09-02T21:48:14.060

Reputation: 596

Would it be a horrible transgression for me to cross post this to unix.stackexchange.com, in the hope of getting more eyes on it? – mkomitee – 2012-09-03T19:27:53.630

1I'm not sure about unix, but how about Stack Overflow? Also, have you approached the author of evil and/or any discussion group(s) that may exist for it? – echristopherson – 2012-09-04T00:27:04.153

I'd rather go talk to the people in the project: mailign list and IRC are listed at http://emacswiki.org/emacs/Evil

– Johan Lindstrom – 2012-09-13T14:39:09.740

I'm trying to understand what you want to do. Does indent-according-to-mode (to indent/unindent) and C-x C-x (to reselect) do (even roughly) what you want ? Because if so, we can work a function and then a keybinding around that. – yPhil – 2012-10-01T09:08:09.540

This is not really an answer to your question, but why did you remap '>' in Vim? Is it only so you can continue indenting by hitting the > key again (while actually seeing what happens)? In that case you could use the dot-operator (.) to repeat indenting so you don't need the mapping. Instead of doing 'V3j>>>' (with your mapping) you can do 'V3j>..' with default vim keybindings. Maybe this works in evil-mode as well? – jeroen – 2012-10-16T12:05:48.297

Answers

4

The evil-shift-right command needs a beginning and end argument, with the requirement that the beginning argument is less than the end argument. One way of achieving this is the following:

(define-key evil-visual-state-map ">" (lambda ()
    (interactive)
    ; ensure mark is less than point
    (when (> (mark) (point)) 
        (exchange-point-and-mark)
    )
    (evil-normal-state)
    (evil-shift-right (mark) (point))
    (evil-visual-restore) ; re-select last visual-mode selection
))

(define-key evil-visual-state-map "<" (lambda ()
    (interactive)
    ; ensure mark is less than point
    (when (> (mark) (point)) 
        (exchange-point-and-mark)
    )
    (evil-normal-state)
    (evil-shift-left (mark) (point))
    (evil-visual-restore) ; re-select last visual-mode selection
))

Mark

Posted 2012-09-02T21:48:14.060

Reputation: 276

2

I ended up using the following:

(define-key evil-visual-state-map (kbd "<") (lambda ()
  (interactive)
  (evil-shift-left (region-beginning) (region-end))
  (evil-normal-state)
  (evil-visual-restore)))
(define-key evil-visual-state-map (kbd ">") (lambda ()
  (interactive)
  (evil-shift-right (region-beginning) (region-end))
  (evil-normal-state)
  (evil-visual-restore)))

swift

Posted 2012-09-02T21:48:14.060

Reputation: 71