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

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

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

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.

Preserve cursor position when using ==

I am trying to make Vim indent lines like Emacs (that is, "make the current line the correct indent" instead of "insert tab character"). Vim can do this with = (or ==) for one line). I have imap <Tab> <Esc>==i in my .vimrc but this makes the cursor move to the first non-space character on the line. I would like the cursor position to be preserved, so I can just hit tab and go back to typing without having to adjust the cursor again. Is this possible?
Example
What I have now (| represents the cursor):
function f() {
doso|mething();
}
Tab
function f() {
|dosomething();
}
What I would like:
function f() {
doso|mething();
}
Tab
function f() {
doso|mething();
}
Also
function f() {
| dosomething();
}
Tab
function f() {
|dosomething();
}
I don't believe there's an "easy" way to do this (that is, with strictly built-in functionality) but a simple function does it just fine. In your .vimrc file:
function! DoIndent()
" Check if we are at the *very* end of the line
if col(".") == col("$") - 1
let l:needsAppend = 1
else
let l:needsAppend = 0
endif
" Move to location where insert mode was last exited
normal `^
" Save the distance from the first nonblank column
let l:colsFromBlank = col(".")
normal ^
let l:colsFromBlank = l:colsFromBlank - col(".")
" If that distance is less than 0 (cursor is left of nonblank col) make it 0
if l:colsFromBlank < 0
let l:colsFromBlank = 0
endif
" Align line
normal ==
" Move proper number of times to the right if needed
if l:colsFromBlank > 0
execute "normal " . l:colsFromBlank . "l"
endif
" Start either insert or line append
if l:needsAppend == 0
startinsert
else
startinsert!
endif
endfunction
" Map <Tab> to call this function
inoremap <Tab> <ESC>:call DoIndent()<CR>
There's always <C-T> and <C-D> (that is CtrlT and CtrlD) in insert mode to indent and outdent on the fly.
These don't change the cursor position – and they're built-in functionality.
Try using a mark of last insert mode, and use the append command to set the cursor back just where it was before, like:
:imap <Tab> <Esc>==`^a

How to prevent leaving the current buffer when traversing the jump list in Vim?

I frequently have several buffers open in my Vim session. This means that my jump list stores locations from several buffers. However, frequently when I use the Ctrl+O keyboard shortcut to jump to a previous location, I do not want to leave the buffer and want to jump to previous locations “local” to the current buffer. How do I do this?
For example, assume my jump list looks as follows:
4 10 1 ~/aaa.m
3 20 1 ~/aaa.m
2 12 2 ~/xxx.m
1 15 1 ~/aaa.m
I want to jump to line 15 of file aaa.m the first time I press Ctrl+O. Importantly, the next time I press Ctrl+O, I do not want to jump to file xxx.m. Rather, I want to jump to line 20 of file aaa.m, that is, my previous location within the “current” buffer. The default Vim behaviour, though, is to take me to to line 12 of file xxx.m.
Any ideas on how I can achieve this?
Try the following jump-list traversing function. It steps successively
from one jump-list location to another (using Ctrl+O
or Ctrl+I depending on the values that are
supplied to its back and forw arguments), and stops if the current
location is in the same buffer as that buffer it has started from.
If it is not possible to find a jump-list location that belongs to the
current buffer, the function returns to the position in the jump list
that was the current one before the function was called.
function! JumpWithinFile(back, forw)
let [n, i] = [bufnr('%'), 1]
let p = [n] + getpos('.')[1:]
sil! exe 'norm!1' . a:forw
while 1
let p1 = [bufnr('%')] + getpos('.')[1:]
if n == p1[0] | break | endif
if p == p1
sil! exe 'norm!' . (i-1) . a:back
break
endif
let [p, i] = [p1, i+1]
sil! exe 'norm!1' . a:forw
endwhile
endfunction
To use this function as a
Ctrl+O/Ctrl+I-replacement
locked to the current buffer, create mappings as it is shown below.
nnoremap <silent> <c-k> :call JumpWithinFile("\<c-i>", "\<c-o>")<cr>
nnoremap <silent> <c-j> :call JumpWithinFile("\<c-o>", "\<c-i>")<cr>
Maybe the EnhancedJumps plugin will help.
With this plugin installed, the jump to another buffer is only done if the same jump command is repeated once more immediately afterwards.

Vim scrolling without changing cursors on-screen position

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.

Resources