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

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

このコミットは、Go言語のVimプラグインにおけるオートコンプリート機能の改善と、godocコマンドの挙動に関する修正を目的としています。特に、$GOPATH変数の尊重、パッケージ名に含まれる特殊文字(ダッシュ、ドット、アンダースコア)の適切な処理、そして$GOROOT変数の決定方法の堅牢化に焦点を当てています。

コミット

commit 5e8de365dced2d35975361760c6df9e4357fd6b9
Author: Tobias Columbus <tobias.columbus@gmail.com>
Date:   Tue Aug 28 03:59:16 2012 +0800

    misc/vim: fix for autocompletion
    
        Vim autocompletion respects the $GOPATH variable and does not
        ignore dashes ('-'), dots ('.') and underscores ('_') like found
        in many remote packages.
        Environment variable $GOROOT is determined by calling
        'go env GOROOT' instead of relying on the user's environment
        variables.
    
        Fixes #3876
        Fixes #3882
    
    R=golang-dev, franciscossouza, dsymonds, minux.ma
    CC=golang-dev
    https://golang.org/cl/6443151

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

https://github.com/golang/go/commit/5e8de365dced2d35975361760c6df9e4357fd6b9

元コミット内容

Vimのオートコンプリート機能の修正。

Vimのオートコンプリートは$GOPATH変数を尊重し、多くのリモートパッケージに見られるようなダッシュ (-)、ドット (.)、アンダースコア (_) を無視しないように修正されました。 環境変数$GOROOTは、ユーザーの環境変数に依存するのではなく、go env GOROOTを呼び出すことによって決定されるようになりました。

Fixes #3876 Fixes #3882

変更の背景

このコミットが行われた背景には、Go言語のVimプラグインにおける既存のオートコンプリート機能の限界と、GOROOT環境変数の扱いの問題がありました。

  1. $GOPATHの不完全なサポート: 以前のVimプラグインは、Goのパッケージを検索する際に$GOROOT下の標準ライブラリパスのみを考慮し、ユーザーが設定した$GOPATH内のワークスペースを適切に参照していませんでした。これにより、$GOPATHにインストールされたサードパーティ製パッケージやユーザー自身のプロジェクトのパッケージがオートコンプリートの候補として表示されないという問題がありました。
  2. 特殊文字を含むパッケージ名の問題: 多くのリモートリポジトリやGoモジュールでは、パッケージ名にダッシュ (-)、ドット (.)、アンダースコア (_) などの特殊文字が含まれることがあります。しかし、Vimプラグインのオートコンプリート機能がこれらの文字を適切に扱わず、オートコンプリートの候補から除外してしまうことがありました。これは、特にGitHubなどのバージョン管理システムから取得したパッケージで顕著な問題でした。
  3. $GOROOTの決定の不安定性: GOROOTはGoのインストールディレクトリを示す重要な環境変数ですが、ユーザーの環境設定に依存している場合、設定ミスや環境の違いによってVimプラグインが正しいGOROOTを特定できない可能性がありました。go env GOROOTコマンドは、Goツールチェイン自体が認識しているGOROOTのパスを正確に返すため、このコマンドを使用することでより堅牢かつ正確なGOROOTの決定が可能になります。

これらの問題は、Go開発者がVimをIDEとして使用する際の生産性を著しく低下させるものであり、本コミットによってこれらの課題が解決されました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびVimに関する基本的な知識が必要です。

  1. Go言語の環境変数 (GOROOT, GOPATH):
    • GOROOT: Go言語のSDKがインストールされているルートディレクトリを指します。Goの標準ライブラリやツールチェインの実行ファイルなどがこのパス以下に配置されます。
    • GOPATH: Goのワークスペースのルートディレクトリを指します。Go 1.11以降のGo Modulesの導入によりその重要性は薄れましたが、このコミットが作成された2012年当時は、サードパーティ製パッケージやユーザー自身のプロジェクトのソースコード、コンパイル済みバイナリ、パッケージアーカイブなどがこのパス以下に配置される、Go開発における非常に重要な概念でした。GOPATHは複数のパスをコロン (:) で区切って指定することができ、Goツールはこれらのパスを順に検索してパッケージを見つけます。
  2. Vimのオートコンプリート機能:
    • Vimには、コード補完を支援する機能が組み込まれています。Go言語のVimプラグインは、このVimの補完フレームワークを利用して、Goのパッケージ名や識別子を補完候補として提示します。
    • globpath(): Vimスクリプト関数で、指定されたパスパターンに一致するファイルやディレクトリのリストを返します。これは、特定のディレクトリ構造内でGoパッケージのアーカイブファイル(.aファイル)やディレクトリを検索するために使用されます。
    • substitute(): Vimスクリプト関数で、文字列内のパターンを別の文字列に置換します。正規表現を使用できます。
    • isdirectory(): Vimスクリプト関数で、指定されたパスがディレクトリであるかどうかを判定します。
  3. Goのパッケージ構造:
    • Goのパッケージは、通常、$GOROOT/pkg/<OS>_<ARCH>/または$GOPATH/pkg/<OS>_<ARCH>/以下にコンパイル済みのアーカイブファイル(.a拡張子)として配置されます。例えば、Linux 64bit環境であればlinux_amd64ディレクトリの下に置かれます。
    • ソースコードは$GOROOT/src/または$GOPATH/src/以下に配置されます。
  4. go envコマンド:
    • Go 1.0から導入されたコマンドで、Goの環境変数の値を表示します。特にgo env GOROOTは、Goツールチェインが認識しているGOROOTの正確なパスを返します。これは、ユーザーが手動で設定した環境変数よりも信頼性が高い情報源となります。
  5. Vimスクリプト:
    • Vimの機能を拡張するために使用されるスクリプト言語です。このコミットで変更されているファイルはVimスクリプトで書かれています。

