VIM: How to turn off search highlight after timeout (X seconds) - vim

I know about :nohl and use it all the time, I also use easy motion so the highlight is not in my way when just moving to the search location. It gets in my way after pressing n or N.
I am looking for a way to disable the search highlight after 3 seconds of pressing n or N, also for completeness sake I would like a way to also disable it after searching e.g. /search_word<CR>.
Finally, it has to be a non-blocking command.
Thanks, I am slowly getting into vimscript but this one is out of my league since I haven't seen many examples of commands with timeouts out there.
EDIT:
After some of the comments and online research there are lots of indications there isn't a good way to do this with vimscript.
I am looking for a way to do this in a stable way with any language, e.g. Perl, Python, Ruby.
EDIT 2:
This is my solution based on #dhruva-sagar's response:
(I marked his answer as correct because he gave me the skeleton for it).
augroup NoHLSearch
au!
autocmd CursorHold,CursorMoved * call <SID>NoHLAfter(4)
augroup END
function! s:NoHLAfter(n)
if !exists('g:nohl_starttime')
let g:nohl_starttime = localtime()
else
if v:hlsearch && (localtime() - g:nohl_starttime) >= a:n
:nohlsearch
redraw
unlet g:nohl_starttime
endif
endif
endfunction

If you check :h :nohl, you will see that the command :nohl does not work within an autocmd, the reason being that vim saves & restores the search & search highlighting state after executing an autocmd. Hence technically it is not feasable to do so. The best way is to create a mapping like nnoremap <C-l> :nohlsearch<CR> and use it when you want to temporarily disable the search highlighting.
However there is a slight hack that i'd like to demonstrate that does work in a way like you expect by using set nohlsearch instead of nohlsearch, the downside of course is that it turns off search highlighting completely and you need to re-enable it using set hlsearch, so it isn't really a solution but it makes for a good example to demonstrate how one could perform time based operations within vim.
NOTE: This is more for educational purposes to demonstrate how you could do time based non-blocking tasks in vim. The performance of these could vary depending on what you do within the event triggered function.
augroup NoHLSearch
au!
autocmd CursorHold,CursorMoved * call <SID>NoHLAfter(3)
augroup END
function! s:NoHLAfter(n)
if !exists('g:nohl_starttime')
let g:nohl_starttime = localtime()
else
if v:hlsearch && (localtime() - g:nohl_starttime) >= a:n
set nohlsearch
unlet g:nohl_starttime
endif
endif
endfunction

IMHO, a simpler and more predictable technique is the following:
noremap <silent><esc> <esc>:noh<CR><esc>
You're always hitting escape anyway, so it's a no-brainer to hit it when you want to clear hlsearch. In addition, you can choose to leave the matches highlighted if you want by not hitting an extra in normal mode.

The following will clear the search highlight after 'updatetime' milliseconds of inactivity. 'updatetime' defaults to 4000ms or 4s but I have set mine to 10s. It is important to note that 'updatetime' does more than just this so read the docs before you change it.
function! SearchHlClear()
let #/ = ''
endfunction
autocmd CursorHold,CursorHoldI * call SearchHlClear()

Kevin Cox's answer was on the right track except for a couple problems.
Modifying #/ is a heavy handed approach to disable highlighting of the existing search
Vim saves/restores the search highlighting state before/after calling a function, so trying to change that in a function isn't very useful.
The solution is to simplify. There's no need for a function call.
autocmd CursorHold,CursorHoldI * let v:hlsearch = 0 | redraw

A little bit of necromancy, but I hope it's useful:
I tweaked #DhruvaSagar's answer to improve performance by deactivating the autocmd when it's not needed. Also I removed the CursorHold event: I like to look at my results in peace.
I'm using this together with the second answer from here to automatically deactivate the highlight after a while when I'm not searching (via / or n).
My code:
function! s:NoHLAfter(n)
if (localtime() - g:nohl_starttime) >= a:n
call <SID>Onnohl()
endif
endfunction
function! s:Onnohl()
set nohlsearch
augroup AutoNohl
au!
augroup END
endfunction
function! s:Onsearch()
set hlsearch
nohlsearch
let g:nohl_starttime = localtime()
augroup AutoNohl
au!
" autocmd CursorHold,CursorMoved * call <SID>NoHLAfter(4)
" only do it on CursorMoved: We sometimes 'hold' and look at the
" findings.
autocmd CursorMoved * call <SID>NoHLAfter(4)
augroup END
endfunction
noremap n :call <SID>Onsearch()<cr>n
noremap N :call <SID>Onsearch()<cr>N
noremap / :call <SID>Onsearch()<cr>/
noremap ? :call <SID>Onsearch()<cr>?

Related

setting vim map in function in vim script

