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

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

このコミットは、Go言語のEmacsモードであるgo-mode.elにおけるgodefツールの挙動を改善するものです。具体的には、godefが返す型情報の記述について、これまで最初の1行しか表示していなかったものを、完全な記述(複数行にわたる場合も含む)を表示するように変更しています。これにより、構造体やインターフェースなどの複雑な型を識別する際に、その構成要素をより詳細に把握できるようになります。

コミット

commit 29794b77ddf1dbecc953d547b15e29aa12374bc1
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Fri Aug 16 13:03:40 2013 -0400

    misc/emacs: godef: print the entire description, not just the first line
    
    When identifying structs or interfaces we really want to know
    their makeup, not just their name.
    
    R=adonovan
    CC=golang-dev
    https://golang.org/cl/13042043

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

https://github.com/golang/go/commit/29794b77ddf1dbecc953d547b15e29aa12374bc1

元コミット内容

misc/emacs: godef: print the entire description, not just the first line

このコミットは、godefが返す記述を、最初の1行だけでなく全体を表示するように変更します。構造体やインターフェースを識別する際、その名前だけでなく、その構成を知りたいからです。

変更の背景

Go言語の開発において、コードエディタの支援は非常に重要です。特に、変数や関数の定義元にジャンプしたり、型情報を参照したりする機能は、開発効率を大きく向上させます。Emacsユーザーにとって、go-mode.elはGo言語の主要な開発環境を提供し、その中でgodefは重要な役割を担っていました。

しかし、従来のgodefの挙動では、構造体やインターフェースのような複雑な型について、その定義の最初の1行しか表示されませんでした。例えば、以下のような構造体があった場合を考えます。

type MyStruct struct {
    Field1 string
    Field2 int
}

