Vim scrolling without changing cursors on-screen position - vim

When cursor is at middle of screen and i scroll down, the cursor moves upwards on the screen. I don't want it to do that.
How can i scroll without changing cursors on-screen position?
Solution, added after answer:
noremap <C-k> 14j14<C-e>
noremap <C-l> 14k14<C-y>

There are two ways I can think of: ctrl-E and ctrl-Y scroll the buffer without moving the cursor's position relative to the window. I think that is what you want. Also, if you set scrolloff to a large number, you will get the same effect as ctrl-E and ctrl-Y with the movement keys. scrolloff setting will make it hard to get the cursor to move vertically relative to the window though. (Use something like :set so=999, so is an abbreviation for scrolloff.)
:help 'scrolloff'
:help scrolling

ctrl-D and ctrl-U is what you want.
ctrl-D has the same effect as 14j14<C-e> (just that the number 14 is not hard coded and the amount of movement depends on the actual size of your screen): You move the cursor several lines down in the text but the cursor stays in the middle of the screen.
Similarly ctrl-U works like 14k14<C-y>.
Addendum: If your screen has 30 lines then the the two are exactly the same.

If you want to both move the cursor and the viewport with the cursor anywhere in the screen, perhaps you should set up some custom key bindings to do both at once.
Such as:
:nnoremap <C-M-u> j<C-e>
This will move the cursor down (j) and move the viewport (Ctrl-e) whenever you press Ctrl-Alt-u (only in normal mode).

Try this mapping in .vimrc
map <ScrollWheelUp> 5<C-Y>
map <ScrollWheelDown> 5<C-E>

There are two methods I know of. Add these lines to your .vimrc file (selecting only one of the two methods):
Method 1:
function! s:GetNumScroll(num)
let num_rows = winheight(0)
let num_scroll = a:num
if (a:num == -1)
let num_scroll = (num_rows + 1) / 2
elseif (a:num == -2)
let num_scroll = num_rows
endif
if (num_scroll < 1)
let num_scroll = 1
endif
return num_scroll
endfunction
function! s:RtrnToOrig(before_scr_line)
normal H
let delta = a:before_scr_line - winline()
while (delta != 0)
if (delta < 0)
let delta = winline() - a:before_scr_line
let iter = 1
while (iter <= delta)
execute "normal" "gk"
let iter +=1
endwhile
elseif (delta > 0)
let iter = 1
while (iter <= delta)
execute "normal" "gj"
let iter +=1
endwhile
endif
let delta = a:before_scr_line - winline()
endwhile
endfunction
function! s:scrollUP(num)
let num_scroll = <SID>GetNumScroll(a:num)
let num_rows = winheight(0)
" -------------
let before_scr_line = winline()
normal L
let after_scr_line = winline()
let extra = num_rows - after_scr_line
let extra += num_scroll
" move by 1 to prevent over scrolling
let iter = 1
while (iter <= extra)
execute "normal" "gj"
let iter +=1
endwhile
" -------------
call <SID>RtrnToOrig(before_scr_line)
endfunction
function! s:scrollDN(num)
let num_scroll = <SID>GetNumScroll(a:num)
" -------------
let before_scr_line = winline()
normal H
let after_scr_line = line(".")
execute "normal" "gk"
let after_scr2_line = line(".")
if ( (after_scr_line == after_scr2_line) && (after_scr_line > 1) )
execute "normal" "gk"
endif
let extra = (num_scroll - 1)
let extra += (winline() - 1)
" move by 1 to prevent over scrolling
let iter = 1
while (iter <= extra)
execute "normal" "gk"
let iter +=1
endwhile
" -------------
call <SID>RtrnToOrig(before_scr_line)
endfunction
nmap <silent> <C-J> :call <SID>scrollUP(1)<CR>
nmap <silent> <C-K> :call <SID>scrollDN(1)<CR>
nmap <silent> <C-F> :call <SID>scrollUP(-1)<CR>
nmap <silent> <C-B> :call <SID>scrollDN(-1)<CR>
nmap <silent> <PageDown>:call <SID>scrollUP(-2)<CR>
nmap <silent> <PageUp> :call <SID>scrollDN(-2)<CR>
This uses the normal H, L to go to screen top, bot and the gk, gj commands to move up, down by screen line instead of actual line. Its more complicated than would seem needed just to work correctly when lines are longer than the screen width and wordwrap is on.
Or this method (which has previously been posted in vim tips wiki and on Stack Exchange):
Method 2:
" N<C-D> and N<C-U> idiotically change the scroll setting
function! s:Saving_scrollV(cmd)
let save_scroll = &scroll
execute "normal" a:cmd
let &scroll = save_scroll
endfunction
" move and scroll
nmap <silent> <C-J> :call <SID>Saving_scrollV("1<C-V><C-D>")<CR>
vmap <silent> <C-J> <Esc> :call <SID>Saving_scrollV("gv1<C-V><C-D>")<CR>
nmap <silent> <C-K> :call <SID>Saving_scrollV("1<C-V><C-U>")<CR>
vmap <silent> <C-K> <Esc> :call <SID>Saving_scrollV("gv1<C-V><C-U>")<CR>
nmap <silent> <C-F> :call <SID>Saving_scrollV("<C-V><C-D>")<CR>
vmap <silent> <C-F> <Esc> :call <SID>Saving_scrollV("gv<C-V><C-D>")<CR>
nmap <silent> <PageDown> :call <SID>Saving_scrollV("<C-V><C-D>")<CR>
vmap <silent> <PageDown> <Esc>:call <SID>Saving_scrollV("gv<C-V><C-D>")<CR>
nmap <silent> <C-B> :call <SID>Saving_scrollV("<C-V><C-U>")<CR>
vmap <silent> <C-B> <Esc> :call <SID>Saving_scrollV("gv<C-V><C-U>")<CR>
nmap <silent> <PageUp> :call <SID>Saving_scrollV("<C-V><C-U>")<CR>
vmap <silent> <PageUp> <Esc> :call <SID>Saving_scrollV("gv<C-V><C-U>")<CR>
The only issue I have with the second method is when lines are longer than the screen width and wordwrap is on then the cursor can move up or down some to account for the extra lines from the wrap. Also at the very top and bottom of the file the cursor can move. The first method really attempts to never move the cursor in all cases.

