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

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

このコミットは、Go言語のEmacsモードであるgo-mode.elにおけるgodef-jump機能がWindows環境で正しく動作しない問題を修正するものです。具体的には、godefツールが出力するファイルパス、行番号、列番号の形式を解析する際に、Windowsのファイルパスに含まれる可能性のあるバックスラッシュ(\)を考慮していなかった点を改善しています。

コミット

commit d9f5c64f6e49bff22fce355961cd828624430da1
Author: Charles Lee <zombie.fml@gmail.com>
Date:   Wed Jun 26 13:59:25 2013 -0700

    misc/emacs: fix godef-jump on Windows.
    
    Fixes #5555.
    
    R=adonovan, dominik.honnef, iant
    CC=gobot, golang-dev
    https://golang.org/cl/9762045

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

https://github.com/golang/go/commit/d9f5c64f6e49bff22fce355961cd828624430da1

元コミット内容

misc/emacs: fix godef-jump on Windows.

このコミットは、EmacsのGoモード(go-mode.el)におけるgodef-jump機能がWindows環境で正しく動作しないバグを修正することを目的としています。これはGoプロジェクトのIssue #5555に対応するものです。

変更の背景

Go言語の開発において、コードの定義元へジャンプする機能は非常に重要です。godefは、Goのソースコード内で識別子(変数、関数、型など)がどこで定義されているかを特定し、そのファイルパス、行番号、列番号を出力するコマンドラインツールです。Emacsのgo-modeは、このgodefツールと連携して、ユーザーがカーソルを置いた識別子の定義元へジャンプするgodef-jump機能を提供していました。

しかし、Windows環境では、godefが出力するファイルパスの形式がLinuxやmacOSとは異なり、ディレクトリの区切り文字としてバックスラッシュ(\)を使用します。従来のgo-mode.elgodef--find-file-line-column関数は、filename:line:columnという形式の文字列をコロン(:)で単純に分割していました。この方法では、Windowsのファイルパスに含まれるドライブレター(例: C:\path\to\file.go)や、パス内のバックスラッシュがコロンと誤って解釈され、ファイルパスの解析に失敗していました。

この解析の失敗により、godef-jumpはWindows上で正しく定義元にジャンプできず、ユーザーエクスペリエンスを損ねていました。このコミットは、この問題を解決するために、より堅牢な正規表現ベースの解析方法を導入しています。

前提知識の解説

  • Go言語: Googleによって開発された静的型付けのコンパイル型プログラミング言語。
  • Emacs: 高度にカスタマイズ可能なテキストエディタであり、統合開発環境(IDE)としても機能する。Lisp方言であるEmacs Lisp(Elisp)で拡張可能。
  • go-mode.el: EmacsでGo言語のコードを編集するためのメジャーモード。シンタックスハイライト、インデント、コード補完、定義へのジャンプなどの機能を提供する。
  • godefツール: Go言語のソースコードから識別子の定義元を特定するためのコマンドラインツール。通常、filename:line:columnの形式で定義元の情報を標準出力に出力する。
  • godef-jump: go-mode.elが提供する機能の一つで、godefツールを利用してカーソル下の識別子の定義元にジャンプする。
  • Windowsのファイルパス: Windowsオペレーティングシステムにおけるファイルやディレクトリのパス表現。ドライブレター(例: C:)で始まり、ディレクトリの区切り文字としてバックスラッシュ(\)を使用する(例: C:\Users\user\Documents\file.go)。
  • 正規表現 (Regular Expression): 文字列のパターンを記述するための強力な記法。このコミットでは、特定の文字列パターン(filename:line:column)を正確に抽出するために使用されている。
  • Emacs Lisp (Elisp): Emacsエディタの拡張言語。Emacsの動作をカスタマイズしたり、新しい機能を追加したりするために使用される。

技術的詳細

このコミットの核心は、go-mode.el内のgodef--find-file-line-column関数の変更にあります。この関数は、godefツールから受け取った文字列(例: /path/to/file.go:10:5 または C:\path\to\file.go:10:5)を解析し、ファイル名、行番号、列番号を抽出して、Emacsでその位置にジャンプするために使用されます。

変更前は、split-string関数を使ってコロン(:)で文字列を分割していました。

(let* ((components (split-string specifier ":"))
       (line (string-to-number (nth 1 components)))
       (column (string-to-number (nth 2 components))))
  ;; ...
  (find-file (car components)))

このアプローチは、ファイルパスにコロンが含まれないLinux/macOSのような環境では問題ありませんでしたが、Windowsのドライブレター(例: C:)や、パス自体にコロンが含まれる可能性(非常に稀ですが)がある場合には破綻します。例えば、C:\Users\user\file.go:10:5という文字列をコロンで分割すると、C\Users\user\file.go105といった誤ったコンポーネントが生成され、ファイル名が正しく抽出できませんでした。

新しい実装では、string-match関数と正規表現を導入しています。

(if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
    (error "Unexpected godef output: %s" specifier)
  (let ((filename (match-string 1 specifier))
        (line (string-to-number (match-string 2 specifier)))
        (column (string-to-number (match-string 3 specifier))))
    ;; ...
    (find-file filename)))