技術的詳細

このコミットは、主にmisc/vim/autoload/go/complete.vimmisc/vim/plugin/godoc.vimの2つのVimスクリプトファイルを変更しています。

misc/vim/autoload/go/complete.vimの変更点

このファイルはGoパッケージのオートコンプリート機能を提供します。主な変更点は以下の通りです。

  1. GOROOTの決定方法の変更:
    • 変更前は、単にVimの環境変数$GOROOTを参照していました。
    • 変更後は、まずexecutable('go')goコマンドが実行可能かを確認します。
    • 実行可能であれば、system('go env GOROOT')コマンドを実行し、その出力からGOROOTのパスを取得します。これにより、Goツールチェイン自体が認識している正確なGOROOTパスを使用するようになります。
    • v:shell_error変数でコマンドの実行エラーをチェックし、エラーが発生した場合はメッセージを表示します。
    • goコマンドが実行可能でない場合や、go env GOROOTが失敗した場合は、フォールバックとして引き続き$GOROOT環境変数を参照します。
  2. GOPATHのサポート追加:
    • 変更前は、GOROOT下のパッケージのみを検索対象としていました。
    • 変更後は、$GOPATH環境変数をコロン (:) で分割し、複数のワークスペースパスを取得します。
    • これらのGOPATH内のワークスペースパスも、GOROOTと同様にパッケージ検索の対象ディレクトリリスト (dirs) に追加されます。
  3. パッケージ検索ロジックの改善:
    • パッケージを検索するルートディレクトリが、単一のGOROOTから、GOROOTGOPATH内のすべてのワークスペースを含むdirsリストに変更されました。
    • for dir in dirsループが導入され、各ルートディレクトリに対してglobpath()関数を用いてパッケージを検索するようになりました。これにより、GOROOTGOPATHの両方からパッケージが適切に補完候補として収集されるようになります。

misc/vim/plugin/godoc.vimの変更点

このファイルはVimからgodocコマンドを実行する機能を提供します。主な変更点は以下の通りです。

  1. godoc検索ワードの正規表現の緩和:
    • 変更前は、godocで検索するワードから[^a-zA-Z0-9\\/](英数字とスラッシュ以外の文字)を厳密に除去していました。
    • 変更後は、[^a-zA-Z0-9\\\\/._~-]という正規表現に変更され、ダッシュ (-)、ドット (.)、アンダースコア (_)、チルダ (~) も許容されるようになりました。これにより、これらの特殊文字を含むパッケージ名や識別子もgodocで正しく検索できるようになります。

これらの変更により、VimのGo開発環境は、より現代のGoエコシステム(特にGOPATHの利用や特殊文字を含むパッケージ名)に適合し、開発者の利便性が向上しました。

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

misc/vim/autoload/go/complete.vim

--- a/misc/vim/autoload/go/complete.vim
+++ b/misc/vim/autoload/go/complete.vim
@@ -29,21 +29,43 @@ if len(s:goarch) == 0
 endif
 
 function! go#complete#Package(ArgLead, CmdLine, CursorPos)
-  let goroot = $GOROOT
-  if len(goroot) == 0
-    " should not occur.
-    return []
+  let dirs = []
+
+  if executable('go')
+      let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
+      if v:shell_error
+          echo '\'go env GOROOT\' failed'
+      endif
+  else
+      let goroot = $GOROOT
   endif
-  let ret = {}
-  let root = expand(goroot.'/pkg/'.s:goos.'_'.s:goarch)
-  for i in split(globpath(root, a:ArgLead.'*'), "\n")
-    if isdirectory(i)
-      let i .= '/'
-    elseif i !~ '\.a$'
-      continue
-    endif
-    let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
-    let ret[i] = i
+
+  if len(goroot) != 0 && isdirectory(goroot)
+    let dirs += [ goroot ]
+  endif
+
+  let workspaces = split($GOPATH, ':')
+  if workspaces != []
+      let dirs += workspaces
+  endif
+
+  if len(dirs) == 0
+      " should not happen
+      return []
+  endif
+
+  let ret = {}
+  for dir in dirs
+    let root = expand(dir . '/pkg/' . s:goos . '_' . s:goarch)
+    for i in split(globpath(root, a:ArgLead.'*'), "\n")
+      if isdirectory(i)
+        let i .= '/'
+      elseif i !~ '\.a$'
+        continue
+      endif
+      let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
+      let ret[i] = i
+    endfor
   endfor
   return sort(keys(ret))\n endfunction

