Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 13199] ファイルの概要

このコミットは、EmacsエディタのGo言語モード(go-mode.el)におけるインデントの挙動を修正するものです。go-mode.elは、Go言語のソースコードをEmacsで編集する際に、シンタックスハイライト、自動インデント、コード補完などの機能を提供するメジャーモードです。このファイルは、Goコードの構造を解析し、適切なインデントを適用するためのEmacs Lispコードを含んでいます。

コミット

Emacs go-mode: 文字列内部のインデントを行わないように修正。

インデントの問題に対する2つの修正:

  1. 複数行文字列を正しく認識する。これらは"ではなく`で始まる。
  2. 行の先頭が複数行文字列の終わりである場合、その行をインデントしない。これは例えば、複数行文字列の後に閉じ括弧を挿入した際に発生していた。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2d1fa08967a6bcb064f7690719ea9638866c7715

元コミット内容

commit 2d1fa08967a6bcb064f7690719ea9638866c7715
Author: Peter Kleiweg <pkleiweg@xs4all.nl>
Date:   Tue May 29 16:30:06 2012 -0400

    Emacs go-mode: don't indent the inside of strings.
    
    Two fixes for indentation problems:
    
    1. Properly recognize multi-line strings. These start with `, not ".
    
    2. Don't indent a line if the beginning of the line is the end of a multi-line string. This happened for instance when inserting a closing bracket after a multi-line string.
    
    R=sameer
    CC=golang-dev
    https://golang.org/cl/6157044

変更の背景

このコミットは、EmacsのGoモード(go-mode)における、特に複数行文字列(Go言語の「raw string literal」)のインデントに関する既存のバグを修正するために行われました。

具体的な問題点は以下の2点です。

  1. 複数行文字列の誤認識: go-modeがGoのraw string literal(バッククォート`で囲まれた文字列)を正しく複数行文字列として認識できていませんでした。その結果、文字列内部のコードが不適切にインデントされたり、文字列の途中でインデントが適用されたりする問題が発生していました。
  2. 文字列終了後のインデント問題: 複数行文字列の直後に閉じ括弧(})など)を挿入した際に、その閉じ括弧を含む行が誤ってインデントされてしまう問題がありました。これは、go-modeが文字列の終了位置を正確に判断できていなかったため、文字列の終わりをコードの一部として誤って解釈し、インデントルールを適用してしまっていたことに起因します。

これらの問題は、Go言語のコードをEmacsで記述する際のユーザーエクスペリエンスを著しく損なうものであり、開発効率に影響を与えるため、修正が必要とされました。

前提知識の解説

Emacs Lisp

Emacs Lispは、Emacsエディタの拡張言語であり、Emacsのほぼ全ての機能がこの言語で実装されています。ユーザーはEmacs Lispを使って、Emacsの挙動をカスタマイズしたり、新しい機能を追加したりすることができます。go-mode.elもEmacs Lispで書かれたファイルです。

Emacsのインデントシステム

Emacsは、各プログラミング言語モードにおいて、その言語の構文規則に基づいた自動インデント機能を提供します。これは通常、ユーザーがEnterキーを押したり、特定のインデントコマンドを実行したりした際に、現在の行やリージョンを適切に整形するものです。インデントのロジックは、現在のカーソル位置の構文コンテキスト(例えば、関数定義内、ブロック内、文字列内、コメント内など)を解析して決定されます。

Go言語の文字列リテラル

