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

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

このコミットは、Go言語のドキュメントツールであるgodocをVimエディタから利用するためのプラグインmisc/vim/plugin/godoc.vimに対する改善です。具体的には、log.Printのような「パッケージ名.関数名」形式の識別子に対して、VimのGodocコマンドが正しく動作するように修正されています。

コミット

commit d7434816c1a2407030f59837496a8dc86e6d1968
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Tue Mar 26 17:39:46 2013 +1100

    misc/vim: make Godoc command work with "log.Print".
    
    R=dsymonds
    CC=golang-dev
    https://golang.org/cl/7757043

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

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

元コミット内容

このコミットは、Vimのgodoc.vimプラグインにおいて、log.Printのようにドットを含む識別子に対してGodocコマンドが期待通りに動作しない問題を修正します。以前は、log.Printのような識別子に対してGodocコマンドを実行すると、logまたはPrintのいずれか一方しか認識されず、logパッケージ全体のドキュメントを表示したり、Printという単独の識別子に対するドキュメントを検索したりすることができませんでした。この変更により、ドットを含む識別子全体を正しく認識し、パッケージのドキュメントを表示した上で、必要に応じてより詳細なフィードバックを提供するようになります。

変更の背景

Go言語では、標準ライブラリやサードパーティライブラリの関数や変数にアクセスする際に、パッケージ名.識別子名という形式(例: fmt.Println, log.Print)が頻繁に用いられます。Vimのgodoc.vimプラグインは、カーソル下の単語(cword)に基づいてgodocコマンドを実行し、Goのドキュメントを表示する機能を提供していました。

しかし、Vimのデフォルトの「単語」の定義(iskeywordオプション)では、ドット(.)が単語の一部として扱われないことが一般的です。このため、log.Printのような識別子にカーソルがある状態でGodocコマンドを実行すると、VimはlogまたはPrintのいずれか一方のみを単語として認識し、godoc log.Printというコマンドを生成できませんでした。結果として、ユーザーはlogパッケージ全体のドキュメントを見たい場合や、Print関数の具体的なドキュメントを見たい場合に、期待する結果を得ることができませんでした。

このコミットは、このVimのiskeyword設定と、godocコマンドへの引数の渡し方を調整することで、ユーザーがGo言語の慣用的なコードスタイルでドキュメントを効率的に参照できるようにすることを目的としています。

前提知識の解説

  • Go言語のgodocコマンド: godocはGo言語に標準で付属するツールで、Goのソースコードからドキュメントを生成・表示します。パッケージ名(例: godoc fmt)を指定するとそのパッケージのドキュメントを、パッケージ名.識別子名(例: godoc fmt.Println)を指定すると特定の関数や変数のドキュメントを表示できます。
  • Vimのiskeywordオプション: Vimエディタにはiskeywordというオプションがあり、これは「単語」を構成する文字の集合を定義します。例えば、デフォルトでは英数字やアンダースコアなどが含まれますが、ドット(.)は通常含まれません。この設定は、w(単語移動)や*(カーソル下の単語を検索)などのコマンドの挙動に影響を与えます。
  • Vimscriptのexpand('<cword>'): Vimscriptの関数で、カーソル下の「単語」(iskeywordの設定に基づく)を取得します。
  • Vimscriptのsetlocal: 現在のバッファ(ファイル)のみにオプションを設定するコマンドです。グローバルな設定を変更せずに一時的に挙動を変える際に使用されます。
  • Vimscriptのsplit()関数: 文字列を指定した区切り文字で分割し、リスト(配列)として返します。
  • Vimscriptのsearch()関数: 現在のバッファ内で指定されたパターンを検索します。

技術的詳細

