Neovim + vim-lsp + solargraph + docker

## 概要
Railsのローカル開発環境をDocker化したら、LSPサーバーもDockerで動かしたくなりました。
記事を漁ると`VS Code`や`Neovim + COC`での設定方法は見つかったのですが、
`Neovim + vim-lsp`での構成が見つからなかったので試してみました。

## やったことポイント
- Solargraph on Docker :whale:
    - socketモードで起動する([参考](https://solargraph.org/guides/language-server))
    - ソースパスがDockerとホストで一致するようにシンボリックリンクを設定([参考](https://github.com/castwide/vscode-solargraph/issues/156))
    - solargraphの起動完了がホスト側で検知できるように`healthchek`を設定する
        (Neovimの起動スクリプトでコンテナを起動+Lsp登録するため)
- vim-lsp
    - tcpサポートはNeovimでは使えない:cry:([参考](https://github.com/prabirshrestha/vim-lsp/issues/1148))のでnetcat:cat:を使う
        - nvim-lspに乗り換えようかと思ったけどそっちもtcpサポートしてなかった:cry:([参考](https://github.com/neovim/neovim/issues/11911))
    - Neovim起動時にコンテナを起動して、終了時にコンテナを落とす

## やったこと
### 1. Solargraph on Dockerの起動コマンドのイメージ
こんなかんじのコマンドで起動する。
```sh
docker run -d --rm -p 7658:7658 -v $PWD:/usr/src/app \
  --health-cmd 'ps aux | grep port=7658 | grep -vc grep || exit 1' \
  --health-interval=1s --health-timeout=1s --health-retries=10 \
  XXXXX/YYYYY:latest \
  bash -c "mkdir -p `dirname $PWD` && ln -snf /usr/src/app $PWD && solargraph socket --host=0.0.0.0 --port=7658"
```

- Dockerコンテナ内のソースパスは`/usr/src/app`です（これは自分のコンテナにあった値を使いましょう)
- `-v $PWD:/usr/src/app`でホストのソースパスをコンテナにマウントしてます
- `ps aux | grep port=7658 | grep -vc grep || exit 1`でポート7658でsolargraphが起動しているかをhealthチェックで確認してます(ここも自分のコンテナで使えるコマンドでいい感じにしましょう)
- solargraphを実行する前に、ホストのソースパス(`$PWD`)と同じパスをコンテナ内に作成して、コンテナ内のソースパスにリンクを貼ってます。(`mkdir -p $(dirname $PWD) && ln -snf /usr/src/app $PWD`)
- solargraphをsocketモードで起動してコンテナの外から問い合わせできるようにします(`solargraph socket --host=0.0.0.0 --port=7658`)

### 2. vim-lspでサーバー登録のイメージ
こんなかんじのコマンドで登録する
```vim
  autocmd User lsp_setup call lsp#register_server({
    \    'name': 'solargraph',
    \    'cmd': {server_info->['nc', '127.0.0.1', "7658"]},
    \    'allowlist': ['ruby'],
    \    'initialization_options': {"logLevel": "debug"},
    \})
```
- netcat(`nc`)でSolargraph on Dockerコンテナとやり取りします
- lsp_setupってautocmdで登録します

## 最終的にこうなりました
Neovim起動時に以下を読み込んで、コンテナ起動、起動が完了するまで待ってからLSP登録、Neovim終了時にコンテナ停止します
```vim
let s:solargraph_port = 0
let s:solargraph_docker_id = ""                                                          
                                            
function! s:setup_solargraph() abort
  if s:solargraph_port > 0
    return
  endif

  let s:solargraph_port = 7658
  let l:result =  system("lsof -i:".s:solargraph_port)
  while len(l:result) > 0
    let s:solargraph_port = s:solargraph_port + 1
    let l:result =  system("lsof -i:".s:solargraph_port)
  endwhile

  let s:solargraph_docker_id = system("docker run -d --rm -p ".s:solargraph_port.":7658 -v $PWD:/usr/src/app --health-cmd 'ps aux | grep port=7658 | grep -vc grep || exit 1' --health-interval=1s --health-timeout=1s --health-retries=10 XXXXX/YYYYY:latest bash -c \"mkdir -p `dirname $PWD` && ln -snf /usr/src/app $PWD && solargraph socket --host=0.0.0.0 --port=7658\"")
  let l:health = system("docker inspect -f '{{if eq \"healthy\" .State.Health.Status}}0{{else}}1{{end}}' ".s:solargraph_docker_id)
  while l:health == 1
    sleep 1
    let l:health = system("docker inspect -f '{{if eq \"healthy\" .State.Health.Status}}0{{else}}1{{end}}' ".s:solargraph_docker_id)
  endwhile

  call lsp#register_server({
    \    'name': 'solargraph',
    \    'cmd': {server_info->['nc', '127.0.0.1', s:solargraph_port]},
    \    'allowlist': ['ruby'],
    \    'initialization_options': {"logLevel": "debug"},
    \})
endfunction

function! s:shutdown_solargraph() abort
  if len(s:solargraph_docker_id) == 0
    return
  endif

  let l:result = system("docker stop ".s:solargraph_docker_id)
endfunction

augroup solargraph_setup
  au!
  autocmd User lsp_setup call s:setup_solargraph()
  autocmd ExitPre * call s:shutdown_solargraph()
augroup END
```