VIM中括号的自动补全与删除

很多现代 IDE 都有自动补全配对括号的功能,比如输入了左括号“(”,IDE 就自动在后面添加一个对应的右括号“)”,并且将光标移到括号中间。VIM 虽然没有直接提供这个功能,但要实现其实非常简单,只要在你的 .vimrc 文件中添加下面的内容就可以了:

" 插入匹配括号
inoremap ( ()<LEFT>
inoremap [ []<LEFT>
inoremap { {}<LEFT>

原理很简单,就是将左括号的键映射为一个新的操作,在输入左括号时,让 VIM 立刻输入右括号,同时再将光标左移一格到括号中间。

除了括号的自动补全,有时我们也需要括号的自动删除。比如在输入了左括号后突然发现输错了,本来只需要简单地按一下退格键,将刚才输入的左括号删除就行了,但现在 VIM 自动加了一个右括号,退格键只能删除左括号,这个自动加上右括号还得按一下 DELETE 键才能删掉。

所以,我们还需要一个功能,如果按退格键删除了左括号,那么也要自动地把对应的右括号删除。这个操作使用简单的键盘映射就有点难度了,需要借助函数,如下:

" 按退格键时判断当前光标前一个字符,如果是左括号,则删除对应的右括号以及括号中间的内容
function! RemovePairs()
	let s:line = getline(".")
	let s:previous_char = s:line[col(".")-1] " 取得当前光标前一个字符

	if index(["(", "[", "{"], s:previous_char) != -1
		execute "normal! v%xi"
	else
		execute "normal! a<BS>"
	end
endfunction
" 用退格键删除一个左括号时同时删除对应的右括号
inoremap <BS> <ESC>:call RemovePairs()<CR>a

注:上面这段后来被 weakdancer 指出了一些问题,经过考虑及测试之后,我把它改写为这样了:

" 按退格键时判断当前光标前一个字符,如果是左括号,则删除对应的右括号以及括号中间的内容
function! RemovePairs()
	let l:line = getline(".")
	let l:previous_char = l:line[col(".")-1] " 取得当前光标前一个字符

	if index(["(", "[", "{"], l:previous_char) != -1
		let l:original_pos = getpos(".")
		execute "normal %"
		let l:new_pos = getpos(".")

		" 如果没有匹配的右括号
		if l:original_pos == l:new_pos
			execute "normal! a<BS>"
			return
		end

		let l:line2 = getline(".")
		if len(l:line2) == col(".")
			" 如果右括号是当前行最后一个字符
			execute "normal! v%xa"
		else
			" 如果右括号不是当前行最后一个字符
			execute "normal! v%xi"
		end

	else
		execute "normal! a<BS>"
	end
endfunction
" 用退格键删除一个左括号时同时删除对应的右括号
inoremap <BS> <ESC>:call RemovePairs()<CR>a

这样就比较完美了。不过在非括号时其实还有个小问题(比如“abcde”这样的情况下,按了退格键后,“a”虽然被删除了,但光标不是移到行首,而是移到 b 的后面),但应该不会对使用造成太大影响。

另外,在自动补全了右括号之后,如果用户再输入右括号会怎么样呢?一般来说,比较合理的做法似乎是忽略掉这个后输入的多余的右括号,直接将光标向右移到一格。代码如下:

" 输入一个字符时,如果下一个字符也是括号,则删除它,避免出现重复字符
function! RemoveNextDoubleChar(char)
	let l:line = getline(".")
	let l:next_char = l:line[col(".")] " 取得当前光标后一个字符

	if a:char == l:next_char
		execute "normal! l"
	else
		execute "normal! i" . a:char . ""
	end
endfunction
inoremap ) <ESC>:call RemoveNextDoubleChar(')')<CR>a
inoremap ] <ESC>:call RemoveNextDoubleChar(']')<CR>a
inoremap } <ESC>:call RemoveNextDoubleChar('}')<CR>a

这样,在 VIM 中输入或删除括号就方便多了!

18 Replies to “VIM中括号的自动补全与删除”

  1. 你好,你的代码的第二段的 line07execute “normal! v%xi”最后的i是insert但是在我的VIM中这样不好,我改成了a才很好的工作。比如输入main(<光标在这里>)我按backspace后会变成这样:mai<光标在这里>n可能是我的backspace设置和你的不一样把。我的是:set backspace=indent,eol,start不过我感觉应该大部分人的设置都是这样的吧,所以提醒一下。另外感谢你的代码。

    1. 感谢你的留言。我测试了一下,的确存在你说的问题。
      不过,使用 v%xa 也有问题,比如输入 main(<光标在这里>)somewords ,按 backspace 后会变成:
      mains<光标在这里>omewords。
      不过右括号后还有内容的情况似乎少一些,总的来说还是 v%xa 更合理一点。暂时没有想到更好的解决方案…

  2. 经过我的测试execute “normal! v%xi”在这种情况下有问题:main()<光标>这时按下backspace就不行了。我又改成了execute “normal! 3dwa”这样就可以了

    1. dw 的话,如果括号中不止一个参数怎么处理呢?比如:
      main(argv1, argv2, argv3)
      PS. 我更新了脚本,现在应该能比较完美地处理括号删除的各种情况了,见文章正文增加的部分。:-)

  3. 你好,我刚加入了插入括号的配置,虽然自动补全右括号。但是光标并未移到括号中央

  4. 我感觉十分难用,问题如下:我是直接把你的代码加紧vimrc中的,然后保存在打开并在该文件的末尾测试测试如下:#include<stdio.h>int main(){ printf(“hello world”); return 0;}1.当我的光标world的w上按下退格时,“(”hello”被删除留下后面的东西,可我原本只想修改一下hello.2.当我的光标在“;”的后面时我按下退格,vim直接调用了你的RemovePairs(),在继续按退格,vim进入visual模式,竟然是把退格变成了选中,真正删除还得靠选完之后按d来删除。

  5. 自动删除的部分我按退格键后没反应,可是直接在命令模式调用那个函数却是有效的,所以猜测应该是的映射没成功,请问博主什么原因啊?

    1. 主要是文档,比如 ,也有一些是从其他人的博客上学来的。

      不过除了VIM外,现在我也会经常使用Intellij IDEA等IDE。一些大型项目还是使用现代IDE管理更方便一些,同时,大部分现代IDE也可以使用插件等方式使用VIM的常用快捷键。 🙂

  6. 至于符号补全,楼主可以选择snipmate+snippets ..然后在snippets里的_.snippets里加自己的snippets..
    比如 :(这里使用大括号作例子)
    snippet {
    {${1}}${0}
    如何用呢 ? 直接在输入模式下 输入 ‘ 然后再Tab ...恩,就可以在{}里面输入了.输入完,再Tab跳出来      ~~

    再说下括号的删除,直接使用surround ds{ 

  7. 博主你好,我想请教下,我主要是用 vim 写 C 的,如何才能在输入花括号的时候,自动换行并tab缩进一下呢?
    根据你的方案,输入 { 后,会变成这样:
    { 光标 }

    而我希望是这样:
    {
    tab 光标
    }

    希望博主可以给我指教,谢谢!

  8. 你好, 我发现一个问题. 我复制了 1, 3, 4 的代码. 在 INSERT 模式下如果使用退格键, 会直接打印出 . 个人不是很清楚 vimscript, 尝试删除 else 和 execute “normal! a” 两行, 不再打印 不过退格键失效. 不知道是不是个例.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s