gopls(v0.4.0)の機能

goplsは更新頻度が高く、何ができるのかをちゃんと把握できていなかったので、LSPのメソッドベースで今の時点(v0.4.0)の機能をざっと調べてみることにしました。

補完(textDocument/completion)

入力中の変数や定数、関数といった各種定義などをbuiltinも含めて補完してくれます。
この機能のためにgoplsを使っているという人もけっこう多いはず。

基本的に文脈に合わせた候補を返したり、優先度が高くなるようになっていますが、

  • おかしな候補が返ってくる
  • 期待する候補が返ってこない

という場合はコントリビュート(issue報告や修正する)チャンスかもしれません。

例えば、

  • スコープに存在しない定義は候補にならない
    • スコープ外の変数など
  • カーソル位置で使えないキーワードは候補にならない
    • iota: const入力中のみ
    • range: for入力中のみ
    • break: for、switch、select内
    • continue: for内
    • など
  • 型が合わないものは候補から外されることがある
    • 代入時

のような制御があります。

また、以下の場合は補完で入力される内容自体が変化します。

  • 参照が要求される場合、値には&がつく
// vへの代入時、
var v *int
i := 1
v = // iは&iになる

// func f(i *int)を呼び出す際、
f( // iは&iに変換される
  • 値が要求される場合、参照には*がつく
// iへの代入時、
var v *int
var i int = // vは*vになる

// func f(i int)を呼び出す際、
f( // vは*vに変換される
  • 可変長引数の場合に...が展開される
func fn(v ...int) {}

func do(v []int) {
    fn( // vはv...になる
}
  • 引数の型がわかる場合はその型が補完される
    • func(...[]int) を呼び出す際は[]int{}が候補になる
    • var _ []int = make()の第1引数は[]intが候補になる

ただし、プレースホルダ*1スニペット*2が有効になっていないと変換されなかったり、候補として表示されません。
VS Codeはデフォルトで有効になっているはずですが、Vimの場合はプラグインによっては有効になっていないかもしれません。
そしてシグネチャを補完・展開する際にはこれらが必須となっているため、もしうまく動かない時は設定を確認してみましょう。

他にも、次の設定で補完の動作を変えることができます。

  • initializationOptions.matcher
    • デフォルトは"fuzzy"なので多少違っている候補も返ってきますが、
    • "caseSensitive""caseInsensitive"に変更できます
  • initializationOptions.completeUnimported
    • デフォルトはtrueなので未importのパッケージからも候補が返ってきますが、
    • falseで無効化できます
  • initializationOptions.deepCompletion
    • デフォルトはtrueなので構造体のフィールドも候補として返ってきますが、
    • falseで無効化できます
// deepCompletion=trueだと

type param struct{ i int }

func fn(i int) {}

func do(p param) {
    fn( // p.iが候補として表示される
}

それ以外の特殊な動作としては、

  • if err != nilスニペット展開
    • errorを返す関数内のみ
  • 公開されるvarのgodoc補完
    • 次のバージョンではconstfunctypeも対象になる

があります。

ジャンプ系

自分の場合は補完よりこちらを多用しています。
ただ、definitiontypeDefinitionは違いがわからないまま使っていました。

textDocument/definition

カーソル位置にあるシンボルの定義元にジャンプする時に使用します。
関数内の変数はその関数内で定義(宣言)された場所にジャンプします。

type myType struct{}

func fn() {
    t := myType{}    // ←
    t.method()       // tが宣言されたのは1行上
}

コマンドラインからはgopls query definitionsで使えます。
※次のバージョンからqueryが不要になる

gopls query definition internal/lsp/definition.go:14:67

textDocument/typeDefinition

カーソル位置にあるシンボルの型定義にジャンプする時に使用します。
関数内の変数は実際の型のある場所にジャンプします。

type myType struct{} // ←

func fn() {
    t := myType{}
    t.method()       // tの型定義はfnの上にあるmyType
}

textDocument/implementation

カーソル位置にあるシンボルの、

  • インタフェースから型
  • 型からインタフェース

にジャンプする時に使用します。

コマンドラインからはgopls implementationで使えます。

gopls implementation internal/lsp/implementation.go:14:10

textDocument/references

カーソル位置にあるシンボルが参照されている(使われている)場所にジャンプする時に使用します。

コマンドラインからはgopls referencesで使えます。

gopls references internal/lsp/references.go:14:18

textDocument/documentSymbol

現在開いているファイルのシンボル一覧を取得します。
結果はジャンプするためや、ファイルのアウトライン表示のためにも使われます。

コマンドラインからはgopls symbolsで使えます。
CLIでうまく動かないhttps://go-review.googlesource.com/c/tools/+/232557

workspace/symbol

プロジェクト(リポジトリ本体と依存パッケージ)からシンボル一覧を検索します。
※LSPの仕様では依存パッケージは含まれないはずですが、その方が便利なのでそういう実装になっています

今のところ結果は100件までに制限されています。
※一度に全部返してしまうとクライアントが高負荷で固まってしまうことがあるため

また、検索方法は補完と同じでinitializationOptions.matcherに従います。

コマンドラインからはgopls workspace_symbolで使えます。

gopls workspace_symbol WorkspaceSymbols

Vimからはtagfunc経由でctagsのように使うこともできます。
設定例は下記参照。 daisuzu.hatenablog.com

その他

Diagnostic以外はほとんど使っていませんでした。。。
しかし、今回調べたことでrenamegorenameの代わりになるくらい完成度が高くなっていたり、SuggestedFixの種類がだんだん増えていることがわかったのは大きな収穫でした。

Diagnostic

go vet(golang.org/x/tools/go/analysis/passes/...)やgofmt -sstaticcheckなどのチェックを行います。
実行するチェッカーは以下のオプションでカスタマイズできます。

  • initializationOptions.analyses
  • initializationOptions.staticcheck

結果にSuggestedFixが含まれている場合、codeAction経由で修正可能です。

SuggestedFixの例:

  • returnの返り値の数があっていない時に追加したり、削除したり
  • 2回目以降の:==に変更したり
  • 未定義変数の<変数名> :=を挿入したり

コマンドラインからはgopls checkで使えます。

gopls check internal/lsp/testdata/lsp/primarymod/analyzer/bad_test.go

textDocument/codeAction

SuggestedFixの実行やimportの追加・削除(go.modへの追加も含む)を行います。

コマンドラインからはgopls fixgopls importsで使えます。

textDocument/codeLens

調べてもイマイチよくわかっていない機能ですが、現状は//go:generateコメントのある行でgo generateが実行できるよ、という情報を返してくれるようです。
また、go.modを開いている場合は依存パッケージのアップデートができるかどうかを教えてくれるようです。

textDocument/hover

カーソル位置のシグネチャや型などの情報を返してくれます。

textDocument/signatureHelp

カーソル位置のシグネチャやヘルプを返してくれます。

コマンドラインからはgopls signatureで使えます。
<position>の指定方法がわからず...は関数呼び出しの()内の位置を指定する必要がある

gopls signature internal/lsp/signature_help.go:21:53

textDocument/documentHighlight

カーソル位置のシンボルが使われている場所をハイライトします。

コマンドラインからはgopls highlightで使えます。

gopls highlight internal/lsp/highlight.go:17:2

現在開いているファイルがimportしているパッケージの https://pkg.go.dev へのリンクや、 <org>/<repo>#<number>形式のコメントからGitHub Issuesへのリンクを返してくれます。

コマンドラインからはgopls linksで使えます。

gopls links internal/lsp/link.go

textDocument/formatting

現在開いているファイルを整形します。

コマンドラインからはgopls formatで使えます。

# -dでdiffを表示する
gopls format -d internal/lsp/testdata/lsp/primarymod/format/bad_format.go.in

textDocument/rename

カーソル位置のシンボルをリネームします。

コマンドラインからはgopls renameで使えます。

# -dでdiffを表示する
gopls rename -d internal/lsp/rename.go:14:18 doRename

リネーム可能かどうかを調べるためにはtextDocument/prepareRenameで確認できます。
コマンドラインからはgopls prepare_renameで使えます。

# rangeが表示されればリネーム可能
gopls prepare_rename internal/lsp/rename.go:14:18

textDocument/foldingRange

コードを折り畳む範囲を返してくれます。

コマンドラインからはgopls folding_rangesで使えます。

gopls folding_ranges internal/lsp/folding_range.go

workspace/executeCommand

codeLensの結果からgo generatego getの実行をしてくれるようです。
また、diagnosticの結果からgo mod tidyの実行をしてくれるようです。

未実装

以下のメソッドはまだ実装されていません。

*1:initializationOptions.usePlaceholders = true

*2:capabilities.textDocument.completion.completionItem.snippetSupport = true