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 :)
Related
I have been trying to implement a function in vimscript to switch between two views of the same buffer. The functionality I want to have is like this:
I press a key and the screen position and cursor are moved to my previously saved location. If there is not yet another saved location, it creates one at the current view and cursor location. I have a vimscript function that does exactly this functionality i described:
fun! SwitchFileMarker(reset)
if a:reset == 1
if exists("b:switch_file_window")
unlet b:switch_file_window
endif
endif
if exists("b:switch_file_window")
let cursor_location = getpos("'0")
let top_location = getpos("'9")
:execute "normal! m0Hm9`0"
call setpos('.', top_location)
:execute "normal! zt"
call setpos('.', cursor_location)
:execute "normal! zv"
else
" save the location
:execute "normal! m0Hm9`0"
endif
let b:switch_file_window = 1
endfun
nmap <leader>b :call SwitchFileMarker(0)<CR>
nmap <leader>B :call SwitchFileMarker(1)<CR>
The only problem with this function is I want it to save the folds of the current view and load the folds of the saved view when the function is called. I can achieve this by using :mkview and :loadview, but the problem with that is if the number of lines in the file changes, the folds are lost. The :mkview function seems to remember the folds at a specific line number, and If ive added several lines above that fold location while editing the file, when i use :loadview, the fold is lost. using the marks, as done in the function i show works to save my cursor position (but not folds) because the marks keep track of the changing line number. The funcionality that I am trying to get is essentially like having two views of a buffer but in the same window, rather than two windows. If i have a two windows editing the same buffer, I can add lines in one window and the folds are not lost in the other, so this is exactly the functionality I want, just in one window instead of two. Any suggestions how this can be done?
Use marks!
To set a mark, you can use m<letter> or :mark <letter>.
To go to a marked line, use '<letter>. To go to the exact position, prefer `<letter>.
If the letter is lowercase, it is buffer-local. If it is uppercase, it is global.
Edit: OP wants to save folds, which this can’t do. But it’s helpful to know anyway, so I’m leaving the answer.
Is there a way to change the active register persistently in Vim?
The " register is used by default, if I want to use the a register I have to prepend "a to my normal mode command. After giving the command, the active register gets switched back to ".
Is there a way to tell Vim to keep a specific register active until explicitly told not to do so?
There is no such option to achieve your goal.
You can read and change the vim source code for your requirement for sure.
It is also not easy to implement it with vimscript, what I can think of:
declare a var, indicate the target "unnamed reg", e.g. a
create mappings for d D s S c C y Y p P ... commands, let them call your function
in your function, you check the variable to get the target, and finally fire a "[targetReg]command or call set/getreg()
However I don't think this is a good idea. You have to think about many corner cases to make your script work for all situations. For example, user pressed "_dd or "bx, your script should know that user has explicitly declared which register he wanted to use, and you should bypass your target var.
So just use the default unnamed reg.
If the goal is just to append to ", these functions and mappings should work for visual and operator pending (e.g. $, w, fx, etc...).
function! AppOp(type, ...)
call App(a:type, a:0)
endfunction
function! App(type, visual)
let r = #"
if a:visual
execute "normal! `<" . a:type . "`>y"
else
execute "normal! `[v`]y"
endif
let #" = r . #"
endfunction
xnoremap <silent> <leader>a :call App(visualmode(), 1)<cr>
nnoremap <silent> <leader>a :set opfunc=AppOp<cr>g#
Swap y for d to delete instead of yank. Define mappings to taste.
Gived this script (packages suggestion was simplified, original takes care of <cword>)
function! CompleteImport()
let packages = ['java.util.Vector','java.lang.String']
call complete(col('.'),packages)
return ''
endfunction
inoremap <F8> import <C-R>=CompleteImport()<CR>
while on insert mode you can add an import and choose between suggested packages pressing F8
But I want to be enable to reach that popup selection from normal mode
function! InsertImport()
exe "normal iimport \<C-R>=CompleteImport()\<CR>"
"this commented line would work too
"exe "normal i\<F8>"
endfunction
map <Leader>ji :call InsertImport()<CR>
so from normal mode ,ji (stands for java import) add an import to word under cursor if it is found
(Move to right position is not a problem so I ommit here)
By now ,ji adds first suggestion from popup and exists insert mode
I have tried :startinsert but no luck.
see on http://vimdoc.sourceforge.net/htmldoc/eval.html#:execute there is a suggested code:
:execute "normal ixxx\<Esc>"
but that final Esc doesn't matter at all (for my vim install at least) This do exactly the same for me:
:execute "normal ixxx"
I would think this is not possible if I haven't found that on docs. So, it's possible to stay on a popup calling from a function?
Other interested docs:
http://vimdoc.sourceforge.net/htmldoc/various.html#:normal
http://vimdoc.sourceforge.net/htmldoc/insert.html#:startinsert
:startinsert usually is the right approach, but it indeed gives control back to the user, so you cannot automatically trigger the completion.
Through the feedkeys() function, you can send arbitrary keys "as if typed". This allows you to start insert mode and trigger the completion:
function! InsertImport()
call feedkeys("iimport \<C-R>=CompleteImport()\<CR>", 't')
endfunction
nnoremap <Leader>ji :call InsertImport()<CR>
PS: You should use :noremap for the normal mode mapping, too; it makes the mapping immune to remapping and recursion.
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
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.