1

Is it possible for vim navigation to be as quick as a normal point-and-click navigation? For example, I've taken the following example here where I've created four points that need to be clicked:

enter image description here

Without any practice or thinking, I'm able to click those four points in 3.9 seconds, video here. What would be the fastest that someone could get to those four locations in vim? I mean if someone were not to "think about it", i.e., just given four random points spaced across a screen what would be the fastest way to get there (fastest, as in lowest amount of time, not fewest keystrokes)?

I am a vim beginner, and my first attempt took about 40s (!!) -- https://gyazo.com/377ca4a30a12c0814ad8b6255b761075. Mostly due to trying to do a search and it basically starting from the top of the document every time. What would be some ways that this can be improved? Note: it seems like I had a bad search option (it was starting from the top of the file), so when I fixed that I was able to click the four points in about ~13.5s.

If helpful, here's the method if someone wants to use that for their answer (can insert ~1500 blank lines above the method):

def build_data(self, build_data=True):
        processing_start_time = time.time()
        log.info('Building data...')

        self.begin_build_data()

        if build_data is True:
            self.pre_build_data()
            self.update_status('URLS')
            self._build_data()
            self.post_build_data()

        # Sample the Data to make sure it's OK -- underscores ignored
        ACCEPTABLE_KEYS = set(['Platform', 'ArtworkURL', 'BaseURL', 'CollectionTitleIDs', 'Credits', 'CustomerRatingAverage', 'CustomerRatingVolume', 'EpisodeNumber', 'Genres', 'LanguageOfMetadata', 'Languages', 'Locales', 'Name', 'PlatformID', 'Products', 'ReleaseDate', 'ReleaseYear', 'RuntimeInMinutes', 'SeasonNumber', 'Studio', 'Synopsis', 'TVSeriesID', 'TrailerURL', 'Type', 'HighestResolution'])
        for num, key in enumerate(self.data):
            if unicode(key).startswith('_'): continue
            if num % 20 == 0:
                keys = set([i for i in self.data[key].keys() if not i.startswith('_')])
                bad_keys = keys - ACCEPTABLE_KEYS
                if len(bad_keys) > 0:
                    print bad_keys, key, keys
                    raise('Bad Keys Found!')

        log.info('Finished building data in %s' % (time.time() - processing_start_time))

        self.create_sorted_keys()

        try:
            items, avails = self.get_number_items_and_avails()
        except:
            pass # PS resets IP and breaks this.

        data_file = '%s_%s.txt' % (self.date_stem, self.platform.lower())
        log.info('Writing data to %s...' % data_file)

        self._write_data(archive=False)
        self.update_status('Saved')

        self.end_build_data()

        log.info('%.4fs to build data' % (time.time() - processing_start_time))
5
  • 1
    What is your use case to go to 4 random points without stop? Typically, you move the cursor to one position and edit or check something. Then you move to another position.
    – jdhao
    Apr 23 '20 at 12:36
  • @jdhao I recorded my screen when I was writing code, and I found the biggest difference in my speed between vim and textmate was navigating the cursor, so I used the "four mouse clicks" as a sort of basic benchmark. In actuality, yes there would be "actions" done after the click, but the actions I'm able to do about the same speed in a normal editor and vim -- so it's the "click" that is by far the slowest part for me, so I'm working on how to improve that.
    – David542
    Apr 23 '20 at 17:23
  • I'm watching your video again and still don't understand why trying to go to CollectionTitleIDs you seem to need to go through all other matches in the file first... Why isn't your search finding that one directly from build_data?
    – filbranden
    Apr 24 '20 at 3:16
  • 1
    @filbranden I had some setting on that was first doing a ctrl-O, but once I fixed that I got it down to ~13s using the search method and it wasn't starting from the beginning.
    – David542
    Apr 24 '20 at 3:32
  • @David542 I think I mentioned this to you in a comment in another question... It seems you're starting to learn Vim with a heavily customized vimrc (I don't know where you got it from, but I've seen people recommend you start Vim with their vimrc, or even whole Vim "distributions" which customize a bunch of stuff.) I'd seriously recommend that you don't take that approach, since you're for a large part not learning Vim, but learning someone's heavily opinionated configuration on top of Vim. Plain Vim is quite functional by itself. You're better off adding little by little as you need more.
    – filbranden
    Apr 24 '20 at 3:36
