Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save yashhere/2c3dc04ca92a92ee9d4460a62fbb2c13 to your computer and use it in GitHub Desktop.
Save yashhere/2c3dc04ca92a92ee9d4460a62fbb2c13 to your computer and use it in GitHub Desktop.
Vim: pushing built-in features beyond their limits

The sitution

Searching can be an efficient way to navigate the current buffer.

The first search commands you learn are usually / and ?. These are seriously cool, especially with the incsearch option enabled which lets us keep typing to refine our search pattern. But / and ? really shine when all you want is to jump to something you already have your eyeballs on but they are not fit for every situation:

  • when you want to search something that's not directly there, those two commands can make you loose context very quickly,
  • when you need to be able to compare the matches.

A better way

A better candidate for those situations would be the awesome :g[lobal]/pattern/command but it uses the p[rint] command by default, which is not that useful:

:g/foo
fqjsfd foo
foo dhqdgqs
    // foo shdjfksgdf
Press ENTER or type command to continue

By using :# (or :nu[mber]), we ask Vim to also display the line numbers that we can then use at the prompt:

:g/foo/#
  3 fqjsfd foo
 12 foo dhqdgqs
 13     // foo shdjfksgdf
 :12

to jump to the desired line.

This doesn't look like much but :g/pattern/# is an immensely useful tool that deserves its place in anyone's toolbox. No setup, no dependency, no third-party plugin… it just works!

And will always do.

A better better way

As is, this command requires us to type:

  • :g/ before our pattern,
  • /#<CR> after our pattern,
  • and : followed by 1 to infinite digits, folowed by <CR>.

The pattern and the line number can't be known in advance, of course, but we can certainly simplify the rest.

Starting with :g/pattern/#<CR>, it's relatively easy to come up with a simple mapping:

nnoremap <key> :g//#<Left><Left>

that populates the command-line with a command stub and moves the cursor between the slashes, ready for us to type the pattern and press <CR>:

:g/|/#

Executing our search is now reduced to <key> + pattern + <CR>, which is not bad at all. But what about the prompt? What if we could reduce : + 10000 + <CR>?

This problem is a bit more complex but it's very doable with a bit of straightforward vimscript:

function! CCR()
    let cmdline = getcmdline()
    if cmdline =~ '\v\C/(#|nu|num|numb|numbe|number)$'
        return "\<CR>:"
    else
        return "\<CR>"
    endif
endfunction
cnoremap <expr> <CR> CCR()

The idea is to automatize the <CR> then : sequence when the command we typed (or ran through a mapping) ends with /# or any abbreviation of /nu[mber].

We are now down to <key> + pattern + <CR> + digits + <CR>. Yes, that's a "core" of only three motherfucking keystrokes for the whole process!

Generalizing

Sure, streamlining that boring (but mighty) :g/foo/#<CR>:17<CR> was quite an achievement but we learned a lot of similar list-like commands in the meantime: :ls, :changes, :ilist and so on. What if we could add an automatic prompt for all of them?

Well, we already have a solid foundation, so let's augment it a bit:

function! CCR()
    let cmdline = getcmdline()
    if cmdline =~ '\v\C/(#|nu|num|numb|numbe|number)$'
        return "\<CR>:"
    elseif cmdline =~ '\v\C^(ls|files|buffers|dli|il|cli|lli|old|changes|ju|marks|undol)'
        return "\<CR>:"
    else
        return "\<CR>"
    endif
endfunction

This seems to work: pressing <CR> after any of those commands executes the command and populates the command-line with a prompt. Another achievement unloc… Wait! Wait! Wait! Nope. That's acually a super dumb way to treat our "problem"!

Why? Because all of those commands actually have different prompts. Sure # being all about listing lines, the only reasonable prompt is a colon, but :ls lists buffers so the right prompt is :b, :changes lists entries in the change list so the right prompt is :norm! g;, and so on. Don't panic! We only have to add a bunch of conditions to our test. That's all.

Hmm… and figure out the right prompt for each command. I'll save you the details (or detail them if you want) and go straight to the result:

" make list-like commands more intuitive
function! CCR()
    let cmdline = getcmdline()
    if cmdline =~ '\v\C^(ls|files|buffers)'
        " like :ls but prompts for a buffer command
        return "\<CR>:b"
    elseif cmdline =~ '\v\C/(#|nu|num|numb|numbe|number)$'
        " like :g//# but prompts for a command
        return "\<CR>:"
    elseif cmdline =~ '\v\C^(dli|il)'
        " like :dlist or :ilist but prompts for a count for :djump or :ijump
        return "\<CR>:" . cmdline[0] . "j  " . split(cmdline, " ")[1] . "\<S-Left>\<Left>"
    elseif cmdline =~ '\v\C^(cli|lli)'
        " like :clist or :llist but prompts for an error/location number
        return "\<CR>:sil " . repeat(cmdline[0], 2) . "\<Space>"
    elseif cmdline =~ '\C^old'
        " like :oldfiles but prompts for an old file to edit
        set nomore
        return "\<CR>:sil se more|e #<"
    elseif cmdline =~ '\C^changes'
        " like :changes but prompts for a change to jump to
        set nomore
        return "\<CR>:sil se more|norm! g;\<S-Left>"
    elseif cmdline =~ '\C^ju'
        " like :jumps but prompts for a position to jump to
        set nomore
        return "\<CR>:sil se more|norm! \<C-o>\<S-Left>"
    elseif cmdline =~ '\C^marks'
        " like :marks but prompts for a mark to jump to
        return "\<CR>:norm! `"
    elseif cmdline =~ '\C^undol'
        " like :undolist but prompts for a change to undo
        return "\<CR>:u "
    else
        return "\<CR>"
    endif
endfunction
cnoremap <expr> <CR> CCR()

Basically, that mapping and its associated function don't really change anything fundamental. We still use :ilist or :oldfiles as we used to, but we don't have to type a different prompt for every command anymore. All we did was reducing friction and have a lot of fun in the process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment