When you press leader _
, you enter command-line mode from visual mode.
If you try to enter command-line mode from visual mode manually, you'll see that Vim automatically inserts this range:
:'<,'>
├┘ ├┘
│ └ mark put automatically on the last line of the visual selection
└ mark put automatically on the first line of the visual selection (see `:h '<`)
So, when :call
calls your MarkCodeBlock()
function, Vim has automatically prefixed it with a range.
And, from :h func-range
:
If [range]
is excluded, ":{range}call" will call the function for each line in
the range, with the cursor on the start of each line.
Because of this, your function is called once for every line in the range.
You have 2 possibilities:
Eliminate the range by making your mapping press C-u
(see :h c^u
):
vnoremap <leader>_ :<c-u>call MarkCodeBlock()<CR>
^^^^^
Make Vim know that the function can handle the range itself and doesn't need :call
to re-invoke it for every line in the range. You can do so by passing the range
argument to :function
:
function! MarkCodeBlock() range
^^^^^
...
endfunction
A few possible improvements/suggestions:
:vnoremap
is applied in 2 modes: visual and select.
But you're probably interested only in visual mode, because (from :h mapmode-s
):
NOTE: Mapping a printable character in Select mode may confuse the user.
And the lhs
of your mapping contains printable characters.
If you want your mapping to be applied only in visual mode, use :xnoremap
instead of :vnoremap
:
xnoremap <leader>_ :<c-u>call MarkCodeBlock()<CR>
^
If your leader
namespace becomes too crowded, and you need to offload some of the mappings to another namespace, you could use m
in visual mode. A m<key>
mapping won't shadow any Vim's built-in command, since you can't put a mark in visual mode.
xnoremap m_ :<c-u>call MarkCodeBlock()<CR>
^
Your MarkCodeBlock()
function is public, which means that its definition could collide with another public function with the same name. You could make it “private” (local to the script) by prefixing its name with s:
in your definition, and with <sid>
in your mapping. From :h <sid>
:
When defining a function in a script, "s:" can be prepended to the name to
make it local to the script. But when a mapping is executed from outside of
the script, it doesn't know in which script the function was defined. To
avoid this problem, use "<SID>
" instead of "s:". The same translation is done
as for mappings. This makes it possible to define a call to the function in
a mapping.
xnoremap m_ :<c-u>call <sid>MarkCodeBlock()<CR>
^^^^^
function! s:MarkCodeBlock()
^^
...
endfunction
If an error is raised while the statements in your function are being executed, you may want Vim to stop executing the next ones (this gives shorter stack traces). In this case, you can pass the abort
argument to :function
(see :h :func-abort
):
function! s:MarkCodeBlock() abort
^^^^^
...
endfunction
Instead of using :normal
to add lines in your buffer, you could use append()
(and line()
; see :h append()
and :h line()
). In general, the latter has the benefit of not making the cursor move.
execute "normal! `<O```\<esc>yy`>p"
⇔
call append(line("'<")-1, '```')
call append(line("'>"), '```')
If your Vim version includes the patch 8.1.0037, you can do the same to any buffer, not just the current one, using the appendbufline()
function instead of append()
.
All in all, this would give:
function! s:MarkCodeBlock() abort
" Add Markdown code-block delimiters to begin and end of current visual group.
call append(line("'<")-1, '```')
call append(line("'>"), '```')
endfunction
xnoremap m_ :<c-u>call <sid>MarkCodeBlock()<CR>