NHN Cloud NHN Cloud Meetup!

新しいマークダウンパーサーが必要な理由

最近の開発者が最も好むドキュメント形式を挙げるとしたら断然マークダウンになるでしょう。マークダウンは、GitHub、GitLab、Bitbucketなど、タスクやイシュー管理に対応するほとんどのサービスにおいて、基本のドキュメント形式として使用されています。また、IntelliJ、VSCode、Vim、Emacsなど、ほぼすべてのテキスト編集ツールでも、プラグインを通じてマークダウン文書の強調構文やプレビュー機能を使用することができます。

TOAST UI Editorはここからさらに一歩進んで、マークダウンエディターとウィジウィグエディターを統合した形式のインターフェースを提供しています。ウィジウィグエディターを使用すると、テーブルなどの複雑な文法をより直感的に簡単に編集することができ、マークダウンに慣れていないユーザーでもマークダウン基盤の文書を簡単に編集できます。特に、開発者と非開発者間の協業において有効です。このような長所からTOAST UI Editorのユーザーはここ数年着実に増加しており、先月はGitHubスター10,000個という記録を達成しました。

TOAST UI Editorはマークダウンエディターというアイデンティティを維持しながら、より良いユーザー体験を提供するために引き続き努力して開発しています。最近リリースしたバージョン2.0では、従来使用していたマークダウンパーサーのmarkdown-itを除去し、commonmark.jsを基盤とした新しいマークダウンパーサーを実装しました。これは既存のバージョンが持っていた多くの問題点を解決するだけでなく、今後のさらなる発展の礎となる重要な変化です。

この記事では、TOAST UI Editorをはじめとする既存のマークダウンエディターが持つ問題と限界を辿り、2.0で新しく実装したマークダウンパーサーがこのような問題をどのように解決できるかについて考察します。またこの新しいパーサーを活用して、マークダウン編集の使用性をどれほど発展させることができるかについて、一緒にみていきたいと思います。

マークダウンエディターの機能と問題点

TOAST UI Editorは、マークダウンエディターとウィジウィグエディターの両方に対応していますが、マークダウンエディターだけでも、他のエディターに劣らないさまざまな機能を提供しています。なかでも構文の強調表示、リアルタイムプレビュー、ツールバーボタンなどは、マークダウンエディターなら必須の機能といえます。しかし、TOAST UI Editorだけでなく他のエディターにおいても、技術的な問題からこれらの機能を完全に実装できてはいません。このチャプターでは各機能の説明とともに、問題の原因についても詳しく調べてみたいと思います。

構文の強調表示(syntax highlighting)

マークダウン文書を編集するときに、まず必要なのは構文の強調表示機能です。開発者なら当然知っていることですが、構文の強調表示は、マークダウン文書を分析して意味のある文法が適用されたテキストを視覚的に容易に区別できるように、スタイルを適用するものです。構文の強調表示をサポートしているエディターでは、実際にレンダリングされた画面を見なくても、現在作成している文書にマークダウン文法が正しく適用されているか確認できます。一方で、GitHubのイシュー登録フォームのように強調表示をサポートしていない環境では、文章を作成しながらプレビューで頻繁に内容を確認しなければなりません。

しかし、マークダウンの強調表示を完全にサポートしているエディターはほとんどありません。エディターごとに強調表示に使用するモジュールの使用方法が異なるため、既存のマークダウンパーサーを使用できないためです。つまり、エディターごとにそれぞれ異なる方法で解析する必要があるため、実際のレンダリングに使用するパーサーと完全に同じ結果を出力させるのは簡単ではないのです。

たとえば、TOAST UI EditorはHTML変換のためにmarkdown-itを使用しますが、強調表示にはCodeMirrorGFMモードを使用しています。CodeMirrorのモードシステムは、解析のための言語別トークナイザー(toeknizer)を実装して使用しますが、完全なパーサーではないためmarkdown-itが分析した結果ほど正確ではありません。したがって下図のように強調表示とプレビューが異なって表示されることがよくあります。

