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

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

このコミットは、Go言語のEmacsメジャーモードであるgo-mode.elに、godef-jump-other-windowという新しい機能を追加するものです。この機能は、Goの定義元へジャンプする際に、現在のウィンドウではなく別のウィンドウでファイルを開くように動作します。

コミット

commit 58ce655fd2efe2270ee852790eede952e179735e
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Wed Jul 17 18:16:44 2013 -0400

    misc/emacs: Add godef-jump-other-window
    
    This will behave like similar "*-other-window" functions in Emacs.
    
    Default key bind is C-x 4 C-c C-j – while awkward, it follows both
    the convention for other-window functions and the convention for
    not using user- or emacs-reserved keys.
    
    R=golang-dev, adonovan
    CC=golang-dev
    https://golang.org/cl/10707045

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

https://github.com/golang/go/commit/58ce655fd2efe2270ee852790eede952e179735e

元コミット内容

このコミットは、EmacsのGoモード(go-mode.el)にgodef-jump-other-windowという新しいコマンドを追加します。このコマンドは、既存のgodef-jumpコマンドと同様にGoコード内のシンボルの定義元にジャンプしますが、その際に定義元ファイルが新しいウィンドウ(または既存の別のウィンドウ)で開かれるように動作します。これはEmacsにおける一般的な「-other-window」系の関数(例: find-file-other-window)の振る舞いに倣ったものです。

新しいコマンドには、デフォルトでC-x 4 C-c C-jというキーバインドが割り当てられます。このキーバインドは一見すると複雑ですが、Emacsの「別のウィンドウで開く」系の機能の慣習(C-x 4プレフィックス)と、ユーザー定義キーやEmacs内部予約キーを使用しないという慣習の両方に従っています。

変更の背景

Go言語の開発において、コードの定義元にジャンプする機能は非常に重要です。godefツールとそれを利用するEmacsのgo-modeは、この機能を提供していました。しかし、既存のgodef-jumpコマンドは、常に現在のウィンドウで定義元ファイルを開くため、元のコードを編集中のウィンドウから移動してしまうという問題がありました。

開発者は、元のコンテキストを維持しつつ定義元を参照したいというニーズを抱えていました。Emacsには、ファイルを別のウィンドウで開くためのfind-file-other-windowのような慣習的な機能群が存在します。このコミットは、このEmacsの慣習をgodef-jump機能にも適用することで、ユーザーエクスペリエンスを向上させることを目的としています。これにより、ユーザーは現在の作業を中断することなく、定義元を素早く確認できるようになります。

前提知識の解説

Emacs (イーマックス)

Emacsは、高度にカスタマイズ可能なテキストエディタであり、統合開発環境(IDE)としても機能します。Lisp方言であるEmacs Lispで拡張されており、ユーザーは独自の関数やキーバインドを定義して、エディタの動作を細かく制御できます。Emacsの大きな特徴の一つは、ウィンドウ管理機能です。Emacsの「ウィンドウ」は、通常、フレーム(GUIウィンドウ)内で分割された表示領域を指し、それぞれが異なるバッファ(ファイルの内容など)を表示できます。

Emacsのキーバインドと慣習

Emacsでは、特定のプレフィックスキーに続くキーシーケンスによってコマンドが実行されます。

  • C-x (Control-x): ファイル操作、バッファ操作、ウィンドウ操作など、多くの基本的なコマンドのプレフィックスとして使われます。
  • C-x 4: これは「別のウィンドウで操作を行う」ことを示す慣習的なプレフィックスです。例えば、C-x 4 f (find-file-other-window) は、ファイルを別のウィンドウで開きます。
  • C-c: ユーザーが定義するコマンドのプレフィックスとして推奨されるキーです。

Go言語のgodefツール

godefは、Go言語のソースコード解析ツールの一つです。Goのソースファイル内の識別子(変数、関数、型など)がどこで定義されているかを特定し、その定義元のファイルパス、行番号、列番号を出力します。これは、IDEやエディタが「定義へジャンプ」機能を実現するために利用されます。godefは、Goの標準ライブラリのgo/parsergo/astパッケージなどを用いて、Goコードの抽象構文木(AST)を解析することで機能します。

Emacsのgo-mode

go-modeは、EmacsでGo言語のコードを編集するためのメジャーモードです。Go言語のシンタックスハイライト、インデント、コメント、そしてgodefのような外部ツールとの連携機能を提供します。このモードは、Go開発者がEmacsを快適に利用できるようにするための重要なコンポーネントです。

