Vim: search and highlight but do not jump - search

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>

Related

How can I define a key mapping in vim and use the repeat number more than once?

I edit files with relative line numbers. Often I would like to copy a line from let's say 16 lines above to the current location.
In normal mode I would enter: 16kyy16jP
But when it is line 14, it is: 14kyy14jP
How can I define a key mapping/command to be able to enter something like 16LK or 14LK in normal mode to achieve the same result?
16kyy16jP
What a waste… You could use :help :t instead:
:-16t.
:-14t.
May be something like
nnoremap <silent> µ :<c-u>exe "normal! ".v:count1."kyy".v:count1."jP"<cr>
But, honestly, I'd use functions here as there is no need to move around that much:
nnoremap <silent> µ :<c-u>call append(line('.')-1, getline(line('.')-v:count1))<cr>
Note that the following also works thanks to :yank
nnoremap <silent> µ :<c-u>exe '-'.v:count1.'y'<cr>P
EDIT: I didn't know about :t, #romainl's answer (with #Kent's patch) makes more sense than mine. If you want a mapping it could be mode with:
nnoremap <silent> µ :<c-u>exe '-'.v:count1.'t-1'<cr>
" which isn't much different than the previous answer.
You can map a function call, which accepts input parameters.
function! YourMap(n)
exec 'normal! '.a:n.'-Y'.a:n.'+P'
endfunction
nnoremap <leader>c :call YourMap(input('lines:')) <CR>
You press <leader>c, then input the relative line numbers, the copy/paste should be done.
The <leader>c is the mapping key, you can change it into other key combinations.

Ag.vim: Use word under the cursor as *part* of a search pattern?

Using the Ag.vim plugin you can easily search for the word under the cursor by calling :Ag without any arguments, but how can I include the word under the cursor as part of a search pattern? I'm trying to add a bit to my vimrc to search for method definitions in Ruby files. Something like:
autocmd FileType ruby noremap K :Ag! "\bdef <C-R><C-W>\b"<CR>
It doesn't recognize the <C-R><C-W>. Also, I've tried both \b and \<, \> as word boundaries. Ag uses \b outside the context of Vim, but I know it translates \< and \> to \b in some contexts (not sure about this context though).
I usually use one trick, just press * on you current word and then use :AgFromSearch, you could specify concrete path e.g :AgFromSearch /any/path/
If you really want to do word as part of your search pattern, you can use <cword> (current word)
nmap <silent> <D-f> :Ag "def <cword>" /any/path/<CR>
augroup Ruby
" clear this augroup's autocmds
autocmd!
" use this version:
autocmd FileType ruby nnoremap <buffer> K :<C-u>execute 'Ag! "\bdef ' . expand('<cword>') . '\b"'<CR>
" or this one, ^R^W is obtained by pressing <C-v><C-r> then <C-v><C-w>:
autocmd FileType ruby nnoremap <buffer> K :Ag! "\bdef ^R^W\b"<CR>
augroup END
Use augroup and autocmd! as above for a cleaner ~/.vimrc.
nnoremap is better than noremap unless you really want a single mapping for normal, visual and operator-pending mode (which doesn't really make any sense).
<buffer> is needed to ensure that your mapping is limited to the current buffer.

how do I conditionally bind a key to do two different actions in vim?

I'd like to bind <C-n> to do one of two things in Vim depending on the state of the editor. If I have tabs open I'd like it to switch to the next tab, otherwise I'd like it to open a new tab. I've looked at the help and come up with this, but it's not working, and I'm a viml noob.
function TabBind()
if range(tabpagenr()) < 2
nno <C-n> :tabnew
else
nno <C-n> :tabn
endif
endfunction
Is this possible? and if so how?
The idea is that you map a function that decides what to do on the fly.
function TabBind()
if tabpagenr('$') < 2
tabnew
else
tabn
endif
endfunction
nno <C-n> :call TabBind()<cr>
You can also define such simple thing as one-liners. For instance I have the following mapping to go to the next diff (in diff mode), or to the next error message otherwise.
nnoremap <expr> <silent> <F3> (&diff ? "]c:call \<sid>NextDiff()\<cr>" : ":cn\<cr>")
In your case, your mapping will be:
nnoremap <expr> <silent> <c-n> (tabpagenr('$') < 2 ? ":tabnew\<cr>" : ":tabn\<cr>")

vim toggling buffer overwrite behavior when deleting

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.

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

Resources