This changes the cursor on-screen position, but does not change the cursor line on-screen position:
noremap <C-k> #="1\<lt>C-D>"<CR>:set scroll=0<CR>
noremap <C-l> #="1\<lt>C-U>"<CR>:set scroll=0<CR>
This however resets the scroll option, so subsequent <C-D> and <C-U> will scroll by half screen. Without set scroll=0, the scroll option would have been set to 1, and subsequent <C-D> and <C-U> would be scrolling by one line (Vim is weird).
Probably a Vimscript function based on 1<C-D> and 1<C-U> would be the best.

Related

How to toggle (all) line numbers on or off

Let's say I have some combination of:
" one if not both is usually on
set number " could be on or off
set relativenumber " could be on or off
Is there a way to toggle these on/off without losing information (not knowing what is set -- i.e., I would like to make a simple keyboard shortcut to toggle the visibility of the current line-number selection)? For example if I have only rnu set and I do:
:set number!
It really doesn't help me at all, since I'll still have rnu set and there will still be a line-number column on the left. If so, how could this be done?
give this a try:
currently, I am mapping it to <F7> you can change the mapping if you like
I am using the global variable, you can change the scope if it is required
This function will disable all line-number displays and restore to the old line number settings.
function! MagicNumberToggle() abort
if &nu + &rnu == 0
let &nu = g:old_nu
let &rnu = g:old_rnu
else
let g:old_nu = &nu
let g:old_rnu = &rnu
let &nu = 0
let &rnu =0
endif
endfunction
nnoremap <F7> :call MagicNumberToggle()<cr>
The one liner solution
:nnoremap <silent> <C-n> :let [&nu, &rnu] = [!&rnu, &nu+&rnu==1]<cr>
To understand what happens try:
:echo [&nu, !&rnu]
&nu ............. gets the value of number
!&rnu ........... the oposite value of relative number
For more :h nu

