Preventing repeated use of hjkl movement keys in vim

Since I frequently don't use the excellent motions and text objects that vim provides, (and since "Holding down 'j' is a vim anti-pattern,") I'd like vim to assist me in training to use these instead of using hjkl more than a few times in a row.

When I started using vim, I was annoyed that I didn't use hjkl to move but would instead use the arrow keys. As a reminder not to do this, I remapped the arrow keys to keep myself from using them - I knew using the home row to navigate would be a better long term plan, so I cut out the positive reinforcement I would get by having working arrow keys.

map <left> <nop>   
map <right> <nop>  
# I quickly removed nop's for up and down because using 
#  the mouse wheel to scroll is sometimes useful

I no longer need these mappings in my .vimrc because this worked very well, and I quickly switched, without having to make a conscious effort to do so. In a similar fashion, I'd now like to cut off my repeated use of basic movement keys hjkl as well. I was envisioning something like this:

let g:last_mov_key = 'none'
let g:times_mov_key_repeated = 0
function! MovementKey(key)
    if (g:last_mov_key == a:key)
        let g:times_mov_key_repeated = g:times_mov_key_repeated + 1
    else
        let g:last_mov_key = a:key
        let g:times_mov_key_repeated = 0
    endif
    if g:times_mov_key_repeated > 3
        echo "Negative Reinforcement!"                             
    endif
endfunction

noremap j :call MovementKey('j')<CR>gj
noremap k :call MovementKey('k')<CR>gk
noremap h :call MovementKey('h')<CR>h
noremap l :call MovementKey('l')<CR>l

But this breaks in visual mode, and I imagine in tons of other cases where using the vim command line in the middle of something changes the state when it shouldn't. How can I constrain myself to have to use more complicated motions?

Edit: Question edited after first two answers for clarity, paragraphs reordered. I want some assistance from vim moving beyond romainl's "level 0". So far answers advise me not to waste my time, but what if we assume that I'm a fan of the learning technique where I change my habits by altering my environment to change incentives? In this model I want to accomplish a task, say, scrolling down a page, and I will more or less randomly attempt key combinations until I achieve that task. Dopamine signalling etc. in my brain will reinforce the action which eventually achieves this result. I could focus on remembering not to use hjkl , or I could focus on the task I was originally trying to do anyway, edit text, and without really thinking about it find myself using more efficient editing techniques.


I would like to propose a solution that is more in line with the OP's way of thinking.

I too decided that blocking certain motions if not preceded by a count was a good idea, as discussed here. I too tried a simply direct remapping, but this did not work in many contexts, as described by the OP.

But I did finally come up with an approach that worked using key maps that returned an expression:

function! DisableIfNonCounted(move) range
    if v:count
        return a:move
    else
        " You can make this do something annoying like:
        " echoerr "Count required!"
        " sleep 2
        return ""
    endif
endfunction

function! SetDisablingOfBasicMotionsIfNonCounted(on)
    let keys_to_disable = get(g:, "keys_to_disable_if_not_preceded_by_count", ["j", "k", "l", "h", "gj", "gk"])
    if a:on
        for key in keys_to_disable
            execute "noremap <expr> <silent> " . key . " DisableIfNonCounted('" . key . "')"
        endfor
        let g:keys_to_disable_if_not_preceded_by_count = keys_to_disable
        let g:is_non_counted_basic_motions_disabled = 1
    else
        for key in keys_to_disable
            try
                execute "unmap " . key
            catch /E31:/
            endtry
        endfor
        let g:is_non_counted_basic_motions_disabled = 0
    endif
endfunction

function! ToggleDisablingOfBasicMotionsIfNonCounted()
    let is_disabled = get(g:, "is_non_counted_basic_motions_disabled", 0)
    if is_disabled
        call SetDisablingOfBasicMotionsIfNonCounted(0)
    else
        call SetDisablingOfBasicMotionsIfNonCounted(1)
    endif
endfunction

command! ToggleDisablingOfNonCountedBasicMotions :call ToggleDisablingOfBasicMotionsIfNonCounted()
command! DisableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(1)
command! EnableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(0)

DisableNonCountedBasicMotions

Note that the code is included here for convenience, but I would check with the gist as well to see if there have been updates/fixes.

