user2146414
user2146414

Reputation: 1038

Center cursor position after search in Vim

I would like Vim to place my cursor vertically in the middle of screen after search. I have achieved that for *, #, n and N commands with the following lines in my vimrc:

nmap * *zz
nmap # #zz
nmap n nzz
nmap N Nzz

My question is: How to map / and ? the same way? In other words, I would like to position the cursor in the same way after some text has been found using the following commands:

/some-text-to-find-forward
?some-text-to-find-backward

Upvotes: 6

Views: 2369

Answers (4)

dsimic
dsimic

Reputation: 390

Below is an excerpt from my ~/.vimrc that improves the search behavior in vim by centering the search matches vertically when the n, N, / or ? commands are used, but only if jumping from the current cursor position to the search match goes up or down more than 3/4 (75%) of the actual window height. This helps a lot with the navigation through the edited file while searching, by preserving the context.

" Center the window vertically at the last search match if the search ends up
" scrolling the window up or down at least 75% (3/4) of the actual window height,
" which preserves the context and makes search navigation much easier
"
function! CenterSearch(command = v:null)
  set lazyredraw
  if a:command isnot v:null
    let winstartold = line("w0")
    let winendold   = line("w$")
    try
      execute "normal! " .. a:command
    catch
      echohl ErrorMsg
      echo substitute(v:exception, "^Vim(.*):", "", "")
      echohl NONE
    endtry
  else
    let winstartold = s:winstartold
    let winendold   = s:winendold
  endif
  let winstartnew = line("w0")
  let winendnew   = line("w$")
  let winframe    = float2nr(winheight(0) * (1.0 - 0.75))
  if (winendnew - winstartnew + 1 > 0 && winendold - winstartold + 1 > 0)
  \  && ((winstartnew < winstartold && winendnew < winendold
  \       && winendnew <= winstartold + winframe)
  \      || (winstartnew > winstartold && winendnew > winendold
  \          && winstartnew >= winendold - winframe))
    execute "normal zz"
  endif
  redraw
  set nolazyredraw
endfunction

nnoremap <silent> n :call CenterSearch("n")<CR>
nnoremap <silent> N :call CenterSearch("N")<CR>

" Execute the search as usual, while remembering the resulting window position
" and possibly centering the window vertically at the resulting match
"
function! ExecuteSearch()
  let cmdtype = getcmdtype()
  if cmdtype ==# "/" || cmdtype ==# "?"
    let s:winstartold = line("w0")
    let s:winendold   = line("w$")
    return "\<CR>\<Esc>:call CenterSearch()\<CR>"
  endif
  return "\<CR>"
endfunction

cnoremap <silent> <expr> <CR> ExecuteSearch()

I know, this is a sizable chunk of vimscript code, but it also does quite a lot, which includes eliminating the screen flickering that may happen quite often as a result of the function scrolling the window.

I find this approach much more useful than just having the search matches always centered vertically, which doesn't preserve the context and, as a result, often makes the navigation through the edited file cumbersome.

I'd highly suggest that you also configure vim to keep a minimal number of screen lines above and below the cursor, to preserve even more context of the edited file, by adding the following to your ~/.vimrc:

set scrolloff=3

See also this answer, which may provide some additional comfort in vim.

Upvotes: 0

aleks
aleks

Reputation: 21

Solution from Randy Morris but as a oneliner:

cnoremap <silent><expr> <enter> index(['/', '?'], getcmdtype()) >= 0 ? '<enter>zz' : '<enter>'

Upvotes: 1

Randy Morris
Randy Morris

Reputation: 40927

Edit: Threw away my initial answer as it was too much of a kludge. Here's a much better solution.

function! CenterSearch()
  let cmdtype = getcmdtype()
  if cmdtype == '/' || cmdtype == '?'
    return "\<enter>zz"
  endif
  return "\<enter>"
endfunction

cnoremap <silent> <expr> <enter> CenterSearch()

The way this works is to remap Enter in command-line-mode to a custom expression.

The function performs the current search followed by zz if the command-line is currently in a search. Otherwise it just executes whatever command was being done.

Upvotes: 4

Wolfie
Wolfie

Reputation: 143

It's not very pretty, but

:nnoremap / :execute "normal! /\<lt>cr>zz"<c-left><right>

will get the job done. (Puts an :execute "normal! /" command on the commandline, then adds a <cr>zz to the end to it so that you automatically zz when you issue the command. The final <c-left><right> just steps into the search pattern at the right spot

Upvotes: 1

Related Questions