This is indeed a tricky one, since the column at both start and end will be the index of the first byte of a multi-byte character and subtracting them will give you a length in bytes and not in characters.
The way I got to find the contents of the selection was to use strpart()
twice, the first time using the start and end index to get all characters before the end character, then one more time to get exactly a single character at the end of the selection. strpart()
takes an optional fourth argument to request a character, so that works perfectly for the last part.
Together with some simplification (you can use col()
to get the column only, and you don't need to assemble a dictionary for the two positions), I got to this:
function! VSelection()
let start = col("'<")
let end = col("'>")
let l:w = strpart(getline('.'), start-1, end-start)
\ . strpart(getline('.'), end-1, 1, v:true)
echo 'Selection: ' | echohl Comment |
\ echon l:w | echohl NONE | echon ' len: ' . strchars(l:w)
endfunction
UPDATE: It seems the optional fourth argument of strpart()
is a recent addition to Vim and might not be available in NeoVim or older versions of Vim.
An alternative then is to use strpart()
to get the whole rest of the line starting at end
, and then strcharpart()
to take only the first character in that string. That way, we can use the byte index to access the rest of the string, but then recognize it as a full character.
function! VSelection()
let start = col("'<")
let end = col("'>")
let l:w = strpart(getline('.'), start-1, end-start)
\ . strcharpart(strpart(getline('.'), end-1), 0, 1)
echo 'Selection: ' | echohl Comment |
\ echon l:w | echohl NONE | echon ' len: ' . strchars(l:w)
endfunction
But perhaps a much simpler approach here is to simply use y
to yank the visual selection into a register and get the contents from that register. You can have your function save and restore the register around the y
operation.
function! VSelection()
let savereg = getreg('"')
let savetype = getregtype('"')
silent normal! gvy
let l:w = getreg('"')
call setreg('"', savereg, savetype)
echo 'Selection: ' | echohl Comment |
\ echon l:w | echohl NONE | echon ' len: ' . strchars(l:w)
endfunction
You need gv
to restore the last Visual selection (once you do :call
in your mapping you're no longer in Visual mode), and the silent
is so that Vim doesn't print "N lines yanked", which seems to interfere with the echo
s at the end.
Saving and restoring the register works flawlessly, once you're done with the function the default register is left in the exact same state it was before the call.
This approach also has an advantage of properly working with selections that spawn multiple lines, and also Visual Line and Visual Block selections.