Operators和movements所包含的理念是Vim中的一個非常重要的概念,也是Vim之所以這么高效的最大原因所在。在這一章我們會在這一塊做更多的實踐,這會讓Vim變得更強大。
假設你現在在往Markdown中寫入一些文字。如果你沒有用過Markdown,不要緊,對于我們現在要做的事情而言,它很簡單。把下面的文字輸入到一個文件中:
~~~
Topic One
=========
This is some text about topic one.
It has multiple paragraphs.
Topic Two
=========
This is some text about topic two. It has only one paragraph.
~~~
使用`=`作為“下劃線”的行會被Markdown當作標題。我們現在創建一些映射,這些映射可以讓我們通過movements定位到標題。運行下面的命令:
~~~
:onoremap ih :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
~~~
這個映射有些復雜。現在把你的光標放到文本中的某個位置(不要放到標題上),然后敲擊`cih`。Vim會刪除光標所在章節的標題,然后保持在插入模式,這可以稱為"修改所在的標題(change inside heading)"。
這里使用了一些我們之前從來沒有見過的東西,所以我們有必要單獨分析下每一部分的含義。這個映射的第一部分,`:onoremap ih`是映射命令,這個我們很熟悉了,無需多言。`<c-u>`上一章講過,我們現在也不深究。
接著看看剩下的部分:
~~~
:execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
~~~
## Normal
`:normal`命令的后面會跟著一串字符,無論這些字符表示什么含義,`:normal`命令都會執行它們,就像是在常用模式下敲擊這些字符一樣。我們會在后面的章節中談論關于`:normal`的更多細節,由于這個它已經出現多次,所以我覺得有必要現在做一個簡單的說明,算是淺嘗輒止吧。執行下面的命令:
~~~
:normal gg
~~~
Vim會將光標跳轉到文件的頂部。現在執行下面的命令:
~~~
:normal >>
~~~
Vim將縮進當前行。
那`normal`后面的`!`是干啥的呢?先別管,以后再說。
## Execute
`execute`命令后面會跟著一個Vim腳本字符串(以后會深究它的細節),然后把這個字符串當作一個命令執行。執行下面的命令:
~~~
:execute "write"
~~~
Vim會寫文件,就像你已經輸入了`:write<cr>`一樣。現在執行下面的命令:
~~~
:execute "normal! gg"
~~~
Vim會執行`:normal! gg`,這個會將光標跳轉到文件的頂部,跟之前一樣。現在問題來了,我們為什么要搞得這么蛋疼,又是`execute`,又是`normal!`,直接執行`normal!`不就可以搞定么?
看看下面的命令,猜猜它會干啥:
~~~
:normal! gg/a<cr>
~~~
這個命令似乎會做下面的一些事情:
* 將光標跳轉到文件的頂部。
* 準備搜索。
* 把“a”當作目標字符串進行搜索。
* 按下return鍵執行搜索。
你自己執行一下,Vim會將光標跳轉到了文件頂部,然后。。沒有然后了!
之所以會這樣是由于`normal!`的一個問題,這問題是`normal!`不能識別“特殊字符”,例如這里的`<cr>`。這個問題有很多辦法可以搞定,最簡單的就是使用`execute`,另外使用`execute`也會讓腳本更易讀。
當`execute`碰到任何你想讓它執行的字符串的時候。它會先替換這個字符串中的所有特殊字符。在這個示例中,`\r`是一個轉義字符,它表示的是"回車符(carriage return)"。兩個反斜線也是一個轉義字符,它會將一個反斜線當作一般字符放到字符串中。
如果我們按照上面的分析替換這個映射中的特殊字符,然后就可以搞清楚這個映射會怎么執行:
~~~
:normal! ?^==\+$<cr>:nohlsearch<cr>kvg_
^^^^ ^^^^
|| ||
這里的<cr>實際上是一個回車符,而不是由4個字符——“左尖括號”,“c“,”r“和“右尖括號”組成的字符串。
~~~
所以現在`normal!`會執行這些字符,如同我們是在常用模式下敲擊了它們一樣。我們以回車符對這些字符進行拆分,然后看看它們是怎么執行的:
~~~
?^==\+$
:nohlsearch
kvg_
~~~
第一部分`?^==\+$`會向后搜索任何由兩個或多個等號組成的行,這些行不會包含除等號外的任何其他字符。這個命令執行后會讓光標停留在符合搜索條件的行的行首。
之所以使用向后搜索,是因為當你想“修改所在的標題(change inside heading)”的時候,而當前光標是位于某一節的文本上,那么你最可能想做的是修改_這_一節的標題,而不是下一節的標題。
第二部分是`:nohlsearch`命令。這個命令只是簡單的清除之前的搜索結果的高亮顯示,防止這些高亮顯示分散我們的注意。
最后一部分是三個常用模式下的命令的序列:
* `k`:向上移動一行。第一部分已經將光標定位到了等號符號組成的行的第一個字符,所以執行這個命令后光標就會被定位到標題的第一個字符上。
* `v`:進入可視模式(characterwise)。
* `g_`:移動到當前行的最后一個非空字符上。這里沒有使用`$`,是因為`$`會選中換行符號,這不是我們所想要的。
## 結果
這個映射做了很多工作,所以看起來有些復雜,不過我們已經搞清楚了這個映射中的每個部分。現在來概括一下:
* 我們為“所在章節的標題內(inside this section's heading)”創建了一個operator-pending的映射。
* 我們使用了`execute`和`normal!`這兩個命令來執行我們用于選擇標題的常用命令,這讓我們可以在這些命令中使用特殊字符。
* 我們的映射會搜索由等號組成的行從而定位到一個標題,然后在常用模式下選中標題的文本。
* Vim進行剩下的處理標題的工作。
再來看一個映射。執行下面的命令:
~~~
:onoremap ah :<c-u>execute "normal! ?^==\\+\r:nohlsearch\rg_vk0"<cr>
~~~
把光標放到某一節的文字上,然后敲擊`cah`試試看。這一次Vim不僅會刪除這一節的標題,而且還會刪除跟這個標題相連的等號組成的行。你可以把這個movement當作是“_環繞_這一節的標題(_around_?this section's heading)“。
這個映射有什么不同呢?讓我們對照之前的映射看一下:
~~~
:onoremap ih :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rkvg_"<cr>
:onoremap ah :<c-u>execute "normal! ?^==\\+$\r:nohlsearch\rg_vk0"<cr>
~~~
唯一的不同是映射的后面用于選擇文本的部分:
~~~
inside heading: kvg_
around heading: g_vk0
~~~
其他的部分都是一模一樣的,所以我們現在從將光標定位到等號組成的行的第一個字符的那個部分開始進行講解:
* `g_`:移動到當前行(譯注:等號組成的行)的最后一個非空字符。
* `v`:進入可視模式(characterwise)。
* `k`:向上移動一行。這會將光標移動到包含標題文字的行上。
* `0`:移動到這一行(譯注:標題行)的第一個字符。
這一系列命令的執行結果就是在可視模式下同時選中標題的文字和等號組成的行,然后Vim會在這兩行上執行相應的操作。
## 練習
Markdown也可以用`-`字符來限定標題。調整上面的正則表達式使得這些映射可以工作在不同類型的標題上。你可能想查看`:help pattern-overview`。記住正則表達是在一個字符串中,所以反斜線需要進行轉義。
添加兩個創建這些映射的自動命令到你的`~/.vimrc`文件中。確保只對合適的緩沖區使用這些映射,并且確保使用自動命令組來防止每次加載`~/.vimrc`的時候創建這些自動命令的副本。
閱讀?`:help normal`。
閱讀?`:help execute`。
閱讀?`:help expr-quote`了解你可以在字符串中使用的轉義序列。
創建一個“在下一個郵件地址內(inside next email address)”的operator-pending映射,然后你就可以使用“修改在下一個郵件地址內(change inside next email address)”。將`in@`作為映射的按鍵是個不錯的選擇。你可能還需要將這個按鍵映射為`/...some regex...<cr>`。
- 前言
- 鳴謝
- 預備知識
- 打印信息
- 設置選項
- 基本映射
- 模式映射
- 精確映射
- Leaders
- 編輯你的Vimrc文件
- Abbreviations
- 更多的Mappings
- 鍛煉你的手指
- 本地緩沖區的選項設置和映射
- 自動命令
- 本地緩沖區縮寫
- 自動命令組
- Operator-Pending映射
- 更多Operator-Pending映射
- 狀態條
- 負責任的編碼
- 變量
- 變量作用域
- 條件語句
- 比較
- 函數
- 函數參數
- 數字
- 字符串
- 字符串函數
- Execute命令
- Normal命令
- 執行normal!
- 基本的正則表達式
- 實例研究:Grep 運算符(Operator),第一部分
- 實例研究:Grep運算符(Operator),第二部分
- 實例研究:Grep運算符(Operator),第三部分
- 列表
- 循環
- 字典
- 切換
- 函數式編程
- 路徑
- 創建一個完整的插件
- 舊社會下的插件配置方式
- 新希望:用Pathogen配置插件
- 檢測文件類型
- 基本語法高亮
- 高級語法高亮
- 更高級的語法高亮
- 基本折疊
- 高級折疊
- 段移動原理
- Potion段移動
- 外部命令
- 自動加載
- 文檔
- 發布
- 還剩下什么?