4

I'd like to primarily challenge the premise of your question/challenge/race:

What would be the fastest that someone could get to those four locations in vim?

When you're editing text, you're not only moving to specific locations on the text, you're moving to those points and adding or modifying text there, before moving on to the next location and editing that one.

The claim that using keyboard commands in Vim allow someone to edit code faster than someone who only has a mouse (and arrow keys) is essentially about how moving your hand between keyboard and mouse constantly takes significant time and breaks/interrupts your flow, while keeping your hands on the keyboard while alternating between motion (normal-mode) and editing (insert-mode) can be more efficient for many common editing tasks.

just given four random points

You typically don't move to random points on the text while editing. In general, you're moving between related parts of the text while performing edits. For example, if you're refactoring code, you're looking at all uses of the same variable or all calls of a specific function. If you're writing new code, you're typically just working on one (or a few) short blocks of code.

spaced across a screen

That assumes that all the locations you are jumping to can fit in one screen and that's rarely the case.

One of the typical cases where mouse-based navigation quickly breaks down is when the locations you want to jump too are off the screen and you need to scroll.

Using logical (move to next paragraph, or next function, or next class, or next occurrence of the word under the cursor, etc.) rather than physical (point-and-click) motions help with moving to the locations you want, even if you don't necessarily see them on the current screen.

Without any practice or thinking, I'm able to click those four points in 3.9 seconds, video here.

You can use the mouse in Vim, even on the terminal. There are many times where "using a mouse" is the right answer and nothing really precludes the use of the mouse in Vim.

what would be the fastest way to get there (fastest, as in lowest amount of time, not fewest keystrokes)? I am a vim beginner, and my first attempt took about 40s (!!) -

Learning more motions definitely helps!

It can take a long time to really master Vim motion commands and be super effective with them.

My advice is next time you find yourself having trouble jumping to a specific point in the text, take a break and try to think hard about it. What's the conceptual nature of this motion? Is it generic enough? How does it relate to the programming language this file is written in?

Then try to find/research motions that will get you there quickly. That might involve searching for them in :help, looking for motion plug-ins that can help, looking for plug-ins that improve handling of the file type you're editing, or even asking on this site how one would move from A to B most efficiently.

The upside of this learning process is that next time you need the same kind of motion, you will know one that works (or at least gets you closer quicker.) With time, you'll be able to move there quickly without really thinking about it. It takes effort to create habits, but once you have them, deploying them is easy.

As mentioned by @Zorzi, there are some settings (such as set relativenumber) and plug-ins (such as vim-easymotion) that you might want to consider looking into pretty soon. I'd say these are some of the obvious starting points for beginners, you might want to begin there and see if/how that helps.

