碎碎念

2007, August 23

Vim中输入法与编码设置的FAQ

Filed under: vim, vi, 编辑器, Python




Up: (dir)

输入法与编码设置的FAQ

从去年到现在一直陆陆续续的都有人在问起中文输入法的设置和编码的设置这方面的问题。我将这些问题整理了一下,也许有些用处。

1 中文输入法

如果每次进出insert模式都要切换一下输入法是很恼人的,我自己就有过这样经历。应该也有不少人在初次尝试Vim后放弃的原因就是因为无法让中文输入法与Vim无间配合。其实这个问题是可以解决地(不过只对图形界面的Vim有效)。

1.1 Windows

:h mbyte-IME

在Windows中如果是直接下载完整的安装包的话,输入法支持已经包涵在gVim中了。如果自行编译的话记得加入以下选项:+multi_byte_ime加入这一选项后,每次退出插入模式后,Vim会依所用命令与上次退出前的输入法状态自动判断使用中文还是英文,在重新进入插入模式时回到上一次退出时的输入状态(即上一次退出时是中文输入还是英文输入的状态)。

举例来说如果你在插入模式、中文输入状态下按

<ESC>dT

在按<ESC>后Vim自动换回英文输入状态,在按到T后Vim又会自动切换至中文输入状态。

1.2 Linux

:h mbyte-XIM

Linux下要实现同样的功能需要一些额外的努力。确保所用的gVim版本包括+xim+GUI_GTK选项。下面是ubuntu下fcitx的设置方式(我不是很清楚用scim如何设置),在.gvimrc(或者.vimrc也行)加入如下设置,

se imak=C imi=2 ims=2 imc
  • imactivatekey (imak)
    imak用来设置桌面系统控制输入法开关所使用的快捷键。fcitx的激活键是Ctrl-spaceCtrl则是中英切换键。如果将imak设为Ctrl-space,则每次进入插入模式都是中文输入。将imak设为Ctrl则可以保留上一次退出插入模式时的输入状态。

  • iminsert/imsearch
    imi/ims告诉Vim在插入模式与搜索时使用输入法。
  • imcmdline (imc)
    imc告诉Vim在使用ex命令时也允许输入法。可根据自己的需要随时设置为开或关。

2 编码问题

:h mbyte.txt

但凡是需要在Windows与Linux之间传递文档的中文用户或多或少会遇到编码方面的问题——通常是utf8/gb2312文件的识别问题。但有时候情况要更复杂一点,特别是对需要处理不同语言文本的人而言。

2.1 怎么让Vim正确识别编码?

:h charset-conversion
:h fencs

通常下面的命令可以解决大部分的问题:

set fileencodings=ucs-bom,utf-8,chinese,big5,latin1

注意:上面的big5与latin1并不起作用可以省略。

问题解决了吗?很好,现在可以收工了……除非你想了解更多细节。

2.1.1 关于编码的一些基础

字符在计算机里面有用一个或多个字节表示的。因此可以直接以十六进制数字(因为2**8==16**2)来表示某个字符,某个字符对应的十六进制数字就是该字符的编码。a在ansi、utf8的编码都是0x61,但通常不同的编码系统(编码标准)对某个数字表示的字符有着不同的规定,因而同一个汉字在GBK与Big5中以不同的十六进制数字不一样。同理,同一个数字在不同的编码系统中也对应着不同的字。如,数字0xbac3中gb2312中是“疑”而在Big5中是“好”。 某个编码系统中的字符的集合就是字集charset。

'termencoding'('tenc'), 'encoding'('enc'), fileencoding'('fenc'),是Vim中三个跟编码设置有关的设置项,它们对应着三个概念:终端编码,Vim编码与文件编码。
文件编码就是文件的数据在磁盘中存储所使用的编码。
Vim编码Vim在处理文本或表示文本时所使用的编码。
终端编码终端处理文本或表示文本时所使用的编码。

(此外还有:scriptencoding命令,设定脚本文件所使用的编码)

下文中将以xterm指代所有模拟终端如xterm, konsole, rxvt还有Windows下的命令窗口(DOS窗口)。

