### [Learn Vimscript the Hard Way](https://learnvimscriptthehardway.stevelosh.com/)
TODO [Vim Script ! ](https://ithelp.ithome.com.tw/articles/10234362)
---
### Vimscript
撰寫 Vim 腳本(Vim script)函數的方法如下:
1. **開始函數:** 在 Vim 腳本中,函數以 `function` 關鍵字開始,後接空格和函數名稱。
```
function MyFunction()
```
2. **函數主體:** 在函數的開始和結束之間,可以撰寫相關的 Vim 指令,以實現你想要的功能。
```
function MyFunction()
" 這裡撰寫你的指令
endfunction
```
3. **參數傳遞:** 你可以在函數括號內指定參數,以便在函數內部使用。
```
function Greet(name)
echo "Hello, " . a:name . "!"
endfunction
```
4. **返回值:** 如果你想要從函數中返回值,可以使用 `return` 關鍵字。
```
function Add(a, b)
return a + b
endfunction
```
5. **調用函數:** 使用 `call` 關鍵字來調用你撰寫的函數。
```
call MyFunction()
```
6. **保存腳本:** 將你的 Vim 腳本保存在 `~/.vimrc` 或其他 Vim 設定文件中,以便在 Vim 中使用這些函數。
### 在函數中調用其他函數
```
function SayHello(name)
echo "Hello, " . a:name . "!"
endfunction
function GreetUser()
" 調用 SayHello 函數並傳遞參數
call SayHello("Alice")
endfunction
```
### 調用系統命令
```
function ListFiles()
let result = system("ls")
echo result
endfunction
```
### execute
在 Vim 腳本中,`execute` 命令通常在需要動態生成和執行 Vim 命令時使用。這在以下情況下特別有用:
1. **動態命令生成:** 如果你需要根據條件動態生成 Vim 命令並執行它們,可以使用 `execute`。這種情況下,你可以將變數的值插入到命令中。
```
let my_command = "echo 'Hello, World!'"
execute my_command
```
2. **使用變數作為命令:** 當你需要將變數的值作為命令名稱時,可以使用 `execute`。
```
let cmd_name = "echo"
execute cmd_name "Hello, Execute!"
```
3. **在循環中使用:** 如果你需要在循環中執行不同的命令,可以使用 `execute` 來動態生成並執行這些命令。
```
for i in range(1, 5)
execute "echo 'Iteration " . i . "'"
endfor
```
4. **處理特殊字符:** 有時你可能需要在命令中處理特殊字符,這些字符可能會干擾命令的解析。`execute` 可以幫助你在命令中處理這些情況。
總之,`execute` 命令允許你在 Vim 腳本中動態生成和執行命令,以增強你的腳本的靈活性和功能。然而,請確保適當地處理變數和特殊字符,以避免潛在的問題。
### 比較差異的作法
```bash
vimdiff file1.txt file2.txt
```
---
### :r 讀取內容
在 Vim 中,:r 是一個命令,用於將指定文件或緩衝區的內容插入到當前光標位置下方。:r 命令後可以跟著文件名或緩衝區名,表示從該文件或緩衝區中讀取內容。
:r 命令的格式如下:
```vim
:r {file}
```
其中 {file} 是要插入內容的文件名或緩衝區名。插入的內容將出現在當前光標位置的下方。
這個命令在許多情況下都很有用,例如當你想要將其他文件的內容插入到當前文件中的特定位置,或者當你想要在當前緩衝區中的特定位置插入其他緩衝區的內容時。這可以幫助你更有效地編輯和組織文件的內容。
---
### 生成數字序列
在 Vim 中,可以使用 :put = 命令搭配 VimScript 的 range() 函數來生成數字序列。下面是使用 Vim 的 :put 命令和 range() 函數來生成序列的步驟:
1. 進入命令行模式:按下 Esc 鍵退出插入模式,然後輸入冒號 : 進入命令行模式。
1. 執行 :put 命令:在命令行中輸入 :put =range(1, 10),其中 1 和 10 是序列的起始值和結束值。按下 Enter 執行該命令。
1. 序列生成完成:Vim 將在當前游標位置插入由 1 到 10 的序列。
### 洗牌
```bash
:%!shuf
# 這將使用Linux的shuf命令對文本進行洗牌,從而打亂數字的順序。
```
### Map 的差異
在 Vim 中,`nnoremap`、`nmap` 和 `map` 都是用於建立按鍵映射的命令,但它們之間存在一些差異。以下是它們的解釋和區別:
1. `nnoremap`:這是 "non-recursive mapping" 的縮寫,表示非遞歸映射。當你使用 `nnoremap` 創建按鍵映射時,它將確保映射不受其他映射的影響。換句話說,即使你在映射的命令中使用了其他映射,這些被嵌套的映射不會被觸發。這在避免不必要的行為和循環映射中很有用。
2. `nmap`:這是 "normal mode mapping" 的縮寫,表示在正常模式(Normal Mode)下的映射。使用 `nmap` 創建的映射會在正常模式下生效,並且可能受到其他映射的影響,如果你在映射的命令中使用了其他映射。
3. `map`:這是一個通用的映射命令,不僅僅局限於正常模式。使用 `map` 創建的映射可能在各種模式下(正常模式、插入模式、命令模式等)都生效。然而,由於它會影響所有模式,所以可能會產生一些不預期的行為。
如果設定(`<C-UP>`)的映射沒有生效,有幾個可能的原因,讓我們一一檢查:
1. **終端機支援**: 確保你的終端機(或终端模擬器)能夠識別 `<C-Up>` 這樣的組合鍵。有些終端機可能不支援所有的組合鍵,或者需要特定的配置才能正確識別它們。
2. **映射碼**: `<C-Up>` 可能有不同的映射碼,具體取決於你的終端機。你可以使用 `Ctrl + V`(按住 Ctrl 鍵的同時按 V 鍵)來插入鍵盤輸入的原始碼。例如,`Ctrl + V` 然後 `Ctrl + Up`,看看你得到了什麼映射碼,然後在映射中使用它。
3. **配置文件**: 確保你的映射命令位於正確的配置文件中。對於大多數情況,你應該將這些映射添加到 `~/.vimrc`(或類似的配置文件)中。
4. **衝突映射**: 檢查是否有其他的映射或插件使用了 `<C-Up>` 作為映射。這可能會導致映射衝突,使你的映射無效。你可以使用 `:verbose map <C-Up>` 命令來查看是否有其他映射使用了相同的按鍵組合。
5. **重新載入配置**: 如果你在配置文件中進行了更改,確保你重新載入了配置或者重新啟動了 Vim。
### 按 = 格式化程式碼採用四個空格
在你的 `.vimrc` 或 `init.vim` 文件中添加以下行:
```vim
" 將 Tab 換成四個空格
set tabstop=4 " 設定 Tab 鍵的寬度為 4 個空格
set softtabstop=4 " 設定在插入模式中按 Tab 鍵時使用 4 個空格
set shiftwidth=4 " 設定自動縮進寬度為 4 個空格
set expandtab " 將 Tab 鍵轉換為空格
" 啟用自動縮進
set smartindent
set autoindent
```
這樣的設定確保了在插入模式中按下 Tab 鍵時,Vim 將插入四個空格,而不是 Tab 字符。同時,在使用 `=` 鍵格式化程式碼時,Vim 也將根據這些設定使用四個空格進行縮進。这是一种常见的设置,使得代码风格更加一致。
### 摺疊
在 Vim 裡,最簡單、最直覺的「把你 Visual 選起來的區塊摺疊」方法就是用 zf(zone fold)指令。以下示範用步驟+範例說明:
1. 先把 fold method 設成 manual (預設通常就是 manual,如果你改過要先確認/設定)
```vim
:set foldmethod=manual
```
2. 進入 Visual 模式選取你要摺疊的範圍
- 按 `V`(大寫 v)→ 用上下鍵或 j/k 移動到結尾
- 或按 `v`(小寫 v)→ 再以左右、上下鍵做精確選取
3. 按 `zf`
- Vim 會自動把你選的那幾行打包成一個 fold
- 選完後畫面上該區塊會變成 `+-- 5 lines: ...`(依行數不同而不同)
4. 摺疊後的操作
- `zc`:關閉(close)目前光標下的折疊
- `zo`:打開(open)目前光標下的折疊
- `za`:切換開/關
- `zR`:全部打開
- `zM`:全部關閉
---
範例:
假設你在 `sample.txt`,內容長這樣(1~10 行):
```
1 line one
2 line two
3 line three
4 line four
5 line five
6 line six
7 line seven
8 line eight
9 line nine
10 line ten
```
步驟:
a. 打開 Vim:
```bash
vim sample.txt
```
b. 確認 foldmethod:
```vim
:set foldmethod=manual
```
c. 在第 2 行按 `V`,往下走到第 6 行(剛好選到 line two ~ line five 共 5 行):
- 看到游標一直擴到第 6 行
d. 按 `zf`
- 畫面會變成類似:
```
1 line one
+-- 5 lines: line two … line six
7 line seven
8 line eight
…
```
e. 確認摺疊:
- 把游標移到 `+-- 5 lines …` 那行,按 `zc` 會收起;再按 `zo` 會展開
---
進階用法
- zf + 移動鍵(不用先 Visual)
例如在行首,輸入 `zf5j`:折疊從目前行往下算 5 行的區塊。
- zf%
當 foldmethod 設成 manual,且游標在一對括號內,`zf%` 等同折疊「從括號開到括號關」的範圍。
- Marker 折疊(foldmethod=marker)
也可以用 `{{{` 和 `}}}` 這類標記,讓 Vim 自動折疊。設定:
```vim
:set foldmethod=marker
:set foldmarker={{{,}}}
```
然後在要摺疊的地方加上 `{{{`/`}}}` 即可。
這樣就能非常靈活地把任何區塊「選了就摺」啦!
---
# 翻頁後程式碼顏色消失問題
在 Vim 中,「突然翻頁(如用 Ctrl-F、PageDown)後程式碼顏色消失」其實並不是畫面(terminal)壞掉,而是 Vim 的 syntax-highlight 同步機制(syntax sync)在救援不到上下文時,乾脆把這段關掉以免整個檔案重頭掃描太慢。
## 問題源頭:Syntax Sync 機制
Vim 的語法高亮並非獨立一行一行高亮,而是要先「找到一個 sync point」(同步點),然後從那裡往下解析給你看。預設同步機制有兩種策略:
1. syn sync fromstart
從檔案開頭一直 parse 到目前行(慢,檔案愈大愈明顯)
2. syn sync minlines/maxlines
往目前行往上找最近的「開始」pattern(例如 `function`、`{`…),最多往上找 `syncmaxlines`(預設 200)行
如果 200 行內找不到,就「放棄」──不高亮這段
當你一下子跳到離前一個 sync point 超過預設行數(200 行)以外的地方,就撲空,Vim 就把該區塊標示成「不做 highlight」。
## 如何確認
在 Vim 裡敲
:syn sync report
你會看到它用的是哪種 sync method、目前已找到的 sync point、maxlines、minlines 設多少⋯⋯
接著跳頁再看一次,就能發現同步點撲空的現象。
## 解方/調校
1. 讓 Vim 往上找久一點(調高 `syncmaxlines`)
在你的 vimrc 加:
```
" 最多往上找 1,000 行
set syntax sync maxlines=1000
" 如果前面 1,000 行都沒看到,就從檔頭重算
set syntax sync fromstart
```
2. 或者只用「從頭 parse」
在 vimrc 加:
```
syntax sync fromstart
```
但大檔案就真的會有點慢。
3. 只調 minlines(多少行內一定要用 maxlines 演算法)
```
set syntax sync minlines=200
set syntax sync maxlines=1000
```
4. 如果你是 C/C++/JavaScript 等,很多檔案都有明顯的 function 開頭,也可把你的 sync patterns 寫得更可靠,讓 Vim 比較容易找到。
範例(vimrc)︰
```
" 先設定最低同步行數:超過 300 行才開始 maxlines 搜尋
set syntax sync minlines=300
" 最多找 2000 行內的同步點
set syntax sync maxlines=2000
" 如果超過,就從頭算起
syntax sync fromstart
```
調好之後,以後用 PgUp/PgDn、`:1000` 跳轉都不會再突然「無色」了。
——
補充:這個問題在大檔案、深度巢狀的程式尤其明顯,因為找到 sync point(像 `if … {`)比較難。適度調高 `syncmaxlines` 或改成 `fromstart`,就能穩定恢復正常的語法著色。
# 目前檔案開分頁切換分支
這是一個非常聰明的做法。結合我們之前使用的 **fzf** 工具,我們可以建立一個「分支選擇器」,讓你選中分支後,自動在新分頁開啟該檔案的歷史版本。
這在進行代碼審查(Code Review)或功能對比時極度高效。
### 解決方案:FZF 分支選擇器
請將這段程式碼覆蓋或加入到你的 `.vimrc` 中。這段腳本會自動抓取當前檔案在 Git 倉庫中的相對路徑,並透過 fzf 讓你挑選分支。
```vim
" --- Vim 7.4 Git 分支檔案選擇器 ---
function! s:open_file_from_branch(lines)
if empty(a:lines) | return | endif
let l:branch = a:lines[0]
" 1. 取得檔案在 Git 倉庫中的正確相對路徑 (處理子目錄問題)
let l:relpath = system('git ls-files --full-name ' . expand('%'))
let l:relpath = substitute(l:relpath, '\n$', '', '') " 移除換行符
" 2. 紀錄目前的檔案類型
let l:ft = &filetype
" 3. 開啟新分頁並讀取內容
execute 'tabnew'
execute 'read !git show ' . l:branch . ':' . l:relpath
" 4. 清除第一行的空白
1delete
" 5. 設定緩衝區屬性
let &filetype = l:ft
setlocal buftype=nofile
setlocal noswapfile
execute 'file ' . l:branch . ':' . l:relpath
echo "已開啟分支 " . l:branch . " 的版本"
endfunction
" 定義指令 :GBR (Git Branch Read)
command! GBR call fzf#run({
\ 'source': 'git branch -a --format="%(refname:short)" | sed "s|^origin/||" | sort -u',
\ 'sink*': function('s:open_file_from_branch'),
\ 'options': '--prompt "選擇分支> " --header "開啟目前檔案的特定分支版本"',
\ 'down': '40%'
\ })
" 綁定快捷鍵
nnoremap <leader>gb :GBR<CR>
```
---
### 這段功能是如何運作的?
1. **動態抓取分支**:`source` 指令會抓取本地與遠端的所有分支名稱,並透過 `sed` 去除掉重複的 `origin/` 前綴,讓你看到一個乾淨的清單。
2. **路徑自動修正**:使用 `git ls-files --full-name`。這是關鍵,因為如果你在 `src/main/java/` 目錄下開啟 Vim,直接用 `expand('%')` 可能會讓 `git show` 找不到檔案,這個指令能確保拿到相對於 Git 根目錄的路徑。
3. **FZF 介面**:它會彈出一個 40% 高度的視窗,你可以輸入關鍵字過濾分支,按下 **Enter** 就會觸發 `tabnew` 開啟檔案。
4. **安全保護**:新開的分頁被設定為 `buftype=nofile`。這意味著它只是一個臨時視窗,當你按下 `:q` 關閉時,它不會要求你存檔,也不會意外覆蓋掉你正在開發的檔案。
---
### 操作建議
* **輸入指令**:在一般模式下輸入 `:GBR`(或是按下你設定的 `<leader>gb`)。
* **搜尋分支**:直接打字,例如輸入 `feat` 快速過濾出 feature 分支。
* **退出查看**:看完後直接輸入 `:q` 即可關閉該分頁,回到你原本的分支。
---
## 修復破圖
在資深開發者的運維過程中,這是一個非常經典的「終端機控制權衝突」問題。
當你執行 `:silent grep!` 時,Vim 會啟動外部程式(如 `rg` 或 `grep`)並接管標準輸出。雖然你使用了 `silent`,但許多現代搜尋工具(尤其是 `rg`)在偵測到執行環境時,仍可能嘗試發送某些 ANSI 轉義序列(用於顏色或格式),或是因為背景處理與 Vim 的 UI 渲染產生競爭,導致畫面殘留、線條斷裂或文字重疊,這就是你看到的「破圖」。
---
### 1. 核心原因分析
* **TTY 搶佔:** `silent` 只是叫 Vim 不要顯示命令本身的輸出,但外部程式可能仍與 Vim 共享同一個 TTY 控制台,並在背景寫入資料。
* **畫面未刷新:** Vim 在執行完外部指令後,若沒有明確收到「重繪」指令,介面會停留在指令執行前的狀態,加上外部程式噴出的零碎字元,畫面就會亂掉。
* **RG 的顏色控制:** `ripgrep` 預設可能會嘗試偵測是否開啟顏色,這會干擾 Vim 的渲染。
---
### 2. 解決方案:徹底修復「破圖」
要優雅地解決這個問題,你需要確保兩件事:**強制搜尋工具純淨輸出**,以及**搜尋後強制 Vim 重新渲染**。
請將你的搜尋函數修改如下:
```vim
function! VisualSearchRG()
" 1. 抓取選取文字
normal! gv"yy
let l:query = escape(@y, '#%^{}[]*+?|()')
" 2. 使用 redraw 確保終端機狀態正確
execute 'silent grep! ' . shellescape(l:query)
" 3. 關鍵修復:強制重繪 UI
" redraw! 會清除所有外部程式留下的殘影並重新渲染 Vim 介面
redraw!
" 4. 自動打開 Quickfix (選用)
copen
endfunction
```
---
### 3. 進階優化:針對 `rg` 的參數調整
如果你在 `.vimrc` 中設定了 `grepprg`,請確保加上 `--no-heading` 和 `--color=never`。這些參數能大幅減少非必要的格式控制符號,進而降低破圖機率:
```vim
if executable('rg')
" --vimgrep: 格式為 file:line:col:text,Vim 最愛這種格式
" --no-heading: 不要顯示檔案標題,減少干擾
" --color=never: 強制關閉顏色轉義字元,這是防止破圖的元兇
set grepprg=rg\ --vimgrep\ --no-heading\ --color=never\ --smart-case
endif
```
---
### 4. 終極方案:使用非同步 Job (Vim 8+)
如果你想徹底杜絕卡頓與破圖,應該避開 `!grep`(同步執行),改用非同步的 Job。雖然代碼較長,但在內網處理數萬行 Java 代碼時,體驗會完全不同:
```vim
" 使用非同步方式搜尋,完全不會卡頓或破圖
function! AsyncRG(query)
let l:cmd = 'rg --vimgrep --no-heading --color=never ' . shellescape(a:query)
" 使用 getqflist({ 'lines': ... }) 直接填充 Quickfix
let l:lines = systemlist(l:cmd)
call setqflist([], 'r', {'title': 'RG Search: ' . a:query, 'lines': l:lines})
copen
redraw!
endfunction
```
### 總結
「破圖」的關鍵在於 **`redraw!`**。同步執行外部命令後,Vim 有時會忘記自己長什麼樣子,這行指令就像是給螢幕「重新整理」。