Making ZZ Behave in Vim
Update Apr 4, 2008: I was recently reminded of this post and thought I should mention that this particular solutions sucks. Although it won't break anything, it doesn't work well. Don't waste your time :) Cheers.
I've gotten quite used to the idea of hitting ZZ in Vim to kill the
window I'm currently working in. The problem is that I'm so used to the
idea, that I hit it almost automatically when I'm done with a particular
buffer and there is only one window left. The result is that the Vim session
is closed completely whether there is only one listed buffer or not.
    My solution to this was not to find the probably existing command or
    setting built into Vim that corrects this problem, but rather to write a
    function that customizes the behavior of the ZZ command. This would be the
    first time I'd ever written a non-trivial function in pure Vim script, so
    I thought I'd give it a shot. The following function is the result. I've put
    lots of comments in there so that you can follow if you are new to Vim
    scripting like I am.
    
function! BehaveZZ()
    " Get the number of *listed* buffers.
    let highbuf = bufnr("$")
    let buflist = []
    let i = 1
    while (i <= highbuf)
        "Skip unlisted buffers.
        if (bufexists(i) != 0 && buflisted(i))
            call add(buflist, i)
        endif
        let i = i + 1
    endwhile
    let bufcount = len(buflist)
    if (bufcount == 1)
        if (bufname("%") == "")
            " This buffer is unnamed (has no associated file).
            if (&modified)
                " Give option to save modifications.
                let choice = input("Lose modifications? [Enter=yes]: ")
                if (choice == "")
                    set nomodified
                else
                    echo "ZZ action aborted..."
                    return
                endif
            else
                " The buffer has no modifications. Just do default ZZ.
                execute "x"
            endif
        else
            " There is only one listed buffer and it is named. In this case, 
            " the standard ZZ works just fine, so do that.
            execute "x"
        endif
    elseif (getbufvar(bufnr("%"), "&buftype") != "")
        " This buffer is a "special" buffer.
        execute "bdelete"
    elseif (bufname("%") == "")
        " This buffer is unnamed (has no associated file).
        if (&modified)
            " Give option to save modifications.
            let choice = input("Lose modifications? [Enter=yes]: ")
            if (choice == "")
                set nomodified
            else
                echo "ZZ action aborted..."
                return
            endif
        endif
        if (winnr("$") > 1)
            " There are multiple windows open. Just do a normal ZZ.
            execute "x"
        else
            execute "buffer! " . bufnr("#")
        endif
    else
        " This is a named buffer. 
        if (&modified)
            execute "write"
        endif
        if (winnr("$") > 1)
            " There are multiple windows. Just do normal ZZ.
            execute "x"
        else
            " There is only one window, but multiple listed buffers.
            let curbuf = bufnr("%")
            " If we have a 'last visited' buffer, go there. Else bnext.
            if (bufnr("#") != -1)
                execute "buffer! " . bufnr("#")
            else
                execute "bnext"
            endif
            execute "bdelete" . curbuf
        endif
    endif
endfunction
command! ZZ call BehaveZZ()
    Now at the time of this writing, the above function has had very limited testing, so if you want to use it, beware. This is what the function does.
- If there are multiple windows open, just close the current
            one. (Same as default 
ZZ) - If there is only one window open, but multiple buffers, delete the current buffer and switch to another one.
 - If there is only one window, and only one listed buffer, exit
            the session. (Same sd default 
ZZ) - If the current buffer is unnamed (ie. has no filename
            associated with it), confirm loss of modifications. If user presses
            just 
Enter, then discard modifications and delete the buffer. Else abort. 
           One behaviour of the default Vim that I found odd is a result of
           opening multiple files from the command line at once like vim
               file1 file2. When you do this, and try to close one you
           get a E173: 1 more file to edit message, and nothing
           happens at all.  It however gives no such warnings if you open only
           one file, then open a second with the :e file2 command
           from inside the editor. This time it just closes the whole session if
           there is only one window open. In both of these cases, my function
           will close the currently active buffer and switch to one of the other
           buffers in the buffer list.
        
I think I've handled most special cases where a buffer is special like a help buffer, or a scratch buffer too. Initially I made it so that if the current buffer was unnamed and modified then the modifications would just be discarded, but that made me nervous so I added a one key confirmation just to be safe. The whole function turned out to be a lot more complex than I thought it would (not to mention longer). Things like special buffers gave me a lot of grief. Being new to this, the whole snippet probably ended up being more complicated than it needed to be. If you see any bugs or optimizations, I'd be interested in hearing about them. Leave a comment after this post.
        To use this function, drop it into your ~/.vimrc and either
        :call it manually or map a key to it like 
        
:map ZZ :call BehaveZZ()<CR>. That's the mapping I use and it simply replaces
ZZ's normal
        behavior with the new one.
        
No comments:
Post a Comment