Emacs Lisp (Elisp)

Emacs Lispは、Emacsエディタの拡張言語です。Emacsのほとんどの機能はEmacs Lispで書かれており、ユーザーはEmacs Lispのコードを記述することで、エディタの動作をカスタマイズしたり、新しい機能を追加したりできます。

  • defun: Emacs Lispで関数を定義するためのマクロです。
  • interactive: 関数が対話的に(キーバインドやM-xコマンドから)呼び出されることを宣言します。引数の指定方法もここで定義できます。
  • define-key: キーマップにキーバインドを定義します。
  • kbd: キーシーケンスをEmacsの内部表現に変換します。
  • with-current-buffer: 指定されたバッファを一時的にカレントバッファにして、その中で式を評価します。
  • funcall: 関数を呼び出します。第一引数に関数名(シンボル)を取り、残りの引数をその関数に渡します。
  • if: 条件分岐を記述します。
  • t: Emacs Lispにおける真理値の真を表すシンボルです。

技術的詳細

このコミットの主要な変更点は、既存のgodef-jump関数を拡張し、新しいgodef-jump-other-window関数を追加することで、定義元へのジャンプ動作に「別のウィンドウで開く」オプションを導入したことです。

  1. godef--find-file-line-column関数の変更:

    • この関数は、godefツールから返されたファイルパス、行番号、列番号に基づいて、Emacsで該当ファイルを開き、指定された位置にカーソルを移動させる役割を担っています。
    • 変更前は、常にfind-file関数を使用していました。
    • 変更後、other-windowという新しいブール型引数を受け取るようになりました。
    • この引数の値に応じて、funcallifを組み合わせて、find-file-other-windowまたはfind-fileのどちらの関数を呼び出すかを動的に決定します。
      • other-windowt(真)の場合、find-file-other-windowが呼び出され、ファイルは別のウィンドウで開かれます。
      • other-windownil(偽)の場合、find-fileが呼び出され、ファイルは現在のウィンドウで開かれます。
  2. godef-jump関数の変更:

    • この関数は、ユーザーがカーソルを置いているGoシンボルの定義元をgodefツールを使って特定し、その結果に基づいてgodef--find-file-line-columnを呼び出します。
    • 変更前は、point引数のみを受け取っていました。
    • 変更後、&optional other-windowという新しいオプション引数を受け取るようになりました。これにより、この関数を呼び出す際に、定義元を別のウィンドウで開くかどうかを指定できるようになります。
    • このother-window引数は、そのままgodef--find-file-line-column関数に渡されます。
  3. godef-jump-other-window関数の新規追加:

    • この関数は、godef-jump関数をラップするシンプルなラッパー関数です。
    • interactive "d"と宣言されており、対話的に呼び出された際に、現在のカーソル位置(point)を引数として受け取ります。
    • 内部では、godef-jump関数を呼び出し、その際にother-window引数にt(真)を渡します。これにより、常に定義元が別のウィンドウで開かれるようになります。
  4. キーバインドの追加:

    • go-modeのキーマップgo-mode-mapに、新しいキーバインド(kbd "C-x 4 C-c C-j")が追加され、godef-jump-other-window関数にマッピングされました。
    • このキーバインドは、Emacsの慣習に従い、C-x 4プレフィックスを使用することで、ユーザーに「別のウィンドウで操作を行う」ことを明確に示します。

これらの変更により、godef-jumpの既存の動作を維持しつつ、ユーザーは必要に応じて定義元を別のウィンドウで開くという柔軟な選択肢を得られるようになりました。

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

misc/emacs/go-mode.elファイルにおける変更は以下の通りです。

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -193,6 +193,7 @@
     (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-x 4 C-c C-j") 'godef-jump-other-window)
     (define-key m (kbd "C-c C-d") 'godef-describe)
     m)
   "Keymap used by Go mode to implement electric keys.")
