實踐中學習vim之按鍵序列對映

2021-06-20 18:55:58 字數 3372 閱讀 2960

【術語說明】

本文對於vim中所有能引起動作的字串行統稱為「命令」,這不僅僅包含以:開頭的命令列模式下的命令,也包括其他模式下的按鍵序列。

【注意】

由於vim各個部分相互關聯緊密,簡單起見,本文中描述時不會完全考慮與之相聯絡的其他主題,由此會造成不準確的描述。

vim採用了「不同模式」設計思想,它擁有很多模式,常見的是 normal(一般模式),insert(插入模式),visual(可視模式),command(命令列模式),replace(替換模式)。

不同模式之間相互隔離,這也是這種設計方法的最大優點。這樣就可以做到不同模式下使用相同的命令名稱而不衝突。

與其他編輯器不同,vim中所有的按鍵序列都可能是命令,即使是在insert模式下仍然成立。比如在插入模式下,使用者通過鍵盤輸入了a這個字元,vim首先識別使用者輸入的字元是否是已經定義的命令,如果不是則原樣插入到緩衝區中。

vim中使用者輸入的基本處理流程為:

(1)使用者輸入

(2)vim識別使用者輸入是否是命令名稱,如果是則(3),如果不是則(4)

(3)執行這條命令,轉到(5)

(4)預設動作(對於,插入模式就是把輸入插入到緩衝區,對於其他模式則不進行任何操作)

(5)完成本次輸入

不同的模式下,vim都內建了一些必不可少的命令。如normal模式下大家熟悉的移動游標命令j、k、h、l,刪除一行的命令dd,貼上命令p等;命令列模式下的寫入檔案命令:w,退出命令:q,查詢命令/something等;有一些內建命令能夠工作在多個模式下,如複製命令yy技能在normal模式下工作,也能在visual模式下工作。比較特殊的是,在insert模式下,vim幾乎沒有什麼內建命令,也許原樣插入就是insert模式下最重要的功能了。

除了內建的命令,vim一開始就支援使用者自定義命令,這正是vim之所以能成為vim的原因。針對不同模式,vim提供了兩種自定義方式:針對命令列模式,使用:command 進行定義;針對其他模式,使用:map 進行定義。本文中,我們只描述了:map這種方式。

先來個小例子。前面說過,vim的insert模式下,好像沒有內建的命令。所以我們首先拿它開刀,自定義乙個insert模式下的命令。在vim中輸入如下:

:imap tks thanks

【說明】其中i表示insert模式,map標識對映,tks是自定義使用者命令名,thanks是對映到的命令名或字串行。

然後在insert模式下輸入tks,看看效果。

我們會看到的現象是:

(1)當輸入t後,游標仍然在t下面,而沒有移動到t後面;

(2)當繼續輸入k後,k出現在t的位置上,t被覆蓋,游標仍然不移動;

(3)當繼續輸入s後,thanks出現在輸入的位置上,游標移動到後面。

下面,分析一下這個過程:

(1)當輸入t後,vim會識別到t是使用者自定義命令tks的開頭,但此時不能斷定使用者像輸入的是tks命令,所以繼續等待後續輸入進行進一步判斷;

(2)當繼續輸入k後,vim識別到目前使用者輸入的tk是命令tks的一部分,但此時仍不能斷定使用者的意圖,所以繼續等待後續輸入;

(3)當繼續輸入s後,vim識別到目前使用者輸入的tks就是命令tks,所以執行這個命令,這個命令的動作是把輸入thanks這個序列;

(4)vim繼續識別thanks是否是個命令的名稱,由於沒有名為thanks的命令,所以原樣把thanks插入緩衝區,重新整理輸出顯示,使用者看到了thanks這幾個字元。

可以看出,map完成的命令對映的功能,即把乙個命令對映為另乙個命令或字串行。另外,map會遞迴進行,也就是說如果對映結果是個命令,則繼續執行這個結果命令。如下

:imap a b

:imap b c

然後在insert模式下輸入a,得到輸出結果不是b,而是c。這種方式會帶來經典的死迴圈問題,如下:

:imap a b

:imap b c