在图形系统中每个窗口都拥有自已的绘图区,它们自已决定所使用编码,自己负责输入输出。在使用gVim时,'enc'的值就是就是gVim使用的编码——缺省使用系统的默认编码。gVim在读入文件时需要判断文件所使用的编码并转换为自己的工作编码即&enc。所以gVim只需要处理'enc'与'fenc'。

与gVim不同,Vim没有自己的窗口,它“借用”xterm的窗口。同其他桌面程序一样xterm也有自己的绘图区域,自己负责输入输出(当然也有自己的编码设置)。在xterm运行Vim时,Vim自己并不负责显示,而是提供数据由xterm将数据打印到绘图区,用户的输入也是经由xterm传给Vim。所以Vim还需要考虑到xterm的编码,即'tenc'。为了数据能正常显示Vim必须先将文本转换为xterm支持的编码再将数据提交到xterm。Vim当然不知道xterm的编码,它用猜的——根据环境变量的值。我们可以使用:set tenc=xxx命令显式地告诉Vim,xterm使用的编码是xxx。

从下面这张图可以看到它们之间的关系:

	
--------------------------------------- <- 用户
        ^       |
        |       |
        |       V
--------------------------------------- <- Xterm(tenc):输入输出
        ^       |
  转换  |       |  转换
        |       V
--------------------------------------- <- Vim(enc):处理
        ^       |
  转换  |       |  转换
        |       V
--------------------------------------- <-- 磁盘(fenc):存储
	

Xterm负责与从接收用户的输入与输出信息至屏幕。Vim负责根据xterm传来的用户指令进行文件内容的增删减。Vim以encoding项设置的编码对文本进行操作。未设置时则使用系统的默认编码。使用ga(:asc)查看的就是Vim工作时使用的编码。磁盘负责存储数据。fileencoding表示的就是文件存储所使用的编码。使用十六进制工具打开文件看到的就是文件的实际编码。1

还有一个设置项没提到,就是'fileencodings'('fencs'),fencs是一套规则帮助Vim判断文件的编码,解决乱码问题就靠它了。

se fencs=ucs-bom,utf8,chinese

这条命令就vim先尝试ucs-bom作为文件编码,如果发现文件数据不符合ucs-bom的编码规则(比如没有Byte Order Mark(BOM)),就试下一个编码直到文件数据符合某个编码规则。因为编码放置的顺序是非常重要的。Big5编码范围与GBK(Vim中chinese是cp936/GBK的别名)编码范围相似所以如果Big5放在GBK后不可能被识别。

注意:我们常见的繁简转换并不是一种编码转换而是一种“翻译”,因为这个转换过程不是在GBK中查找同样的繁体字(GBK包含繁体字编码)而是在GBK编码中查找相应的简体字替代原来的繁体字符。Big5与GBK进行编码转换的一个例子是由操作系统进行的。当你从Big5编码的网页上复制文字到时,会自动进行Big5至GBK的编码转换(在简体中文Windows操作系统上),转换后仍然是繁体字(GBK中包含繁体字的编码)但已经不是Big5编码。在复制日文字符时也会进行类似的转换。所以你看到的日文或繁体汉字也有可能是GBK编码的。看上去很头大是不是。好在大部分情况下我们不需要理会这些。大部分情况下读写都是使用默认编码。

最后一条关于Vim编码的基础知识是:fenc(fencs)可以使用的编码要多于enc可以使用的编码。fenc可以设置为(几乎)任何一个的编码,而enc能支持的编码则取决于系统。一个例子是:Vim中可以使用'iso-2022-cn'(中文)编码存储文件(即,将fenc设置为'iso-2022-cn'),但不支持使用该编码处理文件(也就是将enc设置为'iso-2022-cn')。当然此时可以使用gbk或utf8作为enc——所以这点不是大问题。

2.2 为什么有乱码?

上面图中可以看到在tenc与enc,enc与fenc都发生了编码的转换,许多乱码的出现都与编码转换有关。这里整理一下出现乱码的几个原因:

1. Vim无法识别文件编码。
这种情况下Vim会以默认编码显示文字,当文件编码不等于默认编码时就会出现乱码。
2. 错误地识别文件编码。
在一些情况下Vim错误地识别编码(常见的是将Big5字符识别为GB2312字符或是将ANSI/Latin1的8位字符识别为汉字,如.nfo文件)。这样通过encoding显示出来的就是乱码或是一堆无意义的字符。
3. fenc与enc无法转换。
并不是所有的编码之间都可以相互转换的,转换失败就会导致乱码。如果显示设备支持utf8或其他unicode编码,将enc设为这些编码中的一个可以减少这一问题出现的可能性。
4. enc与tenc无法转换。
使用gVim可不考虑tenc。但终端(如xterm或是Windows的命令行窗口)一般只支持某种特定的编码,所以使用Vim时需要设定为该终端特定的编码。
5. 使用的xterm不支持所使用的encoding。
可以看到从读取数据到提交数据到Terminal的过程中发生了两次的编码转换。在转换失败或者Vim提交至xterm的数据不能被正常显示(通常是由于错误的tenc)就会出现乱码。需要说明的是现代的GUI可以处理多种编码,所以在图形介面下输入输出的编码总是与enc一致的,tenc不起作用。因此需要考虑tenc的只有在term或xterm或者Windows的DOS窗口。
6. 没有正常显示某些字符所需要的字体。

显然要让Vim正确显示我们就要解决上面的6个问题。

上面的第一二点就是我一开始说的:se fencs命令所解决的问题。这已经可以解决大部分的问题了。但错误识别的问题仍无法完全避免,比如你有一个文件已知里面是一个双字节字0xbac3,你没办法判别(也没有工具可以做得到,只有创建者知道)这是GB2312的“好”还是Big5编码的“疑”(也可能是某个日文字符),这两个字在各自的编码中都是0xbac3。如果fencs中Vim先遇到哪个,那个就会被判别为文件的编码(fenc)而这有可能是错的。我们能做的只是通过一些方法提高识别率。

要解决第三、四点建议尽量将将它们设置为一样的值减少编码的转换。如果需要处理多种字符的话建议使用utf8。但仍有可能出现第五个问题,我们可以依tenc的设置选择合适的fenc与enc(好在现在大多模拟终端是可以设置编码的。)

如果你跟许多不同的语言打交道你可能会遇到上面的第6个问题,只能通过添加相应的等宽字体来解决。

提示:Windows上的DotumChe与GulimChe可以显示中日韩字符。

这是略微优化后的设置:

" 前面说过了big5不会被识别,可依需要调整次序
" 将chinese改为相应的CJK编码如果需要识别日韩编码
set fencs=ucs-bom,utf-8,default,chinese,big5
	
" 下面的三项需要依实际情况设置
" 设定新建文档所使用的编码
set fenc=utf8
" 设定Vim工作用的编码
set encoding=utf-8
set termencoding=utf-8

2.3 命令使用的细节

:h 'fenc'

在vimrc中指定fenc只对新建文档有用,在打开已有文档时并不起作用——Vim在每打开一个文件都要根据fencs设置(如果有vimrc中有fencs设置项的话)重新判断fenc,如果没有fencs并且Vim不能判断所用的文件编码的话则使用enc的值(&enc)如果enc也为空的话使用系统的默认编码。编辑时使用:se fenc指定一个与当前fenc不同的值并保存可以转换文件的编码。

前面说过Vim在读入文件时会先进行从&fenc至&enc的编码转换,然后将使用&enc编码内容显示在编辑区。注意这个转换只有在载入文件时进行。如果在文件载入后再使用:se enc命令更改enc,Vim并不对编辑区的文本进行编码转换而是以新的enc的方式“解读”已有的编码。而在此之后打开的文件则仍会进行由&fenc至新enc的转换。enc的设定对之后打开的文档都起作用,而:se fenc设定只对当前文档有影响,在其后每打开一个文档Vim都会重新判断编码。举例而言:

这是“好”和“疑”在GB与Big5中对应的编码(可以看到“好”euc-cn和“疑”的euc-tw编码是一样的):

好 euc-cn 0xbac3
好 euc-tw 0xa66e
疑 euc-tw 0xbac3

添加如下设置至vimrc:

se fencs=chinese enc=big5

现有一个文件内容为euc-cn(chinese是euc-cn/cp936的别名,Big5是euc-tw/cp950的别名)编码的“好”(0xbac3),用Vim打开后还是显示“好”,使用ga指令可看到编码已转换成了(0xa66e)。
将vimrc改为:

se fencs=chinese enc=chinese

现在再打开文件,使用ga可以看到编码仍时0xbac3,此时使用:se enc=big5可以看到“好”字显示成了“疑”,但编码并未发生转换——只是将文本当成了Big5的字符显示了。此时再打开新的文件时就会发生由该文件的&fenc至Big5编码的转换。

正因为编码转换在文本载入前发生,所以没办法通过modeline指定fenc或fencs。

2.4 如何实时地手工设置某个文件的fenc与enc?

如果要告诉Vim某个文档的正确编码是korea,而不是chinese,我们可以修改vimrc中fencs设置,同时设置enc为korea或utf8,然后重新运行Vim再打开该文档。然后等关掉该文档后再将设置改回来——这样做有点费力。现在我们已经知道打开文档后用:se fenc=xx设置fenc的话Vim会以为你要使用新的文件编码存储文件,要让Vim以正确的文件编码(fenc)载入文档可以使用这条命令:

" 强制Vim以korea为fenc读入文件yourfile
" 省略yourfile则重新载入当前buffer的文件
:se enc=utf8 | e ++enc=korea yourfile

设enc为utf8确保Vim能正常处理韩文字符(这里也可以设置为korea)——注意:如果需要设置enc的话,enc的设定命令一定要先于fenc的命令,而++enc用来指定所打开的文档的fenc(而不是让Vim自己判断)。为了显示韩文字符,在打开后你可能还要设置字体:

" Windows(Linux下这条命令会长得多)
se guifont=GulimChe

另外也可以使用:

" 建议Vim以korea为fenc读入文件yourfile
" 省略yourfile则重新载入当前buffer的文件
:se enc=utf8 fencs=korea| e yourfile

与上面的命令的不同之处在于,这条命令只有在文件的内容符合korea(euc-kr/cp949)的编码规则时才将fenc设为korea。而上一条命令则是无条件的将编码设置为korea。

2.5 有没有办法提高识别率?

我们可以通过一些方法提高识别率。其中之一是根据环境变量更改fencs的值——这实在是一种聊胜于无的做法。另外一种方法是使用更专业一点的工具。

2.5.1 enca

Linux下可以使用enca,确保系统中已经安装了enca(很有可能已经预装了),然后将下面的脚本加进vimrc

func! DetectFenc(fname)
  let enc=split(system("enca -Pe ". a:fname),"\n")[0]
  return enc=='unknown'?'default':enc
endfunc
	
au BufReadPre * let fenc=DetectFenc(exapnd("<afile>"))|exe "se fencs=ucs-bom,utf-8,".fenc."default"

enca主要是用以识别东欧的编码,对CJK的支持差一点。所以如果需要识别日韩编码这个工具并不适用——如果不需要打开日韩编码的文件则这个工具可以用以识别GBK与Big5编码。

2.5.2 MultiEnc.vim

在Windows下可以使用MultiEnc.vim——这是在官网找到的一个插件,里面使用了作者Yongwei Wu自己写的一个编码侦测工具tellenc。好处是不需要自己写脚本,缺点同样是不支持侦测日韩编码。

2.5.3 chardet

考虑到大多数中文用户只需要跟gb和Big5打交道上面的两个工具应该可以应付大多多数的情况了。如果你频繁出入各种编码的文件的话,那你需要更趁手的工具。这一节我们要用Universal Encoding Detector (chardet)将Vim打造成一个所有平台上编码识别能力最强的编辑器。Universal Encoding Detector是一个Python库,这意味着几乎所有平台下都可以使用——当然你的系统上要先安装Python。

关于这个包所使用的编码检测算法的描述可以参考:一种语言/编码检测的复合方法

首先下载并安装chardet至Python:

注意:这个库里面有一个Bug会导致utf-16le无法被识别。在安装前先将universaldetector.py第84行处的:

elif aBuf[:4] == '\xFF\xFE':
    # FF FE  UTF-16, little endian BOM

改为:

elif aBuf[:2] == '\xFF\xFE':
    # FF FE  UTF-16, little endian BOM

其次,在vimrc中加入这段脚本:2

