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

[インデックス 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コマンドによって生成されるファイル)のパスを尋ねていました。これは、特に以下のようなシナリオで不便でした。

  1. 既存のカバレッジバッファでの更新: ユーザーが既にカバレッジ情報を表示しているバッファで、コードを変更し、テストを再実行してカバレッジを更新したい場合、再度カバレッジファイルのパスを入力する必要がありました。これは冗長であり、ワークフローを中断させる要因となっていました。
  2. カバレッジバッファからの再実行: カバレッジバッファから直接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関数を大幅に改良し、既存のカバレッジバッファでの再利用性を高めています。主な変更点は以下の通りです。

  1. ヘルパー関数の導入:

    • go--coverage-filego--coverage-origin-bufferという2つの新しいヘルパー関数が導入されました。これらの関数は、カバレッジファイル名と元のソースコードバッファを決定するロジックをカプセル化します。
    • これらの関数は、特定のバッファローカル変数(go--coverage-current-file-namego--coverage-origin-buffer)が既に設定されているかどうかをチェックします。設定されていればその値を再利用し、設定されていなければユーザーにプロンプトを表示するか、現在のバッファをデフォルトとして使用します。
  2. go-coverage関数の引数と対話性の変更:

    • go-coverage関数は、以前は必須の引数input(カバレッジファイル名)を取っていましたが、&optional coverage-fileというオプション引数を取るように変更されました。
    • interactiveフォームが"fCoverage file: "から()に変更されました。これにより、関数が対話的に呼び出された際に、引数を自動的に推論するか、必要に応じてヘルパー関数を通じてユーザーに尋ねるようになりました。
  3. カバレッジファイルと元バッファの取得ロジックの変更:

    • go-coverageの内部で、coverage-filego--coverage-file関数を呼び出して取得されるようになりました。
    • 同様に、origin-buffer(元のソースコードバッファ)はgo--coverage-origin-buffer関数を呼び出して取得されるようになりました。これにより、カバレッジバッファが既に存在する場合、以前のセッション情報が再利用されます。
  4. 間接バッファ作成方法の変更:

    • 新しいカバレッジバッファを作成する際に、clone-indirect-bufferの代わりにmake-indirect-bufferが使用されるようになりました。make-indirect-bufferはより現代的な関数であり、間接バッファの作成においてより細かい制御(例えば、元バッファのモードライン情報を引き継ぐかどうかなど)が可能です。ここでは、tを渡すことで、元バッファのモードライン情報を引き継ぐようにしています。
  5. バッファローカル変数の設定:

    • 新しく作成されたカバレッジバッファ内で、go--coverage-origin-buffergo--coverage-current-file-nameという2つのバッファローカル変数が設定されるようになりました。
    • make-local-variable関数を使用することで、これらの変数がそのバッファに固有の値を持つようにします。これにより、同じEmacsセッション内で複数のカバレッジバッファを開いても、それぞれのバッファが独立したカバレッジファイルと元バッファの情報を保持できます。これが、既存のカバレッジバッファで以前の引数を再利用するメカニズムの核心です。
  6. バッファ表示ロジックの最適化:

    • 最後に、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-filego--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-filego--coverage-fileから取得されるようになりました。
  • origin-buffergo--coverage-origin-bufferから取得されるようになりました。
  • clone-indirect-buffermake-indirect-bufferに置き換えられました。
  • go--coverage-origin-buffergo--coverage-current-file-nameがバッファローカル変数として設定されるようになりました。
  • display-bufferの呼び出しに条件が追加されました。

コアとなるコードの解説

1. 新しいヘルパー関数 go--coverage-filego--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-buffergo--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はよりインテリジェントになり、ユーザーがカバレッジ情報を更新する際の操作が大幅に簡素化されました。

関連リンク

参考にした情報源リンク