哈囉,我是 Maneshwar。我正在打造 git-lrc,一個會在每次提交時執行的微型 AI 程式碼審查器。它是免費的,並且在 GitHub 上以 source-available 形式提供。 給 git-lrc 按星 幫助開發者發現這個專案。歡迎試用並分享你的回饋。


上次,我留下了一份「接下來要做什麼」的清單。

排在最前面的有:

內嵌圖片預覽:透過 Kitty/iTerm 協定或 chafa 在終端機中渲染圖片

分割窗格檔案預覽:在右側顯示選取檔案的內容,讓你不用離開就能偷看

今天,這兩項都上線了。

按下 p。右側會打開預覽面板。再按一次 p。它就關閉了。

就是這樣。從外部看,功能就只有這些。

裡面的實作比較有趣。

預覽面板

現在 peektea 在開啟預覽時會以兩欄方式渲染。

左邊:檔案瀏覽器。右邊:游標所在專案的內容。

它處理四種情況:

  • 文字檔:顯示前 N 行,將 tab 展開,並截斷到面板寬度
  • 圖片:透過 chafa 直接在終端機內渲染
  • 目錄:以與瀏覽器相同的目錄/檔案樣式列出內容
  • 二進位檔:顯示 [binary file] 提示,而不是亂碼

預覽會隨著你的瀏覽而更新。

移動游標,就會載入新的預覽。

進入某個目錄,就會預覽新的選取專案。

Image description

使用 tea.Cmd 非同步載入

預覽不能阻塞 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 顯示圖片預覽

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

模型會儲存 widthheight,並在每次渲染時用它們來計算面板尺寸。

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
}

進入檔名很長的目錄時,左側面板會自動變寬以容納。離開後又會縮回去。

預覽面板則使用剩下的空間。

提示列

還有最後一個小細節:左側面板底部的提示列,會固定在終端機最底部那一列,而不是浮在最後一個專案下方。

面板會計算檔案清單消耗了多少行,並用空白行把差距補滿,讓提示永遠貼在最底端。

這樣看起來更像真正的窗格,而不是文字剛好結束在那裡。

接下來要做什麼

清單越來越短了:

  • 內嵌圖片預覽
  • 分割窗格檔案預覽
  • 輸入即篩選 — Bubble Tea 的 textinput 元件,來自 bubbles,已經在那裡等著了
  • 隱藏檔案切換 — 顯示/隱藏 dotfile

模型持續成長,Update 處理新的按鍵,View 渲染新的狀態。

不過整體模式還是那一套。它依然能擴展。諧音梗也依然源源不絕。

Image description

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


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝9   💬3   ❤️1
301
🥈
alicec
📝1   ❤️2
89
#4
我愛JS
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登