:imap c a

:inoremap a b

:inoremap b c

:inoremap c a

使用:noremap進行對映時,對映結果會直接當作字串輸出,不在當作命令來解釋。所以上述對映的結果是,輸入a得到的是b,輸入b得到的是c,輸入c得到的是a。

定義如下兩個命令。

:imap a  onea

:imap aa twoa

那麼當我們輸入aa的時候,vim會把aa解釋成乙個aa命令呢,還是兩個a命令呢?

實驗證明,vim採用了長度優先的原則,當輸入乙個a後,vim不能確定使用者的意圖,只能等待,當繼續輸入的字元還是a時,此時vim已經能夠確定使用者輸入的就是aa命令(因為不存在以aa開頭的其他命令了),所以執行aa命令,輸出twoa,如果使用者輸入的第二個字元不是a,那麼vim也能夠確定使用者輸入的就是a命令,所以會輸出onea然後輸出使用者輸入的第二個字元。

實際工作中盡量不要讓不同的命令有相同的起始部分,以提高vim的反應速度。

前面討論的對映執行過程,死迴圈問題,命令名最長原則,適用於所有模式下的自定義命令。下面**幾個其他需要注意的問題。

:nmap ohello

【解釋】o新建一行,並進入insert模式,hello直接插入,返回normal模式。

例如,在normal模式下,輸入10然後按下f2鍵,則會輸入10行hello。

儘管前面的例子中使用imap定義了insert模式下的自定義命令,但是這是個不好的做法,因為insert模式本來就應該是原樣完成輸入,否則會讓使用者感到不舒服。同樣的功能,完全可以在normal模式下自定義命令來完成。

vim採用的是後來者居上的方式,後定義的同名命令可以覆蓋掉先定義的同名命令,就連內建命令也可以被覆蓋。如:

:nmap j

之後,導航用的j就會失效。雖然vim為使用者提供了無限自定義的權利,但是保持基本的內建命令的一致性是vim使用者之間交流的基礎。所以,不要覆蓋內建命令!否則,vim將會面目全非!

(1)檢視已經有的自定義命令使用不帶引數的map類命令,如檢視一般模式下的現有自定義命令:

:nmap

(2)刪除自定義命令,使用unmap類命令,如刪除前面定義的j:

:nunmap j

之後,j又可以完成內建的導航功能了。

mapclear類命令則會刪除所有的自定義命令,如刪除insert模式下所有的自定義命令:

:imapclear

命令作用範圍不僅受模式的影響,也受緩衝區buffer的影響,定義在乙個緩衝區中的命令,在另乙個緩衝區中不起作用。自定義命令還可以呼叫內建函式和使用者自定義函式,這將在「自定義命令列模式下的命令」時再做分析。自定義命令不僅能以互動方式進行,還可以寫入vim的配置檔案或外掛程式檔案中(也許這才是更有用的方式)。編寫vim外掛程式本身就是乙個很大的主題。

本文僅僅是vim自定義命令的乙個簡單入門指引,如果能給讀者帶來一點受益,那就謝天謝地了。

在實踐中學習

轉眼時間過得飛快,大學四年就像夢一樣過去了,當初惴惴不安地擔心自己能不能過畢業設計,稀里糊塗畢業證都已經拿到了手上。經過了被乙個非常有前途的單位放了鴿子,當初全力去拼搶的東西到頭來全是泡影,還好大學裡也不是全部都稀里糊塗,也學了點皮毛,找了對口的程式設計工作。可是,技術畢竟不高,在這裡,壓力很大。有...

一 實踐中學習 awk

內建變數 awk 中預先定義好的,內建在 awk 內部的變數。變數名描述 fs輸入字段分隔符,預設是空格 ofs輸出字段分隔符,預設是空格 rs輸入記錄分隔符,預設換行符 ors輸出記錄分隔符,預設換行符 nfnumber of fields,當前記錄中域的個數,也就是每行有多少列 nrnumber...

第一天課程 從實踐中學習

program laotzu.cpp include some programs to surpress unneeded warnings pragma warning disable 4068 pragma argsused int winapi winmain hinstance hinst,...