Saturday, October 21, 2006

Faster Buffer Switches in Vim

I've tried several times in the past to use various buffer management plugins like the very popular minibufexplorer and bufexplorer only to find myself not using them or forgetting to use them after a few days. It seems that the normal "switch by name or number" method is faster and more convenient for me for some unknown reason. I can see how these plugins could be useful if your current number of open buffers gets huge, but until then I think they just take up valuable screen real estate (I'm pretty stingy about my screen space).

Buffers inherit their names from their file names. You can switch buffers by entering :b {name} or by :b {N} where {name} is either the full or partial buffer name that you desire (filename), and {N} is the buffer number. This by default has a number of issues.

  • Buffer numbers are difficult to remember unless you are a robot. Especially if the number of buffers is large.
  • If you have two buffers named file1 and file2 and you attempt to switch buffers with a command like :b file you will get an error message stating that there is more than one buffer matching that pattern.
  • If you have a buffer named Foo and you attempt to switch to it with a command like :b foo it will not work. Switching buffers by name is case sensitive.
In order to overcome these problems, I decided to modify this behaviour a bit to make my preferred method of buffer switching a little more user friendly. Of course before I began writing a function for it, I did a quick search through the tips at the Vim website and found this tip by Cory T. Echols. As suggested in a comment in this tip, this function prompts the user for a choice even if there is only one match to the given pattern. Also, it doesn't solve the case sensitivity problem mentioned above. I decided to work from this function and modify it to better suit my own tastes. First, instead of having a boolean switch for whether a pattern match was found or not, I used a List (Cory's function was written well before Lists were available in Vim. They are a Vim 7 feature.) and appended the number of matching buffers to this list. Once I've cycled through all of the listed buffers, if the length of this list is 1, I immediately make that buffer the active one. Only if there are more than one matching buffers do I prompt the user for a choice. I also chose to exclude unlisted buffers from my search. Unlisted buffers are ones that do not show up in the :ls list because they have been closed with the :bdelete command. I figure if I delete the buffer then I don't want to use it anymore. I only want to select a buffer from the listed ones.

Finally, I added a global variable called g:BufSel_Case_Sensitive that controls whether the match is case sensitive or not. It defaults to false, but can be changed anywhere (like your ~/.vimrc for example). This makes it much easier to swap if your edited file names contain a mix of upper and lower case characters.

The final product looks like this:

function! BufSel(pattern)
    let buflist = []
    let bufcount = bufnr("$")
    let currbufnr = 1

    while currbufnr <= bufcount
        if(buflisted(currbufnr))
            let currbufname = bufname(currbufnr)
            if (exists("g:BufSel_Case_Sensitive") == 0 || g:BufSel_Case_Sensitive == 0)
                let curmatch = tolower(currbufname)
                let patmatch = tolower(a:pattern)
            else
                let curmatch = currbufname
                let patmatch = a:pattern
            endif
            if(match(curmatch, patmatch) > -1)
                call add(buflist, currbufnr)
            endif
        endif
        let currbufnr = currbufnr + 1
    endwhile
    if(len(buflist) > 1)
        for bufnum in buflist
            echo bufnum . ":      ". bufname(bufnum)
        endfor
        let desiredbufnr = input("Enter buffer number: ")
        if(strlen(desiredbufnr) != 0)
            exe ":bu ". desiredbufnr
        endif
    elseif (len(buflist) == 1)
        exe ":bu " . get(buflist,0)
    else
        echo "No matching buffers"
    endif
endfunction
command! -nargs=1 -complete=buffer Bs :call BufSel("<args>")
It's not a real complicated function, but it does add a lot of flexibility when switching buffers via the command line. I added the following line in my ~/.vimrc to replace the already existing command when typed:
    cabbr b Bs
So now when I type :b (notice the space) it is replaced by :Bs automatically and the function is run instead of the built-in buffer command. This suggestion was also taken from the comments in the tip page above, and it works quite well.

3 comments:

  1. exactly what i needed! Thanks!

    ReplyDelete
  2. was looking for a script for partial buffer search, rather than using the expensive Find because I had to type in the entire file path when using :b. thanks!

    ReplyDelete
  3. One thing nice about :b is if you press afterwards you get a list of buffers.. If you use the override :Bs, this functionality is lost. Anyway to have the completion still work?

    ReplyDelete