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

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

このコミットは、Go言語のVimプラグインにおけるgodocコマンドの機能を拡張し、パッケージとそのメンバー(関数、型、変数など)を個別にドキュメント検索できるようにするものです。これにより、ユーザーは特定のパッケージ全体、またはそのパッケージ内の特定の要素(例: encoding/jsonパッケージのMarshal関数)のドキュメントをVim内から直接参照できるようになります。

コミット

commit fcee50c46eebf742afd3b90150a4b5e6c730715d
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Wed Oct 2 08:52:51 2013 +1000

    misc/vim: Separate package and package members.
    This change allow to godoc:
        :Godoc github.com/mattn/go-gtk/gtk
        :Godoc github.com/mattn/go-gtk/gtk NewWindow
        :Godoc encoding/json
        :Godoc encoding/json Marshal
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/14171043

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

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

元コミット内容

misc/vim: Separate package and package members.
This change allow to godoc:
    :Godoc github.com/mattn/go-gtk/gtk
    :Godoc github.com/mattn/go-gtk/gtk NewWindow
    :Godoc encoding/json
    :Godoc encoding/json Marshal

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/14171043

変更の背景

この変更の背景には、Go言語のドキュメンテーションツールであるgodocをVimエディタ内でより柔軟に利用したいというニーズがありました。以前のVimプラグインでは、godocコマンドがパッケージ全体を対象とするか、あるいは現在のカーソル位置にある単語を対象とするかのいずれかであり、特定のパッケージのメンバー(例えば、fmtパッケージのPrintln関数)を直接指定してドキュメントを検索することが困難でした。

開発者は、godoc encoding/json Marshalのように、パッケージ名とメンバー名をスペースで区切って指定することで、そのメンバーのドキュメントを直接参照できるような機能拡張を求めていました。これにより、VimでのGo開発におけるドキュメント参照の効率が大幅に向上し、開発者はより迅速に必要な情報を得られるようになります。

前提知識の解説

Go言語のドキュメンテーションとgodocコマンド

Go言語は、コードに記述されたコメントから自動的にドキュメンテーションを生成する仕組みを持っています。このドキュメンテーションは、godocというツールによって提供されます。godocは、Goのソースコードを解析し、パッケージ、関数、型、変数などのドキュメントを生成・表示するコマンドラインツールです。

  • パッケージドキュメント: パッケージの概要や目的を説明します。
  • メンバー(関数、型、変数など)ドキュメント: 各要素の機能、引数、戻り値などを説明します。

godocコマンドは通常、以下のように使用されます。

  • godoc <package_path>: 指定されたパッケージのドキュメントを表示します。例: godoc fmt
  • godoc <package_path> <member_name>: 指定されたパッケージ内の特定のメンバーのドキュメントを表示します。例: godoc fmt Println

Vimエディタとプラグイン

Vimは、高機能なテキストエディタであり、その機能を拡張するためにプラグインシステムを備えています。Go言語開発のためのVimプラグインは、Goのコード補完、シンタックスハイライト、フォーマット、そしてgodocとの連携など、様々な機能を提供します。

  • command!: Vimのユーザー定義コマンドを定義するためのキーワードです。-nargs=*は任意の数の引数を取ることを意味し、-complete=customlist,go#complete#Packageは引数の補完にgo#complete#Package関数を使用することを意味します。
  • expand('<cword>'): カーソル下の単語を取得するVimスクリプトの関数です。
  • system(): シェルコマンドを実行し、その出力を取得するVimスクリプトの関数です。
  • split(): 文字列を区切り文字で分割するVimスクリプトの関数です。
  • substitute(): 文字列内のパターンを置換するVimスクリプトの関数です。

go env GOROOT

go env GOROOTは、Goのインストールディレクトリ(Goの標準ライブラリなどが配置されている場所)のパスを取得するためのコマンドです。VimプラグインがGoのソースコードを検索する際に利用されます。

技術的詳細

このコミットは、VimのGoプラグインにおけるgodocコマンドの引数処理ロジックを改善することで、パッケージ名とメンバー名を区別して解釈できるようにしています。

