One of the more standard vim tweaks is to have parens, braces, brackets, and quotes auto-close—that is, you type (, and the ) is inserted automatically. This can be a big help in keeping track of nested groups. This is easy to do poorly; to do it well takes a little more thought. I took a look at TextMate's rules and mimicked them (mostly). Put all this stuff in your .vimrc (_vimrc on Windows).
Note: A couple of these tweaks are modified or copied outright from the Vim Scripts repository.
Auto-close pairs
The initial auto-close is quite easy; leaving the group is slightly more involved. If you're inside parens, at the end, and you want to leave them, instinct is just to type the closing ); without a little logic, however, that will in no way achieve the desired result. The rule is: if typing the closing character would end the block without auto-close (that is, because it's the character to the immediate right of the cursor), it should end it with auto-close. Here's how you achieve that.
- Map the auto-close for non-quotes. This is trivial:
inoremap ( ()<Left>
inoremap [ []<Left>
inoremap { {}<Left>
autocmd Syntax html,vim inoremap < <lt>><Left>
No big deal; when the opening character is typed, insert the closing character and hit <Left> so the cursor is between them.
- Function to check next character.
function! ClosePair(char)
if getline('.')[col('.') - 1] == a:char
return "\<Right>"
else
return a:char
endif
endf
- Map closing characters. Now, all you need to do is link the closing characters to the function:
inoremap ) <c-r>=ClosePair(')')<CR>
inoremap ] <c-r>=ClosePair(']')<CR>
inoremap } <c-r>=ClosePair('}')<CR>
- Map the auto-close for quotes. This is only slightly more difficult than
()[]{}, because of the case where you might add an escaped quotation mark to a string. It requires a separate function: since the opening and closing characters are the same, we can't map the closing to ClosePair. So we put it all in one function:
function! QuoteDelim(char)
let line = getline('.')
let col = col('.')
if line[col - 2] == "\\"
"Inserting a quoted quotation mark into the string
return a:char
elseif line[col - 1] == a:char
"Escaping out of the string
return "\<Right>"
else
"Starting a string
return a:char.a:char."\<Left>"
endif
endf
Then just need to map that:
inoremap " <c-r>=QuoteDelim('"')<CR>
inoremap ' <c-r>=QuoteDelim("'")<CR>
Dump all that in to .vimrc, source it or restart vim, and try typing some stuff! You may notice a few places where it isn't quite what you'd want, though, which brings us to our next two tweaks.
Wrap visually selected text
One of the nicer minor features of TextMate is its treatment of highlighted text. If you have something highlighted and type a, it replaces the text, like other editors. If you type (, however, it wraps the selected text in parentheses. This is enormously useful. Luckily, it's very easy to recreate in Vim:
vnoremap ( <ESC>`>a)<ESC>`<i(<ESC>
vnoremap ) <ESC>`>a)<ESC>`<i(<ESC>
vnoremap { <ESC>`>a}<ESC>`<i{<ESC>
vnoremap } <ESC>`>a}<ESC>`<i{<ESC>
vnoremap " <ESC>`>a"<ESC>`<i"<ESC>
vnoremap ' <ESC>`>a'<ESC>`<i'<ESC>
vnoremap ` <ESC>`>a`<ESC>`<i`<ESC>
vnoremap [ <ESC>`>a]<ESC>`<i[<ESC>
vnoremap ] <ESC>`>a]<ESC>`<i[<ESC>
Backspace in empty pair deletes both
Now that you have auto-closing parens, let's say you type one by mistake. Unfortunately, deleting it now involves two extra keystrokes (<ESC>xx or <Right><Bksp><Bksp> instead of <Bksp>). TextMate gets around this by deleting both if you're in an empty block. I was tired of mapping the same thing for every pair, though, so I wrote a function that uses the matchpairs option, which exists to tell Vim what delineates a discrete block, to figure it out dynamically (thanks to Aristotle Pagaltzis for cleaning it up!). It checks whether the characters on either side of the cursor constitute a valid pair:
function! InAnEmptyPair()
let cur = strpart(getline('.'),getpos('.')[2]-2,2)
for pair in (split(&matchpairs,',') + ['":"',"':'"])
if cur == join(split(pair,':'),'')
return 1
endif
endfor
return 0
endfunc
And then the actual deletion function:
func! DeleteEmptyPairs()
if InAnEmptyPair()
return "\<Left>\<Del>\<Del>"
else
return "\<BS>"
endif
endfunc
Then map it to Backspace, so it's called whenever Backspace is:
inoremap <expr> <BS> DeleteEmptyPairs()
So, in plain English: when Backspace is hit, we check to see if the characters on the immediate left and right of the cursor are part of a matching pair, looking at the matchpairs option to see what counts of a valid pair. If we are in a pair, delete both characters; otherwise, send a normal Backspace.
Next (and probably last) in the series: Whatever's left in my .vimrc, namely, assorted tweaks to make one's coding life easier.
Read More...