@@ -870,7 +871,7 @@ 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)
+(defun godef--find-file-line-column (specifier other-window)
   "Given a file name in the format of `filename:line:column',
 visit FILENAME and go to line LINE and column COLUMN."
   (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
@@ -878,7 +879,7 @@ visit FILENAME and go to line LINE and column COLUMN."
     (let ((filename (match-string 1 specifier))
           (line (string-to-number (match-string 2 specifier)))
           (column (string-to-number (match-string 3 specifier))))
-      (with-current-buffer (find-file filename)
+      (with-current-buffer (funcall (if other-window 'find-file-other-window 'find-file) filename)
         (goto-char (point-min))
         (forward-line (1- line))
         (beginning-of-line)
@@ -910,7 +911,7 @@ description at POINT."
           (message "%s" description)))
     (file-error (message "Could not run godef binary"))))
 
-(defun godef-jump (point)
+(defun godef-jump (point &optional other-window)
   "Jump to the definition of the expression at POINT."
   (interactive "d")
   (condition-case nil
@@ -924,7 +925,11 @@ description at POINT."
           (message "%s" file))
          (t
           (push-mark)
-          (godef--find-file-line-column file))))
+          (godef--find-file-line-column file other-window))))
     (file-error (message "Could not run godef binary"))))
 
+(defun godef-jump-other-window (point)
+  (interactive "d")
+  (godef-jump point t))
+
 (provide 'go-mode)

コアとなるコードの解説

1. キーバインドの追加

    (define-key m (kbd "C-x 4 C-c C-j") 'godef-jump-other-window)

go-mode-map (mとして参照) に新しいキーバインドが追加されています。C-x 4 C-c C-jというキーシーケンスが、新しく定義される関数godef-jump-other-windowに割り当てられます。C-x 4はEmacsで「別のウィンドウで操作する」ことを示す標準的なプレフィックスであり、このキーバインドがその慣習に従っていることを示しています。

2. godef--find-file-line-column関数の変更

-(defun godef--find-file-line-column (specifier)
+(defun godef--find-file-line-column (specifier other-window)
   ;; ...
-      (with-current-buffer (find-file filename)
+      (with-current-buffer (funcall (if other-window 'find-file-other-window 'find-file) filename)
         ;; ...

この関数は、godefツールが返すファイル名:行番号:列番号の形式の文字列(specifier)を解析し、該当するファイルを開いて指定された位置にジャンプする内部ヘルパー関数です。

  • 関数の定義が変更され、other-windowという新しい引数を受け取るようになりました。この引数は、定義元を別のウィンドウで開くべきかどうかを示すブール値(tまたはnil)です。
  • ファイルを開く部分のロジックが変更されました。以前は常にfind-fileを使用していましたが、funcall (if other-window 'find-file-other-window 'find-file) filenameという式に置き換えられました。
    • if other-window 'find-file-other-window 'find-file): other-windowt(真)であればシンボル'find-file-other-windowを返し、そうでなければシンボル'find-fileを返します。
    • funcall: if式の結果として得られたシンボル(関数名)を、filenameを引数として呼び出します。 この変更により、godef--find-file-line-columnは、呼び出し元が指定したother-windowの値に応じて、現在のウィンドウまたは別のウィンドウでファイルを開くことができるようになりました。

3. godef-jump関数の変更

-(defun godef-jump (point)
+(defun godef-jump (point &optional other-window)
   ;; ...
           (t
            (push-mark)
-           (godef--find-file-line-column file))))
+           (godef--find-file-line-column file other-window))))
   ;; ...

この関数は、ユーザーがカーソルを置いているGoシンボルの定義元にジャンプする主要なコマンドです。

  • 関数の定義が変更され、&optional other-windowというオプション引数を受け取るようになりました。これにより、この関数はother-windowの値を省略可能となり、省略された場合はnilとして扱われます。
  • godef--find-file-line-columnを呼び出す際に、新しく追加されたother-window引数をそのまま渡すようになりました。これにより、godef-jumpは、呼び出し元から受け取ったother-windowの指示を、実際にファイルを開くヘルパー関数に伝えることができます。

4. godef-jump-other-window関数の新規追加

+(defun godef-jump-other-window (point)
+  (interactive "d")
+  (godef-jump point t))

これは、新しいキーバインドC-x 4 C-c C-jに割り当てられる関数です。

  • interactive "d": この関数が対話的に呼び出されることを宣言し、現在のカーソル位置(point)を引数として受け取るように指定します。
  • (godef-jump point t): 既存のgodef-jump関数を呼び出します。この際、point(現在のカーソル位置)と、other-window引数にt(真)を明示的に渡しています。これにより、この関数が呼び出されると、常に定義元が別のウィンドウで開かれるようになります。

これらの変更により、godef-jumpの既存の動作(現在のウィンドウで開く)を維持しつつ、godef-jump-other-windowという新しいコマンドを通じて、ユーザーは定義元を別のウィンドウで開くという選択肢を得られるようになりました。これはEmacsの慣習に沿った、より柔軟なナビゲーション機能を提供します。

関連リンク

参考にした情報源リンク