🗒️

Rust製テキストエディタhelixの特徴まとめ

2021/06/08に公開

https://zenn.dev/hasu_83/articles/6db226b1abc26567ad91

で紹介されていたRustテキストエディタhelixだが、丁度自分もデザインやコードを眺めていたので、調べている中で得た特徴や知見をまとめていく。

キーバインディングは主にkakouneを踏襲

KakouneはC++で書かれたテキストエディタで、vi系列の「モード切り替え」と「少ないキーストローク数」を維持しつつも、viよりも学習しやすいキーバインディングや機能を目指して開発されている。

例を上げると、viにおいては「カーソル位置からその単語の終わりまでの単語を削除」するストロークはdwなのに対し、Kakouneではwdとなる。
どういうことかというと、wには「単語の終わりまでを選択」というバインディングがなされている。また、選択された単語はUI上でもハイライトによってそれを確認できる状態になる。
で、その選択した範囲を dで削除するというストロークが wd

この他にも、「カーソル上の単語全てを選択」や「行の終わりまでの選択」、「{}で囲われた内部を選択」などのコマンドが存在し、それに対し c(編集)やy(ヤンク)を適用していく。といった感じ。
Kakouneのキーバインディング一覧

結果、キータイプの量は増加するものの、各コマンドのルールにviのそれよりも一貫性を持たせることが出来ている。
自分は既にviキーバインディングに慣れすぎてしまったので、未だkakouneで快適な編集をするまでには至っていないが、モード切り替えがあるエディタを初めて触る人には、より親しみやすいのかもしれない。

で、helixの話をすると、公式サイトを見ると

Editing is focused on being easy to reason with, even if it takes a keystroke or two more than Vim or Kakoune.

とあり、Kakouneからさらにキーストローク数を増やしてでも、推し量ることの容易さを重視する方針のようだった。
そこら辺の思考実験的なものもwikiにあった。

テキストバッファにはRopeを採用

テキストエディタというアプリケーションには実は独特の要件として

  • ある程度の長さの文字列の任意の位置を編集/置換したい
  • コピー&ペーストを行いたい
  • Undo&Redoはメモリが許す限り行えるようにしたい

などと言ったものがあり、これらは意外と他のアプリケーションでは目にすることが少ない要件だったりする。
これらから、テキストエディタに適した構造も独特であることが知られており、Gap BufferやPiece Table、Ropeなどといった構造が考案されている。
vimを使っていて、1行の長さが巨大なファイルを開いた時に動作が遅くなった経験がある人がいるかと思うが、これはvimはこのような構造を採用しておらず、行毎のbyte列の形でファイルを保持していることからくる現象。

各構造の解説についてはこの記事が詳しい。

https://www.averylaird.com/programming/the text editor/2017/09/30/the-piece-table/

さて、helixではその中でもRopeと呼ばれるツリーベースの構造を採用している。
string(糸)の強化版なのでrope(紐)らしい。
Ropeの実装としてはRustで書かれたRopeyを用いていた。

helixのarcitecture.mdではRopeを採用するメリットとして、その複製のコストの低さからUndo/Redoに用いるスナップショットの取得が容易であることなどが挙げられていた。

また、discontinuedになってしまったプロジェクトだが、同じくRopeを採用していたxi-editorというRust製の野心的なテキストエディタがあり、そのorganizerの回顧録の中でもそのメリットについて語られた項がある。

https://raphlinus.github.io/xi/2020/06/27/xi-retrospective.html

Rustには参照カウンタを表わすRcなどのスマートポインターが備わっており、不要になったRopeの葉を即座に安全に開放できることなどが挙げられていた。

制約充足問題を用いてモーダルを実装している

ここ、自分は他のモーダルエディタの実装読んだことが無いので、もしかするとそれらも同じようなアプローチを取ってるかも?な部分。

モーダルを表示するためには、ポジションやサイズを決定する必要がある。
helixではcassowaryと呼ばれる制約充足問題のアルゴリズムを用いてこれを決定しているようだった。
各UIのサイズなどへ強さのレベル付きで制約を定義することで、最終的にどんなサイズであればモーダルを表示できるかが決定する。みたいなイメージ。

e.g.) https://github.com/helix-editor/helix/blob/c67e31830dda2858d9e4728387920b13eb5a01e4/helix-tui/src/layout.rs#L117

tree-sitterによるsyntax highlighting

これは、まあ、helix-syntax内にtree-sitterの言語毎のparserがsubmoduleとして投入されているって感じだった。

https://github.com/helix-editor/helix/tree/0201ef920516db0fd4a4e6939f87bd0d7e4cf5dd/helix-syntax/languages

Language Server Protocolサポート

まだあまり読んでいない。
helix-lsp crateからエディタのコアをどう触りにいくかとかは今後調べると思うので調べたら書く。

Discussion