LSPのcompletionメモ

LSPを使ううえで、最も嬉しいことのひとつに、補完機能があるだろう。 LSPをサポートした各種IDEテキストエディタのサンプル画面や動画では、補完機能を利用してサクサクと入力していく例が示されている。

ただ、実際のLSPサーバから送られる補完情報(Completion Response)は、ややこしいという感想。 現時点での各種サーバのレスポンスをメモってみる。

Solargraph

rubyのLSPサーバのSolargraph。個人的に一番使っている。 Rubocopによるメッセージ(Diagnostics)をがしがし送ってくる。 formattingリクエストを出すと、整形後のテキスト全部を一気に送り返してくる。 あとは、onTypeFormatting機能があればなぁ。

0.48.0というバージョンでの動作。 putという文字を入力したときのcompletion response。

{
    "jsonrpc": "2.0",
    "id": 9,
    "result": {
        "isIncomplete": false,
        "items": [
            {
                "label": "putc",
                "kind": 2,
                "detail": "(ch)",
                "data": {
                    "path": "Kernel#putc",
                    "return_type": "undefined",
                    "location": null,
                    "deprecated": false,
                    "uri": "file:///Users/masahino/Program/mrbmacs/mruby/test.rb"
                },
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 0,
                            "character": 0
                        },
                        "end": {
                            "line": 0,
                            "character": 9
                        }
                    },
                    "newText": "putc"
                },
                "sortText": "0000putc"
            },
            {
                "label": "puts",
                "kind": 2,
                "detail": "(*args)",
                "data": {
                    "path": "Kernel#puts",
                    "return_type": "undefined",
                    "location": {
                        "filename": "/Users/masahino/Program/mrbmacs/mruby/mrbgems/mruby-io/mrblib/kernel.rb",
                        "range": {
                            "start": {
                                "line": 19,
                                "character": 2
                            },
                            "end": {
                                "line": 21,
                                "character": 5
                            }
                        }
                    },
                    "deprecated": false,
                    "uri": "file:///Users/masahino/Program/mrbmacs/mruby/test.rb"
                },
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 0,
                            "character": 0
                        },
                        "end": {
                            "line": 0,
                            "character": 9
                        }
                    },
                    "newText": "puts"
                },
                "sortText": "0000puts"
            }
        ]
    }
}

labelは必須項目。

detailには、この場合引数の情報を入れているのかな?

選択したときのテキストはtextEditで指定している。 textEditで送ってもらえると、入力途中の文字も含めて編集できるのでありがたい。 この場合は、すでに1文字putを入力しているが、そのputを置き換える形で、putcputsを入力することができる。

付加情報はdataで送られる。

sortTextは補完候補に出すソート順だが、0000を文字列の前に付けているようだ。

clangd

次はclangd。C/C++用のLSP。機能豊富なイメージ。onTypeFormattingも対応。

バージョンは、15.0.7。

こちらもputという文字を入力したときのレスポンス。

{
    "id": 5,
    "jsonrpc": "2.0",
    "result": {
        "isIncomplete": false,
        "items": [
            {
                "documentation": {
                    "kind": "plaintext",
                    "value": "From <stdio.h>"
                },
                "filterText": "putc_unlocked",
                "insertText": "putc_unlocked",
                "insertTextFormat": 1,
                "kind": 1,
                "label": " putc_unlocked(x, fp)",
                "score": 1.119854211807251,
                "sortText": "4070a89eputc_unlocked",
                "textEdit": {
                    "newText": "putc_unlocked",
                    "range": {
                        "end": {
                            "character": 5,
                            "line": 3
                        },
                        "start": {
                            "character": 2,
                            "line": 3
                        }
                    }
                }
            },
            {
                "detail": "int",
                "documentation": {
                    "kind": "plaintext",
                    "value": "From <stdio.h>"
                },
                "filterText": "putc",
                "insertText": "putc",
                "insertTextFormat": 1,
                "kind": 3,
                "label": " putc(int, FILE *)",
                "score": 0.5933540463447571,
                "sortText": "40e819f3putc",
                "textEdit": {
                    "newText": "putc",
                    "range": {
                        "end": {
                            "character": 5,
                            "line": 3
                        },
                        "start": {
                            "character": 2,
                            "line": 3
                        }
                    }
                }
            },
            {
                "detail": "int",
                "documentation": {
                    "kind": "plaintext",
                    "value": "From <stdio.h>"
                },
                "filterText": "puts",
                "insertText": "puts",
                "insertTextFormat": 1,
                "kind": 3,
                "label": " puts(const char *)",
                "score": 0.5933540463447571,
                "sortText": "40e819f3puts",
                "textEdit": {
                    "newText": "puts",
                    "range": {
                        "end": {
                            "character": 5,
                            "line": 3
                        },
                        "start": {
                            "character": 2,
                            "line": 3
                        }
                    }
                }
            }
        ]
    }
}

