2
1

More than 3 years have passed since last update.

Atom の PHP 補完を vim-lsp で使う

Posted at

Vim で PHP を書くときは vim-lsp で Intelephense を使っているのですが、プロプライエタリなソフトウェアなのでサーバサイドのコードが公開されていません。Intelephense のウェブサイトはあまり親切ではなく、初めてインストールしたときは正しく動かせているのかよく分からなかった事を覚えています。
そのため OSS の PHP LSP を探していたところ Serenata というソフトウェアを見つけました。

Serenata は Atom エディタのプラグインとして公開されていたようです。サーバ型の Serenata 本体と、Atom プラグインの php-ide-serenata の2つで構成されています。バージョン4までは Atom エディタ用ですがバージョン5で Language Server Protocol をサポートしたようです。
対応している LSP の機能は Language Server Protocol Support Table のページ で確認できます。

php-ide-serenataの動作画面

php-ide-serenata.png

今回はこの Serenata をvim-lspから使えるように設定してみました。

vim-lsp の動作画面
serenata_completion.png

Serenata はTCPソケットしかサポートされていなかったので、vim-lsp の標準入出力とつなげるために socat コマンドを使いました。
ひとまず vim 側での補完やホバー表示ができるところまでは確認したのですが、速度面で難があったり、vim-lsp の起動initialize/終了exitとうまく連動できないので実用レベルには至りませんでした。 もう少し調査して使えるようにしたいのですが一旦ここまでの記録を公開します。

事前準備

vim-lsp が動く状態になっていることが前提です。その他に必要なソフトウェアは以下の通りです。

  • PHP 7.1+
    • mbstring
    • xml
    • libxml
    • dom
    • openssl
    • pdo_sqlite
  • socat コマンド
  • vim
    • vim-lsp

socat コマンドがインストールされているか確認してください。macOS は brew でインストール可能です。

$ which socat
/usr/local/bin/socat
$ socat -V
socat by Gerhard Rieger and contributors - see www.dest-unreach.org
socat version 1.7.3.3 on May 11 2019 02:21:21
...
$ brew install socat # インストールされていなければ

Serenata の導入

1. 実行可能な PHAR 形式のファイルをダウンロード

Serenata サーバをダウンロードしてください。インストール方法はいくつかありますが実行可能な PHAR 形式のファイルが簡単です。PHAR compatible with PHP 7.x を選択してください。

2. ポート 11111 で起動

Serenata サーバをポート 11111 で起動してみましょう。Serenata はソースコードの解析結果をデータベースに保存します。デフォルト設定では SQLite をインメモリで使用します。メモリ使用量が多いので php の起動オプションでメモリを多めに確保します。

$ php -d memory_limit=2048M ~/bin/serenata.phar --uri tcp://127.0.0.1:11111
Starting server bound to socket on URI tcp://127.0.0.1:11111...

正常に起動できれば Starting server bound .... と表示されて LISTEN 状態になります。

3. 動作確認

動作確認のために initialize メソッドを実行してみます。socat を使って Serenata サーバのポート 11111 に送信します。Content-Length には JSON のバイト数が書かれていますので、rootPath rootUri を変更したら Content-Length も忘れずに変更してください。1バイトでも合わないと正しく動作しません。
また、保存するファイルのフォーマットにも注意してください。LSP の仕様で改行コードは CRLF 、ファイル最終行の行末には改行を含めないようにしてください。

serenata.jsonrpc
Content-Length: 1429

{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":19002,"rootPath":"/Users/foobar/src/foobar/foobarbazfo/","rootUri":"file:///Users/foobar/src/foobar/foobarbazfo/","workspaceFolders":[],"capabilities":{"workspace":{"applyEdit":true,"configuration":false,"workspaceEdit":{"documentChanges":true},"workspaceFolders":false,"didChangeConfiguration":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":false},"symbol":{"dynamicRegistration":false},"executeCommand":{"dynamicRegistration":false}},"textDocument":{"synchronization":{"dynamicRegistration":false,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":false,"completionItem":{"snippetSupport":true,"commitCharactersSupport":false},"contextSupport":true},"hover":{"dynamicRegistration":false},"signatureHelp":{"dynamicRegistration":false},"references":{"dynamicRegistration":false},"documentHighlight":{"dynamicRegistration":false},"documentSymbol":{"dynamicRegistration":false,"hierarchicalDocumentSymbolSupport":true},"formatting":{"dynamicRegistration":false},"rangeFormatting":{"dynamicRegistration":false},"onTypeFormatting":{"dynamicRegistration":false},"definition":{"dynamicRegistration":false},"codeAction":{"dynamicRegistration":false},"codeLens":{"dynamicRegistration":false},"documentLink":{"dynamicRegistration":false},"rename":{"dynamicRegistration":false}},"experimental":{}}}}

動作確認するだけならパスの変更は不要です

実行して Content-Length で始まるレスポンスが返ってくれば成功です。

$ socat stdio tcp4:127.0.0.1:11111,shut-none < serenata.jsonrpc
Content-Length: 951

{"jsonrpc":"2.0","id":0,"result":{"capabilities":{"textDocumentSync":{"openClose":false,"change":1,"willSave":false,"willSaveWaitUntil":false,"save":{"includeText":true}},"hoverProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":null},"signatureHelpProvider":{"triggerCharacters":["(",","]},"definitionProvider":true,"typeDefinitionProvider":false,"implementationProvider":false,"referencesProvider":false,"documentHighlightProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":false,"codeActionProvider":false,"codeLensProvider":{"resolveProvider":true},"documentFormattingProvider":false,"documentRangeFormattingProvider":false,"documentOnTypeFormattingProvider":null,"renameProvider":false,"documentLinkProvider":null,"colorProvider":false,"foldingRangeProvider":false,"executeCommandProvider":null,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"experimental":null}}}

vim-lsp 設定

1. lsp#register_server の設定

Serenata が正しく動いていることが確認できたので vim-lsp の設定を追加してください。

au User lsp_setup call lsp#register_server({
    \ 'name': 'serenata',
    \ 'cmd': {server_info->['socat', 'stdio', 'tcp4:127.0.0.1:11111,shut-none']},
    \ 'initialization_options': {"rootPath":"/Users/foobar/src/foobar/foobarbazfo/","rootUri":"file:///Users/foobar/src/foobar/foobarbazfo/"},
    \ 'whitelist': ['php'],
    \ 'workspace_config': { 'serenate': {
    \   'files.associations': ['*.php'],
    \ }},
    \ })

2. 動作確認

Serenataサーバを起動した状態で vim で PHP のファイルを開きます。vim-lsp が Serenata サーバに正しく接続できれば :LspStatus の実行結果が serenata: running になります。vim-lsp と Serenata の起動・終了の仕様に齟齬があるので、vim を終了した場合は Serenata サーバも再起動しないと LspStatus の結果が failed になります。

まとめ

Serenata はサーバ型なので LSP の exit メソッド実行されても終了せず、vim-lsp が再度 initialize メソッドを実行するとエラーになります。速度も Atom エディタで使うより遅いのでもう少し改善の余地がありそうです。
また成果があれば公開します。

その他のスクリーンショット

クラス名の補完
serenata_completion_type.png
メソッド補完
selenata_completion_method.png
クラスのホバー表示
serenata_hover_type.png
メソッドのホバー表示
serenata_hover_method.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1