[インデックス 17735] ファイルの概要
misc/vim/autoload/go/complete.vim
ファイルは、VimエディタにおけるGo言語開発のための補完機能を提供するVimscriptファイルです。具体的には、Go言語のパッケージ名や、本コミットで追加されたパッケージメンバー(関数、変数、型など)の補完を担っています。Vimのautoload
ディレクトリに配置されているため、必要に応じて遅延ロードされ、Vimの起動時間を短縮する役割も果たしています。
コミット
misc/vim: Autocompletion for :Godoc command
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/45b830ed319ee1d82ff9d41c35d6145ab7ecbb82
元コミット内容
misc/vim: Autocompletion for :Godoc command
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/14259043
変更の背景
このコミットが行われる以前は、Vimの:Godoc
コマンド(Go言語のドキュメントを表示するコマンド)において、パッケージ名の補完は可能でしたが、そのパッケージ内のメンバー(関数、変数、型など)の補完は提供されていませんでした。例えば、:Godoc fmt.
と入力した際に、Printf
やErrorf
といったfmt
パッケージのメンバーが補完候補として表示されないため、ユーザーは手動で完全な名前を入力する必要がありました。
この変更の背景には、開発者の生産性向上という明確な目的があります。Vimのような強力なエディタにおいて、コマンドの引数補完は非常に重要な機能であり、特にドキュメント参照のような頻繁に行われる操作においては、その利便性が大きく影響します。本コミットは、:Godoc
コマンドの使い勝手を向上させ、Go言語開発者がよりスムーズにドキュメントを参照できるようにするために導入されました。これにより、ユーザーはタイプミスを減らし、より迅速に目的のドキュメントにアクセスできるようになります。
前提知識の解説
Vim (Vi IMproved)
Vimは、プログラマーに広く利用されている高機能なテキストエディタです。コマンドラインインターフェースで動作し、キーボード操作のみでほとんどの編集作業を完結できる点が特徴です。モードの概念(ノーマルモード、挿入モード、ビジュアルモードなど)を持ち、効率的なテキスト編集を可能にします。Vimは非常にカスタマイズ性が高く、Vimscriptと呼ばれる独自のスクリプト言語を用いて機能を拡張できます。
Vimscript
Vimscriptは、Vimエディタの機能を拡張するために使用されるスクリプト言語です。Vimの設定ファイル(.vimrc
)やプラグインの記述に用いられます。関数定義、変数、条件分岐、ループなどの基本的なプログラミング構造をサポートしており、Vimの内部機能や外部コマンドとの連携も可能です。本コミットでも、Vimscriptで記述された関数が補完ロジックを実装しています。
Autocompletion (Vimにおける補完機能)
Vimにおける補完機能は、ユーザーが入力している内容に基づいて、関連する候補を提示する機能です。これにより、入力の手間を省き、タイプミスを減らすことができます。Vimには、キーワード補完、ファイル名補完、辞書補完など、様々な種類の補完機能が組み込まれています。また、プラグインによってさらに高度な補完機能を追加することも可能です。コマンドライン補完は、Vimのコマンドモードでコマンドやその引数を入力する際に機能する補完です。
godoc
コマンド
godoc
は、Go言語に標準で付属するドキュメンテーションツールです。Goのソースコードからコメントを解析し、HTML形式やプレーンテキスト形式でドキュメントを生成・表示します。Goの標準ライブラリだけでなく、ユーザーが作成したパッケージのドキュメントも表示できます。例えば、godoc fmt
と実行するとfmt
パッケージのドキュメントが表示され、godoc fmt Printf
と実行するとfmt.Printf
関数のドキュメントが表示されます。このツールは、Go言語のコードベースを理解し、APIの利用方法を調べる上で不可欠なものです。
:Godoc
コマンド (Vimプラグインにおけるコマンド)
Go言語のVimプラグイン(go.vim
など)によって提供される:Godoc
コマンドは、Vimエディタ内からgodoc
ツールを実行するためのラッパーコマンドです。これにより、Vimを離れることなく、Goのドキュメントを参照できるようになります。通常、:Godoc <package_name>
や:Godoc <package_name> <member_name>
といった形式で使用されます。
autoload
ディレクトリ
Vimのプラグイン開発において、autoload
ディレクトリは特別な意味を持ちます。このディレクトリに配置されたVimscriptファイル内の関数は、Vimの起動時に自動的にロードされるのではなく、その関数が初めて呼び出されたときにロードされます。これにより、Vimの起動時間を短縮し、必要な機能だけをオンデマンドでロードすることができます。本コミットで変更されたgo/complete.vim
ファイルもこのautoload
ディレクトリ内にあり、補完機能が実際に必要になったときにロードされるようになっています。
技術的詳細
このコミットの主要な目的は、Vimの:Godoc
コマンドにおいて、Goパッケージのメンバー(関数、変数、型など)の自動補完機能を追加することです。この機能は、go#complete#PackageMembers
という新しいVimscript関数によって実現されています。
go#complete#PackageMembers
関数の役割
この関数は、指定されたGoパッケージのドキュメントをgodoc
コマンドから取得し、その内容を解析してパッケージメンバーの候補を抽出します。
-
godoc
コマンドの実行:silent! let content = system('godoc ' . a:package)
この行では、Vimscriptのsystem()
関数を使用して、シェルコマンドgodoc <package_name>
を実行しています。例えば、a:package
がfmt
であれば、godoc fmt
が実行されます。silent!
は、コマンドの出力をVimのメッセージ領域に表示しないようにするためのものです。content
変数には、godoc
コマンドの標準出力が文字列として格納されます。 -
エラーハンドリングと空コンテンツのチェック:
if v:shell_error || !len(content)
v:shell_error
は、直前のsystem()
コマンドがエラーを返したかどうかを示すVimの組み込み変数です。!len(content)
は、godoc
コマンドが何も出力を返さなかった場合をチェックします。これらの条件のいずれかが真であれば、補完候補は空のリスト[]
を返します。 -
godoc
出力の解析:let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
godoc
の出力は複数行のテキストなので、まずsplit(content, "\n")
で各行に分割します。次に、filter()
関数と正規表現'^\\s\\+$'
を使って、空白文字のみで構成される空行を削除しています。これにより、意味のある行のみが残ります。 -
パッケージメンバーの抽出: この部分が最も重要なロジックです。
godoc
の出力形式に基づいて、パッケージメンバーを識別するための2つの正規表現が定義されています。let mx1 = '^\\s\\+\\(\\S+\\)\\s\\+=\\s\\+.*'
この正規表現は、const
やvar
で定義された定数や変数を捕捉することを意図しています。例えば、const Error = errors.New("...")
のような行からError
を抽出します。\s\+
は1つ以上の空白文字、\S\+
は1つ以上の非空白文字、\(...\)
はキャプチャグループです。let mx2 = '^\\%(const\\|var\\|type\\|func\\) \\([A-Z][^ (]\\+\\).*'
この正規表現は、const
、var
、type
、func
キーワードで始まる行から、その後に続く識別子(メンバー名)を抽出します。例えば、func Printf(...)
からPrintf
を、type MyType struct { ... }
からMyType
を抽出します。\%(...)
は非キャプチャグループ、[A-Z]
は大文字で始まる識別子を想定しています。
これらの正規表現を使って、
filter()
とmap()
関数を組み合わせ、lines
から該当する行を抽出し、substitute()
関数でメンバー名のみを抽出しています。抽出されたメンバー名はcandidates
リストに結合されます。 -
部分一致フィルタリング:
return filter(candidates, '!stridx(v:val, a:member)')
最後に、抽出されたcandidates
リストを、ユーザーが入力した部分文字列a:member
でフィルタリングします。stridx(v:val, a:member)
は、v:val
(候補文字列)の中にa:member
(ユーザー入力)が最初に現れるインデックスを返します。もしa:member
がv:val
の先頭に存在すれば0
を返します。!stridx(...)
は、a:member
がv:val
の先頭に一致する場合に真となります。これにより、ユーザーが入力した部分文字列で始まるメンバーのみが補完候補として返されます。
go#complete#Package
関数の変更
既存のgo#complete#Package
関数は、:Godoc
コマンドの引数を解析し、補完のコンテキストを判断する役割を担っています。このコミットでは、この関数が以下のように変更されました。
let words = split(a:CmdLine, '\s\+', 1)
if len(words) > 2
- " TODO Complete package members
- return []
+ " Complete package members
+ return go#complete#PackageMembers(words[1], words[2])
endif
以前は、コマンドライン引数が2つ以上ある場合(例: :Godoc fmt P
)、パッケージメンバーの補完は「TODO」として実装されておらず、空のリストが返されていました。この変更により、len(words) > 2
の条件が満たされた場合、つまりユーザーがパッケージ名に加えてさらに何かを入力している場合に、新しく定義されたgo#complete#PackageMembers
関数が呼び出されるようになりました。
words[1]
はコマンドラインの2番目の単語、つまりパッケージ名(例:fmt
)をgo#complete#PackageMembers
のa:package
引数として渡します。words[2]
はコマンドラインの3番目の単語、つまりユーザーが入力している部分的なメンバー名(例:P
)をgo#complete#PackageMembers
のa:member
引数として渡します。
この連携により、ユーザーが:Godoc <package_name> <partial_member_name>
と入力すると、go#complete#PackageMembers
が呼び出され、適切なパッケージメンバーの補完候補が提供されるようになります。
コアとなるコードの変更箇所
--- a/misc/vim/autoload/go/complete.vim
+++ b/misc/vim/autoload/go/complete.vim
@@ -28,13 +28,31 @@ if len(s:goarch) == 0
endif
endif
+function! go#complete#PackageMembers(package, member)
+ silent! let content = system('godoc ' . a:package)
+ if v:shell_error || !len(content)
+ return []
+ endif
+ let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
+ try
+ let mx1 = '^\\s\\+\\(\\S+\\)\\s\\+=\\s\\+.*'
+ let mx2 = '^\\%(const\\|var\\|type\\|func\\) \\([A-Z][^ (]\\+\\).*'
+ let candidates =
+ \ map(filter(copy(lines), 'v:val =~ mx1'), 'substitute(v:val, mx1, "\\1", "")')
+ \ + map(filter(copy(lines), 'v:val =~ mx2'), 'substitute(v:val, mx2, "\\1", "")')
+ return filter(candidates, '!stridx(v:val, a:member)')
+ catch
+ return []
+ endtry
+endfunction
+\
function! go#complete#Package(ArgLead, CmdLine, CursorPos)
let dirs = []
let words = split(a:CmdLine, '\s\+', 1)
if len(words) > 2
- " TODO Complete package members
- return []
+ " Complete package members
+ return go#complete#PackageMembers(words[1], words[2])
endif
if executable('go')
コアとなるコードの解説
このコミットの核となる変更は、以下の2点です。
-
go#complete#PackageMembers(package, member)
関数の新規追加: この関数は、指定されたGoパッケージ(package
引数)のドキュメントをgodoc
コマンドで取得し、その出力からパッケージメンバー(定数、変数、型、関数など)を抽出します。抽出された候補は、ユーザーが入力した部分文字列(member
引数)でフィルタリングされ、補完候補のリストとして返されます。 内部では、system()
関数でgodoc
コマンドを実行し、その標準出力をVimscriptの文字列として取得します。取得した文字列は、正規表現を用いて解析され、Goのメンバー名に合致するパターンが抽出されます。特に、mx1
とmx2
という2つの正規表現が定義されており、それぞれ異なる形式で宣言されたメンバーを捕捉するように設計されています。最終的に、filter()
関数とstridx()
関数を組み合わせて、ユーザーの入力に前方一致する候補のみを絞り込んでいます。 -
go#complete#Package
関数内の呼び出し箇所の変更: 既存のgo#complete#Package
関数は、:Godoc
コマンドの引数を解析し、補完のコンテキストを判断する役割を担っています。この変更により、コマンドラインの単語数が2つを超える場合(例::Godoc fmt P
のように、パッケージ名に加えてさらに何か入力されている場合)、以前は「TODO」として実装されていなかったパッケージメンバーの補完が、新しく追加されたgo#complete#PackageMembers
関数を呼び出すように修正されました。 具体的には、words[1]
(パッケージ名)とwords[2]
(入力中のメンバー名)を引数としてgo#complete#PackageMembers
に渡し、その戻り値を補完候補として利用します。
これらの変更により、Vimの:Godoc
コマンドは、パッケージ名だけでなく、そのパッケージ内のメンバーについても自動補完を提供できるようになり、Go開発者の利便性が大幅に向上しました。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
godoc
コマンドのドキュメント:go help godoc
または https://pkg.go.dev/cmd/godoc (Go 1.13以降はgo doc
が推奨されていますが、当時のgodoc
の挙動を理解する上で参考になります)- Vim公式ドキュメント (補完機能):
:help complete
または https://vimhelp.org/insert.txt.html#completion - Vim公式ドキュメント (Vimscript):
:help vimscript
または https://vimhelp.org/usr_41.txt.html - Vim
autoload
の概念: https://learnvimscriptthehardway.stevelosh.com/chapters/42.html
参考にした情報源リンク
- https://github.com/golang/go/commit/45b830ed319ee1d82ff9d41c35d6145ab7ecbb82
- https://golang.org/cl/14259043 (Go Gerrit Code Review)
- Vimのヘルプドキュメント (
:help
コマンドでアクセス可能) - Go言語の公式ドキュメント
- Vimscriptに関する一般的な情報源 (例: "Learn Vimscript the Hard Way")
godoc
コマンドの挙動に関する情報 (ローカルでのgodoc
実行結果の確認)- 正規表現の構文に関する情報