You can relax the constraint for horizontal motions, or add/remove other motions by setting the list of the keys/commands that are effected, g:keys_to_disable_if_not_preceded_by_count , in your ~/.vimrc . For example, the following only enforces count-prefixed motions for "j" and "k":

let g:keys_to_disable_if_not_preceded_by_count = ["j", "k"]

Also, as noted in the gist, you might find it really useful to add something like this in your ~/.vimrc as well as the code above:

set number
if has('autocmd')
augroup vimrc_linenumbering
    autocmd!
    autocmd WinLeave *
                 if &number |
                   set norelativenumber |
                 endif
    autocmd BufWinEnter *
                 if &number |
                   set relativenumber |
                 endif
    autocmd VimEnter *
                 if &number |
                   set relativenumber |
                 endif
augroup END

Or install a plugin line vim-numbers.

Even though this question has (long) been answered to the OP's satisfaction with an alternate approach, I am adding my solution here in case anyone is searching for something different.


You listen to other people waaay too much. Just use for movement whatever keys you like best, and leave other keys alone. Remapping hjkl keys is somewhat troublesome, and best not done, because they're hardcoded in vim due to historical reasons.


EDIT

After reading your edit and your comment here is the radical solution I propose: remap hjkl to do super annoying things:

nnoremap h $
nnoremap l 0
nnoremap j gg
nnoremap k G
" and so on for other modes

hjkl are mapped to the extreme opposite of their original behaviour, essentially making them completely unusable. You should do the same for their synonyms ( :hh , :hj , :hk , :hl ) too for completeness but there are cases when character-by-character/line-by-line movement is useful. In such cases you will be glad to have + or - or <Space> .

I don't really like that kind of pavlovian method, though. I find it too brutal and the risk that you actually get used to these weird mappings seems quite high.

END EDIT

Short version:

Neither the arrow keys nor hjkl are worth using so your remappings are ultimately useless. After a while you will get used to Vim's motions and text-objects. Use what feels more natural to you in the meantime. Don't rush it. It will come. Naturally.

Long version:

Are you a touch typist? Or do you plan to learn touch typing?

I'm not a touch typist and I don't plan to ever become one: I use hjkl only to actually input hjkl in INSERT mode.

When I need to move 1 or 2 lines above or below without a particular "target" I use the arrow keys, when I need to move a couple letters to the left or to the right I use the arrow keys. There is no shame in doing that.

There is a problem, however, when you type jjjjjjjjjj or hit the down arrow key 10 times to move down 10 lines: jjjjjjjjjjj is obviously just as bad as ↓↓↓↓↓↓↓↓↓↓

This stupid mantra of "don't use the arrow keys" is just the tree that hides the forest: it's repeated so often that it makes you focus on useless patterns/antipatterns instead of actually learning more powerful ways.

I don't want to paraphrase the beautiful Your problem with Vim is that you don't grok vi. but you could see it as "levels of enlightenment":

  • Level 0 would be

    jjjjjjjjjjj or ↓↓↓↓↓↓↓↓↓↓ then all the necessary horizontal movements to reach your target.

    Reading the line above, isn't it rather obvious that the hjkl Vs ←↓↑→ debate is absolutely stupid?

    Whether you use one method or the other you end up mashing your keyboard moving vertically AND horizontally to reach the value you want to edit. What a waste. And what a waste of time and energy to force yourself to use one method over another since both are equally bad.

  • Level 1 would be

    10j22l to go down 10 lines and reach your target 22 characters to the right. While it's a lot less typing, you now have to count lines and characters which is not particularly better.

  • Level 2 would be

    10jwww to go down 10 lines and reach your target 3 words to the right or 10jf# to go down 10 lines and jump to the first letter of your target (an hex color value).

    Hmm, that's a lot better.

  • Level 3 would be

    /#<CR> to jump directly to your target (an hex color value, as before). If it's above your current position you would do ?#<CR> .

  • If you are a touch typist, or are training to become one, the debate is settled: hjkl (or jkl; as some like to remap them) are quick and natural and you don't need arrow keys at all and if you are not, the benefits of using hjkl over ←↓↑→ are at best minimal.

    Focus on eEbBwWfFtT/?} and Co. instead. One bit at a time. Don't "force" yourself.

    链接地址: http://www.djcxy.com/p/49508.html

    上一篇: Qt Creator中最有用的(生产性)快捷方式

    下一篇: 防止在vim中重复使用hjkl移动键