程式設計師的文字編輯器就是他們的城堡。

玩具軟體在星辰對齊、位元屏息時執行任務是一回事,能夠優雅地處理現實世界中各種怪異資料則是另一回事。

我對自己的文字編輯器一直不太滿意。大約十年前我選擇了 Howl;它輕量且使用效率高,但在多個方面表現不佳:

開發已經停滯多年。我維護著自己的分支,但編輯器是用 MoonScript 寫的,我不想深入學習這語言和程式碼庫,只能做些小修小補。

Howl 在整個專案檔案搜尋時會卡頓。雖不至於糟糕,但足以打斷我的工作流程,讓我不想用它。我不喜歡這點:我不習慣使用 LSP,搜尋文字是我理解大型程式碼庫的重要方法。

Howl 是 GUI 編輯器。雖然主要用鍵盤操作,但無法輕易透過 SSH 遠端執行。近年我大量時間登入遠端機器,SFTP 功能有限。

它沒有整合終端機。可以執行外部指令並看輸出,但無法即時互動,大部分 ANSI 控制碼不支援,因此無法顯示顏色。

過去幾年我嘗試過多款替代編輯器:

它們各有優勢,但沒有一款擁有我想要的直覺感受。我用 Helix 最久,但一個月後就失去興趣。它很不錯,但缺少我在意的那種難以言喻的特質。

於是,過去兩年我開始自行開發。以下是開發過程中一些心得,歡迎跳到你感興趣的部分閱讀。

起初,我縮小了開發範圍。以下是我排除的功能:

非我個人使用的功能:沒有切換開關,所有偏好設定都寫死在編輯器裡。

效能:目前用字串作為緩衝區還可以,效能問題再說。

完整支援 Unicode 字元群組。我只用一種語言,也不常用表情符號。只要貨幣符號 £ 佔一欄,我就不在意。

語法高亮多樣性:我主要用的語言不多,先支援這些,其他則用通用分隔符高亮。

剛開始進展緩慢。盯著空白畫面寫基本終端渲染很挫折,常常好幾週沒動它。

這是我第二次寫文字編輯器,我選擇先做一個簡單且可組合的 TUI 框架,簡化事件和狀態邏輯。事後看來,早期努力有點過頭,後來逐漸拆除,改用更直接細緻的方式。

某時,我達到一個關鍵門檻:編輯器功能足以開啟單一文字檔,做簡單編輯並儲存。我開始練習三件事,事後證明對專案持續推進很重要:

我的編輯器取代了 nano。每次要編輯系統檔或快速記筆記,我都強迫自己用它,不管多痛苦。

每遇到缺少功能、錯誤、怪異行為或限制,我都會在 README.md 記錄,無論多小。知道空閒時該修什麼很重要。

只要有問題讓我煩躁,就當場修正。

這些習慣讓我從每月一小時進步到每週數小時。近六個月內,約一萬行程式碼幾乎全寫在這段時間。

游標操作很難!使用文字輸入元件時,很多理所當然的行為你甚至沒意識到。像 ctrl + shift + 左鍵的行為可能是肌肉記憶,但要寫出邏輯讓它們協調運作並不輕鬆。

我建議用更原始的操作實作高階輸入。例如要做逐字刪除,先用逐字游標移動選取範圍,再刪除。實作復原時,要把這三步合成一組,避免復原結果怪異。模態編輯器通常直接暴露原始操作給使用者,讓他們自行串接。

有一項功能讓我一直回到 Howl,那就是它的檔案瀏覽器,表現驚人。

Howl 的檔案瀏覽器外觀普通,但使用起來很愉快。它有即時更新的模糊過濾器,且過濾效果很好;通常第一兩個字就能找到目標檔案,第四五個字幾乎必定找到。若檔案不存在,能直接在同一介面建立。輸入 ~/ 會自動切換到家目錄,不管你目前在哪個路徑。主編輯視窗還會預覽即將開啟的檔案。

Howl 的檔案瀏覽器做對了很多事,讓我很困惑為何其他編輯器在開檔問題上表現如此平庸。很多編輯器要我用滑鼠、跳出 GTK 預設開啟對話框,或要我猜檔名而非展示可選項。

重寫這功能並加上個人化特色並不複雜。檔案過濾器我曾考慮用 Levenshtein 距離,但後來發現只要三件事就足夠:

條目是否以過濾字串開頭

條目是否包含過濾字串

條目最近修改/存取時間

依此排序,允許不分大小寫比對,但大小寫相符條目排名略高。

就這樣!即使在數萬檔案的專案中,這三條件也能讓我在兩個字元輸入後,95% 情況下目標檔案排名前兩名。

正則表達式支援用於多種功能:

這三者都需高效實作,前兩者尤其重要。我曾考慮用現成套件如 regex-automata,但我需要支援 Rust 風格原始字串等語境敏感邊界,普通正則做不到。

