最近的幾個月里,我一直在尋找一種合適的方式來構建底層應用??赡苁?Web、App 應用已經缺乏了一些新鮮感;也可能是受受國際局勢的影響,我開始思考構建底層應用架構的能力。
于是,在我學習了一段時間的 Rust 之后,我便不斷地往系統的底層探索。在那之前,另外一門合適的語言大抵是 Golang,不過我偏向于認為 Golang 是一個 Web 應用或者普通應用的開發語言,而非一個系統編程語言。
這其中的一個話題就是:編輯器以及 IDE。畢竟,討論 Emacs 和 Vim 哪一個更好,已經不能滿足我的需求。程序員的樂趣應該在于如此去寫一個 Emcas,便實現一個 Vim —— 畢竟 Emacs 操作系統太復雜了。
1. 無 UI 式:命令行編輯器
作為過程的第一步,我開始尋找一些合適的編輯器(PS:主要是簡單),以作為我的編輯器和 IDE 生涯的第一步。然而,這并不是一件容易的事,畢竟我先前構建 Client 端的經驗,都是使用現成的 UI 組件,如 WebView 的 Textarea、Android 框架中的 EditText 組件。
于是乎,我模仿 + 復制 Iota 項目的一部分代碼,形成了一份最小可工作的代碼,以了解命令行編輯器如何運作的整體原理:
字符移動
對于終端編程來說,并不存在組件可以使用,所以我們所要做的事情是:在特定的位置顯示特定的字符,如:
-
rustbox.print_char(1,1,RustBoxStyle::empty,Color::White,Color::Black,'A')
這樣一來,它就在屏幕上的 1,1 的位置畫了一個 A,它的前景是白色,背景是黑色。嗯,沒錯,這種體驗就是我大學玩的嵌入式編程。
于是,第一步我們要做的就是讀取文本,然后渲染。這里用的庫是 RustBox,它封裝了 C 語言下的 Termbox。對于一些人來說,更為熟悉的名字可能是 curses,又或者是 GNU 里的 ncurses。在另外的一個 Rust 編寫的編輯器 [amp] 中,使用的是 termion。
快捷鍵識別隨后,我們可以啟動起編輯器,而后做各種事件輪詢,等待用戶的交互,如快捷鍵。同樣的,這個功能也是由底層的 Termbox 提供了支持,我們只需要創建行為與快捷鍵的綁定即可。
狀態欄 + 命令模式。有了上面的基礎之后,這個也不會遇到什么困難。
語法高亮。這里我就被 iota 這個項目坑了,項目的截圖上是有語法高亮的,但是代碼上已經刪了?;厮萘艘幌逻^程,發現這部分的功能刪了,因為原先的設計并不合理。合理的方式應該是使用 syntect 這種現成的方案,它使用了 Sublime Text 的語法定義格式。
理解了原理,快速畫了個瓢之后,我就轉向 UI 式的文件編輯器。
GitHub: https://github.com/rapilab/rinput
2. UI 式:進程分離
隨后,我在 Awesome-rust 項目中,物色到了第二個可以項目:xi-editor。Xi Editor 是 google 員工開源的一款用Rust 語言編寫的文本編輯器。從之前的新聞來看,像是火了一段時間,但是好像已經沒有那么活躍了。
它最主要的特點有:
前后端分離。編輯器分為兩部分,后端和前端。后端(即核心部分)負責保存文件緩沖區,并負責所有潛在的昂貴編輯操作。你可以將等價為前后端分離應用的關系,又或者是 Electron 應用中:Electron 的 Node.js 和 WebView 部分的區別。
JSON RPC。xi-editor 提供了 JSON 形式的 RPC(遠程過程調用)用于前后端之間進行通訊。采取 JSON 的主要原因是減少開發插件的成本,更好的擴大生態。如,我從 UI 上修改編輯器的主題,將通過 RPC 的方式通知后端,并將對應的配置存儲到系統中。并且諸如于 IDEA 的索引模式,它應該也會在后臺運行,而不占用 UI 進程,影響用戶體驗。如此一來,我們所面臨的卡頓問題,會進一步得到緩解。
不限 UI。因為 xi-editor 本身只提供 core 模塊,所以,我們可以看到有各種各樣的 xi-editor 的前端,如原生 macOS 實現、基于 Electron 實現等等。
所以,我嘗試基于這種架構模式,開發了一個基于這種架構模式的系統狀態應用 Stadal。有了這樣的模式,我們就可以分離 UI 進程,提供更好的用戶體驗。
順便一題,在這種模式之后,編輯器的模型都統一由后端管理(PS:這一點與 Web 應用是相似的,笑~)。
Emacs 架構:M-V-C 架構
這樣一類比的情況下,Emcas 的架構就好似一個大單體一樣。畢竟這是 M-V-C 架構(源自《架構之美》:
-
模型。程序所操作數據的底層描述,如文本屬性、緩沖區等等;并與系統進行交互。
-
視圖。面向用戶展示數據的方法,如對于窗口增量顯示更新邏輯等。
-
控制器。負責實現用戶與視圖的交互(如按鍵、鼠標事件等),并對模型進行更新(采用 Lisp 作為支撐)
至于插件部分則是由 Lisp 腳本來實現,至于是插件好還是腳本好就是另外一個問題了。
GitHub :https://github.com/phodal/stadal
IDE:IDEA 的插件化
因為偶然地原因,我分析了一段時間的 Intellij IDEA 社區版 + Android Studio 的源碼之后,我有了一些新的感受 —— 這個系統架構有點復雜,哈哈。當然,也發現了一些相似的模式。
隔離層:獨立二進制
對于工具的制造者來說,開發者并不希望工具被捆綁在某一個開發工具上。因此,對于開發者而言,優先做的是提供一個可獨立運行的程序,而后再封裝一個針對于該工具的實現。
于是乎,這個運行的程序,它可能是:
-
C/C++ 編寫的二進制應用。通過 daemon 的方式來運行,并能通過解析輸出來進行錯誤處理。
-
Gradle 開發的插件。并借助于 Gradle Tooling API 來實現插件的調用。
-
JAVA 編寫的應用。通過直接集成的方式進行。
-
……
這樣一來,我們就在 IDE 中集成了這樣的能力,并引入到我們的系統中使用。
語言擴展
IDEA 本身的插件體系已經設計得很完善了,如我們可以快速添加一門語言,只需要:
-
注冊文件類型
-
實現 Lexer(詞法分析)
-
實施 Parser(語法分析) 和 PSI(程序結構接口)
-
語法高亮顯示和錯誤高亮顯示
-
代碼補全
-
查找用法
-
重構:重命名、安全刪除
-
代碼格式化程序
-
……
這樣一來,我們就能快速地具備一個語言 IDE 應該有的能力。比如 IDEA 的 Rust 插件就是這樣一個不錯的示例。
總結
一個好的編輯器/IDE 應該能:
-
滋長的特性:通過插件化支撐
-
可維護性:具備良好的可讀性
-
進程分離
-
速度
討論哪個編輯器/IDE 是一件沒意義的事。
只有自己挖的坑才是好的。
我行我上。