Using execute for visual command in vim - vim

I know this must be something simple.
Why does vnoremap <leader>rl di[<esc>pa] wrap selected text in brackets, but:
vnoremap <leader>rl :call VisAddRefLink()<CR>
function! VisAddRefLink()
execute "normal! di[\<esc>pa]"
endfunction
doesn't?!
Any help appreciated!

Try this:
vnoremap <leader>rl :<C-u>call VisAddRefLink()<CR>
function! VisAddRefLink()
execute "normal! gvdi[\<esc>pa]"
endfunction
The <C-u> in front of the call avoids that the mapping inserts the '<,'> visual range in front of it; we want the function called only one time, not once for every line in the range.
Inside the function, we need to re-establish the visual selection first before operating on it; the gv does that.

You need to make your function handle a range. One way of accomplishing this would be like this:
vnoremap <leader>rl :call VisAddRefLink()<CR>
function! VisAddRefLink() range
exe a:firstline . "normal! ^i["
exe a:lastline . "normal! $a]"
endfunction
The reason your example isn't working is because the exe doesn't operate on a visual selection per say. For example, try visually selection something and then doing :norm d. You'll notice it doesn't delete. If you add a range to your function like :help function-range-example it helps with visual selections by operating in a similar fashion (line by line). However, it still isn't a true visual selection. The range addition does allow you the a:firstline and a:lastline variables though, which can be used to accomplish this. You can also accomplish this with a single like in this manner:
vnoremap <leader>rl <esc>:norm! '<^x2Phr['>$x2pr]<cr>
This first uses <esc> to end the visual selection. Then it executes a normal command that will only run once. If the visual selection was left then it would run once for each line in the visual selection. Once it's run once it
'<^ jumps to the first line of the visual selection and to the first non-blank space on that line.
x2Phr[ deletes that character, pastes it twice in front, moves to the left so we're over the new character, and replaces it with the opening [
'>$ move to the last character on the last line of the visual selection
x2pr] same as before but in the opposite direction
As usual, there's more than one way to skin a cat, especially with vimscript. As you learn more you see a lot of possibilities for accomplishing things.

Related

Mark selections in Vim