VSCodeマークダウンプラグインも同様に、HTML変換のためにmarkdown-itを使用していますが、解析のためには他のエディターで多く使用されているTextMate文法形式を使用しています。下図を見ると、CodeMirrorとはまた違った問題が発生していることがわかります。

強調表示におけるもう1つの問題はパフォーマンスです。エディターでテキストを変更するたびに、毎回文書全体を分析して強調表示を更新する必要があるため、文書のサイズが大きいほど編集速度が遅くなります。CodeMirrorのモードシステムは、このような現象を防止するため、内部的に最適化を行っているので大きな問題はありませんが、VSCodeのようにTextMate文法形式を使用しているエディターでは、大容量のマークダウン文書を編集すると強調表示のために速度が著しく遅くなるでしょう。

リアルタイムプレビュー(Preview)

構文の強調表示だけでも大いに役立ちますが、マークダウン文書が変換された結果を完全に予測できるわけではありません。特にイメージやテーブルなどの要素は、目で見てなくては画面に表示される結果を正確に予測するのは難しいでしょう。プレビューは、文章を保存する前に実際にレンダリングされた画面をあらかじめ確認することで、不要な試行錯誤を削減することができます。特にTOAST UI Editorは画面を半分に分割して、エディターとプレビュー画面を同時に表示するため、編集と同時にレンダリングされる結果を確認できます。GitHubでイシューを登録するとき、[プレビュー]タブと編集タブを切り替えながら長文のマークダウン文書を作成したことがある方なら、この機能がどれほど有用か理解できるでしょう。

しかし、リアルタイムプレビューも構文の強調表示と同様に、パフォーマンスの問題から逃れることはできません。エディターのテキストが変更されるたびに、文書全体を変換してHTMLを生成する必要があるため、文書のサイズが大きくなるほど編集速度が遅くなります。また、イメージなどの外部リソースを毎回レンダリングし直すため点滅現象が生じることがあります。特にプラグインなどから文法を拡張してスクリプトを実行する場合は、そのスクリプトが毎回実行されるため、文書の編集を妨げる恐れがあります。

TOAST UI Editorではこれらを解決するため、すでにデバウンス(debounce)技法を用いて一定時間以上の変更がない場合にのみレンダリングを更新していますが、これはプレビューの反応速度を遅らせ使用性も落としています。また、変更と関係のない部分まで毎回レンダリングしたり、スクリプトが再実行される問題は引き続き解決されていません。下記は、TOAST UI Editorでプレビューが遅れて更新される現象と、チャートプラグインのアニメーションが毎回再実行される現象を示しています。

もう1つの解決策としては、従来のDOMツリーと新たにレンダリングされたDOMツリーを比較し、変更された部分のみを更新するような方法があります。しかし、この方法はDOMツリーを比較するための追加演算が必要で、別のパフォーマンスの問題を引き起こす可能性があります。

ツールバー(Toolbar)による文書の構造変更

マークダウンは文書を作成するためのマークアップ言語であるため、プログラミング言語とは異なり、テキストの一定領域を選択してスタイルを変更するなどの作業が多く行われます。そのため、TOAST UI Editorのような一部マークダウンエディターでは、ボールド、イタリック、リストなどのスタイルを変換したり、リンク、イメージ、テーブルなどの要素を入力すると、ボタン形式で簡単に使用できるようにツールバーを提供しています。しかし、マークダウンエディターのツールバーを実装するには、次の2つの機能を満たさなければならないため、かなり難しい機能だといえるでしょう。

まず、エディターのカーソル位置と選択領域の範囲によってボタンの状態を同期する機能が必要です。たとえば、カーソルがリスト内にあるときはリストボタンが有効になり、ボールド処理されたテキストを選択すると「ボールド」ボタンが有効になっているといった具合です。Google DocsやMicrosoftワードなどを使ったことがある方はおなじみの機能ですね。