具体的には、以下の2つのファイルが変更されています。

  1. misc/vim/autoload/go/complete.vim:

    • go#complete#Package関数が修正されています。この関数は、VimのGodocコマンドの引数補完に使用されます。
    • 変更前は、コマンドラインの引数が2つ以上ある場合に、パッケージメンバーの補完を適切に処理できていませんでした。
    • 変更後、a:CmdLine(コマンドライン全体)を解析し、引数の数が2つを超える場合は、パッケージメンバーの補完ロジックを将来的に追加するためのTODOコメントが追加されています。これにより、パッケージとメンバーの分離を意識した補完の基盤が作られています。
  2. misc/vim/plugin/godoc.vim:

    • Godocコマンドの定義が変更されています。
      • command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<q-args>) から
      • command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<f-args>) へ変更されています。
      • <q-args>は引数をクォートして渡しますが、<f-args>は引数をそのまま(スペース区切りで)関数に渡します。これにより、s:Godoc関数が複数の引数(パッケージ名とメンバー名)を個別に受け取れるようになります。
    • s:GodocWord関数が変更されています。
      • returnステートメントがreturn 0に変更され、関数が成功したか失敗したかを示す戻り値を持つようになりました。これは、s:Godoc関数でのエラーハンドリングを改善するために使用されます。
    • s:Godoc関数が大幅に修正されています。
      • 引数a:000(可変長引数リスト)の長さをチェックし、引数が与えられていない場合は、以前と同様にカーソル下の単語を取得します。
      • カーソル下の単語を取得する際に、substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g') を追加し、単語から不要な文字を除去しています。
      • split(word, '\\.\\ze[^./]\\+$') を使用して、単語をパッケージ名とメンバー名に分割します。\\.\\ze[^./]\\+$という正規表現は、最後のドット(.)で区切り、その後にスラッシュ(/)やドット(.)が続かない文字列(つまりメンバー名)がある場合に分割することを意味します。これにより、encoding/json.Marshalのような形式をencoding/jsonMarshalに分割できます。
      • s:GodocWord(words[0])を呼び出して、まずパッケージのドキュメントを検索します。
      • もし複数の単語(パッケージ名とメンバー名)がある場合、search()関数を使って、ドキュメント内でメンバー名(words[1])が定義されている行を検索します。これにより、godocの出力から特定のメンバーのドキュメント部分にジャンプできるようになります。
      • search('^\\%(const\\|var\\|type\\|\\s\\+\\) ' . words[1] . '\\s\\+=\\s') は、定数、変数、型の定義を検索します。
      • search('^func ' . words[1] . '(') は、関数の定義を検索します。
      • 検索に成功した場合、returnして処理を終了します。
      • 検索に失敗した場合、echo 'No documentation found for "' . words[1] . '".' と表示し、メンバーのドキュメントが見つからなかったことをユーザーに通知します。

これらの変更により、VimのGodocコマンドは、godoc encoding/json Marshalのように、パッケージとメンバーを区別してドキュメントを検索できるようになりました。

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

misc/vim/autoload/go/complete.vim

--- a/misc/vim/autoload/go/complete.vim
+++ b/misc/vim/autoload/go/complete.vim
@@ -31,6 +31,12 @@ endif
 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 []
+  endif
+
   if executable('go')
     let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
     if v:shell_error

misc/vim/plugin/godoc.vim

--- a/misc/vim/plugin/godoc.vim
+++ b/misc/vim/plugin/godoc.vim
@@ -31,7 +31,7 @@ if !exists('g:go_godoc_commands')
 endif
 
 if g:go_godoc_commands
-  command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<q-args>)
+  command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<f-args>)
 endif
 
 nnoremap <silent> <Plug>(godoc-keyword) :<C-u>call <SID>Godoc('')<CR>
@@ -71,7 +71,7 @@ function! s:GodocWord(word)
     echo "godoc command not found."
     echo "  install with: go get code.google.com/p/go.tools/cmd/godoc"
     echohl None
-    return
+    return 0
   endif
   let word = a:word
   silent! let content = system('godoc ' . word)
