VIM解决中文编码问题


虽然VIM自7.0之后对双字节的编码已经支持的很不错了,但是,还是需要一些配置才能完全实现的哦。

要解决的问题:

1.识别双字节编码格式

需要先了解的知识:

vim中的内置变量:

enc(encoding):vim的内部编码

fenc(fileencoding):vim解析出来的当前文件编码(有可能解析成错的哦)

fencs(fileencodings):vim解析文件时猜测的编码格式顺序列表

需要的配置:

1.vimrc中的代码如下:

set encoding=utf-8
set fenc=cp936
set fileencodings=cp936,ucs-bom,utf-8
if(g:iswindows==1)
    source $VIMRUNTIME/delmenu.vim
    source $VIMRUNTIME/menu.vim
    language messages zh_CN.utf-8
endif
if v:lang =~? '^\(zh\)\|\(ja\)\|\(ko\)'
    set ambiwidth=double
endif
set nobomb

解释如下:

set encoding=utf-8这行是将vim的内部编码格式变为utf-8,这样vim识别文件正确的准确性会提高很多。set fenc=cp936是指当新建一个文件的时候,默认编码是gbk,而set fileencodings=cp936,ucs-bom,utf-8 这一行会让vim按照gbk,utf-8(没有头),utf-8的顺序识别。由于笔者的工作环境下大部分代码要求为gbk,所以才如上述设置,如果读者需要默认为utf-8格式,可以如下配置:

接下来:

这段代码是防止菜单乱码。

是为了让vim能够默认以双字节处理那些特殊字符。

set nobomb 是让vim不要自动设置字节序标记,因为并不是所有编辑器都可以识别字节序标记的。

2.由于即便配置了上述的代码,也不一定能够100%认识对编码,所以推荐大家安装一款插件,是国人写的,挺不错的。

fencview.vim

这款插件集成了自动检测编码格式的能力,但是笔者测试过似乎不是很准,所以还是建议关掉自动检测,只有在vim检测失败的时候,才调出fencview,手动选择编码比较好。

在vimrc中配置如下:

这样按下F2就可以直接呼出fencview界面,再按下就会关闭。

当然,如果您是一位经常处理多国编码的用户,那么可能直接安装fencview.vim,并且将自动检测打开会更好些,但是对于vim自己的控制就少了,我想作为vimer肯定不想如此吧,呵呵。

另外,按照上面的格式来配置vim的话,在保存文件时,是不会更改文件格式的,如果想要强制更改,例如要改成utf-8,可以用set fenc=utf-8来执行,之后写入即可。

好啦,就到这里。

版权所有,转载请注明出处。 http://www.vimer.cn

vim对于文件的编解码有三个参数,分别是encoding、fileencoding,fileencodings,下面说的应该可以解决绝大多数的问题了。 

        1、支持中文编码的基础 
        要更好地支持中文编码需要两个特性:+multi_byte和+iconv,可以用|:version|命令检查当前使用的Vim是否支持,否则的话需要重新编译。 

        2、影响中文编码的设置项 
        Vim中有几个选项会影响对多字节编码的支持: 
        encoding(enc):encoding是Vim的内部使用编码,encoding的设置会影响Vim内部的Buffer、消息文字等。在Unix环境下,encoding的默认设置等于locale;Windows环境下会和当前代码页相同。在中文Windows环境下encoding的默认设置是cp936(GBK)。 
        fileencodings(fencs):Vim在打开文件时会根据fileencodings选项来识别文件编码,fileencodings可以同时设置多个编码,Vim会根据设置的顺序来猜测所打开文件的编码。 
        fileencoding(fenc):Vim在保存新建文件时会根据fileencoding的设置编码来保存。如果是打开已有文件,Vim会根据打开文件时所识别的编码来保存,除非在保存时重新设置fileencoding。 
termencodings(tenc):在终端环境下使用Vim时,通过termencoding项来告诉Vim终端所使用的编码。 
        termencodings(tenc):在终端环境下使用Vim时,通过termencoding项来告诉Vim终端所使用的编码。 

        3、Vim中的编码转换 
        Vim内部使用iconv库进行编码转换,如果这几个选项所设置的编码不一致,Vim就有可能会转换编码。打开已有文件时会从文件编码转换到encoding所设置的编码;保存文件时会从encoding设置的编码转换到fileencoding对应的编码。经常会看到Vim提示[已转换],这是表明Vim内部作了编码转换。终端环境下使用Vim,会从termencoding设置的编码转换到encoding设置的编码。 
        可以用|:help encoding-values|列出Vim支持的所有编码。 

        4、具体应用环境的设置 
        只编辑GBK编码的文件 
        set fileencodings=cp936 
        set fileencoding=cp936 
        set encoding=cp936 

        只编辑UTF-8编码的中文文件 
        set fileencodings=utf-8 
        set fileencoding=utf-8 
        set encoding=cp936 或者 set encoding=utf-8 

        同时支持GBK和UTF-8编码 
        set fileencodings=ucs-bom,utf-8,cp936 
        set fileencoding=utf-8 
        set encoding=cp936 或者 set encoding=utf-8 

        如果在终端环境下使用Vim,需要设置termencoding和终端所使用的编码一致。例如: 
        set termencoding=cp936 或者 set termencoding=utf-8 

        Windows记事本编辑UTF-8编码文件时会在文件头上加上三个字节的BOM:EFBBBF。如果fileencodings中设置ucs-bom的目的就是为了能够兼容用记事本编辑的文件,不需要的话可以去掉。Vim在保存UTF-8编码的文件时会去掉BOM。去掉BOM的最大好处是在Unix下能够使用cat a b>c来正确合并文件,这点经常被忽略。 

        5、FAQ 
        为什么在Vim中一次只能删除半个汉字? 
        因为encoding设置错误,把encoding设置为cp936就可以解决此问题。在Unix环境下Vim会根据locale来设置默认的encoding,如果没有正确设置locale并且没有设置encoding就会一次只能删除半个汉字。 
        VIM为什么不能输入繁体字? 
        把euc-cn或者GB2312改为cp936就可以了。euc-cn是GB2312的别名,不支持繁体汉字。cp936是GBK的别名,是GB2312的超集,可以支持繁体汉字。 
        VIM为什么提示不能转换? 
        因为在编译Vim时没有加入iconv选项,重新编译Vim才能解决。 
        如何打开一个GBK编码的文件并另存为UTf-8编码? 
        保存文件时运行命令|:set fileencoding=utf-8|就可以了。 

 Go语言(golang)第一个正式版Go1发布了,但是这个新兴的编程语言还是非常不完善。这不,我(Liigo)又发现它的编译器竟然不支持编译带BOM的UTF-8编码的.go源文件。这就很奇怪,该语言明明要求源代码文件.go必须是UTF-8编码,但有不允许带UTF-8 BOM。要知道,这个世界上带BOM的文件太多了,很多文本编辑器/代码编辑器/IDE都会默认生成带有BOM的UTF-8文件。如果仅仅因为源代码文件多了BOM,编译器将不能编译这个文件,我觉得它太低能了。

Go语言编译器(gc)不支持带有BOM的UTF-8源文件
Golang's compiler (gc) don't accept the .go files with UTF-8 BOM: 

[plain]  view plain copy
  1. E:\liigo\golang\src>go run hello.go  
  2. package :  
  3. hello.go:1:1: illegal character U+FEFF  
  4.   
  5. E:\liigo\golang\src>go run hello.go  
  6. # command-line-arguments  
  7. .\hello.go:9: illegal UTF-8 sequence  
  8. ce d2  

  好在Go语言是开源项目,我(Liigo)来贡献代码,让它支持编译带UTF-8 BOM的.go源代码文件。经过分析后发现,Go语言编译器(gc)源代码中有两处地方涉及从磁盘文件中读取.go文件:一个是C语言写的词法分析器(src/cmd/gc/lex.c),一个是Go语言写的.go文件解析器(src/pkg/go/parser/interface.go)。解决思路也很简单,就是从磁盘读取文件内容后,判断前三个字节,与UTF-8 BOM的三个字节(0xef, 0xbb, 0xbf)核对,如果一致则忽略这三个字节,从第四个字节算作文件的真正内容,然后再交给词法分析器和解析器处理,后面就一切正常了。

  Go语言的词法分析器是用C语言手工编写的,其中用到了lib9/libbio库,是一个磁盘文件读写缓冲区,逐字节读取该缓冲区时,它可以做到“反读取”最近的4个字节,就是说,假设我刚刚读取了abcd是个字节,现在我“反读取”后两个字节,实际上就相当于我刚刚读取了ab还没有读取cd。利用它的这个“凡读取”机制,恰恰可以很容易的忽略掉UTF-8文件最前面的BOM:首先读出前三个字节,如果这三个字节正好是UTF-8 BOM的三个字节(0xef, 0xbb, 0xbf),那么直接把刚刚读出的三个字节扔掉就完事了,后面词法分析器处理时正好从BOM后面的字节开始读取;如果已经读出的三个字节不是UTF-8 BOM呢,需要“反读取”,即把他们再放回去,就当作我没有读取过它们。修改后的代码如下:

[cpp]  view plain copy
  1. // src/cmd/gc/lex.c :   
[cpp]  view plain copy
  1. 319                 // Try to read and ignore UTF-8 BOM  
  2. 320                 c1 = Bgetc(curio.bin);  
  3. 321                 c2 = Bgetc(curio.bin);  
  4. 322                 c3 = Bgetc(curio.bin);  
  5. 323                 if(c1 != 0xef || c2 != 0xbb || c3 != 0xbf) {  
  6. 324                         // If not UTF-8 BOM, restore the bytes.  
  7. 325                         // Bungetsize > 3, so we can safely call Bungetc() 3 times.  
  8. 326                         Bungetc(curio.bin);  
  9. 327                         Bungetc(curio.bin);  
  10. 328                         Bungetc(curio.bin);  
  11. 329                 }  

  Go语言的源代码解析器(pkg/go/parser)是用Go语言自己编写的,其功能是解析.go源代码文件为语法树。Go语言官方提供的go build命令用pkg/go/parser分析处理编译前的库依赖项。go build命令(pkg/go/parser)是把.go文件整个读入内存后再解析的。我要做的工作就是,在pkg/go/parser开始正式解析前,把前面可能存在的UTF-8 BOM删除掉即可,这个工作仅仅涉及Go语言中byte slice的基本操作,是很轻量级的廉价操作。

[plain]  view plain copy
  1. // src/pkg/go/parser/interface.go :   
[plain]  view plain copy
  1. +// The data read from .go files maybe start with the UTF-8 BOM(byte order mark),  
  2. +// we ignore the bytes here to make sure that the parser parses properly.  
  3. +//  
  4. +func ignoreUTF8BOM(data []byte) []byte {  
  5. +   if data == nil {  
  6. +       return nil  
  7. +   }  
  8. +   if len(data) >= 3 && data[0] == 0xef && data[1] == 0xbb && data[2] == 0xbf {  
  9. +       return data[3:]  
  10. +   }  
  11. +   return data  
  12. +}  
  13. +  
  14.  // If src != nil, readSource converts src to a []byte if possible;  
  15.  // otherwise it returns an error. If src == nil, readSource returns  
  16.  // the result of reading the file specified by filename.  
  17. @@ -27,22 +40,23 @@  
  18.         case string:  
  19.             return []byte(s), nil  
  20.         case []byte:  
  21. -           return s, nil  
  22. +           return ignoreUTF8BOM(s), nil  
  23.         case *bytes.Buffer:  
  24.             // is io.Reader, but src is already available in []byte form  
  25.             if s != nil {  
  26. -               return s.Bytes(), nil  
  27. +               return ignoreUTF8BOM(s.Bytes()), nil  
  28.             }  
  29.         case io.Reader:  
  30.             var buf bytes.Buffer  
  31.             if _, err := io.Copy(&buf, s); err != nil {  
  32.                 return nil, err  
  33.             }  
  34. -           return buf.Bytes(), nil  
  35. +           return ignoreUTF8BOM(buf.Bytes()), nil  
  36.         }  
  37.         return nil, errors.New("invalid source")  
  38.     }  
  39. -   return ioutil.ReadFile(filename)  
  40. +   fileData, err := ioutil.ReadFile(filename)  
  41. +   return ignoreUTF8BOM(fileData), err  
  42.  }  


  我把上面修改的代码提交到Go语言官方源码库,代码审查页面地址是:http://codereview.appspot.com/6036054/, 或 http://codereview.appsp0t.com/6036054/。

  但是Go语言的作者/官方开发者拒绝这一改进。Go开发组老大Rob Pike亲自回复给出就拒绝的理由:

[plain]  view plain copy
  1. Strictly speaking, a BOM is legal in UTF-8 but only as a marker for  
  2. the type of the data stream, a magic number if you will. Since Go  
  3. source code is required to be UTF-8, a BOM is never necessary and  
  4. arguably erroneous. We've come this far without accepting BOMS and I'd  
  5. like to keep it that way.  

  在我看来,理由非常勉强,在逻辑上甚至都不成立。哦,既然规定了.go必须使用UTF-8编码,所以就一定不能加UTF-8 BOM了?加了BOM就拒不接收了?前面已经说过了,很多文本编辑器都会自动添加UTF-8 BOM的,怎么到你这里就不合法了。一个很现实的例子是,Windows XP / Windows 7系统内置的“记事本”程序(notepad.exe)在保存UTF-8文件时是一定会自动添加UTF-8 BOM的。也就是说,你想用记事本保存的.go源代码是一定不能编译通过的。但就是这么严重的问题,Go作者们就是不当一回事;这么容易就可以改进的问题,他们就是拒绝改进。生生的为用户使用go语言又凭空制造一道阻力。要说是面临技术方案的妥协选择折中还可以理解,偏偏要在无关紧要的地方坚持己见宁死不去考虑方便用户。我只能说他们是死脑筋。类似的情况我遇到也不止一次了,总结下来就是:技术大牛挂帅做产品害人害己;闭门造车的代价是死都不知道咋死的。(这样的总结也为我们做易语言产品敲响了警钟:绝对不能单纯以技术人员的心态做产品。)


---------------------------------------------------------------------------------------------------


2013-7-15 Liigo 补记

2013年5月13日发布的 golang 1.1 终于支持带UTF-8 BOM的源代码文件了

[plain]  view plain copy
  1. "The Unicode byte order mark U+FEFF, encoded in UTF-8, is now permitted as the first character of a Go source file."  
http://golang.org/doc/go1.1#unicode

这一日,距离Go语言开发组老大Rob Pike拒绝UTF-8 BOM那番话发表(2012-4-17,内容见上文),已过去了一年多,可能他已经忘记了自己说过什么。

而且他们对源代码的修改思路也是跟我一致的(都修改了lex.c同一处;我虽然没有直接修改scanner,但通过修改parser也间接达到了目的):

cc2bca9c03ef by Rob Pike, 2012-9-10: gc: initial BOM is legal

3d58333e8e2a by Russ Cox, 2012-10-7: cmd/gc: skip over reported BOMs

4245c8cdc599 by Robert Griesemer, 2012-9-7: go/scanner: skip first character if it's a BOM

30444b809a9e by Robert Griesemer, 2013-4-12: go/scanner: reject BOMs that are not at the beginning

看到没!三个作者(包括两位老大)先后修改提交了至少4次,前后跨度7个月,才总算达到了我一次性修改提交源代码的效果。



---------------------------------------------------------------------------------------------------


2014-3-18 Liigo 补记

历史总是惊人的相似,时隔两年之后,我(Liigo)又修改了Rust编程语言编译器的源代码,为其增加了支持UTF-8 BOM的功能。与Go语言官方开发组形成鲜明对比的是,Rust官方开发人员很爽快的采纳了我这个Pull Request#12976:libsyntax: librustdoc: ignore utf-8 BOM in .rs files。



你可能感兴趣的:(Linux基础)