しかし、特定位置のテキストに実際にどのような文法が適用されたかを調べるのは容易ではありません。このためには最終的に別途の解析作業が必要ですが、マークダウン文法の特性上、単にカーソルが位置している行だけを分析してはいけません。なぜなら、文脈を把握するために周辺の行まですべて分析する必要があるためです。CodeMirrorのモードシステムを使用すると、構文の強調表示のため分析した情報を取得できますが、前述したように強調表示に使われる分析装置は完璧ではないため、取得できる情報も限られています。

次に必要な機能は、ボタンをクリックしたときにカーソルの位置に目的の要素を入力したり、選択した領域に適用されるスタイルを変更する機能です。実際のところ、カーソル位置にイメージやリンクなどの文法を入力するのはあまり難しいことではありません。しかし、複数行にわたるテキスト領域のスタイルを変更するのは、ボタンの状態を同期するときと同様に構文解析が必要なため、かなり難しい作業になります。

たとえば、リスト内の特定の項目にカーソルが位置したとき「順序付きリスト(Ordered List)」ボタンを押すと、対象のリスト全体を順序付きリストに変更するため、カーソル周辺のすべての行を巡回しながら同じリスト内にあるすべての項目を確認し、開始記号(-あるいは*)を探して数字とドットに変更しなければなりません。TOAST UI Editorは、このようなリストを処理するためのモジュールが別途存在しますが、大量のコードを含んでいるにも関わらず下図のようにリストの項目が複数行にまたがっている場合は、適切に処理できていません。

統合開発環境(IDE)から学べること

ここまで読むと、既存のマークダウンエディターの問題が何なのか、ある程度予想できるでしょう。まさしく、マークダウンパーサーのフラグメンテーションです。つまり、構文の強調表示、HTML変換、ツールバーなど、すべての機能で解析が必要にも関わらず、各モジュールが別の構文解析を行うからです。当然ですが1つのパーサーがこのすべてを処理してくれれば、より正確で統一された使用性を保証し、かつ機能の実装ロジックも単純になるでしょう。なぜこのような無駄なことが起きているのでしょうか?

その理由は、ほとんどのマークダウンパーサーが主にマークダウン構文をHTMLに変換するための目的としてのみ、作成されたからです。構文を強調表示したり、文書の構造を変更するには、ソースコードと分析された構文ツリー間のマッピング情報が必要ですが、markdown-itのように現在広く使われているマークダウンパーサーはこのような機能を提供していません。また、全体のマークダウン文書を一度に変換する用途として作られているため、エディターのようにリアルタイムで内容を変更しなければならない状況では、満足できる性能が保証されていません。

では、この問題を解決する方法はないのでしょうか?答えは近くにあります。開発者が使用している統合開発環境(IDE)です。

実際にマークダウンエディターの問題は、開発者が使用するテキストエディターや統合開発環境(IDE)で同じように経験するものです。たとえば、VSCode、SublimeText、Eclipseなどの多くのエディターは、構文解析のために正規表現基盤のTextMate文法形式を使用していますが、前述したように、これは構文の意味を完全に分析できないため、キーワードの色を区別するレベルに留まっています。さらに、オートコンプリート、リファクタリング、ショートカット(Go To Definition)などを処理するためには別途の分析が必要ですが、これも各エディターがサポートする言語とAPIに合わせて別途実装する必要があるため、エディターによって言語別に対応レベルが異なる状況です。

開発環境に関心がある方はご存知だと思いますが、最近このような問題の解決策として注目されている技術が2つあります。それがTree-Sitter言語サーバープロトコル(Language Server Protocol:以下LSP)です。

Tree-Sitter

Tree-Sitterは、正規表現基盤の解析の限界を超えて、実際の文書全体の意味を分析して構文ツリー(Syntax Tree)を作り出すシステムです。Atomエディターの内部モジュールから始まり、今では多くのエディター環境で使用されています。Tree-Sitter基盤のパーサーは、リアルタイム編集が可能なエディター環境で使用することを目的に作られたため、一般的なパーサーとは異なる2つの特徴を持っています。1つは、ソースコードとの1:1マッピング情報を持つ構想構文ツリー(Concrete Syntax Tree)を作り出すこと、もう1つは、漸進的分析(Incremental Parsing)をサポートする点です。