此外,整個專案是建立並理解自有技術棧的練習,我決定自己實作正則引擎,並擴充支援語境敏感和任意巢狀模式。

起初嘗試很慢。我用 chumsky 寫解析器,對輸入每字元遍歷 AST 找匹配。

後來做了多項優化:

單次優化器,將常見模式合併,如多字元匹配合併成字串節點,避免間接。

尋找所有匹配的共同前綴,減少匹配範圍,對專案搜尋大幅提升。

將 AST 遍歷改寫成簡單的執行緒代碼虛擬機(VM),用 Rust 動態呼叫實作。

將 VM 改為 CPS 形式,讓編譯器優化成尾呼叫。

包裝 Rust 慢速動態呼叫,避免 vtable 查找,提升指令碼效能。

盡量用位元組而非 Unicode 碼點實作指令,UTF-8 設計讓 ASCII 字串操作技巧仍適用。

我曾嘗試用跳轉查找表(LUT)編譯正則,但效能提升有限且複雜度高,最後放棄。

這些優化讓我能在不到 10 毫秒內完成最大 Rust 檔案(5 萬行自動生成綁定)的完整高亮,快過螢幕刷新速度。

我最初每次變更都重新高亮整個檔案,效能隨檔案變大而下降。

後來寫了快取,按需求分塊高亮。當緩衝區受損,重置受影響及後續區塊。

實際上這方法很快。最糟情況是編輯大檔中間,仍保留前面高亮狀態,且不會高亮螢幕下方未顯示部分,因為沒請求。多視窗編輯同一檔案不同區域也適用。

專案搜尋流程很簡單:

從目前目錄往上找 .git/ 判斷專案根目錄

遞迴遍歷根目錄所有資料夾,正則比對檔案內容

擷取匹配片段並語法高亮,供結果預覽

依距離排序結果,附近檔案較相關

有基本過濾規則避免遍歷建置目錄等。

有趣的是這過程多執行緒,工作分配用簡單搶工作法。判斷所有執行緒完成時,等待執行緒會進入臨界區增加原子計數器並短暫睡眠。當計數器等於執行緒數且工作佇列空,代表所有工作完成。

實務中,正則優化加上現代 SSD 速度,讓大型程式碼庫的簡單模式搜尋幾乎瞬間完成。火焰圖顯示我多半受限於 IO,但相信還有更聰明的檔案存取批次方法。

這對生產力提升極大:能在編輯器內以思考速度搜尋大型程式碼庫,隨時解答問題,是我以前用過的編輯器沒體驗過的感覺。

我的編輯器是分割窗格式,支援多個緩衝區並排開啟。很快發現內建終端窗格非常方便,勝過依賴終端模擬器,避免管理兩套快捷鍵。

我曾想手寫 ANSI 解析器,但支援新終端功能如 OSC52、Kitty 鍵盤擴充等太複雜且無趣,改用 alacritty_terminal 套件,該套件實作 Alacritty 終端核心解析與狀態管理。這讓功能實作變得簡單,雖然沒太多話說,因為用第三方庫。

現在我的編輯器可完全取代 screen / tmux 核心功能,且支援更豐富的逃脫序列,這點很棒。

我的編輯器是 TUI,但用終端不代表自動快!我仍在意遠端行動連線頻寬,瀏覽大檔案仍有挑戰。

為減少頻寬,編輯器內部有雙緩衝終端畫面。重繪時比對新舊畫面,只輸出受損儲存格的 ANSI 序列,且只在必要時輸出游標移動、樣式變更等序列。

結果是,在大多數終端模擬器(Ghostty 除外)中,開啟我的編輯器在終端窗格中,cat 大檔案再關閉,比直接在主機終端 cat 檔案還快,因為編輯器減輕了主機終端的輸出負擔(感謝 alacritty_terminal!)。

一般認知(或常識)認為自製編輯器/工具是徒增痛苦的行為。但我試過後堅決不同意:對有動力的工程師來說,有很多優點:

量身打造:編輯器完全符合我的需求,剛剛好。

學到很多:建編輯器過程讓我深入理解多項實用技術,如正則、ANSI、偽終端(pty)、TUI 設計、UTF-8 細節等。

長期生產力提升:熟悉自家工具並為個人工作流程設計功能,減少與工具抗爭時間,更多享受編程樂趣。軟體開發從「文書工作」轉向「思考工作」。

非常有趣:解決許多獨立問題,感受成果在指尖流動,重新點燃我近月來難以維持的編程熱情,這熱情也影響開源、工作與生活。寫程式時會不自覺傻笑,這是多年未有的感覺。希望這是好兆頭,結果還待觀察。

所以:去做你自己的工具吧!不一定是文字編輯器。並且,請享受挑戰,別急著把難題丟給統計箱。掙扎中有樂趣。