6
  • Disagree on only one minor point: the travel distance between kbd/mouse+arrows is negligible. Its the imprecision of the mouse (effectively 0 accuracy) and the slowness of the arrow keys that makes them awkward to use. Vim motions are both precise and accurate, and usually require fewer keystrokes than hammering an arrow key. Thats what makes it faster for me.
    – D. Ben Knoble
    Apr 23 '20 at 12:27
  • @filbranden -- When you're editing text, you're not only moving to specific locations on the text, you're moving to those points and adding or modifying text there, understood. I recorded my screen using Textmate and Vim and found that (with my basic skillset) I'm able to do the editing at around the same speed -- it's the navigation for me that is so much slower. So I'm trying to benchmark and optimize that. It would also be nice with your answer to see what time you get with your own suggestions on the given text.
    – David542
    Apr 23 '20 at 17:30
  • 1
    @filbranden -- additions, You can use the mouse in Vim, even on the terminal., thanks for this tip, that is so cool! Especially for adding new windows and resizing them -- i.e., things that are outside of normal text editing and navigation.
    – David542
    Apr 23 '20 at 19:18
  • @David542 I'm not really going to time how long it takes me since I mostly use Vim on my phone, so I'd expect that to be slow... From build_data I'd jump to the second point with /Coll<CR> to find the word, followed by fT to jump to the T in the middle of the word. (Using :set incsearch helps a lot when searching!) Then to the third point I'd use 7j$ since it's seven lines down. (With :set relativenumber I wouldn't have to count or do math in my head.)
    – filbranden
    Apr 24 '20 at 3:21
  • 1
    "I recorded my screen using Textmate and Vim and found that (with my basic skillset) I'm able to do the editing at around the same speed -- it's the navigation for me that is so much slower. So I'm trying to benchmark and optimize that." But if you read my answer again you'll see that the claim that you can be faster/more efficient in Vim is about how it's easier to switch from motion to editing, while with a point-and-click approach that involves switching from mouse to keyboard and that's the inefficient part... Comparing motion only by itself is unlikely to give you best results...
    – filbranden
    Apr 24 '20 at 3:27
3

FWIW, I tried it out, and without thinking about it, this is what I ended up typing:

  1. /def<CR>w
  2. /Tit<CR>
  3. /print<CR>$
  4. /- p<CR>nw.

I was a little unclear as to whether you wanted the cursor exactly on the red dots, or on the more significant characters next to the dots. If I guessed wrong, there may be slight adjustments required to the above.

In real life, I almost definitely wouldn't have actually used search for all of these, but since you primed me with "jump fast to a bunch of arbitrary positions on screen" I was predisposed to do so: search is very often the quickest method for moving to a position that is conceptually unrelated to my current position.

I didn't time it, but the vast majority of my time was spent looking back at your picture to find where the next red dot was. I'd be amazed if I could do this quicker with the mouse presuming I also had to move my hands back and forth between the keyboard and mouse between every movement (which I would do if I were editing code at each position: as others have stated, if you're not performing edits, there's no reason to move the cursor).

If I weren't performing edits and were instead just randomly moving the cursor around (for fun?) then I'd probably be slightly quicker with the mouse, but if I were a better typist, I doubt I still would be for this test.

1

Here's what i did to reach the 4 points:

Given I start from the 1st point,

  • /Coll<ENTER>tT
    Just basic searching, and then using t to reach the next occurrence of a specific character on the same line.

  • 1600G
    Move to the line number 1600, The cursor is already a the end of the line. It might be easier if you have the option relativenumber set, so that you can hit 7j or 7<DOWN>

  • GB
    The G brings me to the end of the last line, and B, to the first white space preceding the cursor.


I was able to do this in around 5 seconds.

That's my flow without thinking, but there are a thousand ways to do those moves, and half of them would be faster than what I did I'm sure!

Here are some other that I usually use:

  • H, M and L to go to the top, middle and bottom of the current view

  • [( and ]) to go to the beginning / end of the current paragraph

  • [{ and ]} to go to the opening brace before the cursor, or closing after the cursor (useless with a language like python)

Plugins

If the native vim motions are not enough for you, there are some great plugins that could help you, vim-easymotion is a great one

My motions

