Gopls: ナビゲーション機能

このページでは、Gopls のソースコードナビゲーション機能について説明します。

定義

LSP の textDocument/definition リクエストは、カーソル下のシンボルの宣言位置を返します。ほとんどのエディタは、その位置に直接移動するコマンドを提供しています。

定義クエリは、次のような予期せぬ場所でも機能します

  • インポートパス上では、インポートされたパッケージのファイル内の各パッケージ宣言の位置のリストを返します。
  • パッケージ宣言上では、そのパッケージのドキュメントを提供するパッケージ宣言の位置を返します。
  • go:linkname ディレクティブ内のシンボル上では、そのシンボルの宣言位置を返します。
  • ドキュメントリンク上では、( hover と同様に) リンクされたシンボルの位置を返します。
  • go:embed ディレクティブ内のファイル名上では、埋め込みファイルの場所を返します。
  • Go 以外の関数(本体のない func)の宣言上では、もしあれば、アセンブリ実装の位置を返します。
  • return ステートメント上では、関数の結果変数の位置を返します。
  • gotobreak、または continue ステートメント上では、それぞれラベル、関連するブロックステートメントの閉じ括弧、または関連するループの開始位置を返します。

クライアントサポート

  • VS Code: 定義へ移動F12 または -クリック)を使用します。カーソルがすでに宣言位置にある場合、リクエストは代わりに「参照へ移動」として解釈されます。
  • Emacs + eglot: M-x xref-find-definitions を使用します。
  • Vim + coc.nvim: ??
  • CLI: gopls definition file.go:#offset

参考文献

LSP の textDocument/references リクエストは、カーソル下のシンボルを参照するすべての識別子の位置を返します。

参照アルゴリズムは、構文の様々な部分を次のように扱います。

  • シンボルへの参照は、そのシンボルのすべての使用箇所を報告します。エクスポートされたシンボルの場合、これには他のパッケージ内の位置が含まれる場合があります。
  • パッケージ宣言への参照は、そのパッケージへの直接インポートと、同じパッケージ内の他のすべてのパッケージ宣言です。
  • intappend のような組み込みシンボルへの参照を要求することはエラーです。なぜなら、それらは数が多すぎて関心がないと想定されているからです。
  • インターフェースメソッドへの参照には、インターフェースを実装する具象型の参照が含まれます。同様に、具象型のメソッドへの参照には、対応するインターフェースメソッドへの参照が含まれます。
  • struct{T}のような構造体型における埋め込みフィールド T は、Go では型への参照とフィールドの定義の両方であるという点でユニークです。references 操作は、フィールドとしての参照のみを報告します。型への参照を見つけるには、まず型宣言にジャンプしてください。

参照クエリは、選択したファイルを解析するために使用されたビルド構成に関する情報のみを返すことに注意してください。したがって、foo_windows.go で定義されたシンボルへの参照を要求した場合、たとえ bar_linux.go ファイルが同じ名前のシンボルを参照していても、結果に bar_linux.go が含まれることはありません。詳細については、https://go.dokyumento.jp/issue/65755 を参照してください。

クライアントは、宣言をリファレンスに含めるように要求できます。ほとんどのクライアントはこれを行います。

クライアントサポート

  • VS Code: Go to References を使用して参照を素早く「プレビュー」するか、Find all References を使用して参照パネルを開きます。
  • Emacs + eglot: xref パッケージ経由で: M-x xref-find-references を使用します。
  • Vim + coc.nvim: ??
  • CLI: gopls references file.go:#offset

実装

LSP の textDocument/implementation リクエストは、抽象型と具象型、およびそれらのメソッド間の関係を照会します。

インターフェースと具象型はメソッドセットを使用して照合されます

  • インターフェース型への参照で呼び出されると、そのインターフェースを実装する各型の宣言位置を返します。
  • 具象型で呼び出されると、一致するインターフェース型の位置を返します。
  • インターフェースメソッドで呼び出されると、インターフェースを満たす型の対応するメソッドを返します。
  • 具象メソッドで呼び出されると、一致するインターフェースメソッドの位置を返します。

  • implementation(io.Reader) には、io.ReadCloser のようなサブインターフェースや、*os.File のような具象実装が含まれます。また、io.Reader と同等の他の宣言も含まれます。
  • implementation(os.File) には、io.Readerio.ReadCloser のようなインターフェースのみが含まれます。