I sometime dream of a mark-selection feature in Vim, just like when you type:
ma
in normal mode, you can then hit, from anywhere:
`a
to get back to this place a.
I wish there were something like:
ma
in visual mode. This would save your visually selected area. And you would just type then, from any place in normal mode:
<someHeader>a
to enter visual mode back with this a selection.
Is there such a feature in Vim? Or an extension that looks like? Or an easy way to implement it?
I'm fairly certain there isn't any way to name selections similar to how marks or registers can be referenced.
The closest feature that I can think of is the gv command which enters Visual mode with the last previously selected block already re-selected. I find this to be a lot more convenient than having to manually re-selecting the same block of text more than once.
It should be possible to write a function using Vimscript that saves the start and ends of a visual block as marks that can be re-used to reselect a Visual block. I had a look to see if it was possible to use Vimscript to save the < and > marks as other (less ephemeral) marks but I didn't see anything.
I have written this which I believe is what you are asking for:
function! VisualMark()
call inputsave()
let registers = input("m")
call inputrestore()
exec "normal! `<m" . registers[0]
exec "normal! `>m" . registers[1]
endfun
function! GetVisualMark()
call inputsave()
let registers = input("`")
call inputrestore()
exec "normal! `" . registers[0]
if strlen(registers) > 1
exec "normal! v`" . registers[1]
endif
endfun
vnoremap m <esc>:call VisualMark()<cr>
nnoremap ` :call GetVisualMark()<cr>
they should behave in the same way as a regular mark except only in visual mode, and they both need two arguments.
First, to get a visually selected area saved, in visual mode with the block selected, hit the m key like you normally would. It will then let you type in some more text... here the function will expect 2 characters which are the two registers that the beginning and end marks will be saved to... so for example entering mab in visual mode and then pressing enter (I could not figure out a way to make it work without an enter), would create a mark in register a for the beginning of the block, and b would be the end of the block.
To reselect the block visually, go to visual mode and do the ` which is the same as a regular mark... this too will expect two characters
EDIT:
I have made the implementation better in my opinion. Before I had it so that to bring back the visually selected block you had to be in visual mode then hit the backtick (same button that you would use for a normal mark) button and then the 2 registers. Now you do it in normal mode... This means that the default use of the backtick now uses this function, but the function now checks how many registers you provide... so if you only use one register, it will only take you back to that one. if you provide 2 registers, then it will visually select them... if you provide more than 2, it will only use the first 2.
The enter key is still required to enter in your selection though. It is probably better this way with the new implementation anyways.
For those that want to see, or use the old implementation this is it:
function! VisualMark()
call inputsave()
let registers = input("m")
call inputrestore()
exec "normal! `<m" . registers[0]
exec "normal! `>m" . registers[1]
endfun
function! GetVisualMark()
call inputsave()
let registers = input("`")
call inputrestore()
exec "normal! `" . registers[0]
exec "normal! v`" . registers[1]
endfun
vnoremap m <esc>:call VisualMark()<cr>
vnoremap ` <esc>:call GetVisualMark()<cr>
Here is the kind solution of Steven Hall, along with some modifications I needed for the visual marks not to overwrite regular registers.
https://github.com/iago-lito/vim-visualMarks
It is a small vimScript allowing one to mark a visually selected area by typing, in visual mode:
ma
(for mark a)
Then retrieve it later from any place by typing, in normal mode:
<a
It does still need some basic improvements, but now anyone can try, improve and share. Thank you Steven for having launched the process :)

Define an operator to enclose text in braces without going into visual mode?

This function defines an operator which encloses in braces text moved over in normal mode only. The operator works just fine as it is.
function! EncloseInBraces(type)
exe "normal! `[v`]\<esc>a}\<esc>`<i{\<esc>"
endfunction
nnoremap <f4> :set opfunc=EncloseInBraces<CR>g#
However, it needs to go into visual mode in order to position `< where '[ was.
How can I define such an operator without going into visual mode?
You don't need to use visual mode to apply the operator; it's just a convenient way to implement an operator. To illustrate, here is a variant of your operator function that uses a named mark (via ma / `a) to save and return to the beginning of the moved-over text:
function! EncloseInBraces(type)
exe "normal! `[ma`]a}\<esc>`ai{\<esc>"
endfunction
You could also use getpos() / setpos(); that's more cumbersome, but would avoid clobbering the visual selection or a mark. Or you could use those functions to save / restore the '<,'> marks in your function.

vimscript: Get visual mode selection text in mapping

So I'm trying to have a binding that runs lvimgrep on the currently selected text.
fun! s:get_visual_selection()
let l=getline("'<")
let [line1,col1] = getpos("'<")[1:2]
let [line2,col2] = getpos("'>")[1:2]
return l[col1 - 1: col2 - 1]
endfun
vnoremap <expr> <script><leader>* ":lvimgrep /" . <SID>get_visual_selection() . "/j **/*." . expand("%:e") . " \|lopen"
The function's from the comment on the question: How to get visually selected text in VimScript
Thing is that it is behaving really weird: Most of the times the text returned by the function doesn't match the visual selection and more often than not, it is the text of the last visual selection - not the current one.
Have gone through tons of posts around getting visual selection text in vimscript but can't get it to work.
I have also tried https://stackoverflow.com/a/1534347/287085 without success (copying selection to register) - get an error when called from my binding.
The problem is that the '<,'> marks are not set until after the current selection has been left (either by executing a command on it, or through <Esc>). Here, your expression mapping makes it more complex to prepend an <Esc> to leave visual mode first, so it's easier to insert the expression with :help i_CTRL-R and the expression register =:
:vnoremap <script> <leader>* <Esc>:lvimgrep /<C-R><C-R>=<SID>get_visual_selection()<CR>/j **/*.<C-R><C-R>=expand("%:e")<CR>\|lopen
If you don't mind clobbering the default register, you could also just yank the selection:
:vnoremap <leader>* y:lvimgrep /<C-R><C-R>"/j **/*.<C-R><C-R>=expand("%:e")<CR>\|lopen

How to paste a block while creating the necessary lines to give room for the block solely?

In VIM, text block yanking in Visual Mode, and pasting the block afterwards, paste it after the desired column given by the cursor, but pastes in-place, overwriting contents of the current and following lines.
Sometimes I don't want this, what I want is to paste a block with the indentation given by the cursor position, but pasting inside new empty lines, without overwriting text.
Is there a way to do that?
Currently, to achieve this, I create a good amount of empty lines, and then paste the block, eliminating the remaining empty lines after (not very clever... ).
Note: I use set virtualedit=all to be able to paste at any column in the said empty lines.
You can try something like the following. Block-wise yank something, position the cursor and hit <Leader>p, whatever your leader key is.
function! FancyPaste()
let paste = split(#", '\n')
let spaces = repeat(' ', col('.')-1)
call map(paste, 'spaces . v:val')
call append(line('.'), paste)
endfunction
nnoremap <Leader>p :call FancyPaste()<CR>
You can of course change the mapping to be anything you want; it's just a suggestion.
Update: Here's a version that accepts an argument. This let's you e.g. paste from the system clipboard instead. It also uses virtcol() instead of col() to take account for the possible use of 'virtualedit':
function! FancyPaste(reg)
let paste = split(getreg(a:reg), '\n')
let spaces = repeat(' ', virtcol('.')-1)
call map(paste, 'spaces . v:val')
call append(line('.'), paste)
endfunction
nnoremap <Leader>p :call FancyPaste('"')<CR>
nnoremap <Leader>cp :call FancyPaste('+')<CR>
Keep in mind it will only indent with spaces, not tabs. Indenting with the appropriate amount of tabs (and spaces if needed) would require some extra lines of code, but is quite doable.
If I understand correctly what you want, you could try this based on an ex command and the = operator:
nmap <leader>p :put "<cr>'[=']
Another possibility:
nmap <leader>p :let #"=#"<cr>]p
The #"=#" seems to make Vim forget about the lines being copied, character-wise and ]p pastes reindented.
The UnconditionalPaste plugin can also help you paste like that.

vim: how to select pasted block

Is there a vim command to directly select a block of text which has just been pasted?
ps. I know about gv to reselect a block after exiting visual mode. It doesn't apply to this case.
If you want to select it just after paste (before you change anything else), use
nnoremap <expr> gV "`[".getregtype(v:register)[0]."`]"
. [ and ] marks point to start and end of the last change, v:register is set to the last register used (which is register used for the paste command unless you, for example, yank something), [0] selects only first byte of register type (it is required because for blockwise register it returns <C-v>{width}) and register type is one byte which is just the same as the keystroke you should use in normal mode to invoke visual mode.
I saw this solution somewhere on SO, you may want to search for it in order to get some alternatives.
In my case I have this map:
:nnoremap gp `[v`]
After more research I think the better solution is:
" https://vim.fandom.com/wiki/Selecting_your_pasted_text
nnoremap <expr> gp '`[' . strpart(getregtype(), 0, 1) . '`]'
I have had the following maps in my vimrc forever:
nnoremap <leader>p `[V`]
nnoremap <leader>[ `[V`]<
nnoremap <leader>] `[V`]>
They do the following:
visually select the recently pasted block
de-indent the recently pasted block
indent the recently pasted block
I probably use the indent ones even more than the selection one.

Resources