I am trying to do a small plugin for vim to let me browse the clear case history of a file, but I am having trouble getting started. I need help with setting up the mapping exposed to the user.
Here is the bare minimum version of what I've done
function! s:DefineLeftKeyMaps()
echom 'in defineLeft'
nnoremap <leader>k <Plug>MinimalDoSomething
endfunction
noremap <script> <Plug>MinimalDoSomething <SID>DoSomething
noremap <SID>DoSomething :call <SID>DoSomething()<CR>
function! s:DoSomething()
echom 'In DoSomething'
endfunction
command! InitCCTTSample call s:DefineLeftKeyMaps()
I need to set the final map exposed to the user in a function cause it will be used in a split where I open up different files (eventually using said mapping). Since it is a buffer local map i assume i need to reset it every time.
Edit:
The problem with the above solution was that even thou the mapping was it didn't do anything.
:map reported:
....
....
\k * <Plug>MinimalDoSomething
....
....
The reason was that I used noremap instead of map, so the subsequent mapping to the function call was ignored
Cheers
<plug>-mappings are difficult to define as buffer local mappings. Indeed the end-user cannot define them in their .vimrc, and to define them in a ftplugin, that'll mean you'll need to associate a filetype to your split buffers.
You'll need to provide a hook to the end user. I see two solutions
Provide a function where the end user will be able to register her/his preferences in their .vimrc.
" .vimrc
call your#plug#tune_mappings({'<Plug>MinimalDoSomething': '<localleader>left'})
Expose a User Event for the user to do stuff (like defining the mappings) when you trigger the event from your plugin.
" .vimrc
augroup YourPlugUser
au!
au User TuneKeyBindings let b:maplocalleader = ',,'
au User TuneKeyBindings nmap <buffer> <localleader><left> <Plug>MinimalDoSomething
aug END
" your plugin
split yourplug://foobar
doautocommand User TuneKeyBindings
if !hasmapto('<Plug>MinimalDoSomething, 'n')
nmap <buffer> <leader>k <Plug>MinimalDoSomething
endif
The reason it doesnt work is because I used nnoremap <leader>k <Plug>MinimalDoSomething rather than nmap <leader>k <Plug>MinimalDoSomething
Cheers

Execute :nohlsearch on InsertEnter

I can do <C-O>:noh<CR> when I'm in insert mode, but it doesn't work when done automatically:
autocmd InsertEnter * :nohlsearch
This works, but it behaves differently:
autocmd InsertEnter * :set nohlsearch
To clarify, what I want is to run :nohlsearch if I enter insert node, but I still want to keep the ability to do /<CR>N to search for another item.
I think, what you want can be accomplished by setting the search register directly:
:autocmd InsertEnter * :let let #/=''
If you want to restore the highlighting when returning from insert mode, you would need to save and restore the pattern, something like this should do it:
:autocmd InsertEnter * :let b:_search=#/|let #/=''
:autocmd InsertLeave * :let #/=get(b:,'_search','')
This saves and restores the current search pattern in the buffer local variable b:_search.
You should write a function calling :nohl and then redraw:
function DisableHL()
nohl
redraw
endfunction
and then autocmd InsertEnter * :call DisableHL()
I think I came up with a smart-ish way of accomplishing exactly the same effect (at least it does exactly what I want and I seem to want what you asked for).
I have this in my vimrc:
Disable highlighting when entering Insert mode
autocmd InsertEnter * set nohlsearch
Re-enable highlighting when pressing any of the nN?/ keys before sending the key, then send the key
for s:k in ['n', 'N', '?', '/']
execute('nnoremap ' . s:k . ' :set hlsearch<cr>' . s:k)
endfor
to hide last search's hightlighting
(I like to be able to quickly disable it without entering insert mode)
nnoremap <silent> <bs> :set nohlsearch<cr>
It basically sets [no]hlsearch on the fly as you use the commands.
I also checked, and at least on my setup it doesn't mess if using nN?/ as normal mode command (for vim's default commands at least) arguments (like dtN to delete until next N), although this might be a concern if you ever remap any of those keys, or if a plugin does it without you noticing it.

vim: Automatically advance to next misspelled word after taking action on current one

I want to configure vim so that when I perform some spellcheck related action that changes state (zg, z= where one option was chosen, etc.) that the cursor will advance to the next misspelled word as if I had typed ]s. How would I go about this?
The zg mapping should be just simple as #U2744 SNOWFLAKE suggested:
noremap zg zg]s
I imagine the tricky part is coming up with a mapping for z= that will do ]s afterwards. Put the following in your ~/.vimrc file:
nnoremap z= :<c-u>call SpellNext()<cr>z=
function! SpellNext()
augroup SpellNext
autocmd!
autocmd CursorMoved <buffer> execute 'normal! ]s' | autocmd! SpellNext
augroup END
endfunction
Note: I have not tested this. This method will not work if you do not change the word as it will not trigger the CursorMoved event in that case.

Vim highlight a word with * without moving cursor

I would like to be able to highlight the word under cursor without going to the next result.
The solution I found uses a marker:
noremap * mP*N`P
Is there any better solution ?
I noticed vim is not really good to keep the position of its cursor after the execution of commands. For instance, when I switch buffer, the cursor go back to the begining of the line.
I might be interesting to have a global setting for this.
There's unfortunately no global setting for that.
One common solution to the "highlight without moving" problem is:
nnoremap * *``
One solution to the "keep the cursor position when switching buffers" problem is:
augroup CursorPosition
autocmd!
autocmd BufLeave * let b:winview = winsaveview()
autocmd BufEnter * if(exists('b:winview')) | call winrestview(b:winview) | endif
augroup END
As detailed here, you can define a map as follows:
:nnoremap <F8> :let #/='\<<C-R>=expand("<cword>")<CR>\>'<CR>:set hls<CR>
My SearchHighlighting plugin changes the * command, so that it doesn't jump to the next match; you can still jump by supplying a [count]. It also extends that command to visual mode (another frequently requested feature not in Vim), and some more.

How do I change the background color of current buffer or pane in vim?

Imagine I'm coding, and I have different split panes open. What settings should I pass into vimrc to change the background color as I switch from one buffer/pane to another?
I have tried:
autocmd BufEnter * highlight Normal ctermbg=black
autocmd BufLeave * highlight Normal ctermbg=white
I would like to add that I am sure that I've got 256 colors enabled
Actually, there is a way to get this effect. See the answer by #blueyed to this related question: vim - dim inactive split panes. He provides the script below and when placed in my .vimrc it does dim the background of inactive windows. In effect, it makes their background the same colour specified for the colorcolumn (the vertical line indicating your desired text width).
" Dim inactive windows using 'colorcolumn' setting
" This tends to slow down redrawing, but is very useful.
" Based on https://groups.google.com/d/msg/vim_use/IJU-Vk-QLJE/xz4hjPjCRBUJ
" XXX: this will only work with lines containing text (i.e. not '~')
" from
if exists('+colorcolumn')
function! s:DimInactiveWindows()
for i in range(1, tabpagewinnr(tabpagenr(), '$'))
let l:range = ""
if i != winnr()
if &wrap
" HACK: when wrapping lines is enabled, we use the maximum number
" of columns getting highlighted. This might get calculated by
" looking for the longest visible line and using a multiple of
" winwidth().
let l:width=256 " max
else
let l:width=winwidth(i)
endif
let l:range = join(range(1, l:width), ',')
endif
call setwinvar(i, '&colorcolumn', l:range)
endfor
endfunction
augroup DimInactiveWindows
au!
au WinEnter * call s:DimInactiveWindows()
au WinEnter * set cursorline
au WinLeave * set nocursorline
augroup END
endif
You can't. The :highlight groups are global; i.e. when you have multiple window :splits, all window backgrounds will be colored by the same Normal highlight group.
The only differentiation between active and non-active windows is the (blinking) cursor and the differently highlighted status line (i.e. StatusLine vs. StatusLineNC). (You can add other differences, e.g. by only turning on 'cursorline' in the current buffer (see my CursorLineCurrentWindow plugin.))
One of the design goals of Vim is to work equally well in a primitive, low-color console as in the GUI GVIM. When you have only 16 colors available, a distinction by background color is likely to clash with the syntax highlighting. I guess that is the reason why Vim hasn't and will not have this functionality.
Basically what Kent said above will work -- surprisingly well at that. The only limitation is that you can only dim the foreground color. Copy this into vimrc, and evoke :call ToggleDimInactiveWin().
let opt_DimInactiveWin=0
hi Inactive ctermfg=235
fun! ToggleDimInactiveWin()
if g:opt_DimInactiveWin
autocmd! DimWindows
windo syntax clear Inactive
else
windo syntax region Inactive start='^' end='$'
syntax clear Inactive
augroup DimWindows
autocmd BufEnter * syntax clear Inactive
autocmd BufLeave * syntax region Inactive start='^' end='$'
augroup end
en
let g:opt_DimInactiveWin=!g:opt_DimInactiveWin
endfun
A few things I had to look up in writing this:
(1) windo conveniently executes a command across all windows
(2) augroup defines autocommand groups, which can be cleared with autocmd! group
For Neovim users, there is the winhighlight option. The help file provides the following example:
Example: show a different color for non-current windows:
set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
Personally, I use my statusline to let me know this. I use the WinEnter and WinLeave autocmds to switch to an inactive status line (grayed out) when leaving and an active statusline (bright colors) when entering. The split panes you mention are windows in vim. This also works because :help statusline tells us that its setting is global or local to a window, so you can use :setlocal statusline=... or let &l:statusline=... to only apply to the current window.
Your method won't work because a) BufEnter and BufLeave aren't necessarily the events that you want and b) highlight groups are global, so changing Normal's definition changes it for every window.

Resources