misc/vim/plugin/godoc.vim

--- a/misc/vim/plugin/godoc.vim
+++ b/misc/vim/plugin/godoc.vim
@@ -72,7 +72,7 @@ function! s:Godoc(...)\n   if !len(word)\n     let word = expand('<cword>')\n   endif\n-  let word = substitute(word, '[^a-zA-Z0-9\\/]', '', 'g')
+  let word = substitute(word, '[^a-zA-Z0-9\\\\/._~-]', '', 'g')
   if !len(word)\n     return\n   endif

コアとなるコードの解説

misc/vim/autoload/go/complete.vim

  1. let dirs = []:
    • パッケージを検索するルートディレクトリのリストを初期化します。これにより、GOROOTGOPATHの両方のパスを柔軟に扱うことが可能になります。
  2. if executable('go') ... else ... endif ブロック:
    • このブロックはGOROOTのパスを決定するためのロジックです。
    • executable('go')goコマンドがシステム上で利用可能かを確認します。
    • もし利用可能であれば、system('go env GOROOT')を実行してGoツールチェインが認識しているGOROOTのパスを取得します。substitute(..., '\n', '', 'g')は、system()コマンドの出力に含まれる改行文字を除去しています。
    • v:shell_errorは、直前のsystem()コマンドの実行が失敗したかどうかを示すVimの組み込み変数です。失敗した場合はエラーメッセージを表示します。
    • goコマンドが利用できない場合や、go env GOROOTが失敗した場合は、従来の$GOROOT環境変数の値を使用します。これにより、goコマンドがパスにない環境でも最低限の機能が維持されます。
  3. if len(goroot) != 0 && isdirectory(goroot) ...:
    • 取得したgorootパスが空でなく、かつ有効なディレクトリである場合に、そのパスをdirsリストに追加します。
  4. let workspaces = split($GOPATH, ':') ...:
    • $GOPATH環境変数の値をコロン (:) で分割し、複数のワークスペースパスを取得します。
    • if workspaces != []で、GOPATHが設定されている場合にのみ、これらのワークスペースパスをdirsリストに追加します。これにより、GOPATHで指定されたすべてのワークスペースがパッケージ検索の対象となります。
  5. for dir in dirs ... endfor:
    • このループは、dirsリストに含まれる各ルートディレクトリ(GOROOTGOPATH内の各ワークスペース)に対して、パッケージの検索処理を実行します。
    • let root = expand(dir . '/pkg/' . s:goos . '_' . s:goarch): 各ルートディレクトリの下にある、現在のOSとアーキテクチャに対応するコンパイル済みパッケージのディレクトリパスを構築します。
    • 内側のfor i in split(globpath(root, a:ArgLead.'*'), "\n")ループは、指定されたrootディレクトリ内で、ユーザーが入力している部分文字列 (a:ArgLead) に一致するパッケージ(ディレクトリまたは.aファイル)を検索します。
    • if isdirectory(i) ... elseif i !~ '\.a$' ... endif: 検索結果がディレクトリであれば末尾にスラッシュを追加し、.aファイルでなければスキップします。
    • let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g'): 取得したパスからルートパスと.a拡張子を除去し、Windowsパスのバックスラッシュをスラッシュに変換して、純粋なパッケージ名を取得します。
    • let ret[i] = i: 取得したパッケージ名をret辞書に追加します。辞書を使用することで重複するパッケージ名を自動的に排除できます。
  6. return sort(keys(ret)):
    • 収集されたすべてのユニークなパッケージ名をソートして返します。これがVimのオートコンプリート候補として表示されます。

misc/vim/plugin/godoc.vim

  1. let word = substitute(word, '[^a-zA-Z0-9\\\\/._~-]', '', 'g'):
    • この行は、godocコマンドに渡す検索ワードをクリーンアップするための正規表現を変更しています。
    • 変更前は[^a-zA-Z0-9\\/]で、英数字とスラッシュ以外のすべての文字を除去していました。
    • 変更後は[^a-zA-Z0-9\\\\/._~-]となり、英数字、スラッシュに加えて、ドット (.)、アンダースコア (_)、チルダ (~)、そしてダッシュ (-) も許容されるようになりました。
    • これにより、github.com/user/repo-nameのようなパスや、my_packageのようなパッケージ名がgodocで正しく扱えるようになり、より多くのGoパッケージに対してgodoc機能が利用可能になります。

これらの変更は、Go開発者がVimをより効果的に使用できるようにするための重要な改善であり、Goエコシステムの進化に合わせて開発ツールの互換性を維持する上で不可欠でした。

関連リンク

参考にした情報源リンク

  • コミット情報: commit_data/13696.txtの内容
  • Go言語のドキュメント(go envコマンド、GOPATHGOROOTに関する情報)
  • Vimスクリプトのドキュメント(globpath(), substitute(), isdirectory(), executable(), system(), v:shell_errorに関する情報)
  • GitHub上のGoリポジトリのコミット履歴
  • Go言語のパッケージ管理に関する一般的な知識
  • Vimのオートコンプリート機能に関する一般的な知識