3

Situation

Recently I was investigating the behaviour of word motions in vim and to me it seems like ge is grossly inconsistent with the other 3. Here is an example with ^ showing the cursor:

word1 word2        de:  wo word2         dw:   woword2
  ^

word1 word2        db:  word1 rd2        dge:  wordd2
        ^

It seems to me that dge is inconsistent in 2 ways:

  1. It deletes the character the cursor is on (inconsistent with db)
  2. It deletes the last character of the previous word (inconsistent with dw)

What I tried

The following fixed the issue with leaving the character the cursor was on:

onoremap ge vge

And the following fixes the issue with leaving the last character on the previous word:

onoremap <expr> ge ":<c-u>normal! gel<cr>"

But I cannot find a convenient way to combine these two behaviours.

Question

How can I remap ge so it behaves this way:

word1 word2        dge:  word1rd2
        ^

Also, maybe my perception about ge being inconsistent is wrong?
Would be very interested in hearing comments disputing that.

3

One easy way to fix ge is to make a backwards search:

onoremap ge :call search('\>', 'b')<CR>

You can use a more complex regex if you want.

Or, you could also move to the right position using the normal command:

onoremap ge :normal!ge<SPACE><CR>

" Or, with working counts (version from @Karolis_Koncevičius):

onoremap ge :execute "normal! " . v:count1 . "ge<space>"<cr>

Note that we use <SPACE> here, and not l or <RIGHT>, because <SPACE> will allow us to get back on the right line if we use dge on the fist word of the line. Disclaimer - This breaks if the line above ends with whitespaces.


As you underline in the comments, those 2 functions can break in some cases. For a foolproof ge, I see no other way than to write a small function:

function! BetterGE()
    let curline = line('.')
    normal! gel

    if line('.') != curline
        call setpos('.', [0, curline, 1, 0])
    endif
endfunction

onoremap ge :call BetterGE()<CR>

This function will call the default ge, move one character to the right, and if the line is not the same as the initial one, we bring back the cursor on the 1st column of the initial line.

This doesn't seem to break in any of the mentioned cases.


The ge motion seem inconsistent indeed, but it makes sense: it's an inclusive character-wise motion, which means that:

When inclusive, the start and end position of the motion are included in the operation.

(from :h inclusive)

So that's why the character under the cursor and the last character of the previous word are deleted

Also, as @filbranden noted:

exclusive motions are always exclusive to the right, not to the direction the motion is going. That's why db being an exclusive motion excludes the character the cursor was on... And that's why ge isn't an exclusive motion, because exclusive-to-the-left would make sense, but the regular exclusive doesn't make as much sense

| improve this answer | |
  • 1
    Regarding the inconsistency, I'd explain that that's mainly because exclusive motions are always exclusive to the right, not to the direction the motion is going. That's why db being an exclusive motion excludes the character the cursor was on... And that's why ge isn't an exclusive motion, because exclusive-to-the-left would make sense, but the regular exclusive doesn't make as much sense... – filbranden Apr 19 at 14:08
  • 2
    @Zorzi thank you for the answer, it goes into the right direction, but both maps are still lacking. The first one for example goes to the end of the word but it fails to go to non-keyword characters. The second one will fail in some situations, like working on the first word of the line. – Karolis Koncevičius Apr 19 at 14:12
  • 1
    Yup, well spoted. I added a 3rd solution which should work! – Zorzi Apr 19 at 14:34
  • 1
    @Zorzi, I think I found a better solution: we can replace the l with <space> in the gel command, so it becomes ge<space>. If you do not see any downsides to that, maybe you can add that, and I will accept the answer? – Karolis Koncevičius Apr 19 at 15:34
  • 2
    @filbranden very good observation, currently it will not work with counts, but here is the version that does: onoremap E :execute "normal! " . v:count1 . "ge<space>"<cr>. It probably still will be missing a few edge cases, like you mentioned – Karolis Koncevičius Apr 19 at 17:36

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.