コードブロックの末尾行が空白文字のみのときにEscapeできないバグを修正

basecamp/lexxy

コードブロックの末尾行にスペースやタブのみが入力されている場合、Enterキーで抜け出せなくなるバグが修正されました。2回のEnterで脱出するという既存の動作と一貫した方式で、空白のみの行を段階的に処理します。

背景

EarlyEscapeCodeNode には「最後の行が空行ならEnterでコードブロックを脱出する」仕組みが元々備わっていました。しかし、最後の行がスペースやタブのみで構成されている場合、この脱出機能が動作しないという問題がありました。

原因は #isCursorOnEmptyLastLine の実装にあります。このメソッドは textContent.endsWith("\n") で末尾が改行かどうかを判定していましたが、最後の行に空白文字が含まれる場合、textContent の末尾は \n ではなくスペースになります。このため、「末尾が空行」という判定が成立せず、脱出パスに到達できませんでした。

技術的な変更

insertNewAfter のディスパッチロジックと、空白のみの末尾行を検出・処理するメソッド群が追加されました。

変更前:

if (this.#isCursorAtStart(selection)) {
  this.insertBefore($createParagraphNode())
  return null
}

if (this.#isCursorOnEmptyLastLine(selection)) {
  $trimTrailingBlankNodes(this)

  const paragraph = $createParagraphNode()
  this.insertAfter(paragraph)
  return paragraph
}

return super.insertNewAfter(selection, restoreSelection)

変更後:

if (this.#isCursorAtStart(selection)) {
  return this.#insertParagraphBefore()
} else if (this.#isCursorOnWhitespaceOnlyLastLine(selection)) {
  return this.#insertBlankLineBelow(selection, restoreSelection)
} else if (this.#isCursorOnEmptyLastLine(selection)) {
  return this.#escapeToNewParagraphAfter()
} else {
  return super.insertNewAfter(selection, restoreSelection)
}

新たに追加された #isCursorOnWhitespaceOnlyLastLine は、textContent から最後の \n 以降の文字列を取り出し、長さが0より大きく trim() 後に空文字になるかどうかで判定します。

#isCursorOnWhitespaceOnlyLastLine(selection) {
  if (!$isCursorOnLastLine(selection)) return false

  const textContent = this.getTextContent()
  const lastNewlineIndex = textContent.lastIndexOf("\n")
  const lastLine = lastNewlineIndex === -1 ? textContent : textContent.slice(lastNewlineIndex + 1)
  return lastLine.length > 0 && lastLine.trim() === ""
}

空白のみの末尾行でEnterが押された場合、#insertBlankLineBelow が呼び出されます。これは super.insertNewAfter で新しい行を追加したあと、直後に getLastChild().remove() でその行を削除します。結果としてカーソルは空の末尾行に移動し、次のEnterで通常の #isCursorOnEmptyLastLine による脱出が発動します。

#insertBlankLineBelow(selection, restoreSelection) {
  super.insertNewAfter(selection, restoreSelection)
  this.getLastChild().remove()
  return null
}

この2段階処理により、空白のみの行を1回目のEnterでクリアし、2回目のEnterでコードブロックを脱出するという動作になります。これは内容のある行から脱出する際の既存の2回Enter方式と一貫しています。

設計判断

「空白行をクリアしてから既存の脱出フローに乗せる」 という2段階の設計が採用されました。

#isCursorOnEmptyLastLine の判定ロジックを変更して空白のみの行も「空行」として扱う方法もありえましたが、この変更では判定ロジックを拡張せず、空白のみの行を専用のハンドラで処理することで、既存の判定の意味を変えずに済んでいます。また、insertNewAftergetLastChild().remove() の組み合わせで「子ノードを削除しつつカーソル位置だけを更新する」副作用を意図的に利用しているのが特徴的です。

if/else if チェーンへのリファクタリングにより、各条件のパスが排他的に明示され、以前の複数の独立したif文よりも制御フローが読みやすくなっています。

まとめ

本修正は、既存の脱出ロジックを書き換えるのではなく、空白のみの末尾行を事前に処理する前段ステップを追加することでバグを解消しています。新しい判定メソッドと既存の脱出フローを組み合わせた最小限の変更で、ユーザーが直感的に期待する「Enterで脱出できる」動作を一貫して実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
d5e54194

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)、背景・技術的な変更・設計判断(各論)、まとめ(結論)という3部構成が明確で、ガイドラインを完全に満たしています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```javascript:ファイルパス)とGitHubのPRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

「ディスパッチロジック」「if/else if チェーン」などの技術用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られていて非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているすべてのコードブロック(変更前、変更後、新規メソッド)は、提供されたDiff情報と正確に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「textContent」「#isCursorOnEmptyLastLine」など、PR Descriptionやコード内で使われている技術用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「2段階処理」や「`super.insertNewAfter`と`getLastChild().remove()`の組み合わせ」に関する説明は、PRの意図とコードの動作を技術的に正しく解説しています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(バグの原因、修正方法、動作の一貫性)は、PRのDescriptionとDiffの内容によって完全に裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#1006)およびそのリンク先URLが正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRのタイトル「Fix code block escape when last line contains only whitespace」の内容を的確に和訳・要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョン情報やサポート状況など、PR情報に記載のない外部知識の追加はなく、事実に基づいた記述に徹しています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現に関する記述はなく、PRの内容を歪曲するような表現は見られません。