コードブロック先頭でのEnterキー操作を修正
コードブロックの先頭にカーソルがある状態でEnterを押したとき、ブロックの前に段落が挿入されるように修正されました。これにより、コードブロックの上に新しい内容を追加する操作が直感的に行えるようになります。
背景
Lexxyのコードブロック(EarlyEscapeCodeNode)は、末尾の空行でEnterを押したときにコードブロックを「脱出」してその後ろに段落を挿入する機能を持っていました。しかし、ブロックの先頭でEnterを押した場合の挙動は考慮されておらず、super.insertNewAfter()が呼ばれてコード内で改行が挿入されてしまうという問題がありました。
エディタのUXとして、ブロック要素の先頭でEnterを押す操作は「その要素の前に新しいブロックを追加したい」という意図と解釈されるのが一般的です。ドキュメントの冒頭にコードブロックが置かれた場合など、コードの前にテキストを書き足したいときにこの問題が顕在化します。
技術的な変更
EarlyEscapeCodeNodeの insertNewAfter メソッドに、カーソルがノード先頭にある場合の分岐が追加されました。
変更前:
insertNewAfter(selection, restoreSelection) {
if (!selection.isCollapsed()) return super.insertNewAfter(selection, restoreSelection)
if (this.#isCursorOnEmptyLastLine(selection)) {
// ...
}
return super.insertNewAfter(selection, restoreSelection)
}
変更後:
insertNewAfter(selection, restoreSelection) {
if (!selection.isCollapsed()) return super.insertNewAfter(selection, restoreSelection)
if (this.#isCursorAtStart(selection)) {
this.insertBefore($createParagraphNode())
return null
}
if (this.#isCursorOnEmptyLastLine(selection)) {
// ...
}
return super.insertNewAfter(selection, restoreSelection)
}
#isCursorAtStart(selection) {
const { anchor } = selection
if (!$isAtNodeStart(anchor)) return false
const anchorNode = anchor.getNode()
return this.is(anchorNode) || this.getFirstChild()?.is(anchorNode)
}
先頭判定には既存のヘルパー $isAtNodeStart が活用されています。さらに、アンカーノードが EarlyEscapeCodeNode 自身か、その最初の子ノードかを確認することで、コードブロック内の構造(コードノードが子要素として存在するケース)にも対応しています。先頭と判定された場合は this.insertBefore($createParagraphNode()) でブロックの前に空の段落を挿入し、null を返して super.insertNewAfter の呼び出しを防ぎます。
合わせて、ブラウザテスト test/browser/tests/formatting/code_block_navigation.test.js が新たに追加されました。テストでは、コードブロック先頭でEnterを押したあとにArrowUpで移動してテキストを入力し、段落とコードブロックの両方が期待通りの内容を保持しているかをPlaywrightで検証しています。
設計判断
insertBefore + return null という組み合わせが採用されています。insertNewAfter の戻り値はLexical内部で「挿入後のカーソル移動先ノード」として利用されますが、先頭へのブロック挿入ではカーソルをコードブロック内にとどめる必要があるため、null を返して追加のカーソル操作を抑制しています。
また、#isCursorAtStart の判定を独立したプライベートメソッドとして分離した点も注目に値します。末尾判定の #isCursorOnEmptyLastLine と同じパターンに揃えることで、insertNewAfter 本体はポリシーの列挙に集中した読みやすい構造を維持しています。
まとめ
本PRは「コードブロック末尾の脱出」と対称的な「コードブロック先頭への挿入」を実装し、Enterキーの挙動をコードブロックの両端で一貫させた変更です。既存の $isAtNodeStart ヘルパーと統一されたプライベートメソッドの設計パターンを活用することで、コードの変更量を最小限に抑えながらUXの穴を塞いでいます。