2

I need a keybinding that, given some text selected in visual mode (line visual mode, block visual mode, or whatever):

  • Add > _ before the text
  • Add _ after the text
  • In case of empty lines, > should output instead of > __

I came up with:

vim.api.nvim_set_keymap(
  "v",
  "<Leader>q",
  ":s/^/> _/<CR>:'<,'>s/$/_/<CR>:'<,'>s/> __/>/<CR>",
  {
    noremap = true,
    desc = "Quote selected text"
  }
)

As you can see, the first command starts with :s, while subsequent commands starts with :'<,'>s.

This is because, when you select some text and press :, vim will insert the text span '<,'> automatically, so if you put it again the first time it would effectively run :s'<,'>'<,'> and won't work.

I find this command confusing to read, as the text span in which the command is run is not specified the firsts time.

What is the best way to keybind subsequent substitutions in visual mode?

3 Answers 3

2

To avoid the double '<,'> problem the old way is to add <C-u> at the start of the mapping:

vim.api.nvim_set_keymap(
  "v",
  "<Leader>q",
  ":<C-u>'<,'>s/^/> _/<CR>:'<,'>s/$/_/<CR>:'<,'>s/> __/>/<CR>",
  {
    noremap = true,
    desc = "Quote selected text"
  }
)

What you can also do is reactivate the selection with the gv command in between the substitutions:

vim.api.nvim_set_keymap(
  "v",
  "<Leader>q",
  ":s/^/> _/<CR>gv:s/$/_/<CR>gv:s/> __/>/<CR>",
  {
    noremap = true,
    desc = "Quote selected text"
  }
)
1
  • 2
    There's also the * range which is equivalent to '<,'> and is far easier to type.
    – D. Ben Knoble
    Feb 6 at 14:19
1

The point is that Vim switches from mode to mode as it reads buttons. No matter if they come from keyboard or mapping. So you only start from Visual mode. But then you get into cmdline, then into Normal(!), then into cmdline again, etc. And so this is not "subsequent substitutions in Visual mode". They are very much different.

The mode switching is one of the reasons why Vim key mappings are hard to read and debug. So if you think one becomes too long then switch to commands/function calls using <cmd> notation or similar.

Though, in case of Visual mode you should also take into account that the marks '< and '> gets updated only when the Visual mode has been finished. There could be different solutions for this issue, but, maybe the simplest one, is to prepend <esc> to your mapping stopping Visual mode forcefully. So it becomes sort of <esc><cmd>call DoStuff()<CR>, while moving all the real code into DoStuff() function. Btw. then all the substs, including the first one, need to start with '<,'>.

0

I would probably write

xnoremap <leader>q :substitute/^\(.*\)$/\=empty(submatch(1)) ? '>' : printf('> _%s_', submatch(1))<enter>
" or
xnoremap <leader>q :substitute/^/> _/ | *substitute/$/_/ | *substitute/^> __/>/<enter>
" or
xnoremap <leader>q <cmd>call getline("'<", "'>")->map({_, val -> empty(val) ? '>' : printf('> _%s_', val)})->setline("'<")<enter>

where that latter really deserves to be wrapped in a function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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