if has('python')
func! SetFencs(fname)
python <<EOF
try:
  import chardet,vim
  from chardet.universaldetector import UniversalDetector
  # 'chardet' can be found at http://chardet.feedparser.org/
  chardetImported=True
except ImportError:
  chardetImported=False
	
def norm(encname):
  ''' replace encoding names with ones which can be
  recoginzed by Vim '''
	
  encgroups=[
["latin1","ascii"],
["chinese","euc-cn","gbk","gb18030","gb2312"],["iso-2022-cn"], # chinese
["korea","euc-kr"],["iso-2022-kr"],     # korean
["shift-jis"],["iso-2022-jp"],["euc-jp"], # japanese
["big5","euc-tw"],                    # big5
["cp874","tis-620"],                  # thai
["cp1250","windows-1250"],            # east europe
["cp1251","windows-1251","maccyrillic","ibm855","ibm866"],
["cp1252","windows-1252"],["cp1253","windows-1253"],
["cp1255","windows-1255"],["koi8-r"], # russia
["ucs-4be","utf-32be"],["ucs-4le","utf-32le"]
]
  for aliases in encgroups:
    if encname in aliases:return aliases[0]
  # return as is for other encodings like, utf8, iso-8859-xx
  return encname
	
def detectEnc(fname):
  ''' detect the file encoding of a file then returns
  a valid Vim *encoding-value* '''
	
  detector = UniversalDetector()
  for line in open(fname,'r'):
      detector.feed(line)
      if detector.done: break
  detector.close()
  result=detector.result['encoding']
  if result==None:return 'default'
  return norm(result.lower())
	
def detectEnc2(fname,samplesize=20000):
  ''' This is a shrunk copy of detectEnc() with the max
  sample length limited to 20K byte to accelerate a little
  bit at the cost of accuracy lose '''
	
  result=chardet.detect(open(fname,'r').read(samplesize))['encoding']
  if result==None:return 'default'
  return norm(result.lower())
	
if __name__=='__main__':
  if chardetImported:
    enc=detectEnc(vim.eval('a:fname'))
    #enc=detectEnc2(vim.eval('a:fname'))
    vim.command('se fencs='+enc)
  else:
    # use the original 'fencs' setting
    pass
EOF
	
endfunc
au BufReadPre * :call SetFencs(expand('<afile>:p'))
endif
" au BufRead * :if &fenc=='cp874'|set guifont=Courier\ MonoThai:h9:cTHAI

注意:python对空格很敏感所以格式不能弄乱。另外使用这个脚本Vim要有+python。如果没有+python可以将检测代码放在单独的python脚本中。然后在vim中使用system()调用——就像上面的enca一样。这里不再赘述。

现在可以试着打开一个文档,看编码是不是被正确识别了。这个方案的优点是支持编码多并且侦测结果准确,缺点同样明显——慢(使用detectEnc2()代替detectEnc()可以改善这个问题,但准确率也会因此下降)。当然,没有任何工具可以100%识别编码的。

