withはテキストエディターですが、ファイルに記録された文字の並びをエディターに表示しているわけではありません。フォルダーの構造とそれぞれのテキストファイルにある見出し、そして読み仮名や圏点(﹅)などの行中タグを抽象構文木――AST(Abstract Syntax Tree)にして、そこから抽出したテキストをエディターに表示して、編集しています。
たとえば以下の文です(withのサンプル小説の書き出しです)。
# ガラスの下の母
[#地付き]藤井太洋
## プロローグ
母はスーツケースの|傍《かたわら》に立っていた。1行目はMarkdownの見出しレベル1。文書のタイトルです。次の段落はインラインタグの[#地付き]があり、その後ろが横書きなら右寄せ、縦書きなら下付きになります。見出しレベル2がプロローグ。そして「 母はスーツケースの|傍《かたわら》に立っていた。」はプロローグ下の文書ということになり、傍にルビのタグがマークアップされています。
最終的なプレゼンテーションこうなります。

まだ見出しレベの表現に差をつけていないのでちょっと雰囲気が出ませんが、テキストエディターの画面としては上出来でしょう(ルビを肩付きにしているのも、これが本番の組版ではなく簡易表示であることを表明するためです)。
エディターはmacOS標準のNSTextViewを用いています。Apple Intelligenceの校正ツールなんか出てくる標準のテキストエディットの画面です。その画面に「1行目はゴシックを使ってください。2行目の0文字目から後ろは右寄せ、4行目はゴシック。5行目の10、12、17番目のテキスト(タグの記号)は出力しないで11文字目文字の脇に、小さな13〜16番目の文字を並べます――」みたいな指示をするわけですが、何番目が見出しかどうかを知るには、テキストを検索するしかありません。
行頭に#がある行は大見出し、みたいな処理です。検索は確実ですし、これが一回で済めばいいのですが、何千行、何万文字にもなるテキストを全てスタイリングしたままにしておくのは無駄が多いので、画面に見えそうな範囲を評価して、スタイリングすることになります。文字が変わらなければファイルを開いたときの検索結果をキャッシュしておけばいいのですが、エディターなのでそうもいきません。修正した文字の後ろの「N文字目」は全部ずれてしまいますから、タイピングのたびに「N文字目」を再評価しなければなりません。さらに、タグの表現を一つ増やすたびに、検索回数は増えてしまいます。入れ子になった時の処理も複雑になりますし、評価範囲外で始まるスタイリング(回想や手紙で使う字下げは五画面分にまたがることもありますね)があると、タグの末尾を拾い損ねたりするなど、バグの温床になります。
そこでwithでは、プロジェクト全体を通した構文木を作ることにしました。それがASTです。
上記のテキストは、withの中では以下のような形式でメモリに載っています。これはエディター用なので、ちょっと長い。
children:
- block 1
id: d79274de7c6e42cb
proseKind: narrative
sourceFile: Untitled.txt
range: {10, 10}
lines: 2...2
text: ""
semanticHash: e36cf2af4be81ba9
children:
- paragraph
id: e5ddfce12abd9327
proseKind: narrative
sourceFile: Untitled.txt
range: {10, 10}
lines: 2...2
text: "[#地付き]藤井太洋"
semanticHash: cadf1740633e72b7
inlineMarkups:
- aozoraAnnotation
id: cadab84632425619
fullText: "[#地付き]"
contentText: "地付き"
fullRange: {10, 6}
contentRange: {12, 3}
metadata: aozoraCommand("地付き")
markers:
- opening "[#" range: {10, 2}
- annotation "地付き" range: {12, 3}
- closing "]" range: {15, 1}
- paragraph
id: 4f9343474567af45
proseKind: narrative
sourceFile: Untitled.txt
range: {21, 0}
lines: 3...3
text: ""
semanticHash: 7ae39404b7b92d83
- chapter "プロローグ"
id: 480708c7b439de41
proseKind: narrative
sourceFile: Untitled.txt
range: {22, 8}
lines: 4...4
text: "プロローグ"
semanticHash: 52575a141860cfa8
children:
- block 1
id: 04db94014bd6528f
proseKind: narrative
sourceFile: Untitled.txt
range: {31, 24}
lines: 5...5
text: ""
semanticHash: 25d95d8de13545f0
children:
- paragraph
id: 464884ad669ed167
proseKind: narrative
sourceFile: Untitled.txt
range: {31, 24}
lines: 5...5
text: "母はスーツケースの|傍《かたわら》に立っていた。"
semanticHash: c8fdecbdaf6722c2
inlineMarkups:
- ruby
id: d52c5ebbcd0f94a9
fullText: "|傍《かたわら》"
contentText: "傍"
fullRange: {40, 8}
contentRange: {41, 1}
metadata: rubyText("かたわら")
markers:
- prefix "|" range: {40, 1}
- opening "《" range: {42, 1}
- annotation "かたわら" range: {43, 4}
- closing "》" range: {47, 1}
- paragraph
id: 990e861a8ae9c087
proseKind: narrative
sourceFile: Untitled.txt
range: {56, 0}
lines: 6...6
text: ""
semanticHash: 7ae39404b7b92d83ファイルを開いたときにこれを作ると、そのあとは全文検索を何度も何度もやる必要がありません。どこにルビがあるか見出しがあるかわかっているからです。エディターで編集すると、段落単位でこの構造を作って差し替えていく。アウトラインで文章のブロックをごそっと入れ替える機能があるんですが、ASTを使うとあっという間に実装できました。レスポンスを保つために、データを入れ替えてエディターに反映して、それからファイルに書き込んでいくようなことも可能です。
検索も、ちょっと面白いことになりました。まだまだ作りかけなんですが、プロジェクトの一括検索で、ファイルを舐めなくても、どこに検索対象があるかわかる。そして「ファイルにある」だけではなくて、見出しレベルでどこの下にあるかも表示できます。

履歴やスナップショットから復元する時など、差分が必要になることもあります。そこでもASTは大活躍です。これはスナップショットから復元する時の変更部分の表示なのですが、並び順の変更、テキストの変更部分がどの見出し下にあるかを表示することができています。追加と削除だけでなく、ちょっとだけ変わった文がどこからどこに移動したのかを知ることだって可能です。
下の画面は見てわかるように選択的に上書き部分を受け入れる統合用のインターフェイスですが、それほど悩まずに作れました。何せハッシュとidがあるAST同を比較するだけですから、それほど負荷は高くありません。ハッシュを比べるだけで、ファイルを読むことなく判定できるからです。

文章そのものを構文木にすることも考えたんですが、メリットが見えないので流石に手はつけていません。
つらつらと書きましたが、withの中身の話でした。