@@ -80,12 +80,12 @@ function! s:GodocWord(word)
       silent! let content = system('godoc ' . s:last_word.'/'.word)
       if v:shell_error || !len(content)
         echo 'No documentation found for "' . word . '".'
-        return
+        return 0
       endif
       let word = s:last_word.'/'.word
     else
       echo 'No documentation found for "' . word . '".'
-      return
+      return 0
     endif
   endif
   let s:last_word = word
@@ -96,30 +96,34 @@ function! s:GodocWord(word)
   silent! normal gg
   setlocal nomodifiable
   setfiletype godoc
+  return 1
 endfunction
 
 function! s:Godoc(...)
-  let word = join(a:000, ' ')
-  if !len(word)
+  if !len(a:000)
     let oldiskeyword = &iskeyword
     setlocal iskeyword+=.
     let word = expand('<cword>')
     let &iskeyword = oldiskeyword
+    let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
+    let words = split(word, '\\.\\ze[^./]\\+$')
+  else
+    let words = a:000
   endif
-  let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
-  let words = split(word, '\.')
   if !len(words)
     return
   endif
-  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
+  if 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] . '(')
+        silent! normal zt
+        return
+      endif
+      echo 'No documentation found for "' . words[1] . '".'
     endif
-    echo 'No documentation found for "' . word . '".'
   endif
 endfunction
 

コアとなるコードの解説

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

このファイルは、VimのGoプラグインにおけるコマンドライン補完のロジックを扱っています。

   let words = split(a:CmdLine, '\s\+', 1)
   if len(words) > 2
     " TODO Complete package members
     return []
   endif
  • a:CmdLineは、現在のコマンドラインの文字列全体を指します。
  • split(a:CmdLine, '\s\+', 1)は、コマンドラインを1つ以上の空白文字で分割し、wordsリストに格納します。1は空の要素を生成しないためのフラグです。
  • if len(words) > 2の条件は、コマンドラインに3つ以上の単語がある場合(例: :Godoc encoding/json Marshal の場合、Godoc, encoding/json, Marshalの3つ)に真となります。
  • このブロックは、将来的にパッケージメンバーの補完機能を追加するためのプレースホルダーとして機能します。現時点では、3つ以上の単語がある場合は空のリストを返し、補完を行わないようにしています。これは、パッケージとメンバーの分離を考慮した補完ロジックの第一歩です。

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

このファイルは、VimのGodocコマンドの主要なロジックを含んでいます。

Godocコマンドの定義変更

-  command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<q-args>)
+  command! -nargs=* -range -complete=customlist,go#complete#Package Godoc :call s:Godoc(<f-args>)
  • command!はVimのユーザー定義コマンドを宣言します。
  • -nargs=*は、コマンドが任意の数の引数を取ることができることを示します。
  • -rangeは、コマンドが範囲指定(例: :'<,'>Godoc)を受け入れることを示します。
  • -complete=customlist,go#complete#Packageは、このコマンドの引数補完にgo#complete#Package関数を使用することを指定します。
  • 最も重要な変更は、:call s:Godoc(<q-args>)から:call s:Godoc(<f-args>)への変更です。
    • <q-args>は、コマンドラインの引数を単一の文字列として、適切にクォート(エスケープ)して関数に渡します。例えば、Godoc encoding/json Marshal"encoding/json Marshal"として渡されます。
    • <f-args>は、コマンドラインの引数をスペースで区切られた複数の引数として、そのまま関数に渡します。例えば、Godoc encoding/json Marshals:Godoc("encoding/json", "Marshal")のように渡されます。
  • この変更により、s:Godoc関数がパッケージ名とメンバー名を別々の引数として受け取ることが可能になり、それぞれの引数を個別に処理できるようになります。

s:GodocWord 関数の変更

-    return
+    return 0
-        return
+        return 0
-      return
+      return 0
+  return 1
  • s:GodocWord関数は、与えられた単語(パッケージ名またはメンバー名)に対してgodocコマンドを実行し、その結果をVimのバッファに表示する役割を担っています。
  • 変更前は、エラー発生時やドキュメントが見つからない場合に単にreturnしていました。
  • 変更後、return 0またはreturn 1を返すようになりました。0は失敗、1は成功を示します。これにより、呼び出し元のs:Godoc関数がs:GodocWordの実行結果に基づいて、後続の処理を制御できるようになります。