構想構文ツリーは抽象構文ツリー(Abstract Syntax Tree)とは異なり、ソースコードから省略される部分がなく、すべての構文の情報を持っています。Tree-Sitterの構文ツリーはこれに加えて構文ごとに一致するソースコードの開始位置と終了位置を保存しているため、ソースコードの特定の領域が文法的にどのような意味があるか正確に把握できます。AtomエディターブログのTree-Sitter紹介文から、この情報をどのように活用しているかがよくわかりますが、構文の強調表示だけでなく、コードフォールディング(folding)、文法による自動領域選択(Syntax-aware selection)などの機能も完全に改善されていることが確認できるでしょう。

漸進的分析は、文書全体を毎回分析する代わりに、変更された部分のみを分析して構文ツリーを更新する機能です。つまり、既存の構文ツリーを保存し、変更されたテキスト情報を受け取って、必要な部分だけを再分析した後、既存のツリーを一部だけ更新するものです。この機能は、エディターのように継続して変更される文書をリアルタイムで分析する必要があるといった用途に最適化された、最大のメリットといえます。これによって、Tree-Sitterは大容量のソースコードを編集するときにも、パフォーマンスを損なうことなく構文ツリーの情報を更新することができます。

CodeMirrorでも、最近のメジャーアップデートであるCodeMirror 6を進めながらTree-Sitterにインスピレーションを受けた新しいパーサーであるLezerを作成しました。開発者のブログ記事に試行錯誤の過程と結果が詳しくまとめられていますので、興味のある方は、ぜひご覧ください。

言語サーバープロトコル(LSP)

LSPは、IDE環境でオートコンプリート、リファクタリングなどの機能をサポートするために必要な言語サーバーのスペックを定義したプロトコルです。LSPは、従来のIDEのアプローチとは異なり、クライアント(エディター)に独立した使用可能な言語のサーバーを作成し、プロセス間(inter-process)通信を行う方式を使用しています。つまり、プロトコルに合った言語サーバーを実装するだけで、どのようなエディターでも同じIDE機能を使用できるというものです。Microsoftで初めて作成され、Visual Studioのコードなどのエディターで使われ始めたLSPは、現在IntelliJ、Emacs、Vimなどさまざまなエディターで使用されています。

実際にウェブで使われるマークダウンエディターを作成するとき、クライアントに独立した構造やプロセス間通信などの概念が重要なわけではありません。それよりも重要なのは言語サーバーという概念ですが、おそらく最も身近な例としてはTypeScriptのts-serverが挙げられるでしょう。

TypeScriptは、大きくtscとts-serverで構成されています。tscは一度にファイル全体をコンパイルする用途として、主にWebpackなどのツールと連結してバンドリングするときに使用します。一方、ts-serverはエディター内で実行され、テキストが変更されるたびにリアルタイムで解析結果を更新し、必要に応じてオートコンプリート、タイプエラーなどの結果を返します。VSCode、IntelliJなどのエディターでTypeScriptの開発ツールに感嘆した方は、すべてts-serverのサポートを享受しているのです。

(参考までに、ts-serverはまだLSPをサポートしておらず、その代わりにLSPのラッパーライブラリーを使用することができます。)

言語サーバーでもう1つ注目すべきはリファクタリング機能です。TypeScriptを開発する際に「関数名の変更」などのリファクタリング機能を使用したことがあるでしょう。これは言語サーバーが単にソースコードを解析して情報を返すという役割を超えて、直接ソースコードを修正する役割までも果たすという意味です。つまり、内部で構文解析ツリーの構造を変更すると、変更された構造に合わせてソースコードを更新した後、変更された内容が返されます。LSPにはこのためにCodeActionリクエストが定義されていますが、この機能は「ツールバーによる文書の構造変更」で説明した問題を解決する際、良い参考になるでしょう。

