[インデックス 17254] ファイルの概要
このコミットは、Go言語のEmacsメジャーモードであるgo-mode.el
に対する変更です。go-mode.el
は、Emacsエディタ内でGo言語のコードを編集する際に、シンタックスハイライト、インデント、コード補完、そして本コミットで関連するコードカバレッジの表示など、Go言語に特化した機能を提供するLispファイルです。
コミット
misc/emacs: Allow go-coverage to work in existing coverage buffers by reusing previous arguments
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3fe9b11cbb70b86951a5a73109df021ecb585939
元コミット内容
commit 3fe9b11cbb70b86951a5a73109df021ecb585939
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date: Wed Aug 14 16:03:44 2013 -0700
misc/emacs: Allow go-coverage to work in existing coverage buffers by reusing previous arguments
R=golang-dev, adonovan, bradfitz
CC=golang-dev
https://golang.org/cl/12721043
---
misc/emacs/go-mode.el | 46 ++++++++++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/misc/emacs/go-mode.el b/misc/emacs/go-mode.el
index faa316a642..022c077c6d 100644
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -1001,18 +1001,37 @@ description at POINT.\"
(defstruct go--covered
start-line start-column end-line end-column covered count)
-(defun go-coverage (input)
+(defun go--coverage-file ()
+ \"Return the coverage file to use, either by reading it from the
+current coverage buffer or by prompting for it.\"
+ (if (boundp 'go--coverage-current-file-name)
+ go--coverage-current-file-name
+ (read-file-name \"Coverage file: \" nil nil t)))
+
+(defun go--coverage-origin-buffer ()
+ \"Return the buffer to base the coverage on.\"
+ (if (boundp 'go--coverage-origin-buffer)
+ go--coverage-origin-buffer
+ (current-buffer)))
+
+(defun go-coverage (&optional coverage-file)
\"Open a clone of the current buffer and overlay it with
-coverage information gathered via go test -coverprofile=INPUT.\"
- (interactive \"fCoverage file: \")
- (let ((ranges '())
- (file-name (file-name-nondirectory (buffer-file-name)))\n- (gocov-buffer-name (concat (buffer-name) \"<gocov>\"))
- (max-count 0)\n- divisor)\n+coverage information gathered via go test -coverprofile=COVERAGE-FILE.
\n+If COVERAGE-FILE is nil, it will either be infered from the
+current buffer if it's already a coverage buffer, or be prompted
+for.\"
+ (interactive)
+ (setq coverage-file (or coverage-file (go--coverage-file)))
+ (let* ((ranges '())
+ (cur-buffer (current-buffer))
+ (origin-buffer (go--coverage-origin-buffer))
+ (file-name (file-name-nondirectory (buffer-file-name origin-buffer)))
+ (gocov-buffer-name (concat (buffer-name origin-buffer) \"<gocov>\"))
+ (max-count 0)
+ divisor)
(with-temp-buffer
- (insert-file-contents input)\n+ (insert-file-contents coverage-file)
(go--goto-line 2) ;; Skip over mode
(while (not (eobp))\n (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) \":\"))
@@ -1044,7 +1063,10 @@ coverage information gathered via go test -coverprofile=INPUT.\"
\n (with-current-buffer (or
(get-buffer gocov-buffer-name)
- (clone-indirect-buffer gocov-buffer-name nil))\n+ (make-indirect-buffer origin-buffer gocov-buffer-name t)))
+ (set (make-local-variable 'go--coverage-origin-buffer) origin-buffer)
+ (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
+\n (save-excursion
(remove-overlays)
(overlay-put
(point-min) (point-max) 'face 'default)
@@ -1074,7 +1096,7 @@ coverage information gathered via go test -coverprofile=INPUT.\"
\n (overlay-put ov 'face face)
(overlay-put ov 'help-echo (format \"Count: %d\" count))))))
-\n- (display-buffer (current-buffer) 'display-buffer-reuse-window))))
+ (if (not (eq cur-buffer (current-buffer)))
+ (display-buffer (current-buffer) 'display-buffer-reuse-window))))))
\n (provide 'go-mode)
変更の背景
このコミットの背景には、Emacsのgo-mode
におけるgo-coverage
機能のユーザーエクスペリエンスの改善があります。
以前のgo-coverage
関数は、コードカバレッジ情報を表示するために、常にユーザーにカバレッジファイル(go test -coverprofile
コマンドによって生成されるファイル)のパスを尋ねていました。これは、特に以下のようなシナリオで不便でした。
- 既存のカバレッジバッファでの更新: ユーザーが既にカバレッジ情報を表示しているバッファで、コードを変更し、テストを再実行してカバレッジを更新したい場合、再度カバレッジファイルのパスを入力する必要がありました。これは冗長であり、ワークフローを中断させる要因となっていました。
- カバレッジバッファからの再実行: カバレッジバッファから直接
go-coverage
を呼び出す場合、元のソースコードバッファや以前使用したカバレッジファイルの情報を自動的に引き継ぐことができませんでした。
このコミットは、これらの問題を解決し、go-coverage
が既存のカバレッジバッファで実行された際に、以前の引数(カバレッジファイルと元のソースコードバッファ)を再利用できるようにすることで、よりスムーズで直感的な操作を可能にすることを目的としています。これにより、ユーザーはカバレッジ情報を頻繁に更新する際に、毎回ファイルパスを入力する手間を省くことができます。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
1. Go言語のコードカバレッジ
Go言語には、標準でコードカバレッジを測定する機能が組み込まれています。これは、go test -coverprofile=output.out
コマンドを使用することで実現されます。
-
go test
: Go言語のテストを実行するコマンドです。 -
-coverprofile=output.out
: このフラグをgo test
に渡すと、テスト実行中にどのコードが実行されたかの情報(カバレッジプロファイル)がoutput.out
というファイルに書き出されます。このファイルは、各ソースファイルのどの行が何回実行されたかといった詳細な情報を含んでいます。 -
カバレッジプロファイルの形式:
output.out
のようなカバレッジプロファイルは、通常、以下のような形式で記述されます。mode: set github.com/example/package/file.go:10.11,12.13 1 1 github.com/example/package/file.go:15.16,17.18 1 0 ...
各行は、
ファイルパス:開始行.開始列,終了行.終了列 実行ブロック数 実行回数
という形式で、コードの特定のブロックがテスト中に何回実行されたかを示します。実行回数
が0であればそのブロックは実行されておらず、1以上であれば実行されたことを意味します。
2. Emacs Lisp (Elisp)
Emacs Lispは、Emacsエディタの拡張機能や設定を記述するために使用されるプログラミング言語です。Emacsのほとんどの機能はElispで実装されており、ユーザーはElispを使ってEmacsを高度にカスタマイズできます。
defun
: 関数を定義するためのマクロです。interactive
: 関数がユーザーによって対話的に呼び出されることを宣言するフォームです。引数をどのように取得するかを定義します。例えば、"fCoverage file: "
は、ファイル名をプロンプトで尋ねることを意味します。let
/let*
: ローカル変数を定義するための特殊フォームです。let*
は、前の変数定義に依存する形で変数を定義できます。with-temp-buffer
: 一時的なバッファを作成し、その中でコードを実行するためのマクロです。with-current-buffer
: 指定されたバッファを一時的にカレントバッファにし、その中でコードを実行するためのマクロです。buffer-name
/buffer-file-name
: バッファの名前や、バッファが関連付けられているファイルの名前を取得する関数です。concat
: 文字列を結合する関数です。get-buffer
: 指定された名前のバッファが存在すればそれを返す関数です。boundp
: シンボルがバインドされている(値を持っている)かどうかをチェックする関数です。set
: シンボルに値を設定する関数です。make-local-variable
: 変数をカレントバッファのローカル変数にする関数です。これにより、その変数の値はバッファごとに独立して保持されます。display-buffer
: バッファを表示するための関数です。
3. Emacsの間接バッファ (Indirect Buffers)
Emacsの間接バッファは、既存のバッファ(「元バッファ」または「親バッファ」)の内容を共有する特別なバッファです。間接バッファは元バッファのテキストを直接参照するため、元バッファの内容が変更されると、間接バッファの内容も自動的に更新されます。
- 用途: 同じファイルの異なる部分を同時に表示したり、同じ内容に対して異なるメジャーモードや表示設定を適用したりする際に便利です。
go-coverage
では、元のGoソースコードバッファの内容を間接バッファに表示し、その上にカバレッジ情報をオーバーレイ(重ねて表示)するために使用されます。 clone-indirect-buffer
: 元バッファのコピーを作成し、それを間接バッファとして返す関数です。make-indirect-buffer
: 元バッファから間接バッファを作成する関数です。clone-indirect-buffer
よりも新しいEmacsバージョンで推奨されることが多く、より柔軟なオプションを提供します。特に、make-indirect-buffer
は元バッファのモードライン情報を引き継ぐかどうかなどを制御できます。
技術的詳細
このコミットは、go-mode.el
内のgo-coverage
関数を大幅に改良し、既存のカバレッジバッファでの再利用性を高めています。主な変更点は以下の通りです。
-
ヘルパー関数の導入:
go--coverage-file
とgo--coverage-origin-buffer
という2つの新しいヘルパー関数が導入されました。これらの関数は、カバレッジファイル名と元のソースコードバッファを決定するロジックをカプセル化します。- これらの関数は、特定のバッファローカル変数(
go--coverage-current-file-name
とgo--coverage-origin-buffer
)が既に設定されているかどうかをチェックします。設定されていればその値を再利用し、設定されていなければユーザーにプロンプトを表示するか、現在のバッファをデフォルトとして使用します。
-
go-coverage
関数の引数と対話性の変更:go-coverage
関数は、以前は必須の引数input
(カバレッジファイル名)を取っていましたが、&optional coverage-file
というオプション引数を取るように変更されました。interactive
フォームが"fCoverage file: "
から()
に変更されました。これにより、関数が対話的に呼び出された際に、引数を自動的に推論するか、必要に応じてヘルパー関数を通じてユーザーに尋ねるようになりました。
-
カバレッジファイルと元バッファの取得ロジックの変更:
go-coverage
の内部で、coverage-file
はgo--coverage-file
関数を呼び出して取得されるようになりました。- 同様に、
origin-buffer
(元のソースコードバッファ)はgo--coverage-origin-buffer
関数を呼び出して取得されるようになりました。これにより、カバレッジバッファが既に存在する場合、以前のセッション情報が再利用されます。
-
間接バッファ作成方法の変更:
- 新しいカバレッジバッファを作成する際に、
clone-indirect-buffer
の代わりにmake-indirect-buffer
が使用されるようになりました。make-indirect-buffer
はより現代的な関数であり、間接バッファの作成においてより細かい制御(例えば、元バッファのモードライン情報を引き継ぐかどうかなど)が可能です。ここでは、t
を渡すことで、元バッファのモードライン情報を引き継ぐようにしています。
- 新しいカバレッジバッファを作成する際に、
-
バッファローカル変数の設定:
- 新しく作成されたカバレッジバッファ内で、
go--coverage-origin-buffer
とgo--coverage-current-file-name
という2つのバッファローカル変数が設定されるようになりました。 make-local-variable
関数を使用することで、これらの変数がそのバッファに固有の値を持つようにします。これにより、同じEmacsセッション内で複数のカバレッジバッファを開いても、それぞれのバッファが独立したカバレッジファイルと元バッファの情報を保持できます。これが、既存のカバレッジバッファで以前の引数を再利用するメカニズムの核心です。
- 新しく作成されたカバレッジバッファ内で、
-
バッファ表示ロジックの最適化:
- 最後に、
display-buffer
を呼び出す前に(if (not (eq cur-buffer (current-buffer))) ...)
という条件が追加されました。これは、カバレッジバッファが既に現在のバッファである場合は、再度表示する必要がないため、不要なバッファ切り替えを防ぐための最適化です。
- 最後に、
これらの変更により、ユーザーは一度カバレッジを表示したバッファで、再度go-coverage
を実行する際に、カバレッジファイルのパスを再入力することなく、最新のカバレッジ情報を簡単に更新できるようになりました。
コアとなるコードの変更箇所
1. 新しいヘルパー関数の追加
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -1001,18 +1001,37 @@ description at POINT."
(defstruct go--covered
start-line start-column end-line end-column covered count)
-(defun go-coverage (input)
+(defun go--coverage-file ()
+ "Return the coverage file to use, either by reading it from the
+current coverage buffer or by prompting for it."
+ (if (boundp 'go--coverage-current-file-name)
+ go--coverage-current-file-name
+ (read-file-name "Coverage file: " nil nil t)))
+
+(defun go--coverage-origin-buffer ()
+ "Return the buffer to base the coverage on."
+ (if (boundp 'go--coverage-origin-buffer)
+ go--coverage-origin-buffer
+ (current-buffer)))
+
+(defun go-coverage (&optional coverage-file)
go--coverage-file
とgo--coverage-origin-buffer
という2つの新しい関数が定義されています。これらは、カバレッジファイル名と元のバッファを、既存のバッファローカル変数から取得するか、ユーザーにプロンプトで尋ねるかして決定します。go-coverage
関数のシグネチャが(&optional coverage-file)
に変更され、引数がオプションになりました。
2. go-coverage
関数の内部ロジックの変更
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -1001,18 +1001,37 @@ description at POINT."
(defstruct go--covered
start-line start-column end-line end-column covered count)
-(defun go-coverage (input)
+(defun go--coverage-file ()
+ "Return the coverage file to use, either by reading it from the
+current coverage buffer or by prompting for it."
+ (if (boundp 'go--coverage-current-file-name)
+ go--coverage-current-file-name
+ (read-file-name "Coverage file: " nil nil t)))
+
+(defun go--coverage-origin-buffer ()
+ "Return the buffer to base the coverage on."
+ (if (boundp 'go--coverage-origin-buffer)
+ go--coverage-origin-buffer
+ (current-buffer)))
+
+(defun go-coverage (&optional coverage-file)
"Open a clone of the current buffer and overlay it with
-coverage information gathered via go test -coverprofile=INPUT."
- (interactive "fCoverage file: ")
- (let ((ranges '())
- (file-name (file-name-nondirectory (buffer-file-name)))\n- (gocov-buffer-name (concat (buffer-name) "<gocov>"))
- (max-count 0)\n- divisor)\n+coverage information gathered via go test -coverprofile=COVERAGE-FILE.
\n+If COVERAGE-FILE is nil, it will either be infered from the
+current buffer if it's already a coverage buffer, or be prompted
+for."
+ (interactive)
+ (setq coverage-file (or coverage-file (go--coverage-file)))
+ (let* ((ranges '())
+ (cur-buffer (current-buffer))
+ (origin-buffer (go--coverage-origin-buffer))
+ (file-name (file-name-nondirectory (buffer-file-name origin-buffer)))
+ (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
+ (max-count 0)
+ divisor)
(with-temp-buffer
- (insert-file-contents input)\n+ (insert-file-contents coverage-file)
(go--goto-line 2) ;; Skip over mode
(while (not (eobp))\n (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":"))
@@ -1044,7 +1063,10 @@ coverage information gathered via go test -coverprofile=INPUT."
\n (with-current-buffer (or
(get-buffer gocov-buffer-name)
- (clone-indirect-buffer gocov-buffer-name nil))\n+ (make-indirect-buffer origin-buffer gocov-buffer-name t)))
+ (set (make-local-variable 'go--coverage-origin-buffer) origin-buffer)
+ (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
+\n (save-excursion
(remove-overlays)
(overlay-put
(point-min) (point-max) 'face 'default)
@@ -1074,7 +1096,7 @@ coverage information gathered via go test -coverprofile=INPUT."
\n (overlay-put ov 'face face)
(overlay-put ov 'help-echo (format "Count: %d" count))))))
-\n- (display-buffer (current-buffer) 'display-buffer-reuse-window))))
+ (if (not (eq cur-buffer (current-buffer)))
+ (display-buffer (current-buffer) 'display-buffer-reuse-window))))))
\n (provide 'go-mode)
interactive
フォームが()
に変更されました。coverage-file
がgo--coverage-file
から取得されるようになりました。origin-buffer
がgo--coverage-origin-buffer
から取得されるようになりました。clone-indirect-buffer
がmake-indirect-buffer
に置き換えられました。go--coverage-origin-buffer
とgo--coverage-current-file-name
がバッファローカル変数として設定されるようになりました。display-buffer
の呼び出しに条件が追加されました。
コアとなるコードの解説
1. 新しいヘルパー関数 go--coverage-file
と go--coverage-origin-buffer
(defun go--coverage-file ()
"Return the coverage file to use, either by reading it from the
current coverage buffer or by prompting for it."
(if (boundp 'go--coverage-current-file-name)
go--coverage-current-file-name
(read-file-name "Coverage file: " nil nil t)))
(defun go--coverage-origin-buffer ()
"Return the buffer to base the coverage on."
(if (boundp 'go--coverage-origin-buffer)
go--coverage-origin-buffer
(current-buffer)))
これらの関数は、go-coverage
が実行される際に必要な情報(カバレッジファイルと元のソースコードバッファ)をどのように取得するかを定義します。
go--coverage-file
:- まず、カレントバッファで
go--coverage-current-file-name
という変数がバインドされているか(つまり、以前にこのバッファでカバレッジが実行され、ファイル名が保存されているか)をboundp
でチェックします。 - もしバインドされていれば、その保存されたファイル名を返します。
- バインドされていなければ、
read-file-name
を使ってユーザーにカバレッジファイルのパスをプロンプトで尋ねます。
- まず、カレントバッファで
go--coverage-origin-buffer
:- 同様に、
go--coverage-origin-buffer
がバインドされているかをチェックします。 - バインドされていれば、その保存された元のバッファを返します。
- バインドされていなければ、
current-buffer
(現在アクティブなバッファ)を元のバッファとして使用します。
- 同様に、
これらのヘルパー関数により、go-coverage
は既存のカバレッジバッファで実行された際に、以前のコンテキストを自動的に再利用できるようになります。
2. go-coverage
関数の変更点
(defun go-coverage (&optional coverage-file)
"Open a clone of the current buffer and overlay it with
coverage information gathered via go test -coverprofile=COVERAGE-FILE.
If COVERAGE-FILE is nil, it will either be infered from the
current buffer if it's already a coverage buffer, or be prompted
for."
(interactive)
(setq coverage-file (or coverage-file (go--coverage-file)))
(let* ((ranges '())
(cur-buffer (current-buffer))
(origin-buffer (go--coverage-origin-buffer))
(file-name (file-name-nondirectory (buffer-file-name origin-buffer)))
(gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
(max-count 0)
divisor)
(with-temp-buffer
(insert-file-contents coverage-file)
;; ... (既存のロジック) ...
(with-current-buffer (or
(get-buffer gocov-buffer-name)
(make-indirect-buffer origin-buffer gocov-buffer-name t)))
(set (make-local-variable 'go--coverage-origin-buffer) origin-buffer)
(set (make-local-variable 'go--coverage-current-file-name) coverage-file)
;; ... (既存のロジック) ...
(if (not (eq cur-buffer (current-buffer)))
(display-buffer (current-buffer) 'display-buffer-reuse-window))))))
interactive
フォームの変更:- 以前は
"fCoverage file: "
という引数指定があり、関数が対話的に呼び出されるとファイル名をプロンプトで尋ねていました。 - 変更後は
()
となり、go-coverage
は引数を自動的に処理するようになりました。これにより、ユーザーは明示的にファイル名を指定しなくても、関数が適切なカバレッジファイルを推論できるようになります。
- 以前は
coverage-file
の取得:(setq coverage-file (or coverage-file (go--coverage-file)))
go-coverage
が引数coverage-file
を受け取った場合はそれを使用し、受け取らなかった場合は新しく定義されたgo--coverage-file
関数を呼び出してカバレッジファイル名を取得します。
origin-buffer
の取得:(origin-buffer (go--coverage-origin-buffer))
- 元のソースコードバッファは、
go--coverage-origin-buffer
関数を呼び出して取得されます。これにより、カバレッジバッファが既に存在する場合、そのバッファが関連付けられている元のソースコードバッファが自動的に再利用されます。
- 間接バッファの作成:
(make-indirect-buffer origin-buffer gocov-buffer-name t)
- 以前の
clone-indirect-buffer
の代わりにmake-indirect-buffer
が使用されています。t
という引数は、間接バッファが元のバッファのモードライン情報を引き継ぐことを意味します。これは、より堅牢で柔軟な間接バッファの作成方法です。
- バッファローカル変数の設定:
(set (make-local-variable 'go--coverage-origin-buffer) origin-buffer)
(set (make-local-variable 'go--coverage-current-file-name) coverage-file)
- 新しく作成された(または既存の)カバレッジバッファ内で、
go--coverage-origin-buffer
とgo--coverage-current-file-name
という2つの変数がmake-local-variable
を使ってバッファローカル変数として設定されます。これにより、これらの変数の値はそのカバレッジバッファに固有のものとなり、次回そのバッファでgo-coverage
が実行された際に、これらの値が自動的に再利用されるようになります。これが、既存のカバレッジバッファで以前の引数を再利用するメカニズムの核心です。
- バッファ表示の最適化:
(if (not (eq cur-buffer (current-buffer))) (display-buffer (current-buffer) 'display-buffer-reuse-window))
- カバレッジバッファが既に現在のバッファである場合、
display-buffer
を呼び出す必要はありません。この条件により、不要なバッファ切り替えが回避され、パフォーマンスとユーザーエクスペリエンスが向上します。
これらの変更により、go-coverage
はよりインテリジェントになり、ユーザーがカバレッジ情報を更新する際の操作が大幅に簡素化されました。
関連リンク
参考にした情報源リンク
- Go言語のコードカバレッジ:
go test -coverprofile
に関する公式ドキュメントやチュートリアル - Emacs Lispのドキュメント:
defun
,interactive
,let
,let*
,with-temp-buffer
,with-current-buffer
,buffer-name
,buffer-file-name
,concat
,get-buffer
,boundp
,set
,make-local-variable
,display-buffer
などに関する情報。 - Emacsの間接バッファ:
clone-indirect-buffer
とmake-indirect-buffer
の違いや使用法に関する情報。 - Go test command
- Go coverage tool