vim toggling buffer overwrite behavior when deleting - vim

Vim is great, but like many people I get really annoyed when I want to copy, delete, then paste -- the yank buffer gets overwritten by the delete action.
Now I know there are 101 work-arounds and mappings, some of which are enumerated in posts like this one: Any way to delete in vim without overwriting your last yank?
But all of these solutions have drawbacks -- even I were a buffer-guru (which I'm not). For instance, excess keystrokes -- whereas I normally xxxx to quickly delete 4 characters (just one keystroke cuz I hold it down and wait for autorepeat), it is not practical for me to now switch to ,x,x,x,x or whatever mapping I have to use a different buffer.
What would really be ideal is simply a mode toggle, whereby you can toggle on and off the side-effect behavior of the D, d, X, and x keys so that they alternately do or do not also write their text to a buffer. That way I can simply enter the "no side-effect" mode and delete away to heart's content, then paste when I'm ready. And re-enable side-effects if desired.
Does anyone know a way to do this?
[UPDATE: SOLUTION] OK I got it: I wrote a function that toggles a "no side-effects" mode... works perfectly! See my accepted correct answer below
[UPDATE #2] My solution still works great and I use it all the time when I'm doing a lot of deleting and pasting. But meanwhile I also found a lighter way to meet the specific use-case of copy, paste, delete for simple cases where the text to delete is contiguous.
After yanking text normally, I then visually highlight the text to delete using the v command, and then simply paste over it with the p command. That achieves the desired effect without any special mapping.
Only problem with this workflow is that if I wanted to paste again, the original paste buffer is overwritten by the act of pasting over the highlighted text, but this behavior is easily changed with the following mapping in .vimrc:
vnoremap p "_dp
vnoremap P "_dP

OK, I got it -- this script in .vimrc lets me effectively toggle a "no buffer side-effects" mode whereby the d and x keys no longer overwrite the buffer when "no buffer side-effects" mode is activated.
Add this in .vimrc
function! ToggleSideEffects()
if mapcheck("dd", "n") == ""
noremap dd "_dd
noremap D "_D
noremap d "_d
noremap X "_X
noremap x "_x
echo 'side effects off'
else
unmap dd
unmap D
unmap d
unmap X
unmap x
echo 'side effects on'
endif
endfunction
nnoremap ,, :call ToggleSideEffects()<CR>
Then to toggle in and out of this mode use the key combination ,, (two commas)

I think trying to "turn-off" the side effects for every delete/change command would be overly difficult if not impossible. The basic ways to handle this situation:
Use the black hole ("_) register with your delete or change commands. e.g. "_dd
Use the "0 register which contains the most recent yank with your paste commands. e.g. "0p
Yanking the text to a named register. e.g. "ayy then later doing "ap
I personally lean toward the "0p approach as this is fits with how my mind works.
Now seeing you asked for no such work-arounds I have provided some functions that alter the paste commands to toggle between my so called paste_copy and nopaste_copy mode. nopaste_copy being Vim's default behavior. Put the following in your ~/.vimrc:
function! PasteCopy(cmd, mode)
let reg = ""
if exists('g:paste_copy') && g:paste_copy == 1 && v:register == '"'
let reg = '"0'
elseif v:register != '"'
let reg = '"' . v:register
endif
let mode = ''
if a:mode == 'v'
let mode = 'gv'
endif
exe "norm! " . mode . reg . a:cmd
endfunction
command! -bar -nargs=0 TogglePasteCopy let g:paste_copy = exists('g:paste_copy') && g:paste_copy == 1 ? 0 : 1<bar>echo g:paste_copy ? ' paste_copy' : 'nopaste_copy'
nnoremap <silent> p :call PasteCopy('p', 'n')<cr>
nnoremap <silent> P :call PasteCopy('P', 'n')<cr>
nnoremap <silent> ]p :call PasteCopy(']p', 'n')<cr>
nnoremap <silent> [p :call PasteCopy('[p', 'n')<cr>
nnoremap <silent> ]P :call PasteCopy(']P', 'n')<cr>
nnoremap <silent> [P :call PasteCopy('[P', 'n')<cr>
vnoremap <silent> p :<c-u>call PasteCopy('p', 'v')<cr>
vnoremap <silent> P :<c-u>call PasteCopy('P', 'v')<cr>
You can toggle your paste_copy mode via :TogglePasteCopy. You may prefer a mapping like so
nnoremap <leader>tp :TogglePasteCopy<cr>
As a closing piece of advice I would highly suggest using "0p or using a named register over this approach as they are native to vim and there is one less "mode" to worry about.

Related

count of command get lost when do key mapping in vim

I have a key mapping for p in my vimrc file as below:
noremap p <ESC>:set paste<CR>p:set nopaste<CR>
The purpose of this key mappinng is to make sure the content from outside of vim can be pasted in its original format in paste mode. And restore it to nopaste mode after paste complete.
But when I run the following commands:
yy
5p
p is only executed once other than 5 times.
It looks like the count is missed from the key mapping.
Any way to pass the count in the key mapping?
Or how to fix this problem?
The fisrt <ESC> drops the count so it is no available later. Instead of <ESC> we would ignore and save count with <c-u> and access it later with v:count1 variable like :<c-u>set paste <CR>... v:count1 ....
But there is another problem: count will be lost after the first <CR>, so we want to rewrite the mapping as a single command. Chaining of commands may be done with |, but in mappings we should write <BAR> instead of |.
Here is the final mapping:
:noremap p :<c-u>set paste <BAR> :exe "normal! " . v:count1 . "p" <BAR> :set nopaste<CR>
:exe "normal! " is a fancy way to execute a command from evaluated string.

Remap yank (y), Change (c) and Delete (d)

I've been trying to extend the functionality of these three commands (y,c,d), so I'd have the content both in the system clipboard ("+ register) and in the normal registers.
This is what I've come up with so far:
nnoremap Y y$v$"+y$
nnoremap yy 0y$0v$"+y$
vnoremap y ygv"+y
" ------
nnoremap C y$v$"+c
nnoremap cc 0y$0v$"+c
vnoremap c ygv"+c
" ------
nnoremap D y$v$"+d$
nnoremap dd 0y$0v$"+d$
vnoremap d ygv"+d
" ------
And I have a few issues with this mapping:
For some odd reason, I have to resource my .vimrc before I can actually use these new bindings.
Trying to use only nmap, vmap or even map just didn't seem to work at all.
When I try to yank (for example) multiple lines (e.g. 2yy), it yanks the text to the "regular" register ("0) as expected, but yanks only the first line into the system clipboard.
I could'nt find a way to create this behaviour to y,c and d in normal-mode.
Can anyone help solve these issues?
I thought of creating a function, perhaps, so I could call it multiple times, but my vim script-fu is novice at best.
If you can do "+y, all these mappings are useless. Just add set clipboard+=unnamedplus to your ~/.vimrc.

Highlighting find next in vim

I often use vim's / search capabilities and will use n to jump to the next match. However, when the cursor jumps to the next match and the screen redraws, it is often not at all obvious where the cursor is on the screen (and thus where the next match is). In the past, I have had to do a jk dance to make the cursor move so I can find it. My cursor does not blink by default (I find that annoying), but what I would like is this: when the cursor jumps to the next match, it should change color or blink briefly to draw attention to its placement on the screen, then switch back to the default cursor behavior. How can I create this behavior in my .vimrc file? My google-fu has failed me thus far.
Note: I am aware there is a setting for highlighting all search matches (hlsearch), but I prefer to keep my view clean. Temporarily highlighting the current match would be fine, but I want to only draw attention to the current match, not all matches.
I use this in my .vimrc, which will center the search term in the middle of your display. This not only makes it easy to find the search term, but also automatically provides me with enough context before and after that I usually don't need to scroll around after searching.
" Center the display line after searches. (This makes it *much* easier to see
" the matched line.)
"
" More info: http://www.vim.org/tips/tip.php?tip_id=528
"
nnoremap n nzz
nnoremap N Nzz
nnoremap * *zz
nnoremap # #zz
nnoremap g* g*zz
nnoremap g# g#zz
Steve Losh made something similar - when you jump to a next location (n key) the cursor blinks! Here's how it works screencast, and here is the code (see older commits for some variations on the theme).
I whipped up a couple of functions that seem to handle this alright. They may not be the best implementation but they seem to work.
function! s:NextMatch()
let param = getreg('/')
let pos = getpos('.')
let next_match = matchadd('Search', '\%'.pos[1].'l\%'.pos[2].'v'.param)
redraw
sleep 250ms
silent! call matchdelete(next_match)
endfunction
function! s:DoNextMatch()
let cmd_type = getcmdtype()
if cmd_type == '/' || cmd_type == '?'
return "\<cr>:call " . s:SID() . "NextMatch()\<cr>"
endif
return "\<cr>"
endfunction
function s:SID()
return matchstr(expand('<sfile>'), '<SNR>\d\+_\zeSID$')
endfun
nnoremap <silent> n n:call <sid>NextMatch()<cr>
nnoremap <silent> N N:call <sid>NextMatch()<cr>
cnoremap <silent> <expr> <cr> <sid>DoNextMatch()
Tested with: vim -u test.vim -N file.txt

Add support for `ci|` and `da|` (seleting text inside pipes)

I often use commands like ci( and di{ when editing source code.
Parameters in Ruby blocks are contained inside pipe characters, like |a, b|
Is it possible to extend this behavior to add support for |, so that commands like ci|, da| and yi| work properly?
I have the following in my vimrc (I have added the va| and vi| commands for completeness):
nnoremap di\| T\|d,
nnoremap da\| F\|d,
nnoremap ci\| T\|c,
nnoremap ca\| F\|c,
nnoremap yi\| T\|y,
nnoremap ya\| F\|y,
nnoremap vi\| T\|v,
nnoremap va\| F\|v,
The , operator repeats the previous F,f,T or t but in the opposite direction. A very useful key!
These mappings can be easily modified to support other delimiters; I use the $ versions all the time when editing LaTeX.
This is what I would use:
vnoremap <silent> a<bar> :<c-u>silent! normal! vF<bar>of<bar><cr>
vnoremap <silent> i<bar> :<c-u>silent! normal! vT<bar>ot<bar><cr>
onoremap <silent> a<bar> :normal va<bar><cr>
onoremap <silent> i<bar> :normal vi<bar><cr>
Basically setup a operator pending mode (that is the onoremap) which will call the appropriate visual mode mapping. The visual mode mappings will search backwards to find a | with F| then go to the other side of the visual selection via the o command, then search forwards with f| to select the other end of the piped area. The inner mappings are the same but instead of using the F and f commands you use T and t.
Sadly these mappings do not work correctly with the ., redo command as they inherently rely visual mode mappings which means the . command will execute the same command again but only for an area that take up the same amount of space.

Vim: search and highlight but do not jump

The super star (*) key in Vim will search for the word under the cursor and jump forward to the next match. The user can jump to the next matches with the n key. If hlsearch is enabled, it will also highlight the matches.
I want to be able to press * and get the highlighted matches and be able to navigate the matches using the n key. However, I do not want Vim to jump to the next match when * is pressed, it should remain on the current word. Is there a way to do this?
I would map:
nnoremap * *``
Works exactly like you want, except that it adds a jump in the jump list. To prevent that you need:
nnoremap * :keepjumps normal! mi*`i<CR>
I found this works pretty well, there's no blink and it doesn't need an intermediate register.
nnoremap <silent> * :let #/= '\<' . expand('<cword>') . '\>' <bar> set hls <cr>
Or if you want the g* behavior:
nnoremap <silent> g* :let #/=expand('<cword>') <bar> set hls <cr>
The best solution:
don't add a jump to the jump list
the behavior of the star key is not be changed
so, try the plugin:
http://www.vim.org/scripts/script.php?script_id=4335
Much better than:
" a jump adds to the jump list
nnoremap * *``
" I got a dead loop on macvim
nnoremap * :keepjumps normal *``<cr>
" the behavior is changed
nnoremap <silent> <Leader>* :let #/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
I haven't seen this one yet:
nmap <silent> * "syiw<Esc>: let #/ = #s<CR>
It's very short and does not involve jumping around which can result in blinking.
Explanation: copy the word under cursor to s register and then set the search register (/) to the content of s register. The search register is not writeable directly, that's why the let is necessary and hence the silent to keep vim's command line clean.
I have the following in my .vimrc, which I think works better than the other alternatives:
" Put word under cursor into search register and highlight
nnoremap <silent> <Leader>* :let #/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
vnoremap <silent> <Leader>* :<C-U>
\let old_reg=getreg('"')<Bar>let old_regtype=getregtype('"')<CR>
\gvy:let #/=substitute(
\escape(#", '/\.*$^~['), '\_s\+', '\\_s\\+', 'g')<CR>
\gV:call setreg('"', old_reg, old_regtype)<CR>:set hls<CR>
If you want to keep the current view and add the search to the history, try this [not so efficient] solution:
noremap * msHmt`s*`tzt`s
It is using the marks s (save) and t (top).
A simple solution came to my mind: put map * *# in .vimrc file (it will blink though).
Many answers here outline rather simple mappings that work well for common cases, but may have side effects (like flickering by jumping back and forth) or lack robustness (may break if some regexp characters are defined as keyword characters).
If you're looking for a robust implementation and don't mind installing a plugin, you can choose from a plethora of alternatives, many of which also offer additional search-related improvements:
My SearchHighlighting plugin changes the * command, extends it to visual selections, and offers optional auto-searching of the word under the cursor.
star search changes the behavior of * to not jump to the next match, and includes the visual search from the next plugin
vim-visual-star-search provides searching of the visual selection
visualstar.vim provides searching of the visual selection
select & search can use either n/N or * in the visual selection, and can avoid jumping.
vim-asterisk provides a z* mapping that also doesn't jump, visual *, more intuitive smartcase handling, and can keep the cursor position when jumping (like ,*)
searchant.vim hooks into the built-in search commands and provides a separate highlighting for the match last jumped to.
The other answers here are good, particularly #rodrigo's, but I wanted to write a solution that preserves scroll position and does so without affecting any of the marks.
This works for me:
function! StarPositionSave()
let g:star_position_cursor = getpos('.')
normal! H
let g:star_position_top = getpos('.')
call setpos('.', g:star_position_cursor)
endfunction
function! StarPositionRestore()
call setpos('.', g:star_position_top)
normal! zt
call setpos('.', g:star_position_cursor)
endfunction
nnoremap <silent> * :call StarPositionSave()<CR>*:call StarPositionRestore()<CR>
Putting normal! * in the function directly doesn't seem to work, as (at least in neovim) it suppresses search highlighting from being triggered (as if :nohlsearch was run).
My solution:
nnoremap <silent><expr> * v:count ? '*'
\ : ':silent execute "keepjumps normal! *" <Bar> call winrestview(' . string(winsaveview()) . ')<CR>'
nnoremap <silent><expr> g* v:count ? 'g*'
\ : ':silent execute "keepjumps normal! g*" <Bar> call winrestview(' . string(winsaveview()) . ')<CR>'
Edit: Recent Vim has <Cmd> mapping, so you can use below to avoid CmdlineEnter/Leave to be fired.
nnoremap <expr> * v:count ? '*'
\ : '<Cmd>silent keepjumps normal! *<CR><Cmd>call winrestview(' .. string(winsaveview()) .. ')<CR>'
nnoremap <expr> g* v:count ? 'g*'
\ : '<Cmd>silent keepjumps normal! g*<CR><Cmd>call winrestview(' .. string(winsaveview()) .. ')<CR>'
Pros:
No flickering.
Jump list remains unchanged.
With a count, it invokes original *; you can use 1* if you miss the original *.
Marks and registers are untouched.
Using original *, its behavior is almost identical with * (except for jumping).
This means 'smartcase' is ignored as * do.
No need to install plugins.
Similar to * we have
[I ..................... it shows where the word under the cursor appears
I also have some useful lines on my vimrc that can, maybe, help you
" When double click a word vim will hightlight all other ocurences
" see CountWordFunction()
" [I shows lines with word under the cursor
nnoremap <silent> <2-LeftMouse> :let #/='\V\<'.escape(expand('<cword>'), '\').'\>'<cr>:set hls<cr>:CountWord<cr>
nnoremap <Leader>* :let #/='\V\<'.escape(expand('<cword>'), '\').'\>'<cr>:set hls<cr>:CountWord<cr>
if !exists('*CountWordFunction')
fun! CountWordFunction()
try
let l:win_view = winsaveview()
exec "%s/" . expand("<cword>") . "//gn"
finally
call winrestview(l:win_view)
endtry
endfun
endif
command! -nargs=0 CountWord :call CountWordFunction()
cnoreabbrev cw CountWord
nnoremap <F3> :CountWord<CR>
I'm adding this answer because I found the other answers either, centered the line in the view (which I found distracting), or seemed overly complicated.
The following command works well for me:
noremap * :let #/ = "\\<<C-r><C-w>\\>"<cr>:set hlsearch<cr>
It simply sets the pattern to the whole word under the cursor and then turns on (or updates) the highlighting for the search pattern.
NOTE: It doesn't modify the search history (which I prefer but might not be quite what you want).
just do
nnoremap * *N
nnoremap # #n
works with this http://www.vim.org/scripts/script.php?script_id=4335 too like so:
vnoremap * :<C-u>call VisualStarSearchSet('/')<CR>/<C-R>=#/<CR><CR>N
vnoremap # :<C-u>call VisualStarSearchSet('?')<CR>?<C-R>=#/<CR><CR>n
nnoremap <silent> ml :<c-u>let #/ = '\<'.expand('<cword>').'\>'\|set hlsearch<CR>wb
and if you want a visual mode one:
function SetSearchVisualSelection()
let clipboard_original_content=#"
normal gvy " this overwrites clipboard
let raw_search=#"
let #/=substitute(escape(raw_search, '\/.*$^~[]'), "\n", '\\n', "g")
let #"=clipboard_original_content
endfunction
vnoremap ml :call SetSearchVisualSelection()<CR>:set hlsearch<CR>

Resources