実際のところ、言語サーバーはTree-Sitterのジョブをほとんど行うことができ、LSPには構文の強調表示向けのAPIも提供されています。しかし、言語サーバーはTree-Sitterよりもはるかに複雑な機能を実行し、漸進的分析をサポートしにくいため、リアルタイムで強調表示やコードフォールディングなどのタスクを実行するには比較的重くて遅い状態です。つまり、Tree-SitterとLSPは実装方式と用途が異なるため、相互補完的な役割を果たすものと考えられます。

新しいマークダウンパーサー:ToastMark

前述した内容をすべて総合した結果、私たちはTree-Sitterと言語サーバーの利点をすべて備えた新しいマークダウンパーサーが、既存エディターの問題を解決してくれると結論付けました。しかし、新しいマークダウンパーサーを作成するには多大な労力を要するため、すでに作られているオープンソースを改善することにし、数日間の分析と議論を経てcommonmark.jsを最終的に選択することになりました。

commonmark.jsは、CommonMarkスペックのリファレンス実装体であり、CommonMarkスペックを完全に遵守している唯一のJavaScriptライブラリです。リファレンス実装体であるため、コードを分析して拡張しやすく、以降のCommonMarkスペックが変更される際にも変更を比較して適用するのが最も容易であると判断しました。

約1ヵ月間の改善作業の末、ついに新しいマークダウンパーサーのToastMarkが誕生しました。ToastMarkは次の3つの特徴を持ちます。

1.ソースマッピング情報を持つ抽象構文ツリー(AST)の作成

ToastMarkはマークダウン文書の抽象構文ツリー(Abstract Syntax Tree)を作成し、各ノードにマッチングされるソースコードの開始位置と終了位置を保存します。保存されたソースコードの位置情報は、構文の強調表示、ツールバーのボタンの同期、リアルタイムプレビューのスクロール同期など、さまざまな用途に使用されます。Tree-Sitterとは異なる抽象構文ツリーを選択した理由は、このパーサーが強調表示のほか、さまざまな用途に使用されるからです。強調表示には構想構文ツリーが有利ですが、文書の特定の要素を検索したり、文書構造を変更するときには、抽象構文ツリーがはるかに有利となります。また、マークダウンは各要素を構成するトークンの形式が単純で、抽象構文ツリーにいくつかの情報を追加するだけでも強調表示を問題なく実現することができます。

commonmark.jsはすでに抽象構文ツリー情報を返却するAPIがあり、ブロック要素のソースコードの位置情報もすでにあるため、インライン要素のソースコードの位置情報を追加するだけで難なく実装できました。

2.文書変更に伴う漸進的分析

ToastMarkは、Tree-Sitterのような段階的分析をサポートします。つまり、以前に分析した構文ツリーを保存して変更された内容によって構文ツリーを一部だけ変更します。エディターで変更事項が生じたら、ToastMarkに変更したソースコードの位置とテキスト情報を伝達し、ToastMarkは構文ツリーを一部更新して変更されたノードの情報を返します。使い方を簡単にみると次のようになります。

const toastMark = new ToastMark('# Hello World');

// 最初の文字の#を-に変更
const result = toastMark.editMarkdown([1, 1], [1, 2], '-');
const { removedNodeRange, newNodes } = result;

// マークダウンエディター構文強調更新
refreshSyntaxHighlighting(newNodes);

// プレビューDOM更新
refreshPreview(removedNodeRange, newNodes);

変更されたノードの情報は強調表示のスタイルとリアルタイムプレビューのDOMノードを更新するために使用されます。削除されたノードのIDと追加されたノードの配列を返してくれるので、強調表示とリアルタイムプレビューでも正確に変更された部分のみを更新することができます。これによって、大容量のマークダウン文書を編集する際にもパフォーマンスを損なうことなく、強調表示とプレビューをリアルタイムに更新して使用性を大幅に向上させることができます。