How can I block moving up/down with multiple j,k keystrokes?

I learned moving with hjkl by blocking arrow keys.
I'd like to do something similiar for moving up/down with jjjjjj/kkkkk.
For example whenever I press j 4 times in a row with small delays it would jump back to original position, so I'd have to think how to move smarter to the place I want.
I'm not a fan of technical solutions to this problem (I'd rather critically reflect on my own typing occasionally), but this can be done by storing subsequent keypresses in an array, and complaining if the size becomes too large:
let g:pos = []
let g:keys = []
function! RecordKey( key )
if v:count || get(g:keys, 0, '') != a:key
" Used [count], or different key; start over.
let g:keys = [a:key]
let g:pos = getpos('.')
echo
return 1
endif
call add(g:keys, a:key)
if len(g:keys) > 4
" Too many identical movements (without count).
let g:keys = [a:key]
call setpos('.', g:pos)
echohl ErrorMsg
echomsg 'Try again'
echohl None
return 0
endif
echo
return 1
endfunction
" Reset counter after a delay in movement.
autocmd CursorHold * let g:keys = []
nnoremap <silent> j :<C-u>if RecordKey('j')<Bar>execute 'normal!' (v:count ? v:count : '') . 'j'<Bar>endif<CR>
nnoremap <silent> k :<C-u>if RecordKey('k')<Bar>execute 'normal!' (v:count ? v:count : '') . 'k'<Bar>endif<CR>
" Add more movements as you wish.
(Trying this out, I'm already annoyed by this :-)
I would suggest the following mappings:
nnoremap jjjj j
nnoremap kkkk k
This will make fast movement up and down very cumbersome. Unfortunately it will also prohibit a normal 'j' from executing very fast, as Vim will wait to see whether you want to add anything else after the first keypress to complete the binding. This can be circumvented by pressing another key afterwards (e.g. switching to insert mode with i/I/a/A or the like).

Disallow subsequent h/j/k/l

I want to force myself to not press jjjjj and rather use 5j instead. I'm looking for a solution that forbids / disables that kind of subsequent motion usage.
For initially practicing h/j/k/l instead of arrows I used
nnoremap <Left> :echoe "Use h"<CR>
nnoremap <Right> :echoe "Use l"<CR>
nnoremap <Up> :echoe "Use k"<CR>
nnoremap <Down> :echoe "Use j"<CR>
I tried to do something similar like
nnoremap jj :echoe "Use xj"<CR>
nnoremap ll :echoe "Use xl"<CR>
nnoremap kk :echoe "Use xk"<CR>
nnoremap hh :echoe "Use xh"<CR>
But this results in that even jumping with 5j needs to wait for the vim timeout.
I've checked vim-hardtime, but it also prevents me from doing things like 2j9j within the timeout, which I would hardly call a bad habit, but rather a sudden change of mind while navigating.
The following might be a starting point (to be put in your .vimrc file) from which you can develop your own plugin:
nno <silent> j :<C-U>execute "call Restrictedj(" . v:count . ")"<CR>
let g:moved1 = v:false
fu! Restrictedj(count)
if a:count > 1
exe line('.') + a:count
let g:moved1 = v:false
else
if !g:moved1
exe line('.') + 1
else
echoe 'Use xj'
end
let g:moved1 = v:true
end
endf
Such a code will make j (without count) error from the second use of it on.
The main fault is that you can only reactivate it by pressing 2j, 3j, or more, and not by pressing any other key (which would be desirable).
In principle the function can be modified in such a way that pressing each one of the four hjkl reactivates the remaining three. However I think that the ideal is that each of hjkl should be reactivated by any action other than pressing that key again.
The timeout is unavoidable by definition, but you could at least reduce the timeout by setting timeoutlen. It defaults to 1000, which is quite long. You could probably get away with lowering it to 500, especially seeing as you are planning on using this only temporarily as a training aid.
The following is a more self-contained solution: one function and four mappings for h, j, k, l.
There is no timer, but the only way to "reactivate" each of the four keys is using it with an explicit count or using one of the three other keys.
fu! NoRepHJKL(count, key, selfCall)
if !exists('g:can_use')
let g:can_use = { 'h': v:true, 'j': v:true, 'k': v:true, 'l': v:true }
endif
if a:count > 0
execute "normal! " . a:key
call NoRepHJKL(a:count - 1, a:key, v:true)
else
if a:selfCall || g:can_use[a:key]
let g:can_use.h = v:true
let g:can_use.j = v:true
let g:can_use.k = v:true
let g:can_use.l = v:true
endif
if !a:selfCall && g:can_use[a:key]
execute "normal! " . a:key
let g:can_use[a:key] = v:false
endif
endif
endf
nn <silent> h :<C-U>call NoRepHJKL(v:count, 'h', v:false)<CR>
nn <silent> j :<C-U>call NoRepHJKL(v:count, 'j', v:false)<CR>
nn <silent> k :<C-U>call NoRepHJKL(v:count, 'k', v:false)<CR>
nn <silent> l :<C-U>call NoRepHJKL(v:count, 'l', v:false)<CR>
The function
defines a global boolean dictionary for the four keys (only the first time it's called) which contains whether each of the four key can be used;
if the a:count passed to it is positive (this includes 1), it uses the key (given through the argument a:key) in normal mode and calls itself recursively, with a reduced a:count argument, and with the information that the it is a:selfCalling.
if the a:count is zero
it will make all four keys available for the next use only if it reached zero by recursion or (if not) if the a:key is not been overused;
if it is not a self call, but the a:key is not been overused, then it uses it in normal mode and makes it unavailable for the next use.

Control + space vim key binding in normal mode does not work

I want to map control+space to :bnext in order to forward switch in normal mode. So I added following to my .vimrc:
nnoremap <silent> <C-Space> :bn<CR>
For backward search following
nnoremap <silent> <C-S-Space> :bp<CR>
However, this solution does not work. Any ideas on what is the problem, and how I may solve it?
Update: My mapping list:
n [m *#:call <SNR>45_Python_jump('?^\s*\(class\|def\)')<CR>
n [[ *#:call <SNR>45_Python_jump('?^\(class\|def\)')<CR>
n ]m *#:call <SNR>45_Python_jump('/^\s*\(class\|def\)')<CR>
n ]] *#:call <SNR>45_Python_jump('/^\(class\|def\)')<CR>
<F7> *#:call Flake8()<CR>
n <Tab> * <C-W>w
n <CR> * :call gruvbox#hls_hide()<CR><CR>
n * * :let #/ = ""<CR>:call gruvbox#hls_show()<CR>*
n ,tj :call Tj()<CR>
n ,st :call Sts()<CR> <Space>
n / * :let #/ = ""<CR>:call gruvbox#hls_show()<CR>/
n ? * :let #/ = ""<CR>:call gruvbox#hls_show()<CR>?
n \ca <Plug>NERDCommenterAltDelims
x \cu <Plug>NERDCommenterUncomment
n \cu <Plug>NERDCommenterUncomment
x \cb <Plug>NERDCommenterAlignBoth
n \cb <Plug>NERDCommenterAlignBoth
x \cl <Plug>NERDCommenterAlignLeft
n \cl <Plug>NERDCommenterAlignLeft
n \cA <Plug>NERDCommenterAppend
x \cy <Plug>NERDCommenterYank
n \cy <Plug>NERDCommenterYank
x \cs <Plug>NERDCommenterSexy
n \cs <Plug>NERDCommenterSexy
x \ci <Plug>NERDCommenterInvert
n \ci <Plug>NERDCommenterInvert
n \c$ <Plug>NERDCommenterToEOL
x \cn <Plug>NERDCommenterNested
n \cn <Plug>NERDCommenterNested
x \cm <Plug>NERDCommenterMinimal
n \cm <Plug>NERDCommenterMinimal
x \c<Space> <Plug>NERDCommenterToggle
n \c<Space> <Plug>NERDCommenterToggle
x \cc <Plug>NERDCommenterComment
n \cc <Plug>NERDCommenterComment
n \b * :CommandTBuffer<CR>
n \t * :CommandT<CR>
v \<Space> * <Esc>:call gruvbox#hls_toggle()<CR>gv
n \<Space> * :call gruvbox#hls_toggle()<CR>
n gx <Plug>NetrwBrowseX
n <Plug>NetrwBrowseX * :call netrw#NetrwBrowseX(expand("<cWORD>"),0)<CR>
n <Plug>NERDCommenterAltDelims * :call <SNR>17_SwitchToAlternativeDelimiters(1)<CR>
x <Plug>NERDCommenterUncomment * :call NERDComment("x", "Uncomment")<CR>
n <Plug>NERDCommenterUncomment * :call NERDComment("n", "Uncomment")<CR>
x <Plug>NERDCommenterAlignBoth * :call NERDComment("x", "AlignBoth")<CR>
n <Plug>NERDCommenterAlignBoth * :call NERDComment("n", "AlignBoth")<CR>
x <Plug>NERDCommenterAlignLeft * :call NERDComment("x", "AlignLeft")<CR>
n <Plug>NERDCommenterAlignLeft * :call NERDComment("n", "AlignLeft")<CR>
n <Plug>NERDCommenterAppend * :call NERDComment("n", "Append")<CR>
x <Plug>NERDCommenterYank * :call NERDComment("x", "Yank")<CR>
n <Plug>NERDCommenterYank * :call NERDComment("n", "Yank")<CR>
x <Plug>NERDCommenterSexy * :call NERDComment("x", "Sexy")<CR>
n <Plug>NERDCommenterSexy * :call NERDComment("n", "Sexy")<CR>
x <Plug>NERDCommenterInvert * :call NERDComment("x", "Invert")<CR>
n <Plug>NERDCommenterInvert * :call NERDComment("n", "Invert")<CR>
n <Plug>NERDCommenterToEOL * :call NERDComment("n", "ToEOL")<CR>
x <Plug>NERDCommenterNested * :call NERDComment("x", "Nested")<CR>
n <Plug>NERDCommenterNested * :call NERDComment("n", "Nested")<CR>
x <Plug>NERDCommenterMinimal * :call NERDComment("x", "Minimal")<CR>
n <Plug>NERDCommenterMinimal * :call NERDComment("n", "Minimal")<CR>
x <Plug>NERDCommenterToggle * :call NERDComment("x", "Toggle")<CR>
n <Plug>NERDCommenterToggle * :call NERDComment("n", "Toggle")<CR>
x <Plug>NERDCommenterComment * :call NERDComment("x", "Comment")<CR>
n <Plug>NERDCommenterComment * :call NERDComment("n", "Comment")<CR>
<F4> :Tlist<CR>
n <C-Space> * :bnext<CR>
n <S-Tab> * <C-W>W
<F3> :NERDTreeToggle<CR><CR>
My buffers when I am trying:
:ls
1 # "views.py" line 73
2 %a "./forms.py" line 19
What cause the problem? Can you help?
In most terminals (which I'm assuming you're using, since you didn't specify gVim/MacVim), <C-Space> and <C-S-Space> don't map to actual ASCII characters. You can confirm this by entering insert mode, typing <C-V> to initiate literal character insertion, and then typing either of those keys. If you see ^# that means you entered the Null character, which can't be mapped to. So unfortunately you'll either need to switch to a GUI flavor of Vim (which does understand non-ASCII maps) or choose a different mapping.
Edit: my mistake, the null character can be mapped to, via <NUL>, as another answer points out. There's still no distinguishing <C-S-Space> and <C-Space> (or <C-j>, and <C-#>), but you can map them at least.
You can use <NUL> for this:
nnoremap <NUL> :w<CR>
saves the current file when you type <c-space> in normal mode.
This works for me in the terminal (I use cygwin terminal in windows, should work in other terminals).
Some key combinations, like Ctrl + non-alphabetic cannot be mapped, and Ctrl + letter vs. Ctrl + Shift + letter cannot be distinguished. (Unless your terminal sends a distinct termcap code for it, which most don't.) In insert or command-line mode, try typing the key combination. If nothing happens / is inserted, you cannot use that key combination. This also applies to <Tab> / <C-I>, <CR> / <C-M> / <Esc> / <C-[> etc. (Only exception is <BS> / <C-H>.) This is a known pain point, and the subject of various discussions on vim_dev and the #vim IRC channel.
Some people (foremost Paul LeoNerd Evans) want to fix that (even for console Vim in terminals that support this), and have floated various proposals, cp. http://groups.google.com/group/vim_dev/browse_thread/thread/626e83fa4588b32a/bfbcb22f37a8a1f8
But as of today, no patches or volunteers have yet come forward, though many have expressed a desire to have this in a future Vim 8 major release.

How to use abbreviations in Vim with arguments?

In Vim, you can make it so each time you write "FF" on insert mode changes to some code by using:
:iab FF for ( int i = 0 ; i < n ; i++ )
But is there any way to use this with arguments? Something like C's #defines, so if I write
FF(e, 10)
It becomes:
for ( int e = 0 ; e < 10 ; e++ )
Take a look at SnipMate (a vim plugin). You won't get arguments, but upon expansion of an abbreviation, it allows you to tab through modifiable areas. In the for example, you'll be brought to the i first, can edit it to be e, and it will change it to e in all areas of the for declaration. Then simply tab to the next area you'd like to change.
From the docs:
snipMate.vim aims to be an unobtrusive, concise vim script that implements some of TextMate's snippets features in Vim. A snippet is a piece of often-typed text that you can insert into your document using a trigger word followed by a .
For instance, in a C file using the default installation of snipMate.vim, if you type "for" in insert mode, it will expand a typical for loop in C:
for (i = 0; i < count; i++) {
}
To go to the next item in the loop, simply over to it; if there is repeated code, such as the "i" variable in this example, you can simply start typing once it's highlighted and all the matches specified in the snippet will be updated.
The following is a nice helpful change to remap tab and s-tab to c-d and c-a, in case you don't want to lose the functionality of tab (in ~/.vim/after/plugin/snipMate.vim):
"""ino <silent> <tab> <c-r>=TriggerSnippet()<cr>
"""snor <silent> <tab> <esc>i<right><c-r>=TriggerSnippet()<cr>
"""ino <silent> <s-tab> <c-r>=BackwardsSnippet()<cr>
"""snor <silent> <s-tab> <esc>i<right><c-r>=BackwardsSnippet()<cr>
"""ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
ino <silent> <c-d> <c-r>=TriggerSnippet()<cr>
snor <silent> <c-d> <esc>i<right><c-r>=TriggerSnippet()<cr>
ino <silent> <c-a> <c-r>=BackwardsSnippet()<cr>
snor <silent> <c-a> <esc>i<right><c-r>=BackwardsSnippet()<cr>
ino <silent> <c-r><tab> <c-r>=ShowAvailableSnips()<cr>
You can include function definitions in abbreviations, but they cannot take arguments. This is an example from the vimdocs:
func Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunc
iabbr <silent> if if ()<Left><C-R>=Eatchar('\s')<CR>
I guess you could maybe parse the abbreviation expression in the function, but I'm not sure if you can also include characters like parenthesis in the abbreviation. Maybe something here will give you an idea.
Edit: You can always do something like this:
:iab for() for(int i = 0; i < ; i++)<C-o>T<
Which lacks the argument autocompletion of course but lets you start typing it immediately.
It worked for me:
iab FF <c-o>:FF
com -nargs=* FF call s:FF(<f-args>)
fu s:FF(i, n)
let t = "for (int a = 0; a < b; ++a) {\e"
let t1 = substitute(t, 'a', a:i, 'g')
exe 'normal! A'.substitute(t1, 'b', a:x, 'g')
exe "normal o\<space>\<BS>\e"
endf
at insert mode FF e 10<cr> will be for (int e = 0; e < 10; ++e) {<cr>.
mu-template support interactive templates. With it, you can either ask something to the user, or reuse any variable, apply computation on it if you which (detecting that i is already use in the current scope is doable), and use the result in the text you will expand.

Resources