哈囉,我是 Maneshwar。我正在打造 git-lrc,一個會在每次提交時執行的微型 AI 程式碼審查器。它是免費的,並且在 GitHub 上以 source-available 形式提供。 給 git-lrc 按星 幫助開發者發現這個專案。歡迎試用並分享你的回饋。
上次,我留下了一份「接下來要做什麼」的清單。
排在最前面的有:
內嵌圖片預覽:透過 Kitty/iTerm 協定或
chafa在終端機中渲染圖片分割窗格檔案預覽:在右側顯示選取檔案的內容,讓你不用離開就能偷看
今天,這兩項都上線了。
按下 p。右側會打開預覽面板。再按一次 p。它就關閉了。
就是這樣。從外部看,功能就只有這些。
裡面的實作比較有趣。
現在 peektea 在開啟預覽時會以兩欄方式渲染。
左邊:檔案瀏覽器。右邊:游標所在專案的內容。
它處理四種情況:
[binary file] 提示,而不是亂碼預覽會隨著你的瀏覽而更新。
移動游標,就會載入新的預覽。
進入某個目錄,就會預覽新的選取專案。

預覽不能阻塞 TUI;讀取大型檔案或執行 chafa 都需要時間。
在 Bubble Tea 中,凡是需要時間的工作都會包成 tea.Cmd:一個在主迴圈外執行,完成後回傳訊息的函式。
type previewMsg struct{ content string }
func loadPreview(path string, entry os.DirEntry, width, height int) tea.Cmd {
return func() tea.Msg {
if entry.IsDir() {
return previewMsg{content: previewDir(path, width, height)}
}
if isImageExt(entry.Name()) {
return previewMsg{content: previewImage(path, width, height)}
}
return previewMsg{content: previewText(path, width, height)}
}
}
模型會把 previewLoading = true,然後送出這個命令。
面板會顯示 loading…。
當 goroutine 執行完畢後,previewMsg 會透過 Update 回來,面板就會渲染結果。
整個過程中 TUI 都保持可回應,你甚至可以在慢速預覽載入時繼續瀏覽。
chafa 是一個終端機圖片檢視器,會把圖片轉換成有顏色的 Unicode 方塊。
它支援 --size WxH,所以輸出可以剛好塞進預覽面板。
func previewImage(path string, width, height int) string {
if _, err := exec.LookPath("chafa"); err != nil {
return "[image — install chafa for inline preview]"
}
out, err := exec.Command("chafa",
"--size", fmt.Sprintf("%dx%d", width, height),
path,
).Output()
if err != nil {
return fmt.Sprintf("[image preview failed: %v]", err)
}
return strings.TrimRight(string(out), "\n")
}
如果沒有安裝 chafa,面板會直接告訴你。沒有當機,也不會默默空白。
peektea init 現在會在安裝設定的最後檢查是否有 chafa,若沒有就印出適用於你發行版的安裝指令。
把二進位檔當成文字開啟只會看到雜訊。
所以在讀取之前,peektea 會先檢查前 512 個位元組是否有 null byte,這是 git diff 和大多數編輯器採用的標準啟發式方法。
func isBinary(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 512)
n, _ := f.Read(buf)
for _, b := range buf[:n] {
if b == 0 {
return true
}
}
return false
}
如果是二進位檔,預覽就會顯示 [binary file] 然後繼續。
要正確分割畫面,peektea 必須知道自己的大小。
每當終端機大小改變時,Bubble Tea 都會送出 tea.WindowSizeMsg。
模型會儲存 width 和 height,並在每次渲染時用它們來計算面板尺寸。
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
左側面板的寬度不是固定的,它先從至少 50 個字元開始,然後再擴展到目前目錄中最長檔名所需的寬度:
func (m model) leftWidth() int {
const minWidth = 50
w := minWidth
for _, e := range m.entries {
nameW := 2 + len([]rune(e.Name()))
if e.IsDir() {
nameW++
}
if nameW > w {
w = nameW
}
}
// 永遠至少保留 30 個字元給預覽面板。
if max := m.width - 32; w > max {
w = max
}
return w
}
進入檔名很長的目錄時,左側面板會自動變寬以容納。離開後又會縮回去。
預覽面板則使用剩下的空間。
還有最後一個小細節:左側面板底部的提示列,會固定在終端機最底部那一列,而不是浮在最後一個專案下方。
面板會計算檔案清單消耗了多少行,並用空白行把差距補滿,讓提示永遠貼在最底端。
這樣看起來更像真正的窗格,而不是文字剛好結束在那裡。
清單越來越短了:
textinput 元件,來自 bubbles,已經在那裡等著了模型持續成長,Update 處理新的按鍵,View 渲染新的狀態。
不過整體模式還是那一套。它依然能擴展。諧音梗也依然源源不絕。

AI 智慧代理寫程式很快。它們也會在不告訴你的情況下,默默刪掉邏輯、改變行為,還引入 bug——你通常要到上線後才發現。
git-lrc 就是為了解決這件事。它會掛接到 git commit,在每個 diff 合併前進行審查。60 秒完成設定。完全免費。
歡迎任何回饋或貢獻者!它已上線、source-available,而且可供任何人使用。
⭐ 到 GitHub 幫它按星:
{% github=https://github.com/HexmosTech/git-lrc
原文出處:https://dev.to/lovestaco/peektea-opens-a-second-eye-side-by-side-file-previews-n6j