3.構文ツリーナビゲーションと変更

ツールバーのボタンの状態を同期するには、カーソルの位置が変更されるたびに、その場所のソースコードがどのマークダウンの要素とマッチするかを知る必要があります。そのためには、文書が変更されなくても位置に応じたノードの情報を特定できなければなりません。また、プレビューのDOMとマッチするノードの情報を取得するには、各ノードにIDを付与し、そのIDによってノードを取得しなければなりません。これらの用途のため、ToastMarkは次のようなメソッドを提供しています。

// 3行目の1番目のノードを返す
toastMark.findFirstNodeAtLine(3); 

// 2行目の5番目にマッチするノードを返す
toastMark.findNodeAtPosition([2,  5]);

// IDが10のノードを返す
toastMark.findNodeById(10);

ツールバーのボタンをクリックしたときは、文書の構造を変更する必要があります。たとえば、段落をリストに変更したり、順不同リストを順序付きリストに変更するなどの作業です。これには構文ツリーを変更した後、実際のマークダウンのソースコードを生成して返す機能まで含んでいます。現在は初期バージョンのためまだ実装されていませんが、続くアップデートではこの機能のためのAPIも追加される予定です。

TOAST UI Editor 2.0で改善された点

TOAST UI Editor 2.0は、マークダウンパーサーを安定的に変更し、モジュールおよびバンドルの構造を変更することに焦点を合わせたため、特別に追加された機能はありません。しかし、パーサーをToastMarkに変更しただけでも、従来のマークダウンエディター機能の精度と安定性が大幅に向上されました。

構文の強調表示の精度向上

既存のCodeMirrorのマークダウンモードを除去し、CodeMirror APIを使用して特定の構文に直接スタイルを追加する方式に変更しました。プレビューと構文の強調表示の両方ともToastMarkの構文ツリーに依存しているので、下図のように2つの結果が完全に同一であることがわかるでしょう。

リアルタイムプレビュー機能とスクロール位置の同期を改善

前述したように、従来のリアルタイムプレビューは、文書全体を解析して全体のDOMを更新するのにかかるコストのため、入力が一定時間以上停止したときのみ更新され、その都度チャートなどの拡張プラグインが毎回新たに実行される問題もありました。しかし、バージョン2.0からはToastMarkの漸進的分析機能を使って変更された内容のみを分析して更新するため、不要な遅延やプラグインの再実行がなく、プレビューを文字通り「リアルタイム」で確認できます。

さらに、マークダウンエディターとリアルタイムプレビューのスクロール位置を同期する機能も、ToastMarkの正確なマッピング情報を用いて、より精密に動作されるように改善しました。

カーソルの位置によるボタンの状態同期を改善

ツールバーのボタンの状態同期の問題は、以前から改善が要求されていた問題です。従来でも、ボールド、イタリック、ストライク(取り消し線)など一部の要素には動作していましたが、リストやテーブルなどの要素については正しく動作していませんでした。CodeMirrorのマークダウンモードでトークナイザーが分析した情報を使うことはできましたが、その情報自体が完璧ではなかったため、正確な同期には追加の分析が必要だったからです。

しかし、ToastMarkはカーソルの位置に応じた正確なマークダウンノード情報を取得することができ、若干のコードだけで機能が大幅に改善されました。

2.Xで改善される点

ほかにもToastMarkを使って改善できる機能はたくさんあります。実際のところ、従来のバージョンでも実装できないわけではありませんが、別途の構文解析作業が必要だという理由から保留されていた機能です。現在は正確な解析情報を思う存分利用できるので、さまざまな機能を難なく実現できるようになりました。そのため、2.0以降のマイナーアップデートでは、ToastMarkでできるさまざまなトライアルを行いながら、マークダウンエディターとプレビューの使用性を改善することに集中する予定です。