長いので一部のみ。

detailintとあるのは、戻り値か?

textEditもあるが、さらにinsertTextも付いている。

付加情報はdocumentationにあるようだ。

sortTextにはなにやら個別の数値が16進数で付いている。 そのほかにscoreなるものもある。

pyls

python用のpyls。バージョンは0.36.2。 printまで入力した。

{
    "jsonrpc": "2.0",
    "id": 14,
    "result": {
        "isIncomplete": false,
        "items": [
            {
                "label": "print(values, sep, end, file, flush)",
                "kind": 3,
                "detail": "builtins",
                "documentation": "print(*values: object, sep: Optional[Text]=..., end: Optional[Text]=..., file: Optional[_Writer]=..., flush: bool=...) -> None\n\nprint(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n\nPrints the values to a stream, or to sys.stdout by default.\nOptional keyword arguments:\nfile:  a file-like object (stream); defaults to the current sys.stdout.\nsep:   string inserted between values, default a space.\nend:   string appended after the last value, default a newline.\nflush: whether to forcibly flush the stream.",
                "sortText": "aprint",
                "insertText": "print"
            }
        ]
    }
}

textEditは無い。なので、選択された場合には、insertTextを挿入することになる。 この場合、既に入力済み文字列の扱いが面倒。

detailにはbuiltinsとある。

付加情報はdocumentationにたっぷりと。

sortTextは挿入する文字列の先頭にaを付けている?

gopls

{
    "jsonrpc": "2.0",
    "result": {
        "isIncomplete": true,
        "items": [
            {
                "label": "print",
                "kind": 3,
                "detail": "func(args ...Type)",
                "preselect": true,
                "sortText": "00000",
                "filterText": "print",
                "insertTextFormat": 1,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 10,
                            "character": 0
                        },
                        "end": {
                            "line": 10,
                            "character": 5
                        }
                    },
                    "newText": "print"
                }
            },
            {
                "label": "println",
                "kind": 3,
                "detail": "func(args ...Type)",
                "sortText": "00001",
                "filterText": "println",
                "insertTextFormat": 1,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 10,
                            "character": 0
                        },
                        "end": {
                            "line": 10,
                            "character": 5
                        }
                    },
                    "newText": "println"
                }
            },
            {
                "label": "fmt.Print",
                "kind": 3,
                "detail": "func(a ...any) (n int, err error)",
                "documentation": "Print formats using the default formats for its operands and writes to standard output.\nSpaces are added between operands when neither is a string.\nIt returns the number of bytes written and any write error encountered.\n",
                "sortText": "00004",
                "filterText": "fmt.Print",
                "insertTextFormat": 1,
                "textEdit": {
                    "range": {
                        "start": {
                            "line": 10,
                            "character": 0
                        },
                        "end": {
                            "line": 10,
                            "character": 5
                        }
                    },
                    "newText": "fmt.Print"
                }
            }
        ]
    },
    "id": 6
}

数が多いので、途中省略。

isIncompletetrueだった。

編集テキストはtextEditのみ。

documentationに詳しい情報。

sortTextには数字が入っている。

今日のまとめ

と、ここまで3つのサーバで試してみたが、それぞれの個性が表われている。 completion responseで必須なのはlabelだけなので、あとはあったりなかったり。 kindは3つともあったが。

clangdとpylsはlabelの内容に実際に補完する文字列以上の情報を入れているが、solargraphは挿入する文字列情報のみと見えた。

detailの内容は、それぞれバラバラのようで、使いづらい。

documentationの内容の詳細具合も違っているようなので、一律で扱うのはしんどそう。

世の中のIDEやらテキストエディタは結構大変なことやってるんだなぁという感想。