[インデックス 15827] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
に、godef
ツールを統合するための機能追加です。godef
はGo言語の式(expression)の定義元を特定し、その情報を提供する外部ツールであり、この統合によりEmacs内で「定義へジャンプ」や「式の情報表示」といったIDEのような機能が利用可能になります。
コミット
commit 24f476409cf92193471006e0224b14cc6b3884a3
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Tue Mar 19 11:29:28 2013 -0400
misc/emacs: Add support for godef
godef[1][2] is a third party tool for printing information about
expressions, especially the location of their definition. This can be
used to implement a "jump to definition" function. Unlike
cross-language solutions like ctags, godef does not require an index,
operates on the Go AST instead of symbols and works across packages,
including the standard library.
This patch implements two new public functions: godef-describe (C-c
C-d) and godef-jump (C-d C-j). godef-describe describes the expression
at point, printing its type, and godef-jump jumps to its definition.
[1]: https://code.google.com/p/rog-go/source/browse/exp/cmd/godef/
[2]: go get code.google.com/p/rog-go/exp/cmd/godef
R=adonovan, cw, patrick.allen.higgins, sameer
CC=golang-dev
https://golang.org/cl/7781043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/24f476409cf92193471006e0224b14cc6b3884a3
元コミット内容
このコミットは、EmacsのGoモードにgodef
ツールのサポートを追加するものです。godef
は、Go言語の式に関する情報、特にその定義場所を出力するためのサードパーティツールです。これにより、「定義へジャンプ」機能が実装できます。ctags
のようなクロス言語ソリューションとは異なり、godef
はインデックスを必要とせず、シンボルではなくGoの抽象構文木(AST)に基づいて動作し、標準ライブラリを含むパッケージ間でも機能します。
このパッチは、godef-describe
(C-c C-d
) と godef-jump
(C-d C-j
) の2つの新しい公開関数を実装しています。godef-describe
はカーソル位置の式を記述し、その型を出力し、godef-jump
はその定義にジャンプします。
変更の背景
Go言語の開発において、コードベースの規模が大きくなるにつれて、特定の関数や変数の定義元を素早く特定する機能は非常に重要になります。従来のctags
のようなツールは、正規表現ベースのマッチングに依存しており、Go言語の複雑な型システムやパッケージ構造を完全に理解することは困難でした。そのため、より正確でGo言語に特化した定義ジャンプ機能が求められていました。
godef
は、Go言語のソースコードを解析し、抽象構文木(AST)に基づいてシンボルの定義を正確に特定できるツールとして登場しました。このコミットは、EmacsのGo開発環境において、このgodef
の強力な機能を統合することで、開発者の生産性を向上させることを目的としています。これにより、Emacsユーザーは、Go言語のコードをより効率的にナビゲートし、理解できるようになります。
前提知識の解説
EmacsとGo-mode
- Emacs: 高度にカスタマイズ可能なテキストエディタであり、統合開発環境(IDE)としても機能します。Lisp方言であるEmacs Lispで拡張されており、様々なプログラミング言語に対応する「メジャーモード」を提供します。
- Go-mode: Emacsのメジャーモードの一つで、Go言語のコード編集に特化した機能(シンタックスハイライト、インデント、コード補完など)を提供します。
godef
godef
は、Go言語のソースコードから特定の識別子(変数、関数、型など)の定義場所を特定するためのコマンドラインツールです。その主な特徴は以下の通りです。
- Go言語に特化:
godef
はGo言語の構文、型システム、パッケージ構造を深く理解しています。これにより、ctags
のような汎用ツールよりもはるかに正確な定義の特定が可能です。 - ASTベースの解析:
godef
は、Goのソースコードを解析して抽象構文木(AST)を構築し、そのASTを走査することでシンボルの定義を特定します。これにより、単なるテキストマッチングではなく、コードのセマンティクスに基づいた正確な情報を提供します。 - インデックス不要:
ctags
が事前に生成されたタグファイル(インデックス)を必要とするのに対し、godef
はオンデマンドで解析を行うため、インデックスの再生成が不要です。 - クロスパッケージ対応:
godef
は、現在のパッケージだけでなく、インポートされた他のパッケージやGo標準ライブラリ内の定義も追跡できます。 - 主な機能:
- 定義へジャンプ: 特定のシンボルの定義元ファイルと行番号に移動します。
- 式の情報表示: カーソル位置の式の型やその他の詳細情報を表示します。
抽象構文木 (Abstract Syntax Tree, AST)
抽象構文木(AST)は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやリンター、コード分析ツールなど、多くのプログラミングツールで中間表現として利用されます。
- 構造: ASTの各ノードは、宣言、文、式など、ソースコード内の構文要素を表します。例えば、関数呼び出しは一つのノード、その引数はその子ノードとして表現されます。
- 抽象化: ASTは、括弧やセミコロンといった具体的な構文の詳細(ホワイトスペースやコメントなど)を抽象化し、コードの論理的な構造に焦点を当てます。
- GoにおけるAST: Go言語には、
go/ast
、go/parser
、go/token
といった標準パッケージがあり、Goコードの解析とASTの操作をサポートしています。godef
のようなツールは、これらのパッケージを利用してGoコードをセマンティックに理解します。
ctagsとの比較
ctags
は、多くのプログラミング言語に対応した汎用的なタグ生成ツールです。ソースコードをスキャンし、関数や変数などの定義場所を記録した「タグファイル」を生成します。エディタはこのタグファイルを利用して、定義へのジャンプ機能を提供します。
特徴 | ctags | godef |
---|---|---|
対象言語 | 複数言語対応(汎用) | Go言語に特化 |
解析方法 | 主に正規表現ベースのパターンマッチング | Go ASTに基づいたセマンティック解析 |
インデックス | 事前生成されたタグファイルが必要 | 不要(オンデマンド解析) |
精度 | 構文的な理解に留まるため、誤認識の可能性あり | Goのセマンティクスを理解するため、高精度 |
機能 | 定義へのジャンプ | 定義へのジャンプ、式の情報表示 |
適用範囲 | ファイル内、またはタグファイル内の定義 | パッケージ間、標準ライブラリを含む定義 |
この比較からわかるように、godef
はGo言語に特化しているため、ctags
よりもGoコードのナビゲーションにおいて高い精度と利便性を提供します。
技術的詳細
このコミットは、Emacs Lispで記述されたgo-mode.el
ファイルに、godef
ツールを呼び出すための関数と、それらをEmacsのキーバインドに割り当てる設定を追加しています。
主な技術的ポイントは以下の通りです。
-
godef
の呼び出し:godef--call
関数が、godef
バイナリを外部プロセスとして呼び出す役割を担います。call-process-region
Emacs Lisp関数を使用して、現在のバッファの内容をgodef
プロセスに渡し、その出力をキャプチャします。godef
には、現在のファイル名 (-f
) とカーソル位置のバイトオフセット (-o
) を引数として渡すことで、正確なコンテキストを提供します。また、-i
(interactive mode) と-t
(print type) オプションも使用されています。godef
の出力は、定義のファイルパス、行番号、列番号、および式の説明(型情報など)を含む文字列として解析されます。
-
定義へのジャンプ (
godef-jump
):godef-jump
関数は、godef--call
の結果から定義のファイルパス、行番号、列番号を取得します。godef--find-file-line-column
ヘルパー関数が、指定されたファイルを開き、正確な行と列にカーソルを移動させます。push-mark
を呼び出すことで、ジャンプ前のカーソル位置をマークリングに保存し、後で元の場所に戻れるようにしています。
-
式の情報表示 (
godef-describe
):godef-describe
関数は、godef--call
の結果から式の説明部分(型情報など)を取得します。- 取得した説明をEmacsのミニバッファに表示し、ユーザーに情報を提供します。
-
エラーハンドリング:
condition-case
を使用して、godef
バイナリが見つからない場合(file-error
)などのエラーを捕捉し、ユーザーに適切なメッセージを表示します。
-
キーバインドの追加:
go-mode-map
に新しいキーバインドが追加され、C-c C-j
がgodef-jump
に、C-c C-d
がgodef-describe
に割り当てられます。- ユーザーが
M-.
(find-tag
のデフォルトキー)にgodef-jump
をバインドするための設定例も提供されています。
-
XEmacsに関する注意:
- XEmacsではマルチバイト文字の扱いが不完全であるため、
godef
が正しく動作しない可能性があるという注意書きが追加されています。
- XEmacsではマルチバイト文字の扱いが不完全であるため、
この統合により、Emacsはgodef
のセマンティックな解析能力を活用し、Go言語のコードナビゲーション機能を大幅に強化しています。
コアとなるコードの変更箇所
変更はすべて misc/emacs/go-mode.el
ファイルに対して行われています。
-
キーバインドの追加:
@@ -163,6 +165,8 @@ (define-key m ":" 'go-mode-insert-and-indent) (define-key m "=" 'go-mode-insert-and-indent) (define-key m (kbd "C-c C-a") 'go-import-add) + (define-key m (kbd "C-c C-j") 'godef-jump) + (define-key m (kbd "C-c C-d") 'godef-describe) m) "Keymap used by Go mode to implement electric keys.")
go-mode
のキーマップに、godef-jump
とgodef-describe
のための新しいキーバインドが追加されています。 -
関数説明の追加:
@@ -391,12 +395,26 @@ The following extra functions are defined: - `go-goto-imports' - `go-play-buffer' and `go-play-region' - `go-download-play' +- `godef-describe' and `godef-jump'
go-mode
のドキュメント文字列に、新しく追加されたgodef-describe
とgodef-jump
関数がリストアップされています。 -
godef
のインストールとキーバインド設定例の追加:+If you want to use `godef-jump' instead of etags (or similar), +consider binding godef-jump to `M-.', which is the default key +for `find-tag': + +\(add-hook 'go-mode-hook (lambda () + (local-set-key (kbd "M-.") 'godef-jump))) + +Please note that godef is an external dependency. You can install +it with + +go get code.google.com/p/rog-go/exp/cmd/godef
godef-jump
をM-.
にバインドする設定例と、godef
ツールのインストール方法が説明として追加されています。 -
godef
関連関数の実装:@@ -823,4 +841,59 @@ will be commented, otherwise they will be removed completely." (message "Removed %d imports" (length lines))) (if flymake-state (flymake-mode-on)))))) +(defun godef--find-file-line-column (specifier) + "Given a file name in the format of `filename:line:column', +visit FILENAME and go to line LINE and column COLUMN." + (let* ((components (split-string specifier ":")) + (line (string-to-number (nth 1 components))) + (column (string-to-number (nth 2 components)))) + (with-current-buffer (find-file (car components)) + (goto-char (point-min)) + (forward-line (1- line)) + (beginning-of-line) + (forward-char (1- column)) + (if (buffer-modified-p) + (message "Buffer is modified, file position might not have been correct"))))) + +(defun godef--call (point) + "Call godef, acquiring definition position and expression +description at POINT." + (if (go--xemacs-p) + (message "godef does not reliably work in XEmacs, expect bad results")) + (if (not buffer-file-name) + (message "Cannot use godef on a buffer without a file name") + (let ((outbuf (get-buffer-create "*godef*"))) + (with-current-buffer outbuf + (erase-buffer)) + (call-process-region (point-min) (point-max) "godef" nil outbuf nil "-i" "-t" "-f" (file-truename buffer-file-name) "-o" (number-to-string (go--position-bytes (point)))) + (with-current-buffer outbuf + (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))))) + +(defun godef-describe (point) + "Describe the expression at POINT." + (interactive "d") + (condition-case nil + (let ((description (nth 1 (godef--call point)))) + (if (string= "" description) + (message "No description found for expression at point") + (message "%s" description))) + (file-error (message "Could not run godef binary")))) + +(defun godef-jump (point) + "Jump to the definition of the expression at POINT." + (interactive "d") + (condition-case nil + (let ((file (car (godef--call point)))) + (cond + ((string= "-" file) + (message "godef: expression is not defined anywhere")) + ((string= "godef: no identifier found" file) + (message "%s" file)) + ((go--string-prefix-p "godef: no declaration found for " file) + (message "%s" file)) + (t + (push-mark) + (godef--find-file-line-column file)))) + (file-error (message "Could not run godef binary"))))\n (provide 'go-mode)
このセクションが、
godef
との連携を実現する主要なEmacs Lisp関数の定義です。godef--find-file-line-column
:godef
の出力からファイル名、行、列を解析し、その位置にジャンプするヘルパー関数。godef--call
:godef
バイナリを呼び出し、その出力を取得する中心的な関数。godef-describe
:godef--call
の結果から式の説明を取得し、表示する関数。godef-jump
:godef--call
の結果から定義の場所を取得し、そこにジャンプする関数。
コアとなるコードの解説
godef--call
関数
この関数はgodef
ツールとの連携の中核をなします。
(defun godef--call (point)
"Call godef, acquiring definition position and expression
description at POINT."
(if (go--xemacs-p)
(message "godef does not reliably work in XEmacs, expect bad results"))
(if (not buffer-file-name)
(message "Cannot use godef on a buffer without a file name")
(let ((outbuf (get-buffer-create "*godef*")))
(with-current-buffer outbuf
(erase-buffer))
(call-process-region (point-min) (point-max) "godef" nil outbuf nil "-i" "-t" "-f" (file-truename buffer-file-name) "-o" (number-to-string (go--position-bytes (point))))
(with-current-buffer outbuf
(split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")))))
point
: 現在のカーソル位置を表す引数。go--xemacs-p
: XEmacs環境であるかをチェックし、もしそうであれば警告メッセージを表示します。これは、XEmacsでのマルチバイト文字の扱いの問題に起因します。buffer-file-name
: 現在のバッファがファイルに関連付けられているかを確認します。ファイル名がないバッファではgodef
は機能しないため、エラーメッセージを表示します。get-buffer-create "*godef*"
:godef
の出力を一時的に格納するためのバッファ(*godef*
)を作成または取得します。call-process-region
: Emacs Lispの強力な関数で、外部コマンドを実行し、その入出力を制御します。(point-min)
(point-max)
: 現在のバッファ全体をgodef
の標準入力に渡します。"godef"
: 実行するコマンド名。nil
: 標準入力はバッファの内容から供給されるため、ここではnil
。outbuf
:godef
の標準出力をリダイレクトするバッファ。nil
: 標準エラー出力はリダイレクトしない。"-i"
,"-t"
,"-f" (file-truename buffer-file-name)
,"-o" (number-to-string (go--position-bytes (point)))
:godef
に渡す引数。-i
: インタラクティブモード。-t
: 式の型を出力。-f
: 現在のファイルパス。file-truename
でシンボリックリンクなどを解決した絶対パスを取得。-o
: カーソル位置のバイトオフセット。go--position-bytes
で現在のカーソル位置のバイト数を取得。
split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"
:*godef*
バッファに書き込まれたgodef
の出力を取得し、改行で分割して文字列のリストとして返します。このリストの最初の要素が定義の場所(ファイル:行:列)、2番目の要素が式の説明(型など)になります。
godef-jump
関数
この関数は、カーソル位置のGoのシンボルの定義元にジャンプします。
(defun godef-jump (point)
"Jump to the definition of the expression at POINT."
(interactive "d")
(condition-case nil
(let ((file (car (godef--call point))))
(cond
((string= "-" file)
(message "godef: expression is not defined anywhere"))
((string= "godef: no identifier found" file)
(message "%s" file))
((go--string-prefix-p "godef: no declaration found for " file)
(message "%s" file))
(t
(push-mark)
(godef--find-file-line-column file))))
(file-error (message "Could not run godef binary"))))
(interactive "d")
: この関数が対話的に呼び出されることを示し、d
はカーソル位置を引数として渡すことを意味します。(condition-case nil ... (file-error ...))
:godef
バイナリが見つからないなどのfile-error
が発生した場合に、エラーメッセージを表示します。(let ((file (car (godef--call point))))
:godef--call
を呼び出し、その結果リストの最初の要素(定義の場所を示す文字列)をfile
変数に格納します。(cond ...)
:godef
の出力に基づいて条件分岐を行います。"-"
:godef
が定義を見つけられなかった場合。"godef: no identifier found"
: カーソル位置に識別子がない場合。"godef: no declaration found for "
で始まる文字列: 宣言が見つからなかった場合。t
(それ以外): 定義が見つかった場合。(push-mark)
: 現在のカーソル位置をマークリングにプッシュし、後でC-u C-SPC
などで元の場所に戻れるようにします。(godef--find-file-line-column file)
: 定義の場所(ファイル:行:列)にジャンプするヘルパー関数を呼び出します。
godef-describe
関数
この関数は、カーソル位置のGoの式の型やその他の説明を表示します。
(defun godef-describe (point)
"Describe the expression at POINT."
(interactive "d")
(condition-case nil
(let ((description (nth 1 (godef--call point))))
(if (string= "" description)
(message "No description found for expression at point")
(message "%s" description)))
(file-error (message "Could not run godef binary"))))
(interactive "d")
:godef-jump
と同様に、対話的な呼び出しとカーソル位置の取得を行います。(let ((description (nth 1 (godef--call point))))
:godef--call
を呼び出し、その結果リストの2番目の要素(式の説明文字列)をdescription
変数に格納します。(if (string= "" description) ... (message "%s" description))
:description
が空でなければ、その内容をEmacsのミニバッファに表示します。空の場合は「説明が見つかりません」というメッセージを表示します。(file-error ...)
:godef
バイナリが見つからない場合のエラーハンドリング。
これらの関数群が連携することで、EmacsのGoモードはgodef
の高度な解析能力を最大限に活用し、Go開発者にとって非常に有用なコードナビゲーション機能を提供しています。
関連リンク
- godef GitHubリポジトリ: 現在の
godef
の公式リポジトリは、コミットメッセージに記載されているcode.google.com/p/rog-go/exp/cmd/godef
からgithub.com/rogpeppe/godef
に移行しています。 - Emacs Go-mode:
- https://github.com/dominikh/go-mode.el (Dominik Honnef氏によるGo-modeのフォーク、このコミットの作者)
- https://github.com/go-mode/go-mode (現在の公式Go-modeリポジトリ)
- Go言語の抽象構文木 (AST) について:
参考にした情報源リンク
- godef - GoDoc
- Emacs go-mode godef integration - honnef.co (このコミットの作者による解説記事)
- ctags vs godef - Stack Overflow
- Abstract Syntax Tree (AST) in Go - Medium
- Go AST: A Comprehensive Guide - LeapcellI have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is provided directly to standard output as requested.