vim的使用環(huán)境比較復(fù)雜,可以通過terminal在本地使用(比如Mac或Linux主機),也可以ssh連接到遠程服務(wù)器使用,還可以使用gvim。這里主要討論terminal下的使用,搞清楚了vim在terminal下的編碼設(shè)置,gvim相對更簡單,自然也就了解了
首先我們要理解字符和字節(jié)的區(qū)別,字符是用來顯示的,而字節(jié)是存儲和傳輸時使用,網(wǎng)絡(luò)傳輸?shù)氖亲止?jié)流,文件存儲的也是字節(jié)流,而編輯器要顯示文件內(nèi)容,就需要轉(zhuǎn)化為字符來顯示,字符和字節(jié)之間的關(guān)系可以定義如下
encode(字符, 編碼方案) -> 字節(jié)
decode(字節(jié), 編碼方案) -> 字符
可見encode和decode是一對逆向操作,它們都需要指定編碼方案,如果編碼方案不一致,則會操作失敗
通過terminal操作遠程vim時,其數(shù)據(jù)流向可以表示如下
terminal -> 本地shell -> ssh -> 遠程shell -> vim
在這個流向里,只有terminal和vim需要顯示字符,其它進程或服務(wù)只是做數(shù)據(jù)傳輸,如果只是單純傳遞二進制數(shù)據(jù),是不需要涉及編碼解碼的,只有當(dāng)顯示字符的時候才需要進行解碼,因此只有terminal和vim需要配置編碼,而terminal需要和本地shell打交道,遠程vim也需要和shell打交道,shell的編碼也至關(guān)重要
Terminal編碼
terminal本身也是一個進程,最終的字符顯示都需要由terminal來完成,我們在terminal上輸入字符也會由它進行編碼之后再傳遞,簡單來說就是
- 輸入時,terminal將字符編碼為二進制,傳遞給其它進程或服務(wù)
- 顯示時,terminal接收到其它進程或服務(wù)的二進制數(shù)據(jù)流,解碼為字符進行顯示
這里編解碼方案就是terminal需要配置的
shell編碼
locale命令也可查看shell編碼設(shè)置,以LC_開頭的代表系統(tǒng)不同類別的編碼方案,分為如下幾類
- 語言符號及其分類(LC_CTYPE)
- 數(shù)字(LC_NUMERIC)
- 比較和排序習(xí)慣(LC_COLLATE)
- 時間顯示格式(LC_TIME)
- 貨幣單位(LC_MONETARY)
- 信息主要是提示信息,錯誤信息,狀態(tài)信息,標(biāo)題,標(biāo)簽,按鈕和菜單等(LC_MESSAGES)
- 姓名書寫方式(LC_NAME)
- 地址書寫方式(LC_ADDRESS)
- 電話號碼書寫方式(LC_TELEPHONE)
- 度量衡表達方式 (LC_MEASUREMENT)
- 默認(rèn)紙張尺寸大小(LC_PAPER)
- 對locale自身包含信息的概述(LC_IDENTIFICATION)。
至于最終選什么方案,其優(yōu)先級如下
LC_ALL > LC_* > LANG
也就是說一切都以LC_ALL為主,如果沒有設(shè)置,則查找LC_*對應(yīng)的設(shè)置項,如果仍舊沒有,則使用LANG的設(shè)置,影響字符顯示的為LC_CTYPE項,為了便于描述,后續(xù)提到shell編碼時一律指LC_ALL項,設(shè)置shell編碼方式如下
export LC_ALL=zh_CN.GBK
export LC_ALL=zh_CN.UTF-8
terminal編碼和shell編碼不一致會出現(xiàn)什么情況?
假設(shè)我們本地terminal編碼設(shè)置為UTF-8,shell編碼設(shè)置為GBK,當(dāng)我們在terminal上輸入中文字符時,會顯示為亂碼或不顯示
我們分析一下在終端輸入shell命令時的數(shù)據(jù)交互
輸入法 -> terminal -> shell -> terminal
- 在terminal上輸入字符,terminal根據(jù)自己的編碼設(shè)置,將字符編碼為utf8字節(jié),傳遞給shell
- shell收到二進制數(shù)據(jù),轉(zhuǎn)碼為gbk格式數(shù)據(jù),再回傳給terminal顯示。這一步轉(zhuǎn)碼會出錯
- terminal收到二進制數(shù)據(jù),按utf8方式解碼為字符并顯示。這一步解碼會出錯
將terminal和shell看做兩個服務(wù),它們之間需要進行數(shù)據(jù)交互,在發(fā)送數(shù)據(jù)時進行編碼,在收到數(shù)據(jù)時會進行解碼,如果編碼方案和解碼方案不一致,就會導(dǎo)致亂碼或失敗,表現(xiàn)形式就是在terminal上輸入中文命令時會顯示異常,執(zhí)行結(jié)果也不符合預(yù)期
如果用ssh登陸遠程shell,則遠程shell的編碼配置和本地shell一致,在通過ssh -v
可以打印ssh在登陸過程中做了哪些事
因此我們第一個要點是
terminal和shell編碼必須設(shè)置為一樣
Vim編碼
vim和編碼相關(guān)的有4個設(shè)置項
-
fileencodings
一個編碼列表,以逗號分隔,打開文件時會依次以列表里的編碼方式去解碼,如果執(zhí)行成功,則該編碼為文件編碼,并設(shè)置fileencoding -
fileencoding
檢測到的文件編碼 -
encoding
vim內(nèi)部使用的編碼,包括文件內(nèi)容,寄存器等 -
termencoding
terminal編碼,在terminal中使用vim時會用到,gvim不需要設(shè)置
可見vim的編碼設(shè)置相當(dāng)復(fù)雜,我們還是以具體的實例來分析這些編碼設(shè)置的作用
不管是打開本地vim,還是打開遠程vim,我們首先保證本地shell的編碼設(shè)置和terminal一致,這樣涉及到編解碼的數(shù)據(jù)流可以簡化為
terminal -> vim
打開文件
vim打開文件,最終還是在terminal上顯示,這個過程和編碼設(shè)置相關(guān)的有
- 打開文件,根據(jù)fileencodings設(shè)置項檢測文件編碼,并設(shè)置fileencoding為對應(yīng)編碼
- 根據(jù)encoding設(shè)置項,將文件二進制進行編碼轉(zhuǎn)換,存儲到內(nèi)存中
- 根據(jù)termencoding設(shè)置項,將內(nèi)存中的二進制轉(zhuǎn)化為對應(yīng)編碼,傳輸給terminal
- terminal根據(jù)自身的編碼設(shè)置,將收到的數(shù)據(jù)解碼并顯示
可見vim在打開文件并顯示的過程中有大量的編碼轉(zhuǎn)化操作,將二進制從編碼A轉(zhuǎn)化為編碼B的步驟為
字節(jié)流 -> decode(A) -> encode(B) -> 字節(jié)流
最終輸出仍舊為字節(jié)流,如果A和B不同,則輸出字節(jié)流和輸入就不一樣(ascii字節(jié)流除外,在所有編碼方案里ascii字符對應(yīng)的字節(jié)流都是一樣的)。轉(zhuǎn)換成功的前提是,decode所采用的編碼方案必須和輸入字節(jié)流編碼方案一致,也就是說如果輸入字節(jié)流是采用C編碼方案生成的,采用A編碼方案去解碼就會失敗
如果vim的某些編碼項沒有設(shè)置,會使用其依賴項的設(shè)置或默認(rèn)設(shè)置,依賴關(guān)系如下
termencoding -> encoding -> shell
vim的這些編碼設(shè)置項里通常我們只設(shè)置fileencodings和encoding,如果只在中英文環(huán)境下使用,可設(shè)置如下
set fileencodings=utf8,gbk
set encoding=utf8
encoding一定要設(shè)置utf8,因為utf8可以表示所有字符
terminal編碼和vim的encoding編碼不一致會出現(xiàn)什么情況?
假設(shè)terminal編碼設(shè)置為gbk,vim的encoding為utf8,此時我們打開一個文件,不管這個文件是utf8還是gbk編碼,它都無法正常顯示
前面提到,vim的termencoding默認(rèn)會繼承encoding的設(shè)置,對應(yīng)前面打開文件的步驟如下
- 打開文件,檢測文件編碼。這里不管是utf8還是gbk都沒有影響
- 將文件內(nèi)容轉(zhuǎn)化為utf8格式,存儲到內(nèi)存中。這一步由于知道了文件的原始編碼,因此轉(zhuǎn)換不會出錯
- 將內(nèi)存數(shù)據(jù)轉(zhuǎn)化為termencoding對應(yīng)的編碼,傳輸給terminal。由于termencoding繼承自encoding設(shè)置,因此這一步實際上不需要做編碼轉(zhuǎn)換
- terminal按gbk解碼。問題出在這一步,由于terminal不知道vim傳過來的數(shù)據(jù)是什么編碼,它會直接按照自己的編碼設(shè)置進行解碼,編碼不一致導(dǎo)致出錯
如果要正常顯示,只需要臨時修改vim的termencoding編碼和terminal編碼一致即可,termencoding只涉及到顯示,不涉及文件內(nèi)容的改變,切勿修改encoding項,準(zhǔn)確來說,在任何時候都不要試圖修改encoding設(shè)置
因此我們的第二個要點是
vim的termencoding(繼承自encoding)必須和terminal編碼設(shè)置一致
修改文件
如果說打開文件的數(shù)據(jù)流是從vim到terminal,那修改文件則是從terminal到vim再到terminal這么一個來回
terminal -> vim -> terminal
和編碼相關(guān)的步驟如下,打開文件顯示的過程前面已經(jīng)描述過,這里只說修改和保存的過程
- 在terminal輸入字符,根據(jù)terminal編碼方案進行編碼,傳輸給vim
- vim收到二進制數(shù)據(jù),將數(shù)據(jù)由termencoding編碼轉(zhuǎn)換為encoding編碼并保存在內(nèi)存中
- 保存文件時,將數(shù)據(jù)從encoding編碼轉(zhuǎn)為fileencoding編碼,若fileencoding為空,則直接以encoding方案保存
fileencoding有兩種情況
- 打開空文件,fileencoding默認(rèn)為空
- 打開已經(jīng)存在的文件,fileencoding是根據(jù)fileencodings中的編碼列表匹配到的編碼方案,若都沒匹配上,則為空
由上可見,encoding方案編碼的數(shù)據(jù)在vim中是一個中轉(zhuǎn)站,接收數(shù)據(jù)時(從文件讀取或從終端輸入)都要轉(zhuǎn)化為encoding編碼方案,保存文件時再由encoding編碼方案轉(zhuǎn)化為fileencoding編碼方案。因此encoding必須設(shè)定為一個能表示所有字符的編碼方案,通常我們設(shè)置為utf8
vim的encoding編碼設(shè)置和terminal編碼設(shè)置不同,如何正常輸入文字
假設(shè)terminal和shell的編碼設(shè)置均為gbk,vim的encoding設(shè)置為utf8,如果想正常輸入和顯示字符,必須將termencoding設(shè)置和terminal編碼一致,這是不管是顯示字符還是輸入字符保存文件,都可以正常工作
我們可以設(shè)置編碼不一致只是為了演示編碼的影響,在實際環(huán)境中,必須保證這些編碼設(shè)置都一致,因此終極要點是
terminal編碼,shell編碼,以及vim的encoding均設(shè)置為utf8
vim編碼配置終極方案
- terminal編碼設(shè)置為utf8
- shell編碼設(shè)置為utf8,
export LC_ALL=zh_CN.UTF-8
- vim設(shè)置encoding為utf8,
set encoding=utf-8
- vim設(shè)置fileencodings,
set fileencodings=utf-8, gbk