LSP の実装機能には、サブタイプに対する組み込みの偏りがあります。おそらく、Java や C++ のような言語では、型とそのスーパータイプ間の関係が構文に明示されているため、対応する「インターフェースへ移動」操作は、「定義へ移動」の2つ以上のステップのシーケンスとして実現できるためです。最初のステップで型宣言を訪れ、残りのステップで祖先を順次訪れます。(https://github.com/microsoft/language-server-protocol/issues/2037 を参照してください。)

Go では、2つの型間に構文上の関係がないため、サブタイプとスーパータイプの間をいずれかの方向に移動する際には検索が必要です。上記のヒューリスティックは多くの場合にうまく機能しますが、io.ReadCloser のスーパーインターフェースを要求することはできません。サブタイプとスーパータイプの間でより明示的なナビゲーションを行うには、[型階層](#Type Hierarchy) 機能を使用してください。

自明でないインターフェースのみが考慮されます。型 any の実装は報告されません。

同じパッケージ内では、すべての一致する型/メソッドが報告されます。ただし、パッケージ間では、エクスポートされたパッケージレベルの型とそのメソッドのみが報告されるため、ローカル型(インターフェース、または埋め込みによるメソッドを持つ構造体型)が結果から欠落する可能性があります。

関数、func 型、および動的関数呼び出しはシグネチャを使用して照合されます

  • 関数定義func トークンで呼び出されると、一致するシグネチャ型と動的呼び出し式の位置を返します。
  • シグネチャ型func トークンで呼び出されると、一致する具象関数定義の位置を返します。
  • 動的関数呼び出し( トークンで呼び出されると、一致する具象関数定義の位置を返します。

ターゲット型または候補型のいずれかがジェネリックである場合、一方が他方を実装できるような両方の型のインスタンス化があれば、結果には候補型が含まれます。(注: 現在、マッチャーは完全なユニフィケーションを実装していないため、型パラメータは任意の型に一致するワイルドカードのように扱われ、メソッドセット全体または単一メソッド内での置換の一貫性は考慮されません。これにより、時折、偽の一致が生じる可能性があります。)

型は関数型であると同時にメソッドを持つ名前付き型である可能性もあるため (たとえば http.HandlerFunc )、両方の種類の実装クエリ (メソッドセットによるものと関数シグネチャによるもの) に参加する可能性があります。メソッドセットを使用するクエリは型またはメソッド名で呼び出す必要があり、シグネチャを使用するクエリは func または ( トークンで呼び出す必要があります。

クライアントサポート

  • VS Code: 実装へ移動⌘F12)を使用します。
  • Emacs + eglot: M-x eglot-find-implementation を使用します。
  • Vim + coc.nvim: ??
  • CLI: gopls implementation file.go:#offset

型定義

LSP の textDocument/typeDefinition リクエストは、選択されたシンボルの型の位置を返します。

たとえば、選択が *bytes.Buffer 型のローカル変数 buf の名前である場合、typeDefinition クエリは bytes.Buffer 型の位置を返します。クライアントは通常、その位置にナビゲートします。

ポインタ、配列、スライス、チャネル、マップなどの型コンストラクタは、名前付き型を検索する際に選択された型から取り除かれます。たとえば、x が chan []*T 型の場合、報告される型定義は T のものになります。同様に、シンボルの型が1つの「興味深い」(名前付きでエラーでない)結果型を持つ関数の場合、その関数の結果型が使用されます。

Gopls は現在、typeDefinition クエリが任意の式ではなくシンボルに適用されることを要求しています。この機能の潜在的な拡張については、https://go.dokyumento.jp/issue/67890 を参照してください。

クライアントサポート

  • VS Code: 型定義へ移動を使用します。
  • Emacs + eglot: M-x eglot-find-typeDefinition を使用します。
  • Vim + coc.nvim: ??
  • CLI: サポートされていません

ドキュメントシンボル

textDocument/documentSymbol LSP クエリは、このファイル内のトップレベル宣言のリストを報告します。クライアントはこの情報を使用して、ファイルの概要と、より高速なナビゲーションのためのインデックスを表示できます。

クライアントが hierarchicalDocumentSymbolSupport を示す場合、Gopls は DocumentSymbol 型で応答します。それ以外の場合は SymbolInformation を返します。

クライアントサポート

  • VS Code: ナビゲーションにはアウトラインビューを使用します。
  • Emacs + eglot: シンボルにジャンプするには M-x imenu を使用します。
  • Vim + coc.nvim: ??
  • CLI: gopls links file.go

シンボル

workspace/symbol LSP クエリは、ワークスペース内のすべてのシンボルのインデックスを検索します。

人気のあるあいまい一致検索ツール FZF から着想を得たデフォルトのシンボル一致アルゴリズム (fastFuzzy) は、クエリ内のスペルミスや略語を修正するために、さまざまな不正確な一致を試みます。たとえば、DocSymDocumentSymbol の一致と見なします。

設定

  • symbolMatcher 設定は、シンボルの一致に使用されるアルゴリズムを制御します。
  • symbolStyle 設定は、シンボル応答でシンボルがどのように修飾されるかを制御します。
  • symbolScope 設定は、クエリのスコープを決定します。
  • directoryFilters 設定は、検索から除外されるディレクトリを指定します。

クライアントサポート

  • VS Code: ⌘T を使用してワークスペーススコープのシンボルへ移動を開きます。(または、Ctrl-Shift-O を使用し、ファイル内を検索するには @ プレフィックスを、ワークスペース全体を検索するには # プレフィックスを追加します。)
  • Emacs + eglot: 検索語に一致するシンボルを表示するには M-x xref-find-apropos を使用します。
  • Vim + coc.nvim: ??
  • CLI: gopls links file.go

選択範囲

textDocument/selectionRange LSP クエリは、現在の選択範囲を囲む各構文要素の語彙的な範囲に関する情報を返します。クライアントはこれを使用して、選択範囲を順次大きな式に拡張する操作を提供できます。

クライアントサポート

  • VSCode: 選択範囲を拡張するには ⌘⇧^→ を、再度縮小するには ⌘⇧^← を使用します。このビデオをご覧ください。
  • Emacs + eglot: 標準ではありません。この設定スニペットで定義されている M-x eglot-expand-selection を使用してください。
  • Vim + coc.nvim: ??
  • CLI: サポートされていません

呼び出し階層

LSP CallHierarchy メカニズムは、クライアントが静的呼び出しグラフの一部を階層的に表示できるようにする3つのクエリで構成されています。

関数宣言内の名前を選択した状態でコマンドを呼び出します。

動的な呼び出しは分析的に検出することが実用的ではないため、含まれていません。したがって、結果が網羅的ではない可能性があることに注意し、必要に応じて 参照 クエリを実行してください。

階層は、ネストされた関数を囲む名前付き関数とは区別しません。(動的呼び出しを検出する機能がなければ、そうする意味はほとんどありません。)

以下のスクリーンショットは、f を根とする発信呼び出しツリーを示しています。このツリーは、f から fmt.Sprint の内部を経て fmt.StringerString メソッドへのパスを示すために展開されています。

クライアントサポート

  • VS Code: Show Call Hierarchy メニュー項目(⌥⇧H)で 呼び出し階層ビューが開きます(注: ドキュメントは C++ に言及していますが、Go でも考え方は同じです)。
  • Emacs + eglot: 標準ではありません。(package-vc-install "https://github.com/dolmens/eglot-hierarchy") でインストールしてください。選択した関数への直接の着信呼び出しを表示するには M-x eglot-hierarchy-call-hierarchy を使用し、直接の発信呼び出しを表示するにはプレフィックス引数 (C-u) を使用してください。ツリーを展開する方法はありません。
  • CLI: gopls call_hierarchy file.go:#offset は発信および着信呼び出しを表示します。

型階層

LSP の型階層メカニズムは、名前付き型に対するサブタイピング関係の一部を階層的に表示できるようにする3つのクエリで構成されています。

型の名前を選択した状態でコマンドを呼び出します。

実装クエリと同様に、型階層クエリは、クエリ対象の型と同じパッケージ内にある関数ローカル型のみを報告します。また、結果にはエイリアス型は含まれず、定義済み型のみが含まれます。

注意点

  • 型階層は、名前付き型とその代入可能性の関係のみをサポートします。対照的に、実装リクエストは、名前のない func 型と関数宣言、関数リテラル、およびこれらの型の値の動的呼び出し間の関係も報告します。

クライアントサポート

  • VS Code: Show Type Hierarchy メニュー項目で 型階層ビューが開きます(注: ドキュメントは Java に言及していますが、Go でも考え方は同じです)。
  • Emacs + eglot: 2025年3月にサポートが追加されました。M-x eglot-show-call-hierarchy を使用します。
  • CLI: まだサポートされていません。

このドキュメントのソースファイルは、golang.org/x/tools/gopls/doc の下にあります。