この正規表現 \\(.+\\):\\([0-9]+\\):\\([0-9]+\\) は以下のように機能します。

  • \\(.+\\): 最初の括弧はキャプチャグループを意味します。.*は任意の文字にマッチしますが、\\.+は少なくとも1文字以上の任意の文字にマッチします。これはファイルパス全体(コロンを除く)をキャプチャします。\はEmacs Lispの文字列リテラル内でエスケープする必要があるため、\\となります。
  • :: ファイルパスと行番号を区切るリテラルのコロンにマッチします。
  • \\([0-9]+\\): 2番目のキャプチャグループで、1桁以上の数字(行番号)にマッチします。
  • :: 行番号と列番号を区切るリテラルのコロンにマッチします。
  • \\([0-9]+\\): 3番目のキャプチャグループで、1桁以上の数字(列番号)にマッチします。

string-matchが成功すると、match-string関数を使って各キャプチャグループの内容を抽出できます。

  • (match-string 1 specifier): 最初のキャプチャグループ(ファイルパス)を取得します。
  • (match-string 2 specifier): 2番目のキャプチャグループ(行番号)を取得し、string-to-numberで数値に変換します。
  • (match-string 3 specifier): 3番目のキャプチャグループ(列番号)を取得し、string-to-numberで数値に変換します。

この正規表現を使用することで、ファイルパス内にコロンやバックスラッシュが含まれていても、最後の2つのコロンが行番号と列番号の区切りとして確実に認識され、ファイルパス全体が正しく抽出されるようになります。これにより、Windows環境でのgodef-jumpの信頼性が向上しました。

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

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

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -873,16 +873,18 @@ will be commented, otherwise they will be removed completely."
 (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")))))
+  (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))\n+      (error "Unexpected godef output: %s" specifier)\n+    (let ((filename (match-string 1 specifier))\n+          (line (string-to-number (match-string 2 specifier)))\n+          (column (string-to-number (match-string 3 specifier))))\n+      (with-current-buffer (find-file filename)\n+        (goto-char (point-min))\n+        (forward-line (1- line))\n+        (beginning-of-line)\n+        (forward-char (1- column))\n+        (if (buffer-modified-p)\n+            (message "Buffer is modified, file position might not have been correct"))))))
 
 (defun godef--call (point)
   "Call godef, acquiring definition position and expression

コアとなるコードの解説

変更されたgodef--find-file-line-column関数は、specifierという引数(godefの出力文字列)を受け取ります。

  1. 正規表現によるマッチング:

    (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
        (error "Unexpected godef output: %s" specifier)
    

    まず、string-match関数を使って、specifierが期待されるfilename:line:columnの形式にマッチするかどうかをチェックします。

    • "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)": この正規表現は、
      • \\(.+\\): 任意の文字が1回以上繰り返されるパターン(ファイルパス部分)をキャプチャグループ1としてマッチさせます。これにより、Windowsのパスに含まれるバックスラッシュやドライブレターもファイルパスの一部として正しく扱われます。
      • :: リテラルのコロンにマッチします。
      • \\([0-9]+\\): 1桁以上の数字のパターン(行番号)をキャプチャグループ2としてマッチさせます。
      • :: リテラルのコロンにマッチします。
      • \\([0-9]+\\): 1桁以上の数字のパターン(列番号)をキャプチャグループ3としてマッチさせます。
    • もしマッチしない場合(not)、error関数を呼び出して「Unexpected godef output」というエラーメッセージを表示し、処理を中断します。これにより、不正なgodef出力に対する堅牢性が向上します。
  2. キャプチャグループからの値の抽出:

    (let ((filename (match-string 1 specifier))
          (line (string-to-number (match-string 2 specifier)))
          (column (string-to-number (match-string 3 specifier))))
    

    string-matchが成功した場合、letフォーム内で、match-string関数を使って正規表現のキャプチャグループから対応する部分文字列を抽出します。

    • filename: キャプチャグループ1(ファイルパス)の内容をそのまま取得します。
    • line: キャプチャグループ2(行番号)の内容をstring-to-numberで数値に変換します。
    • column: キャプチャグループ3(列番号)の内容をstring-to-numberで数値に変換します。
  3. ファイルを開き、指定された位置へジャンプ:

    (with-current-buffer (find-file filename)
      (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"))))))
    

    抽出したfilenameを使ってfind-fileでファイルを開き、with-current-bufferでそのバッファを一時的にカレントバッファにします。 その後、以下のEmacs Lisp関数を使って指定された行と列にジャンプします。

    • goto-char (point-min): バッファの先頭に移動します。
    • forward-line (1- line): 指定された行数だけ前方に移動します。Emacsの行番号は1から始まるため、lineから1を引いています。
    • beginning-of-line: 現在の行の先頭に移動します。
    • forward-char (1- column): 指定された列数だけ前方に移動します。Emacsの列番号も1から始まるため、columnから1を引いています。
    • if (buffer-modified-p) ...: もしバッファが変更されている場合、ファイル位置が正しくない可能性があるというメッセージを表示します。これは、ファイルが保存されていない状態でgodef-jumpを実行した場合に、定義元の位置がずれる可能性があることをユーザーに警告するためです。

この変更により、Windows環境におけるgodef-jumpの信頼性が大幅に向上し、Go開発者がEmacsをより快適に使用できるようになりました。

関連リンク

参考にした情報源リンク