Vim provides the count modifier, which is used to multiply or add iterations to a command. If you use vim, you are probably familiar with it: It allows you to write 50j to move down 50 times.
Occasionally I manage to enter a big number without noticing, while I am actually using other applications. When I then proceed to use vim and for example type o to begin a new line, vim naturally tries to create a huge amount of new lines, which slowly fills up the memory and then gets killed by the kernel OOM killer.
Is there any way to limit the counter or to add a confirmation if it is greater than some threshold?
This almost works:
function! UpdateCount(n) abort
let limit = get(g:, 'counter_limit', 99)
if v:count == 0
return ''.a:n
elseif v:count == limit
return ''
elseif 10 * v:count + a:n > limit
return repeat("\<Del>", strlen(v:count)).limit
else
return ''.a:n
endif
endfunction
nnoremap <expr> 0 UpdateCount(0)
nnoremap <expr> 1 UpdateCount(1)
nnoremap <expr> 2 UpdateCount(2)
nnoremap <expr> 3 UpdateCount(3)
nnoremap <expr> 4 UpdateCount(4)
nnoremap <expr> 5 UpdateCount(5)
nnoremap <expr> 6 UpdateCount(6)
nnoremap <expr> 7 UpdateCount(7)
nnoremap <expr> 8 UpdateCount(8)
nnoremap <expr> 9 UpdateCount(9)
But, unfortunately, it doesn't work for the 0 key, since Vim disables any mappings for 0 while entering a count, which makes sense since 0 by itself is the command to go to the first character of the line and if these mappings weren't disabled then a command such as nnoremap 0 ^ would break usage of 0 in counts...
So, yeah, other than patching Vim to add a limit, I don't really see a good way to fix this in general.
If this is a problem with some commands more than others (i.e. insertion commands, such as o or i or A, etc.) then you might want to consider adding a mapping to those, inspecting v:count in those and preventing them if the count is above a certain limit.
For example:
function! LimitCount(cmd) abort
let limit = get(g:, 'counter_limit', 99)
if v:count > limit
echomsg "Count ".v:count." is too large, aborting execution of '".a:cmd."' command."
" Use Ctrl-C to erase the pending count and avoid applying it to the next command.
return "\<C-C>"
else
return a:cmd
endif
endfunction
nnoremap <expr> o LimitCount('o')
nnoremap <expr> i LimitCount('i')
" And so on for other insertion commands...
Related
I recently fell in love with the f, F, t, and T commands in vim. Now I find myself frequently wanting to insert something at a position that can easily be navigated to with one of these commands, at least frequently enough that I would want to make the entire action "find character and insert text" repeatable via .. Currently, I can repeat the insert action, but I have to retype the find-character movement at every line where I want to repeat the insert.
Is there a command that combines the actions of these movement commands with the action of dropping into insert mode? Or, if not, is it possible to define such a command in my .vimrc?
First, you can repeat the last f/t/F/T motion via ; (reverse via ,), so you can repeat with two keys: ;.
If that's not good enough, the repeat.vim plugin can be used to build a custom mapping that repeats just like the built-in commands:
"<Leader>it{char} Insert text before the [count]'th occurrence of {char}
" to the right.
"<Leader>if{char} Insert text after the [count]'th occurrence of {char}
" to the right.
" These mappings can be repeated atomically, this is
" faster than ";."
function! s:InsertAtCharPrepare( motion, moveOffMotion, repeatMapping )
augroup InsertAtChar
autocmd!
" Enter insert mode automatically after the f/t motion.
" XXX: :startinsert doesn't work on the first movement somehow, use
" feedkeys() instead.
autocmd CursorMoved <buffer> call feedkeys('a', 'n')
" Prime repeat.vim after insertion is done.
execute printf('autocmd InsertLeave <buffer> %scall repeat#set(%s, %d) | autocmd! InsertAtChar',
\ (v:count1 <= 1 || empty(a:moveOffMotion) ? '' : 'execute "normal!" ' . string(a:moveOffMotion) . '|'),
\ string(a:repeatMapping),
\ v:count1
\)
" Abort in case something unexpected happens.
autocmd WinLeave,BufLeave <buffer> autocmd! InsertAtChar
augroup END
return a:motion
endfunction
function! s:InsertAtCharRepeat( moveOffMotion, repeatMapping )
let l:count = v:count1 " Save the original count to pass this on to repeat.vim.
execute 'normal!' l:count . ';.' . (l:count <= 1 ? '' : a:moveOffMotion)
call repeat#set(a:repeatMapping, l:count)
endfunction
" With "t" and [count] > 1, we need to move off from before {char} (where we're
" left when leaving insert mode) onto {char}, so that a repeat will happen
" before the next occurrence, not on the same again.
nnoremap <silent> <Plug>(InsertUntilCharRepeat) :<C-u>call <SID>InsertAtCharRepeat('l', "\<lt>Plug>(InsertUntilCharRepeat)")<CR>
nnoremap <silent> <Plug>(InsertFromCharRepeat) :<C-u>call <SID>InsertAtCharRepeat('', "\<lt>Plug>(InsertFromCharRepeat)")<CR>
nnoremap <expr> <Leader>it <SID>InsertAtCharPrepare('t', 'l', "\<lt>Plug>(InsertUntilCharRepeat)")
nnoremap <expr> <Leader>if <SID>InsertAtCharPrepare('f', '', "\<lt>Plug>(InsertFromCharRepeat)")
I generally like the shiftround option in Vim, but there are a couple of
situations where it doesn't work very well. For instance, take this example:
f(x,
y)
Selecting the two lines and shifting with > and the two lines selected gives
me (and shiftwidth set to 4):
f(x,
y)
When I really wanted:
f(x,
y)
In other words, Vim advanced each line of the block to the next tabstop, when I
really wanted it to insert the same amount of inserted on each line--but I want
the least indented line to end up on the next tabstop.
Is there an easy way to get this behavior in Vim? My goal is to have this work
for < and > (with a visual selection) rather than other workarounds.
Ctrl+V, select vertical column before the text you need to shift (j in your case of just two lines), Shift+I, insert needed number of tabs or spaces, Esc.
You can actually use this method to insert arbitrary text, for example you can insert # or // to comment some code. More info in :help blockwise-operators.
I would solve this via a separate set of mappings that enclose the >> commands with a temporary clearing of 'shiftround'. Here's an implementation:
" g>> Shift [count] lines one 'shiftwidth' rightwards, without
" 'shiftround'.
"{Visual}[count]> Shift the highlighted lines [count] 'shiftwidth'
" rightwards (for {Visual} see |Visual-mode|), without
" 'shiftround'.
" g<<, {Visual}[count]<
function! s:Shift( command )
let s:save_shiftround = &shiftround
set noshiftround
return a:command
endfunction
function! s:RestoreShiftRound()
let &shiftround = s:save_shiftround
return ''
endfunction
nnoremap <expr> <SID>(RestoreShiftRound) <SID>RestoreShiftRound()
nnoremap <expr> <SID>(ShiftRight) <SID>Shift('>>')
xnoremap <expr> <SID>(ShiftRight) <SID>Shift('>')
nnoremap <expr> <SID>(ShiftLeft) <SID>Shift('<<')
xnoremap <expr> <SID>(ShiftLeft) <SID>Shift('<')
nnoremap <silent> <script> <Plug>(ShiftRightNoRound) <SID>(ShiftRight)<SID>(RestoreShiftRound)
xnoremap <silent> <script> <Plug>(ShiftRightNoRoundSelection) <SID>(ShiftRight)<SID>(RestoreShiftRound)
nnoremap <silent> <script> <Plug>(ShiftLeftNoRound) <SID>(ShiftLeft)<SID>(RestoreShiftRound)
xnoremap <silent> <script> <Plug>(ShiftLeftNoRoundSelection) <SID>(ShiftLeft)<SID>(RestoreShiftRound)
nmap g>> <Plug>(ShiftRightNoRound)
xmap g> <Plug>(ShiftRightNoRoundSelection)
nmap g<< <Plug>(ShiftLeftNoRound)
xmap g< <Plug>(ShiftLeftNoRoundSelection)
I didn't cover the >{motion} command, as that would be more complex.
I have created a mapping in visual and normal mode to expedite moving around in a local region of code. if i press 1+direction key it is remapped to 10 instead of 1.
vmap 1j 10j | vmap 1k 10k | vmap 1h 10h | vmap 1l 10l
nmap 1j 10j | nmap 1k 10k | nmap 1h 10h | nmap 1l 10l
This works well. However when I am typing fast I inadvertently type 11 instead of 1 so '11j' insead of '1j'. This is moving me 110 lines down instead of 11.
I would like to only move 11 in a given direction instead of 110 when I make this mistake.
Vim is interpreting this as a 1 and then adding my mapping to get the 110. Similarly if I type '21j' it is interpreted as '210j'.
This should do what you. However I'm not really sure why it works the way it does. It seems that the old count doesn't get cleared when you change the count inside the mapping and the the new count is appended to the old count. (Notice that I only put a 0 in the mapping not a 10)
I also used v:count to find the count of the mapping instead of overloading 1j. v:count returns 0 if no count was specified.
function TenMovement(type)
if v:count == 1
return '0'.a:type
else
return a:type
endif
endfunction
nnoremap <expr> j TenMovement('j')
nnoremap <expr> k TenMovement('k')
nnoremap <expr> l TenMovement('l')
nnoremap <expr> h TenMovement('h')
vnoremap <expr> j TenMovement('j')
vnoremap <expr> k TenMovement('k')
vnoremap <expr> l TenMovement('l')
vnoremap <expr> h TenMovement('h')
To fix this, you have to abort the previously typed count. <C-\><C-n> works like <Esc> in normal mode, but avoids the beep when there's no pending count:
nmap 1j <C-\><C-n>10j
For visual mode, the selection needs to be re-established with gv:
vmap 1j <C-\><C-n>gv10j
I want to push one button in Vim and fold all the code so only code up to a specific (and variable) indent level is showing. Very useful when I want to only see method names for example and not their indented routines.
The “Vim: Fold top level folds only” question has a solution to an indent level, but it requires an environment set each time you change levels.
When my cursor is at an indent level (say level 2), I want the entire file to fold to that indent level across all methods.
Is this built into Vim somewhere? Does anyone know of a good plugin that does this?
Configure folding to be defined by indentation:
:setl foldmethod=indent
and try the following command:
:let &l:foldlevel = indent('.') / &shiftwidth
To quickly access this command, create a mapping for it as follows:
:nnoremap <silent> <leader>z :let&l:fdl=indent('.')/&sw<cr>
Because foldnestmax doesn't apply when foldmethod is expr, I looked for something else when I came across your question. Here is what I came up with, which doubtless can be improved:
function! <sid>CloseFoldOpens(opens_level)
let lineno = 2
let last = line("$")
while lineno < last
if foldclosed(lineno) != -1
let lineno = foldclosedend(lineno) + 1
elseif foldlevel(lineno) > foldlevel(lineno - 1)
\ && foldlevel(lineno) == a:opens_level
execute lineno."foldclose"
let lineno = foldclosedend(lineno) + 1
else
let lineno = lineno + 1
end
endwhile
endfunction
nnoremap <silent> z1 :%foldclose<cr>
nnoremap <silent> z2 :call <sid>CloseFoldOpens(2)<cr>
nnoremap <silent> z3 :call <sid>CloseFoldOpens(3)<cr>
nnoremap <silent> z4 :call <sid>CloseFoldOpens(4)<cr>
nnoremap <silent> z5 :call <sid>CloseFoldOpens(5)<cr>
I prefer the numbered maps, but for yours based on indentation of the current line, something along these lines:
nnoremap <silent> z. :call <sid>CloseFoldOpens(foldlevel('.'))<cr>zv
No need of a plugin, it is builtin in Vim.
'foldlevel' (or shorter 'fdl') and 'foldnestmax' ('fdn') seems to be what we were looking for. You only have to set the 'foldmethod' (or shorter 'fdm') and a 'foldnestmax' (or 'fdn') in you .vimrc file:
set foldmethod=indent foldlevelstart=2 foldnestmax=2
OR the shorter version:
set fdm=indent fdls=2 fdn=2
Then you can change the fold level with direct commands: zm or zr.
When I'm using vim I generally never want to move to a punctuation mark when I press w or b to go forwards or backwards. So I'm wondering if there's a setting or something to change this functionality?
e.g. If I've got some code like
object.method(args)
and my cursor is at the [o] in "object" then I want w to move to the [m] in "method", and another w to move to the [a] in "args". I don't want it to land on the [.] or the [(]. If I've ever wanted to move to a punctuation char I've always used f or F to jump straight to it. I've never personally wanted to move to a punctuation char when I move through words and I just realized this is really bugging me.
I too find that I would like a movement that is more inclusive that w, but not as inclusive as W. In particular, I would like a movement that only considers tokens beginning with alphanumeric characters as significant.
So I came up with the following:
" <SPACE> : forward to next word beginning with alphanumeric char
" <S-SPACE> : backward to prev word beginning with alphanumeric char
" <C-SPACE> : same as above (as <S-SPACE> not available in console Vim
" <BS> : back to prev word ending with alphanumeric char
function! <SID>GotoPattern(pattern, dir) range
let g:_saved_search_reg = #/
let l:flags = "We"
if a:dir == "b"
let l:flags .= "b"
endif
for i in range(v:count1)
call search(a:pattern, l:flags)
endfor
let #/ = g:_saved_search_reg
endfunction
nnoremap <silent> <SPACE> :<C-U>call <SID>GotoPattern('\(^\\|\<\)[A-Za-z0-9_]', 'f')<CR>
vnoremap <silent> <SPACE> :<C-U>let g:_saved_search_reg=#/<CR>gv/\(^\\|\<\)[A-Za-z0-9_]<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
nnoremap <silent> <S-SPACE> :<C-U>call <SID>GotoPattern('\(^\\|\<\)[A-Za-z0-9_]', 'b')<CR>
vnoremap <silent> <S-SPACE> :<C-U>let g:_saved_search_reg=#/<CR>gv?\(^\\|\<\)[A-Za-z0-9_]<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
nnoremap <silent> <BS> :call <SID>GotoPattern('[A-Za-z0-9_]\(\>\\|$\)', 'b')<CR>
vnoremap <silent> <BS> :<C-U>let g:_saved_search_reg=#/<CR>gv?[A-Za-z0-9_]\(\>\\|$\)<CR>:<C-U>let #/=g:_saved_search_reg<CR>gv
" Redundant mapping of <C-SPACE> to <S-SPACE> so that
" above mappings are available in console Vim.
"noremap <C-#> <C-B>
if has("gui_running")
map <silent> <C-Space> <S-SPACE>
else
if has("unix")
map <Nul> <S-SPACE>
else
map <C-#> <S-SPACE>
endif
endif
I have had this for a long time now, and I find that I use <SPACE>/<C-SPACE> movements so much more than w and W; it just seems more useful when coding. You can, of course, map the commands to whatever keys you find useful or more appropriate.
Even running the risk of creating a script for something that's built-in (like
I did last time), here is a little function that may help accomplishing
this.
function! JumpToNextWord()
normal w
while strpart(getline('.'), col('.')-1, 1) !~ '\w'
normal w
endwhile
endfunction
Basically, what it does is executing the standard w and repeating it
if the character under the cursor is not in a word character (feel free to
change that pattern.
If you add that and a little map in your .vimrc:
nnoremap <silent> ,w :call JumpToNextWord()<CR>
It should work.