まず最初にツールバーのボタンの使用性をさらに改善することです。現在はカーソル位置とボタンの状態を同期するレベルの改善に留まっていますが、より良い使用性のためには選択範囲によってボタンの状態を同期させたり、実行不可能なボタンの状態を無効にするなどの改善がさらに必要です。また、実際のボタンをクリックしたときも、カーソルだけでなく選択範囲の状態を考慮して動作するように改善できます。また、従来に加えて構文を解析していたロジックを除去し、コードを簡素化することも可能です。

このほかにも、セクション別のコードフォールディング、コードフォーマット、テーブルの列/行の追加、プレビューの変更された領域を強調するなど、さまざまな機能を追加できます。このような機能はまだ可能性を含めたものですが、今後使用性や優先順位を考慮して着実に追加する予定です。

ToastMarkの未来

ToastMarkは、今まさに第一歩を踏み出したばかりで、まだ改善の余地が多く残っています。また、TOAST UI Editor内部でのみ使用されるライブラリのため、別途のnpmパッケージには登録しておらず、APIもエディターの要件によって引き続き変更される予定です。つまり、まだ外部で使用する用途には適していないという意味です。

しかし、TOAST UI Editorの発展とともにToastMarkも一緒に発展していくものなので、十分な安定性を確保した上で、今後外部に正式に配布、公開する計画です。GitHubストアにToastMarkの特徴と一部APIをまとめましたので、興味のある方はぜひご覧ください。

TOAST UI Editorの未来

ここまで、新しいマークダウンパーサーであるToastMarkを作ることになった理由と、それを使用し改善した点を紹介しました。ToastMarkはマークダウンエディターの使用性の改善に大きく貢献し、今後もさまざまな改善を導く可能性を持っています。しかし、ToastMarkの役割はここで終わることはありません。ToastMarkはマークダウンエディターとウィジウィグエディターを真に統合する道を開く重要な出発点でもあります。

現在のマークダウンエディターの構造では、CodeMirrorとToastMarkがそれぞれテキスト情報を管理し、変更情報を互いに同期しながら相互作用しています。ここからさらに一歩進めて、ToastMarkでのみテキスト情報を管理し、エディターでは単にカーソルの動作とイベント処理のみを担当させることもできます。まるでReduxとReactの関係のように、ToastMarkがステータスマネージャになり、エディターは単にビューの役割をするようになるのです。そうすればCodeMirrorの依存性がなくなり、より軽く統一された構造のマークダウンエディターを作ることができるでしょう。

また、ここからさらに一歩進むと、ウィジウィグエディターにも同じ構造を適用できます。つまり、ToastMarkが1つのステータスマネージャになり、マークダウンエディターとウィジウィグエディターの両方ともビューの役割だけを担当することになります。これが可能なら、現在のウィジウィグエディターの実装で使用しているSquireの依存性も除去することができ、結果的により軽く統一性のあるウィジウィグエディターを作成できます。

たとえば、既存の構造では、ウィジウィグエディターがHTML(DOM)を編集し、マークダウンエディターはマークダウンテキストを編集するためにエディターの切り替えが起きるたび、全体のデータをマークダウン→HTML、HTML→マークダウンに変換する作業が必要でした。この過程で、既存のデータが意図せず変更される問題が頻繁に発生していました。しかし、新しい構造では両方のエディターがToastMarkが管理するASTにのみ依存するため、不要なデータ変換処理による問題がすべてなくなり、エディター間の切り替えもより円滑になりました。

もちろん、現在の段階では考慮すべき技術的な難関がたくさん残っているので、実際に適用するまでに多くの研究とプロトタイピングが必要です。さらに詳しい内容は今後、ほかの記事で紹介したいと思います。今は遠い未来ではなく、新たにリリースされたバージョン2.0に多くの関心を持っていただきたいですし、今後もアップデートを通じて着実に改善されていくマークダウンエディターの使用性を見守っていただきたいと思います。

参考リンク

TextMate文法

Language Server Protocol

Tree-Sitter

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop