Vim中輸入法與編碼設定

helloxchen發表於2010-10-21

目錄




Up: (dir)

輸入法與編碼設定的FAQ

從去年到現在一直陸陸續續的都有人在問起中文輸入法的設定和編碼的設定這方面的問題。我將這些問題整理了一下,也許有些用處。

1 中文輸入法

如果每次進出insert模式都要切換一下輸入法是很惱人的,我自己就有過這樣經歷。應該也有不少人在初次嘗試Vim後放棄的原因就是因為無法讓中文輸入法與Vim無間配合。其實這個問題是可以解決地(不過只對圖形介面的Vim有效)。

1.1 Windows

:h mbyte-IME

在Windows中如果是直接下載完整的安裝包的話,輸入法支援已經包涵在gVim中了。如果自行編譯的話記得加入以下選項:+multi_byte_ime加入這一選項後,每次退出插入模式後,Vim會依所用命令與上次退出前的輸入法狀態自動判斷使用中文還是英文,在重新進入插入模式時回到上一次退出時的輸入狀態(即上一次退出時是中文輸入還是英文輸入的狀態)。

舉例來說如果你在插入模式、中文輸入狀態下按

dT

在按後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。

從下面這張圖可以看到它們之間的關係:

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

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(很有可能已經預裝了),然後將下面的指令碼加進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(""))|exe "se fencs=ucs-bom,utf-8,".fenc."default"

enca主要是用以識別東歐的編碼,對CJK的支援差一點。所以如果需要識別日韓編碼這個工具並不適用——如果不需要開啟日韓編碼的檔案則這個工具可以用以識別GBK與Big5編碼。

2.5.2 MultiEnc.vim

在Windows下可以使用——這是在官網找到的一個外掛,裡面使用了作者自己寫的一個編碼偵測工具。好處是不需要自己寫指令碼,缺點同樣是不支援偵測日韓編碼。

2.5.3 chardet

考慮到大多數中文使用者只需要跟gb和Big5打交道上面的兩個工具應該可以應付大多多數的情況了。如果你頻繁出入各種編碼的檔案的話,那你需要更趁手的工具。這一節我們要用將Vim打造成一個所有平臺上編碼識別能力最強的編輯器。Universal Encoding Detector是一個Python庫,這意味著幾乎所有平臺下都可以使用——當然你的系統上要先安裝Python。

關於這個包所使用的編碼檢測演算法的描述可以參考:一種語言/編碼檢測的複合方法

首先下載並安裝至Python:

注意:這個庫裡面有一個Bug會導致utf-16le無法被識別。在安裝前先將universaldetector.py第84行處的:

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

改為:

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

其次,在vimrc中加入這段指令碼:2

if has('python')
func! SetFencs(fname)
python <: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(':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

--&gt

2 Comments »

The URI to TrackBack this entry is: http://blah.blogsome.com/2007/08/23/vim_cn_faq/trackback/

  1. vimim —— vim 中文輸入法 讓中文輸入法與Vim無間配合

    Comment by — 2009, February 1 @ 18:08


  2. has consistently been accepted for its ultimate superior and accepted in accomplishment superb designs of watches.
    is a blazon of watch which has been advised to clothing the requirements of pilots. Go to a acclaimed retail abundance of
    and boutique to abroad for your new,
    today!

    Comment by — 2010, June 21 @ 08:53

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040146/,如需轉載,請註明出處,否則將追究法律責任。

相關文章