s:Godoc 関数の変更

この関数は、Godocコマンドが呼び出されたときに実際に実行されるメインのロジックです。

 function! s:Godoc(...)
-  let word = join(a:000, ' ')
   if !len(a:000)
     let oldiskeyword = &iskeyword
     setlocal iskeyword+=.
     let word = expand('<cword>')
     let &iskeyword = oldiskeyword
+    let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
+    let words = split(word, '\\.\\ze[^./]\\+$')
+  else
+    let words = a:000
   endif
-  let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
-  let words = split(word, '\.')
   if !len(words)
     return
   endif
-  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
+  if 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] . '(')
+        silent! normal zt
+        return
+      endif
+      echo 'No documentation found for "' . words[1] . '".'
     endif
-    echo 'No documentation found for "' . word . '".'
   endif
 endfunction
  • if !len(a:000): Godocコマンドに引数が与えられなかった場合(例: :Godocとだけ入力された場合)の処理です。
    • expand('<cword>')でカーソル下の単語を取得します。
    • substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')で、取得した単語からGoの識別子として不適切な文字を除去します。
    • split(word, '\\.\\ze[^./]\\+$')で、単語をパッケージ名とメンバー名に分割します。例えば、json.Marshal["json", "Marshal"]に分割されます。\\.\\ze[^./]\\+$は、最後のドットで分割し、そのドットの後にスラッシュやドットが続かない文字列(メンバー名)がある場合にマッチします。
  • else: Godocコマンドに引数が与えられた場合(例: :Godoc encoding/json Marshal)の処理です。
    • words = a:000により、<f-args>で渡された引数リストがそのままwords変数に代入されます。これにより、words[0]がパッケージ名、words[1]がメンバー名となります。
  • if !len(words): wordsリストが空の場合(有効な引数がなかった場合)は、何もせずにreturnします。
  • if s:GodocWord(words[0]): まず、wordsリストの最初の要素(パッケージ名)に対してs:GodocWordを呼び出し、パッケージのドキュメントを表示します。s:GodocWordが成功した場合(1を返した場合)にのみ、次のメンバー検索に進みます。
  • if len(words) > 1: wordsリストに2つ以上の要素がある場合(つまり、パッケージ名とメンバー名が指定された場合)の処理です。
    • search('^\\%(const\\|var\\|type\\|\\s\\+\\) ' . words[1] . '\\s\\+=\\s'): godocの出力バッファ内で、constvartype、または空白で始まる行で、2番目の単語(メンバー名)が定義されている箇所を検索します。これは、定数、変数、型のドキュメントを見つけるためのものです。
    • search('^func ' . words[1] . '('): godocの出力バッファ内で、funcで始まる行で、2番目の単語(メンバー名)が定義されている箇所を検索します。これは、関数のドキュメントを見つけるためのものです。
    • silent! normal zt: 検索に成功した場合、カーソルを検索結果の行に移動し、その行が画面の上部に表示されるようにスクロールします。
    • echo 'No documentation found for "' . words[1] . '".': メンバーのドキュメントが見つからなかった場合に、その旨をユーザーに通知します。

これらの変更により、s:Godoc関数は、引数の有無、引数の形式(パッケージのみか、パッケージとメンバーか)に応じて、適切なgodoc検索とVimバッファ内のナビゲーションを実行できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のgodocコマンドに関する情報
  • Vimscriptの関数(expand, split, substitute, search, systemなど)に関するVimのヘルプドキュメント
  • Vimのコマンド定義(command!, -nargs, -complete, <q-args>, <f-args>など)に関するVimのヘルプドキュメント
  • Go言語のパッケージ構造とドキュメンテーションの慣習に関する情報
  • GitHubのコミット履歴と差分表示
  • Go CL (Change List) 14171043: https://golang.org/cl/14171043 (これはコミットメッセージに記載されているリンクであり、変更の直接的な情報源です。)