Search code examples
vimkeyboard-shortcutsmove

How to use v:count in Vim to switch line with line n lines away?


I would like to create a mapping in vim that would switch the current line with a line that is n lines away.

So far, I have something like this:

:nnoremap gl:<C-U> dd{v:count1}jO<Esc>pjdd{v:count1}o<Esc>p

But it does not work. How can I use the v:count variable in this context?


Solution

  • Mappings are evaluated exactly as typed. Things like v:count are special Vimscript variables, so its value somehow needs to be interpolated into the keys of the right-hand side. With mappings, there are two possibilities:

    execute

    With :execute, you can break out from the normal mode (started by :nnoremap) into Ex mode and assemble the command; you need :normal! to run the normal mode command(s) that were previously just put as the right-hand side. As Vim automatically translates any [count] given to the mapping into an Ex range, <C-u> is necessary to clear that.

    nnoremap <silent> gl :<C-u>execute 'normal! yyma' . v:count1 . "jVp'aVp"<CR>
    

    map-expr

    The <expr> modifier adds a layer of indirection to mappings: the right-hand side is evaluated and only the result is taken as the commands to run. There's a similar complication with [count] here; a preceding <Esc> command cancels it (so you only yank a single line).

    nnoremap <expr> gl '<Esc>yyma' . v:count1 . "jVp'aVp"
    

    your implementation

    I assume you chose gl as the left-hand side, and the :<C-U> is an attempt at escaping, like I've done with my first approach. The space that separates left-hand side from right-hand side is missing there, and the :normal, too.

    I've made the following changes to the implementation:

    • Deleting and pasting next to it complicates the counting; instead, I've used paste over a visual selection (Vp); this automatically puts the removed text into the unnamed register (so it can be reused for the second part of the swap).
    • Instead of using the count a second time to jump back, I've set a mark (ma) for simplicity. You might also be able to employ the jump list (<C-o>) for this.

    other implementations

    For simple swaps, your implementation might suffice, but it's far from perfect. There might be a desire to swap multiple (e.g. visually selected) lines, redo a swap elsewhere with ., and check that the destination line actually exists.

    The unimpaired.vim plugin has a swap mapping [e. My LineJuggler plugin also has a robust and universal implementation (as well as many other related ones).