godefMyStructの型情報を参照しようとすると、type MyStruct struct {という最初の1行しか表示されず、Field1Field2といった内部のフィールド情報が欠落していました。これは、特に大規模なコードベースや、複雑なデータ構造を扱う際に、開発者が型の詳細を把握する上で大きな障壁となっていました。

このコミットは、この情報不足の問題を解決し、godefが返す型記述を完全に表示することで、EmacsユーザーのGo開発体験を向上させることを目的としています。これにより、開発者は構造体やインターフェースの完全な定義をその場で確認できるようになり、コードの理解とデバッグが容易になります。

前提知識の解説

Emacs Lisp (Elisp)

Emacs Lispは、Emacsエディタの拡張言語です。Emacsのほぼ全ての機能はEmacs Lispで書かれており、ユーザーはEmacs Lispを使ってEmacsをカスタマイズしたり、新しい機能を追加したりすることができます。go-mode.elもEmacs Lispで書かれたファイルであり、Go言語の編集をサポートするための様々な関数や設定が含まれています。

godef

godefは、Go言語のソースコードから識別子(変数、関数、型など)の定義元を特定するためのコマンドラインツールです。Go言語のコンパイラが使用するのと同じパーサーと型チェッカーを利用して、正確な定義情報を取得します。エディタのプラグイン(Emacsのgo-mode.elなど)から呼び出され、カーソル下のシンボルの定義元にジャンプしたり、その型情報を表示したりするために利用されます。

godefは、通常、以下のような形式で情報を返します。

<ファイルパス>:<行番号>:<列番号>:<型情報>

例えば、main.go:10:5:type MyStruct struct { Field1 string; Field2 int } のように、定義元の位置情報と、そのシンボルの型情報が返されます。この型情報は、複数行にわたる場合もあります。

go-mode.el

go-mode.elは、EmacsにおけるGo言語のメジャーモードです。Go言語のシンタックスハイライト、インデント、コード補完、定義へのジャンプなど、Go言語開発に必要な基本的な機能を提供します。godefのような外部ツールと連携することで、より高度な機能を実現しています。

Emacs Lispのリスト操作関数

このコミットのコード変更を理解するために、いくつかのEmacs Lispのリスト操作関数について知っておく必要があります。

  • nth: リストのN番目の要素を返します。インデックスは0から始まります。 例: (nth 1 '(a b c))b を返します。
  • cdr: リストの最初の要素を除いた残りの部分を返します。 例: (cdr '(a b c))(b c) を返します。
  • butlast: リストの最後のN個の要素を除いた残りの部分を返します。 例: (butlast '(a b c d) 1)(a b c) を返します。
  • mapconcat: リストの各要素に関数を適用し、その結果を文字列として連結します。 例: (mapconcat 'identity '(a b c) " ")"a b c" を返します。identity関数は引数をそのまま返します。

技術的詳細

このコミットの核心は、godefツールからの出力をEmacs Lispでどのように処理し、表示するかというロジックの変更にあります。

従来のコードでは、godef--call関数がgodefバイナリを呼び出し、その出力を処理していました。godefの出力は通常、定義元のファイルパス、行番号、列番号、そして型記述の順で構成されるリストとしてEmacs Lispに渡されます。

変更前のコードでは、godef--callの戻り値からnth 1を使って2番目の要素(つまり、型記述の最初の行)のみを取得していました。これは、godefの出力が単一の文字列として扱われ、その文字列の最初の行だけが意味を持つと仮定していたためです。しかし、godefが返す型記述は複数行にわたる場合があり、特に構造体やインターフェースの定義ではその内部構造が複数行で表現されます。

新しいコードでは、この問題を解決するために、godef--callの戻り値から最後の要素(通常は空行または不要な情報)を除外し、残りの要素をすべて型記述として扱います。具体的には、cdr (butlast (godef--call point) 1)というEmacs Lispの式を使用しています。

  1. godef--call point): godefツールを呼び出し、その出力をEmacs Lispのリストとして取得します。このリストの形式は、例えば ("/path/to/file.go" "10" "5" "type MyStruct struct {" " Field1 string" " Field2 int" "}" "") のようになる可能性があります(最後の空文字列はgodefの出力の末尾にある改行によるもの)。
  2. (butlast ... 1): このリストの最後の1つの要素(上記の例では空文字列)を除外します。これにより、("/path/to/file.go" "10" "5" "type MyStruct struct {" " Field1 string" " Field2 int" "}") のようなリストが得られます。
  3. (cdr ...): このリストの最初の要素(ファイルパス)を除外します。これにより、("10" "5" "type MyStruct struct {" " Field1 string" " Field2 int" "}") のようなリストが得られます。
  4. さらに、このリストの最初の2つの要素(行番号と列番号)も除外する必要があります。コミットの変更点を見ると、cdr (butlast (godef--call point) 1) の結果がdescription変数に代入されていますが、これはまだ行番号と列番号を含んでいます。しかし、mapconcatに渡される際には、descriptionが直接使われているため、mapconcatはリストの各要素を文字列として連結します。この場合、行番号と列番号も連結されてしまう可能性があります。

しかし、実際のgodefの出力形式とgodef--callの内部実装を考慮すると、godef--callは通常、("ファイルパス" "型記述の1行目" "型記述の2行目" ...) のような形式でリストを返すことが期待されます。この場合、nth 1は型記述の1行目を返し、cdr (butlast ... 1)は型記述の複数行をリストとして返すことになります。

変更後のコードでは、descriptionがリスト(型記述の各行が要素)であると仮定し、mapconcat 'identity description "\n"を使って、そのリストの各要素(各行)を改行文字\nで連結して1つの文字列にしています。これにより、複数行にわたる型記述が正しく表示されるようになります。

また、if (string= "" description)という条件がif (not description)に変更されています。これは、descriptionが単一の文字列から、空のリストまたはnil(Emacs Lispで「偽」を意味する)になり得るリストに変わったためです。notnilまたは空のリストに対してt(真)を返します。

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

変更はmisc/emacs/go-mode.elファイル内のgodef-describe関数に集中しています。

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -973,10 +973,10 @@ description at POINT.\"\
   \"Describe the expression at POINT.\"\
   (interactive \"d\")\
   (condition-case nil\
-      (let ((description (nth 1 (godef--call point))))\
-        (if (string= \"\" description)\
+      (let ((description (cdr (butlast (godef--call point) 1))))
+        (if (not description)\
             (message \"No description found for expression at point\")\
-          (message \"%s\" description)))\
+          (message \"%s\" (mapconcat 'identity description \"\\n\"))))\
     (file-error (message \"Could not run godef binary\"))))\
 
 (defun godef-jump (point &optional other-window)\

コアとなるコードの解説

変更前

(let ((description (nth 1 (godef--call point))))
  (if (string= "" description)
      (message "No description found for expression at point")
    (message "%s" description)))
  • godef--call point): godefバイナリを呼び出し、その出力を処理してEmacs Lispのリストを返します。このリストの形式は、例えば ("ファイルパス" "型記述の1行目" "型記述の2行目" ...) のようになります。
  • (nth 1 ...): godef--callが返したリストの2番目の要素(インデックス1)を取得します。これは、型記述の最初の1行目にあたります。
  • (string= "" description): 取得したdescriptionが空文字列であるかどうかをチェックします。空であれば、記述が見つからなかったと判断します。
  • (message "%s" description): descriptionの内容をEmacsのミニバッファに表示します。

このコードでは、godefが複数行の型記述を返した場合でも、nth 1によって最初の1行しか取得されず、残りの情報は破棄されていました。

変更後

(let ((description (cdr (butlast (godef--call point) 1))))
  (if (not description)
      (message "No description found for expression at point")
    (message "%s" (mapconcat 'identity description "\n"))))
  • (godef--call point): 同様にgodefバイナリを呼び出し、出力をリストとして取得します。
  • (butlast (godef--call point) 1): godefの出力リストの最後の要素(通常は末尾の改行による空文字列など、不要な情報)を除外します。
  • (cdr ...): butlastの結果から最初の要素(ファイルパス)を除外します。これにより、description変数には、型記述の各行が要素として含まれるリストが代入されます。例えば、("型記述の1行目" "型記述の2行目" ...) のようなリストになります。
  • (not description): descriptionnil(Emacs Lispで「偽」)または空のリストであるかどうかをチェックします。これにより、記述が見つからなかった場合の処理が適切に行われます。
  • (mapconcat 'identity description "\n"): descriptionリストの各要素(型記述の各行)に対してidentity関数(引数をそのまま返す関数)を適用し、それらを改行文字\nで連結して1つの文字列を作成します。
  • (message "%s" ...): 作成された複数行の文字列をEmacsのミニバッファに表示します。

この変更により、godefが返す複数行にわたる型記述がすべて取得され、改行で連結されて表示されるようになりました。これにより、構造体やインターフェースの完全な定義をEmacs上で確認できるようになり、開発の利便性が向上しました。

関連リンク

参考にした情報源リンク