Go言語には主に2種類の文字列リテラルがあります。

  1. 解釈済み文字列リテラル (Interpreted String Literal): ダブルクォート"で囲まれた文字列です。バックスラッシュ\によるエスケープシーケンスが解釈されます。複数行にわたることはできません(改行文字を直接含めることはできませんが、\nなどのエスケープシーケンスで改行を表現できます)。 例: "Hello\nWorld"
  2. Raw文字列リテラル (Raw String Literal): バッククォート`で囲まれた文字列です。エスケープシーケンスは解釈されず、囲まれた文字がそのまま文字列の内容となります。複数行にわたることができ、改行文字もそのまま文字列に含まれます。ファイルパスや正規表現、HTML/XMLなどの複数行のテキストを記述するのに便利です。 例: `Hello World`

このコミットの修正は、特に後者のraw文字列リテラルの扱いに焦点を当てています。

Emacs Lispの関連関数

  • point: 現在のカーソル位置(バッファの先頭からの文字数)を返します。
  • save-excursion: 評価中にカーソル位置(ポイント)やマークが変更されても、評価終了後に元の位置に戻すマクロです。
  • goto-char: 指定された位置にカーソルを移動します。
  • looking-at REGEXP: 現在のカーソル位置から正規表現REGEXPがマッチするかどうかをテストします。マッチすれば非nilを返します。
  • get-text-property POS PROP: POS位置のテキストプロパティPROPの値を返します。go-modeでは、文字列やコメントの範囲をテキストプロパティとして管理していることがあります。
  • setq VAR VALUE: 変数VARVALUEを代入します。
  • car LIST: リストの最初の要素を返します(Common Lispの用語で「Content of the Address part of Register」)。
  • cond CLAUSE...: 複数の条件分岐を記述するための特殊フォームです。各CLAUSE(CONDITION EXPRESSION...)の形式で、CONDITIONnil以外であればEXPRESSIONが評価されます。
  • and EXP1 EXP2...: 全ての式がnil以外であれば最後の式の値を返します。そうでなければnilを返します。
  • unless CONDITION BODY...: CONDITIONnilの場合にBODYを評価します。
  • when CONDITION BODY...: CONDITIONnil以外の場合にBODYを評価します。
  • > ARG1 ARG2: ARG1ARG2より大きい場合にtを返します。
  • >= ARG1 ARG2: ARG1ARG2以上の場合にtを返します。
  • 1+ NUM: NUMに1を加えた値を返します。

技術的詳細

このコミットは、misc/emacs/go-mode.elファイル内の2つの主要な変更によって、Goモードのインデントロジックを改善しています。

1. go-mode-mark-cs関数の呼び出し条件と引数の修正

go-mode-mark-cs関数は、コメントや文字列の領域をマークするために使用されると考えられます。この関数が呼び出される条件と、その引数が変更されました。

変更前:

  (when (> pos go-mode-mark-cs-end)
    (go-mode-mark-cs pos))

変更後:

  (when (>= pos go-mode-mark-cs-end)
    (go-mode-mark-cs (1+ pos)))
  • 条件の変更 (> から >=): 以前はposgo-mode-mark-cs-endより厳密に大きい場合にのみgo-mode-mark-csが呼び出されていました。新しいコードでは、posgo-mode-mark-cs-endと等しい場合も呼び出されるようになりました。これは、境界条件(例えば、文字列のちょうど終端にカーソルがある場合)での挙動を修正し、文字列やコメントの範囲をより正確に認識できるようにするためと考えられます。
  • 引数の変更 (pos から (1+ pos)): go-mode-mark-csに渡される引数がposから(1+ pos)posに1を加えた値)に変更されました。これは、go-mode-mark-cs関数が、与えられた位置の「次」の文字からマークを開始する、あるいは与えられた位置を「含む」範囲をマークするために、開始位置を1つずらす必要があることを示唆しています。これにより、文字列やコメントの開始・終了位置の検出がより正確になり、インデントの誤適用を防ぐ効果が期待されます。

2. 複数行文字列の認識ロジックの修正

go-mode-indent-line関数(行のインデントを決定する関数)内で、複数行文字列の開始を検出するための正規表現が修正されました。

変更前:

                  (looking-at "\\s\"")))

変更後:

                  (looking-at "`")))
  • 正規表現の変更 ("\\s\"" から "`"):
    • 変更前の"\\s\""は、正規表現として「空白文字(\s)の後にダブルクォート(")が続く」パターンを意味していました。これは、Go言語の通常の解釈済み文字列リテラル(ダブルクォートで囲まれる)の開始を検出するためのものでした。しかし、Goの複数行文字列(raw string literal)はダブルクォートではなくバッククォート`で囲まれます。
    • 変更後の"`"は、正規表現として「バッククォート(`)そのもの」を意味します。これにより、go-modeはGoのraw string literalの開始を正しく認識できるようになりました。

この修正により、go-modeは、行がraw string literalの内部にある場合に、インデントの自動適用を抑制するようになります。コミットメッセージにある「Inside a multi-line string. Don't mess with indentation.」というコメントが示すように、文字列内部ではコードの構造とは異なるインデントが意図されることが多いため、go-modeが余計なインデントを適用しないようにすることが重要です。

これらの変更は、Go言語の構文、特に文字列リテラルの種類とそれらのインデントに関するEmacsの挙動の間の不一致を解消し、より正確で直感的なインデント体験を提供することを目的としています。

コアとなるコードの変更箇所

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -250,8 +250,8 @@ comment or string."
  
    (unless pos
      (setq pos (point)))
-  (when (> pos go-mode-mark-cs-end)
-    (go-mode-mark-cs pos))
+  (when (>= pos go-mode-mark-cs-end)
+    (go-mode-mark-cs (1+ pos)))
    (get-text-property pos 'go-mode-cs))
  
  (defun go-mode-mark-cs (end)
@@ -597,7 +597,7 @@ indented one level."
       (cond
        ((and cs (save-excursion
                   (goto-char (car cs))
-                  (looking-at "\\s\""))))
+                  (looking-at "`"))))
         ;; Inside a multi-line string.  Don't mess with indentation.
         nil)
        (cs

コアとなるコードの解説

1. go-mode-mark-cs呼び出し部分の修正

この修正は、go-mode-mark-csという関数が、コメントや文字列の範囲を正しくマークするために呼び出されるロジックを調整しています。

  • > から >= への変更: これは、pos(現在のカーソル位置または関連する位置)がgo-mode-mark-cs-end(マークすべき範囲の終了位置)と「等しい」場合も、マーク処理を行うように条件を緩和しています。これにより、文字列やコメントの境界ギリギリにカーソルがある場合でも、その範囲が正しく認識され、インデントロジックに反映されるようになります。
  • pos から (1+ pos) への変更: go-mode-mark-cs関数に渡す引数を、現在の位置posからposの次の位置(1+ pos)に変更しています。これは、go-mode-mark-csが、与えられた位置から「開始」してマークするのではなく、与えられた位置の「次」からマークを開始する、あるいは、与えられた位置を「含む」範囲をマークするために、開始点を1つずらす必要があるという関数のセマンティクスに合わせた調整と考えられます。これにより、文字列やコメントの開始位置が正確にマークされ、インデントの計算に誤りが生じるのを防ぎます。

これらの変更は、文字列やコメントの検出範囲に関するオフバイワンエラーや境界条件の不備を解消し、go-modeがコードの構造をより正確に理解できるようにするために重要です。

2. 複数行文字列認識ロジックの修正

この修正は、go-mode-indent-line関数内で、現在の行が複数行文字列の内部にあるかどうかを判断するロジックを根本的に改善しています。

  • looking-at "\\s\"" から looking-at ""` への変更:
    • 変更前のlooking-at "\\s\""は、現在のカーソル位置が「空白文字の後にダブルクォートが続く」パターンにマッチするかどうかを調べていました。これはGo言語の通常の文字列リテラル("...")の開始を検出するためのものでしたが、Goの複数行文字列(raw string literal)はバッククォート`で囲まれます。したがって、このパターンでは複数行文字列を正しく認識できませんでした。
    • 変更後のlooking-at ""は、現在のカーソル位置が「バッククォート`` ``」にマッチするかどうかを調べます。これにより、go-modeはGoのraw string literalの開始を正確に検出できるようになりました。

この修正が適用されることで、go-modeは、現在の行がバッククォートで始まる複数行文字列の内部にあることを正しく認識し、その場合は「Inside a multi-line string. Don't mess with indentation.」(複数行文字列の内部です。インデントをいじらないでください。)というコメントの通り、自動インデントの適用を抑制します。これにより、複数行文字列内部のテキストが意図せずインデントされてしまう問題が解消され、Goコードの整形がより自然になります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Emacs Lispの公式ドキュメント
  • Gitのdiff形式に関する一般的な知識
  • Emacsのインデントに関する一般的な知識
  • コミットメッセージに記載された情報