Vimpager script: Highly mysterious - vim

I have switched my pager from less to vimpager because vim responds to the mouse wheel.
However recently after updating my vim setup by compiling a newer version of vim 7.3, and adding a series of awesome plugins, vimpager no longer does anything logical when I attempt to search text in a manpage using n and N.
This is really bad so I set out to trace the bindings back using :verbose map.
From the vimpager script (/usr/local/share/vim/vim73/macros/less.vim):
" Search
noremap <script> / H$:call <SID>Forward()<CR>/
if &wrap
noremap <script> ? H0:call <SID>Backward()<CR>?
else
noremap <script> ? Hg0:call <SID>Backward()<CR>?
endif
fun! s:Forward()
" Searching forward
noremap <script> n H$nzt<SID>L
if &wrap
noremap <script> N H0Nzt<SID>L
else
noremap <script> N Hg0Nzt<SID>L
endif
cnoremap <silent> <script> <CR> <CR>:cunmap <lt>CR><CR>zt<SID>L
endfun
fun! s:Backward()
" Searching backward
if &wrap
noremap <script> n H0nzt<SID>L
else
noremap <script> n Hg0nzt<SID>L
endif
noremap <script> N H$Nzt<SID>L
cnoremap <silent> <script> <CR> <CR>:cunmap <lt>CR><CR>zt<SID>L
endfun
call s:Forward()
" Quitting
noremap q :q<CR>
That last one I get and it makes a lot of sense. But this H$nzt<SID>L is completely impenetrable.
What the hell is it doing?
P.S. I've gotten my search back by turning those two functions s:Forward() and s:Backward() into empty no-op functions. So I'm all set, but what does that stuff do?

You can use the scrollwheel in less.
Anyway…
H "puts the cursor on the first printable character
"of the first visible line of the window.
$ "moves the cursor to the end of that line
n "moves the cursor on the next match
zt "scrolls the current line at the top of the window
<SID>L "executes the L mapping defined in the current script
The whole thing looks like a slightly convoluted attempt to make Vim's n work like less's n.

Related

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>")

Toggle highlight while searching in Vim

I like highlighting while searching in vim. Here's what I want:
I search for a word with /
Then, all of the results are highlighted. If I press any key other than n or N, I want the highlighting to be toggled off.
If I press n or N again after any number of commands, I want to toggle on the highlighting.
Where do I start? I'm not even sure what to google.
I have this in my .vimrc
nnoremap <CR> :noh<CR>
so that when I'm done seeing the highlighting, I just hit enter to remove it. It stays gone until I hit n or N again.
Note: If you want to keep the functionality of enter, add another <CR> on the end of the command.
I remap control-l (lower case L) so that it clears the search result as well as repaints the screen. This line in .vimrc does it:
nnoremap <silent> <C-l> :nohl<CR><C-l>
You can manually disable the last highlight with nohl.
I will let you know if I can figure out how to automate this.
One method is to setup a toggle mapping. These are some toggle mappings I have in my .vimrc:
let mapleader="\\"
noremap <silent> <Leader>th :set invhls hls ?<CR>
noremap <silent> <Leader>tn :set invnumber number ?<CR>
noremap <silent> <Leader>ts :set invspell spell ?<CR>
noremap <silent> <Leader>tw :set invwrap wrap ?<CR>
To toggle highlighting just type \th for toggle hls. The others are line number, spell checking, line wrapping. The final hls ? will display the new mode.
I prefer this, because to me it is nothing but natural.
Start searching with /<pattern> and once done, simply type <Leader>/ to stop the highlighting.
nnoremap <silent> <Leader>/ :set nohl<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

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