このコミットの技術的な核心は、Vimのiskeywordオプションの一時的な変更と、取得した単語の解析ロジックの改善にあります。

  1. iskeywordの一時的な変更:

    • let oldiskeyword = &iskeywordで現在のiskeyword設定を保存します。
    • setlocal iskeyword+=.で、現在のバッファに限り、iskeywordにドット(.)を追加します。これにより、log.Printのような文字列全体がexpand('<cword>')によって一つの単語として認識されるようになります。
    • let word = expand('<cword>')で、ドットを含む単語全体を取得します。
    • let &iskeyword = oldiskeywordで、元のiskeyword設定に戻します。これにより、他のVimの操作に影響を与えません。
  2. 単語の解析とgodoc呼び出しのロジック:

    • 取得したword(例: log.Print)をsplit(word, '\.')でドットを区切り文字として分割し、wordsというリスト(例: ["log", "Print"])に格納します。
    • call s:GodocWord(words[0])を呼び出し、まずリストの最初の要素(例: log)に対してgodocを実行します。これにより、logパッケージ全体のドキュメントが表示されます。これは、log.Printのような呼び出しの場合、通常ユーザーが最初に知りたい情報がパッケージの概要であるためです。
    • if len(words) > 1の条件で、もし単語がドットで区切られた複数の部分からなる場合(例: log.Printのように第二要素が存在する場合)の追加処理を行います。
      • search('^\%(const\|var\|type\|\s\+\) ' . words[1] . '\s\+=\s')search('^func ' . words[1] . '(\')というVimscriptのsearchコマンドを使って、現在のファイル内でwords[1](例: Print)がconstvartype、またはfuncとして宣言されているかを検索します。
      • この検索は、ユーザーが入力したパッケージ名.識別子名識別子名部分が、実は現在のファイル内で定義されているローカルな定数、変数、型、または関数である可能性を考慮しています。もしローカルで定義されていれば、それ以上の特別なドキュメント検索は不要と判断し、returnします。
      • もしローカルな定義が見つからず、かつwordsが複数の要素を持つ場合(つまり、godocが直接そのパッケージ名.識別子名の形式でドキュメントを提供しない可能性のある、例えばメソッドや非公開の識別子である場合)、echo 'No documentation found for "' . word . '".'というメッセージを表示します。これは、godocがパッケージレベルのドキュメントは提供したが、指定された完全な識別子に対する直接的なドキュメントは見つからなかったことをユーザーに伝えます。

この一連のロジックにより、log.Printのようなケースでは、まずlogパッケージのドキュメントが表示され、その上でPrintがローカルな定義でなければ「ドキュメントが見つかりません」という補足メッセージが表示されるという挙動になります。

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

変更はmisc/vim/plugin/godoc.vimファイル内のs:Godoc関数に集中しています。

--- a/misc/vim/plugin/godoc.vim
+++ b/misc/vim/plugin/godoc.vim
@@ -70,13 +70,26 @@ endfunction
 function! s:Godoc(...)\
   let word = join(a:000, ' ')\
   if !len(word)\
+    let oldiskeyword = &iskeyword\
+    setlocal iskeyword+=.\
     let word = expand('<cword>')\
+    let &iskeyword = oldiskeyword\
   endif\
   let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')\
-  if !len(word)\
+  let words = split(word, '\.')\
+  if !len(words)\
     return\
   endif\
-  call s:GodocWord(word)\
+  call s:GodocWord(words[0])\
+  if len(words) > 1\
+    if search('^\%(const\\|var\\|type\\|\\s\\+\\) ' . words[1] . '\\s\\+=\\s')\
+      return\
+    endif\
+    if search('^func ' . words[1] . '(\')\
+      return\
+    endif\
+    echo 'No documentation found for "' . word . '".'\
+  endif\
 endfunction\
 \
 command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<q-args>)

コアとなるコードの解説

  • if !len(word)ブロック内:
    • let oldiskeyword = &iskeyword: 現在のiskeyword設定をoldiskeyword変数に保存します。
    • setlocal iskeyword+=.: 現在のVimバッファに対して、iskeywordオプションにドット(.)を追加します。これにより、expand('<cword>')log.Printのようなドットを含む単語全体を認識できるようになります。
    • let word = expand('<cword>'): カーソル下の単語を取得します。この時点でiskeywordにドットが含まれているため、log.Print全体が取得されます。
    • let &iskeyword = oldiskeyword: iskeyword設定を元の値に戻し、他のVim操作への影響を避けます。
  • call s:GodocWord(word)の変更:
    • 元のcall s:GodocWord(word)が削除され、より複雑なロジックに置き換えられています。
    • let words = split(word, '\.'): 取得した単語(例: log.Print)をドットで分割し、wordsリスト(例: ["log", "Print"])に格納します。
    • if !len(words): 分割結果が空の場合(単語が取得できなかった場合)は処理を終了します。
    • call s:GodocWord(words[0]): まず、分割された単語の最初の部分(例: log)に対してs:GodocWordを呼び出します。これにより、logパッケージのドキュメントが表示されます。
    • if len(words) > 1ブロック:
      • search('^\%(const\\|var\\|type\\|\\s\\+\\) ' . words[1] . '\\s\\+=\\s'): wordsの2番目の要素(例: Print)が、現在のファイル内でconstvartype、または空白で始まる行で定義されているかを検索します。これは、Printがローカルな定数、変数、または型である可能性をチェックします。
      • search('^func ' . words[1] . '(\'): wordsの2番目の要素が、現在のファイル内でfuncとして定義されているかを検索します。これは、Printがローカルな関数である可能性をチェックします。
      • 上記のsearchのいずれかが成功した場合、returnして処理を終了します。これは、ローカルな定義が見つかった場合、パッケージのドキュメント表示で十分と判断するためです。
      • どちらのsearchも成功しなかった場合、echo 'No documentation found for "' . word . '".'というメッセージを表示します。これは、godocが直接その完全な識別子に対するドキュメントを提供できない場合に、ユーザーにその旨を伝えるためのものです。

関連リンク

  • Go言語のgodocコマンドに関する公式ドキュメント: https://pkg.go.dev/cmd/godoc
  • Vimのiskeywordオプションに関するヘルプ: :help 'iskeyword' (Vim内で実行)
  • Vimscriptのexpand()関数に関するヘルプ: :help expand() (Vim内で実行)
  • Vimscriptのsplit()関数に関するヘルプ: :help split() (Vim内で実行)
  • Vimscriptのsearch()関数に関するヘルプ: :help search() (Vim内で実行)

参考にした情報源リンク