1

I develop a zsh plugin which is a port of vim-easymotion for zsh in vi emulation mode. Since the zsh line editor has multiline support, I added support for line motions (j and k). The plugin should also work for vi operator pending mode (for example in deletion mode), so I used the MARK and CURSOR variables of zsh to extend the user selection to complete lines (so a line motion will always delete or change whole lines and not only parts of them). This is the behavior a vim user would expect. However, the cursor position after a change/deletion operation is wrong. The cursor should be on the first word on the next line after a deleted block of lines. Instead, the cursor is at the end of the last line before the block of lines. I don't see a possibility to change the cursor after the operator pending mode since any cursor change will be part of the region for the selected operator.

So my question is: Does anyone know how the cursor position can be changed in a zsh custom widget after a vi operator was executed?

Update:

To make the question understandable, I tried to create a minimal example of the problem:

This small zsh script can be sourced:

function test-widget () {
    MARK=3
    CURSOR=6
}

zle -N test-widget

bindkey -M vicmd ' ' test-widget

If you are in zsh's vicmd/normal mode (bindkey -v to activate the vi emulation and <esc> to enter the command mode), you can now press <space> to set the cursor to the sixth character in the current edit buffer. If you enter a vi operator like d followed by <space>, everything from the third to the sixth character will be deleted (due to the setting of the MARK variable).

Deletion example: Consider your edit buffer is (cursor position marked with []):

[a]bc def ghi

and you press d<space>, you will get:

abc[f] ghi

However, I would like to change the position of the cursor after the deletion operation in the same widget call, e.g.:

abcf [g]hi

I don't know how this would be possible since all changes to CURSOR would affect the range of deleted text.

How can I achieve a deletion operation + a cursor positioning?

2
  • I have updated my question trying to make things a bit easier to understand.
    – IngoMeyer
    Jul 28 '20 at 6:07
  • Thanks. It's much clearer with that example. It sounds to me that you would have to wrap the widget bound to d (vi-delete) here. You could also try asking on the zsh-users mailing list. Jul 28 '20 at 6:22
0

It sounds to me you would have to redefine the widgets that take a motion, like the ones bound to d, c, y, gu, gU, g~...

Something like:

test-widget () {
  MARK=3
  CURSOR=6
  MOVE_AFTER_MOTION=2
}
zle -N test-widget
bindkey -M vicmd ' ' test-widget

for widget in vi-{delete,change,yank,{down,oper-swap,up}-case}; do
  $widget () {
    local ret
    zle ".$WIDGET"; ret=$?
    if (($+MOVE_AFTER_MOTION)); then
      (( CURSOR += MOVE_AFTER_MOTION ))
      unset MOVE_AFTER_MOTION
    fi
    return "$ret"
  }
  zle -N $widget
done

You may also have to think of what to do when $NUMERIC is not 1.

3
  • Works perfectly for my use case, thanks!
    – IngoMeyer
    Jul 28 '20 at 6:35
  • Does substitute / s take a motion?
    – IngoMeyer
    Jul 28 '20 at 7:28
  • No, sorry my bad. Fixed now. Jul 28 '20 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.