Now, in my above example, I limited myself to the native vim commands, but in reality, I rely a lot on my custom motions.

  • Hitting <down> or <up> several time in a row will make the cursor go faster
    function MoveFaster(num, count)
        let g:num = a:num
        let g:count = a:count
        if a:count > 0
            if a:num < 0
                let curline = line('.')
                call setpos('.', [0, 2*curline - a:count, col('.'), 0])
            elseif a:num > 0
                call setpos('.', [0, a:count, col('.'), 0])
            endif
        endif
        let b:tap = getbufvar(bufnr(), 'tap', 0)
        let lastTap = b:tap
        if a:num > 0
            let out = "\<DOWN>"
        elseif a:num < 0
            let out = "\<UP>"
        endif

        if a:num > 0 && lastTap > 0 || a:num < 0 && lastTap < 0
            let out = repeat(out, abs(lastTap))

            if abs(b:tap) < 10
                let b:tap = lastTap + a:num
            endif
        elseif b:tap != 0
            let b:tap = 0
        else
            let b:tap = lastTap + a:num
        endif

        exe "norm!".out
    endfunction

    set updatetime=130
    command! -nargs=1 -range MoveFast call MoveFaster("<args>", "<count>")
    nnoremap <silent> <DOWN> :MoveFast 1<CR>
    nnoremap <silent> <UP> :MoveFast -1<CR>
    nnoremap <silent> <RIGHT> :let b:tap=0<CR><RIGHT>
    nnoremap <silent> <LEFT> :let b:tap=0<CR><LEFT>
    autocmd InsertEnter,InsertLeave,CursorHold * let b:tap=0

  • <CTRL>+<LEFT/RIGHT> will be more clever and skip to the next uppercase or _
    nnoremap <silent> <c-left> :<c-u>call search('\<\<bar>\u\l\<bar>_[a-z]\<bar>^', 'b')<cr>
    nnoremap <silent> <c-right> :<c-u>call search('\w\>\<bar>\u\l\<bar>_[a-z]\<bar>$')<cr>
    inoremap <silent> <c-left> :<c-u>call search('\<\<bar>\u\l\<bar>_[a-z]\<bar>^', 'b')<cr>
    inoremap <silent> <c-right> :<c-u>call search('\>\zs\<bar>\u\l\<bar>_[a-z]\<bar>$')<cr>

  • <S-down/up> go to beggining/end of current indented block
    function! IndentJump(direction)
        let curline = line('.')
        let indent = matchstr(getline(curline), '^\s*')
        if a:direction >= 0
            let nextLineNum = search('^.*\S.*$', 'n')
        else
            let nextLineNum = search('^.*\S.*\n\_.*\%#', 'nb')
        endif
        let nextIndent = matchstr(getline(nextLineNum), '^\s*')

        if len(nextIndent) < len(indent) || len(indent) == 0
            if a:direction >= 0
                let flag = ''
            else
                let flag = 'b'
            endif
            call search('^.*\S.*\zs$', flag)
        else
            if a:direction >= 0
                call search('^\('.indent.'\).*\n\(\1.*\n\|\s*\n\)*\1.*\zs')
            else
                call search('^\('.indent.'\)\S.*\zs\ze\n\(\1.*\n\|\s*\n\)*.*\%#')
            endif
        endif
    endfunction
    noremap <silent> <S-UP> :<C-u>call IndentJump(-1)<CR>
    noremap <silent> <S-DOWN> :<C-u>call IndentJump(1)<CR>

Documentation

  • :help various-motions
  • :help t
  • :help G
4
  • Don't forget that <Cmd> is NeoVim-specific. You might want to use the : with a <silent> modifier, in general in your answers, for compatibility with Vim.
    – filbranden
    Apr 23 '20 at 11:25
  • I did specify that I worked on NeoVIm, and that my mappings might not work under classic Vim. I'll add it more explicitly
    – Zorzi
    Apr 23 '20 at 11:47
  • 1
    Yes, of course you did. I'm just pointing out that the OP seems to clearly be on Vim (not NeoVim) and since they're a beginner they might not know how to convert <Cmd> into something that works in Vim. Providing an answer that's compatible with Vim and not NeoVim-specific might be more helpful to the OP, or others who stumble here and would like to try your mappings but they're on Vim so it doesn't work... Especially with something like <Cmd>, which is really easy to convert to a Vim-compatible mapping.
    – filbranden
    Apr 23 '20 at 11:53
  • @Zorzi great, thanks for sharing the shortcut scripts as well!
    – David542
    Apr 23 '20 at 17:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.