OGCJN
OGCJN

Reputation: 393

Dot-repeatable mapping to insert a space from normal mode and move the cursor along

I have a mapping nnoremap <leader>l i<space><esc> that inserts a space while staying in a normal mode, and the mapping is dot-repeatable. However, the cursor stays where it was and the space "expands" to the right of the cursor.

I want to have a mapping that does the same but moves the cursor along, e.g.

nnoremap <leader>h i<space><esc>l

I love breaking sequences of characters with a space from normal mode and I want to decide at will if the cursor should stay or move (using either h to move the cursor along or l to move the text to the left while leaving the cursor where it is).

None of the tricks with i or a or <C-o> seems to work and this is an expected behavior.

Is there any clever hack to accomplish dot-repeatability using nnoremap only? It should also work if I am at the end of the line.

I can probably re-phrase it like this: I want to have functionality similar to X and x that deletes a single character before or at the cursor, but instead of deleting a character I want to insert a <space> before or at the cursor (not after the cursor as with a). And it should be dot-repeatable and take counts. Preferably a mapping that does not use more than a single line in the .vimrc and does not require any plugins. And I don't want to change the behaviour of <esc> (or i, whichever is responsible for moving the cursor when one leaves the insert mode) that moves a cursor one character to the left.

Edit 1: Dot-repeatable nnoremap <leader>l i<space><esc> does this:

<leader>l...

AAAAAAAAAA[B]BBBBBBBB
AAAAAAAAAA[ ]BBBBBBBBB
AAAAAAAAAA[ ] BBBBBBBBB
AAAAAAAAAA[ ]  BBBBBBBBBB

And I want a dot-repeatable <leader>h that would do this:

<leader>h...

AAAAAAAAAA[B]BBBBBBBB
AAAAAAAAAA [B]BBBBBBBB
AAAAAAAAAA  [B]BBBBBBBB
AAAAAAAAAA   [B]BBBBBBBBB

Edit 2:

The workaround mentioned in reply by @romainl does the trick:

function! s:insspace(...)
    if a:0
      " perform operation
      execute 'normal' v:count1.'i '."\<esc>".'l'
    else
      " set up
      let &operatorfunc = matchstr(expand('<sfile>'), '[^. ]*$')
      return "g@\<space>"
    endif
endfunction

nnoremap <silent><expr> <leader>h <sid>insspace()

Can someone explain how it works? I am a beginner in vim...

Upvotes: 2

Views: 235

Answers (2)

hochl
hochl

Reputation: 12950

Maybe I did not understand your question well enough, but for me this does the trick:

:nnoremap <leader>h i<space><esc>w

Rationale: Insert space, after <ESC> go one left, so the cursor stays on the space, use w to get to the next word boundary.

Is that what you want? I can now do 5\h and it stays at the current position while inserting spaces to the left.

Upvotes: 0

romainl
romainl

Reputation: 196751

Given the string below and assuming the cursor is on the -:

lorem-ipsum
     ^

There are various easy and repeatable ways to insert a space after the - and leave the cursor on the space:

a <Esc>
s<C-r>" <Esc>

lorem- ipsum
      ^

The two macros above…

  • are "dot-repeatable", because there is no motion involved,
  • leave the cursor on the space because, after an insertion, Vim places the cursor on the last inserted character.

But this is precisely the latter behaviour that prevents us from doing the same in the other direction without a motion either before or after the insertion.

Without motion, the operation is "dot-repeatable" but the cursor is left on the -:

i <Esc>
a <Esc>
s <C-r>"<Esc>

lorem -ipsum
      ^

With motion, the cursor is left on the space but the operation is not "dot-repeatable":

i <Esc><Left>
<Left>a <Esc>
s <C-r>"<Esc><Left>

lorem -ipsum
     ^

The workaround is a bit contrived but nifty.

Upvotes: 2

Related Questions