Search code examples
vimvi

Limit count modifier to iterate commands


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?


Solution

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