稍微解释一下,

  • def norm() 这个函数将chardet侦测到的编码名称转为Vim能识别的编码名称。比如chardet侦测到的编码是"euc-jp",这个函数将返回下面列表中的第一项
    `["chinese","euc-cn","gbk","gb18030","gb2312"]‘即"chinese"。这个编码名称在windows下与linux下都能被Vim识别。注意,因为我对一些编码尤其是东欧语系的编码之间的关系不熟悉,所以上面的表可能有错误的地方。
  • def detectEnc()这个函数用以检测文件的编码。结果将通过norm()转换为Vim能识别的编码名称。在一些特殊情况下这个函数会很慢。
  • def detectEnc2()功能跟上面的是一样的。只是限制了读入数据的大小,这样可以使识别速度更快一点。当然识别率也会降低一点,可依自己的情况选择用哪一个。
  • 下面这条命令用以设置fencs的值,因为这条命令是在BufReadPre执行的,所以在读入文件时Vim就可以依据这个新的fencs值判断文件的编码。
         vim.command('se fencs=' + enc)
    
  • `au BufReadPre * :call SetFencs(expand('<afile>:p'))‘这条命令使Vim在每次打开文件时都使用这个函数侦测文件编码。
  • `au BufRead * :if &fenc=='cp874'|set guifont=Courier\ MonoThai:h9:cTHAI‘前面说过要正常显示除了编码设置正确外,还要有相应的字体。这命令让Vim在遇到泰文时自动调整为泰文字体。你可以将多条字体选择命令放在独立的脚本文件中,再使用au执行该脚本。

3 小结

  • 通过设置在Vim中可以方便地使用中文输入法
  • 编码的设定要在文件载入编辑区之前设定
  • fencs设置项决定Vim检查文件编码的次序,设置合适的fencs可以解决大部分的乱码问题
  • vimrc中的fenc设置项只对新建文件有用,除非fencs为空
  • e ++enc=xxx命令可以以指定的fenc载入文件
  • fenc支持的编码比enc要多
  • fenc, enc, tenc设转为一样的值可以减少编码转换的次数从而减少乱码
  • 将enc设置为utf8则可以实现更好的兼容性
  • 有时乱码是由于没有设置合适的字体
  • 编码侦测可以通过外部工具达到更好的结果

4 参考资料


Footnotes

[1] 而这些十六进制的字符表示也是用tenc显示出来的。

[2] 实际上与Vim编码相关的还有scriptencoding,而在python中使用汉字也需要声明编码。网页上复制粘贴可能导至编码转换,所以下面的混合脚本不使用中文注释,以免在解决问题时制造更多问题;P

2007, July 29

Vim的潜能

Filed under: vim, vi, 编辑器, Python

Vim潜能

虽然独特的屏幕编辑指令与方便使用的正则接口才是Vi类编辑器最重要的特点;虽然大部分用户只需要用10-20%的Vi功能;虽然Vim的定位是一个普通文本编辑器。但是大家还是想知道除了常规的编辑功能外,这个编辑器到底还可以做什么?

下面这些都是Vim扩展功能的重要手段:

  • 宏(包括了q命令,:map命令和:ab命令)
  • Vim Script(升级版的Ex命令)
  • 外部程序

宏与Vim Script能实现复杂的功能但局限于Vim提供的命令/函数中。使用外部程序的灵活性要差一点,通常只用来过滤编辑区的文本。这就是Vim的全部本事吗?当然不是。Vim还有两大武器——开放源码与编程语言接口。

1 开放源码

Vim是开放源码软件,所以像所有开放源码软件一样,你可以对它做任意的改动。Vim-ShellVimGdb是较为人所知的例子,它们使得在Vim中使用shell及在Vim中进行集成调试成为可能。

得益于开放源码,c程序员们可以在源码级别为Vim“添砖加瓦”。这是扩充Vim的功能最强大的途径——开放源码意味着可能性。一直以来,Bram Moolenaar都声称不会在Vim中集成shell或加入控制调试器的功能1。但由于Vim源码开放,用户还是有机会在Vim中使用Shell及调试程序——虽然它们没能成为Vim的正式发布的一部分,但这些补丁的存在至少让“源码上的无限可能性”不至于成为流于虚无的口号。

Bram认为Vim应该成为一个开发框架的一部分(编辑器部分)而不是成为开发框架本身。而他也身体力行,于是就有了Agide。我想正是由于他对Vim的清晰定位,Vim才不至于成为另一个自带编辑器的操作系统。但小小私心的说,在Vim中加入Shell应该还是符合Vim的自身特点的(因为有行模式的存在),也有利于Vim与其他程序的交互操作。而且只是作为Shell的表现层,不至于增加许多代码。

只是从源码上改进Vim终究是麻烦了一点——需要编译部署的周期。2于是有人为Vim增加了一些接口目的是能在Vim中使用通用脚本语言,重要的是这还成了正式发布的一部分。于是我们就有了perl和Python接口!当然,通过这些接口你还是无法改变Vim的运行机制,无法像c一样无所不能。但这样做还是换来了多方面的好处:首先,你不用为了增加一个绿豆大的功能,而在Vim源码海里打转。其次,你的开发周期中少了编译及重新部署Vim的过程。第三,现今流行的大多数脚本语言都是高级的语言,可以大幅减少开发时间。最后,你还可以选择不加入这些接口——如果你不需要的话,也没有人会强迫你安装一个你不需要用的60MB的脚本引擎。

2 Vim的编程语言接口

现在仍有许多Vim用户对Vim的程序语言接口不了解,。Vim中除了有自带的脚本引擎外还支持多种脚本语言,其中perl和python自Vim 5开始就成为了正式发布的一部分。现在可以使用的有五种脚本语言Perl, Python, Ruby, TCL和MzScheme。据不一定可靠消息称Java和Lua的接口也在开发中。

记住:只有在编译时加入了相应的选项才能使用这些程序语言接口。

??:在Vim脚本中使用这些脚本言与通过!命令使用这些脚本语言有什么不同?外部命令/程序只能修改buffer的内容,而这些接口允许在脚本语言中访问Vim的所有功能。与开发一个更强的Vim脚本引擎相比这样做有什么优势?这些脚本语言都是成熟的通用编程语言,功能上无赘言;多种脚本语言支持也为用户提供更多的选择,用户可以使用自己熟悉的语言。当然,它们都符合Unix工具箱哲学——工具专注于各自的领域,并可以组合使用。

各个脚本语言能直接访问的Vim对象不完全一样。总的来说可以直接在这些程序语言中使用的对象还不多,但Vim提供了访问Vim Script的接口确保所有的Vim功能都可以通过这些脚本语言访问。举例而言,你可以在perl中修改buffer的内容,但没法直接指定某一段文字所使用的语法高亮颜色。然而,你可以通过VIM::DoCommand()调用Vim Script设置高亮。

那利用Vim的程序语言接口到底能做些什么呢?通常使用的分工模式是由Vim负责输入输出,而处理逻辑部分由脚本负责。因为这几个都是通用编程语言,所以负责处理逻辑的脚本实际上可以完成任何编程语言能完成的工作。不过记住不要滥用Vim了!如果你只是打开Vim,运行脚本,然后关闭Vim,说明你可能不需要用到Vim而可以直接用该脚本语言完成同样的工作。想一下能否在Vim中实现下面描述的功能?

  1. 按一个功能键(比如<F2>)就自动从网上抓取最新的Vim Tips,并在一个分割窗口中显示。
  2. 将当前编辑文本发布到twitter。
  3. 按<F2>将当前编辑文本导出为Word格式,并自动进行适当的格式编排。
  4. 与GTalk用户聊天。
  5. 以MySQL(或其他数据库)做为万能补全的匹配源
  6. 写一个机器人应答程序。
  7. 一个音乐播放器。

没错,所有这些功能都可以实现。

  • 解析XML对主流脚本语言来说都只是小菜一碟,你唯一需要确定是在Vim中的显示格式。
  • 发送POST请求同样不是问题。可参考:Twitter Wiki,这里是现成的Vim script(Twitter)
  • 导出为Word格式需要机子上预先安装有Word,脚本语言可以使用自己的com接口来访问Word对象(Word.Application)。将解析过的文本传递给Word的com对象,并根据解析的结果添加适当的样式(控制Word对象或添加Word的样式比较简单,根据文本判断选择样式则相对复杂一点。因为涉及到文本的解析。)见使用Python來控制MS_Word
  • Perl、Python、Ruby都有现成的库XMPP可以与GTalk通信,为Vim添加这个功能并不困难。参考:XMPP Perl Library
  • 通过脚本的SQL接口访问数据库返回自动补全的匹配源。这当然也没问题。
  • 如果你需要的只是不断回答Yes的机器人的话,使用Vim Script足矣。如果你要写一个更智能的机器人话你就需要这些更强大的脚本语言。如果你想用现成的那就看这里,这是Python实现
  • 音乐播放器?没错,还是可以。只是在这个应用中其实不需要Vim。

如果你觉得这不像是Vim能做到什么,而更像是这些接口能做些什么的话——没错,实现“责任和义务的转移”这正是这些接口的意义所在。有了这些通用开发语言加持,Vim能做什么就看你的创意了——你唯一需要确定的是实现这些功能需不需要用到Vim

用这些接口开发Vim扩展的一个局限是,你可以同Vim交互的手段相当有限(比如没办法开一个监听Vim事件的守护进程)而且Vim没有图形方面的接口。只是当你遇到这方面的限制时,说明你很可能已经越界了——你正用Vim来做一些跟编辑/显示文本无关的事。

3 最后

c语言无疑是最强大的武器,但用c扩展Vim功能的开发成本相对较高。如果你会前面说的这几门脚本语言中的任何一种,你就可以用少量的开发成本为Vim增加非常多的可能性。啰嗦一句——如果你不会,你还是可以让Vim助你提高工作效率,要知道在这些接口出现前Vi类编辑器就已经是最受欢迎的编辑器了。

我听到很多人不用Vim的原因竟是因为他们用不了这么多功能-__-!,我写这篇文章时用到的最高级的的Vim指令是搜索,但已经足够提高编辑效率了(类似的话我这几天大概说了200次,只是效果不彰XD)。Vim有很多的高级应用,那演示的是Vim的可能性,但大多数人的大多数操作都只用到几十个最常用的Vi(m)指令及指令组合——而这几个指令才是Vi(m)最有价值的功能。

作为一个Vim的用户,应该要清楚这两点,首先,Vim的目标之一是可以方便地与其他工具协作而不是取代其他工具;其次,Vim的定位是通用文本编辑器。因此使用Vim的脚本接口扩展功能前,你应该先问一下自己:有没有现成的更好/更方便的工具?为什么这个应用中需要用到Vim?

Appendix A Vim开发原则

相信大家选择编辑器无外乎就是在功能、体积、速度、外观(还有操作方式,只是在用Vi之前大家可能没意识到编辑器是可以有不同的操作方式的)中做一个权衡。没有一个编辑器能满足所有要求。对功能,外观上的要求越高,就要牺牲点体积和速度;反之亦然。Vim试图同时满足功能、体积和速度方面的要求——这不是一个容易达成的目标。Bram Moolenaar为此定了一些Vim开发的原则。可以通过以下命令查看:

:h develop.txt

这些既是Vim的开发原则,也解释了Vim之所以成为现在的Vim。通过这些原则我们可以一窥Vim背后的理念。为了减少大家在浏览器与Vim中切换的次数,这里列出一部分:

  • Vim的目标是成为一个更好的Vi而不是一个全新的编辑器。
  • 尽可能地使用键盘,因为大数人没有第三只手握鼠标。
  • 减少使用控制键的使用,因为按控制键不方便。
  • 要能支持多种不同的终端、平台、多种编译器和库。尽可能的在多平台间保持外观和功能上的一致。除非确实是很酷的功能否则不要开发某平台独有的功能。
  • 新的功能都要带有完整的文档。
  • 体积要小,速度要快。确保Vim只用少量的系统资源,让旧的机子也能用Vim。
  • 有些用户使用低带宽的网络连接,要减少网络通讯量。
  • 用户拥有定制的自由,包括可以选择去除那些带来体积增加却又不是大多数人用得上的功能。
  • Vim的目标是方便高效地与其他程序协作,而不是代替所有其他程序。Vim的定位是系统的组件之一,而不是一个包罗所有功能的巨大程序。
  • Vim不是一个shell(交互式命令行解释器/控制台/命令提示符)也不是一个操作系统。所以你没法在Vim中开一个shell或用Vim去控制debugger。按Vim的设计理念,应该是将Vim作为其他shell或IDE中的编辑部件(而不是成为shell或IDE本身)。
  • Vim不会为了更华美的外观而牺牲其外观在不同平台的一致性。
  • 原则上欢迎添加任意功能(除非与其他原则相抵触XD)。

Bram在这篇文章里面对Vim的发展有类似的阐述:工具应专注于各自的领域,并且可以通过组合产生1+1>2的效果,而不是成为一个无所不能的臃肿程序。Vim功能上的增强会被限制在文本编辑方面。它的目标是成为最好的编辑器并且可以与方便地其他工具相配合。3




Footnotes

[1] :h desing-not

[2] 而且考虑到Unix/Linux上大把的开源软件,“可以从源码上扩展功能”这一点实在不是特别吸引人的特性。

[3] 听起来有点耳熟?就是前面提到的Unix的工具箱哲学。关于这段话可以在原文中搜索”World domination”。事实上Bram Moolenaar曾多次提及这些原则,google一下“高效文本编辑的七个习惯”。


vim_chatbot

Get free blog up and running in minutes with Blogsome
Theme designed by Jay of onefinejay.com