たぶん1年くらい前だと思うけど、eclimを使ってNeoVimでJavaの開発環境作ったことがある。
このeclim、バックグラウンドでEclipseを起動してJava開発環境を提供しているだけあってかなり高性能なんだけどEclipseよろしくワークスペースが必要だったり、バックグラウンドでEclipseが立ち上がるのでVimなのになんか重い、インストールがめんどくさくて自動化しづらい等辛い部分も多々あって、ほとんど使わずに消してしまった。
というわけで、結局Javaを書くときはIDE使ってたんだけど、最近になってeclipse.jdt.lsというJavaのLSPがあるのを見つけた。
絶賛、開発環境をLSPに置き換えキャンペーン中の僕としては
「eclipseの公式ぷらぎんみたいだし、環境構築自動化できそうだしこれは勝ったな(白痴)」
的な気持ちで環境構築してみた。
結局IDEには勝てなかったわけだけど、割りと自分的には満足できたので紹介して見る。
僕が使ってるのはNeoVimだけど、通常のVimでも同じような感じで環境構築できるはず。
そもそもさぁ…LSPってなんなの?
LSPとは、Language Server Protocel
の略で、2016年6月にMicrosoftが公開した仕様だ。
こいつが一体どういうもんなのか言うと、IDEに必要となるコード補完やエラー分析などの機能をサービスとして提供する上での共通仕様が定義されている。
このように仕様を共通化することで、LSPの言語実装があり使用しているエディタにLSPクライアントがあるならばどのような環境であってもIDE機能が利用可能となる。
LSPについてはこちらの記事にわりと詳しく書かれているし、公式サイトやGitHubでも仕様が公開されているので興味があれば見て貰いたい。
- language server protocolについて (前編) - Qiita
- Language Server Protocol
- Microsoft/language-server-protocol - GitHub
また、言語ごとの実装やエディタごとのクライアントについて下記でまとめられている。
今回の環境構築では、このLSPのJava実装であるeclipse.jdt.ls
と、NeoVimのLSPクライアントであるLanguageClient-neovim
を使ってJava開発環境を作るのに挑戦してみたわけだ。
今回使用した、最高にイカれたツールを紹介するぜ!
dein.vim
この人を知らないやつはモグリとまで言われている(自分調べ)暗黒美無王ことShougoさん開発のプラグイン管理ツール。
いろんなとこで紹介されているし説明は省略。
詳しいことはここらへんを参考にしてほしい。
deoplete.nvim
同じくShougoさん開発のコード補完プラグイン。
こっちも今更語る必要はないだろう。(適当)
これも詳しいことは別の記事に丸投げする。
eclipse.jdt.ls
前述したLSPのJava実装。今回の主役その1。
NeoVimの起動時に、こいつが自動で立ち上がり、後述する LanguageClient-neovim
とでメッセージのやり取りを行うことで、NeoVimにIDEの風を吹き込んでくれる憎いやつ。
起動速度もメモリ使用量もeclimよりもだいぶマシで、インストールもとりあえず自動化できる程度に簡単。
LanguageClient-neovim
前述したLSPのNeoVimクライアント。今回の主役その2。
コード補完、コードフォーマット、シンタックスチェック、ドキュメント表示、定義元ジャンプなどなど、コーディングに最低限必要と思われることはこいつと言語ごとのLSP用意すれば一通りできるようになる、最高にイカした野郎。
自分の場合は、シンタックスチェックとコードフォーマットは別のプラグインにやらせてるんで使ってない。
中の人がMS社員(とどっかで聞いた気がする)が開発してるvim-lspもあるんだけど、deoplete対応してるんでこっち使ってる。
GitHubのスター数もLanguageClient-neovimのが多い。
ちなみに、名前に neovim
とかついてるけど、普通のvimでも使えるらしい。
試したことはない。
ale.vim
コーディング中に非同期でシンタックスチェックをやってくれるプラグイン。
LanguageClient-neovim
にも同じような機能はあるんだけど、LSP対応してない言語とかはこいつ使ってるんで、統一する目的でこのプラグインを使ってる。
あと自分の好みでチェックツールをカスタマイズできるのも○。
Javaの場合は、標準だと javac
と google-java-format(後述)
1 を使ってシンタックスチェックを行っているらしい。
vim-autoformat
フォーマッターツールを指定してコード整形ができるプラグイン。
ファイルタイプに応じて幾つかデフォルトのフォーマッターが設定されていて、特にフォーマッターの指定がなければそれが実行される。
同じような機能は ale.vim
にもあるのだが、割りと昔からこのプラグインを使っていてそのながれで未だに使用中。
コード保存時に、後述の google-java-format
でフォーマットが行われる用に設定する。
google-java-format
Google Java Styleに併せてコードを整形できるCLIツール。
デフォルトだと、多くのJavaおじさんたちに馴染みの無いであろう2スペースインデントでフォーマットされる。
--aosp
オプション(Android Open Source Project)を指定することで4スペースインデントにできるので安心してほしい。
そのための設定・・・あと、そのための自動化・・・?
というわけで、プラグインの設定とかの説明。
dein.vim
と deoplete.nvim
の設定は今回あまり関係ないと思うので省略。
ちなみにディレクトリ構成はこんな感じ。
$XDG_CONFIG_HOME/nvim
├── autoload
│ └── hook
│ ├── add
│ │ ├── ale.vim
│ │ ├── language_client_neovim.vim
│ │ └── vim_autoformat.vim
│ ├── post_update
│ │ ├── ale.vim
│ │ ├── language_client_neovim.vim
│ │ └── vim_autoformat.vim
│ └── source
│ └── deoplete.vim
├── dein
│ ├── dein.toml
│ └── dein_lazy.toml
└── init.vim
LanguageClient-neovim周りの設定
まずは、目玉の LanguageClient-neovim
の設定からだ。
dein.toml
で以下の用に定義している。
[[plugins]]
repo = 'autozimu/LanguageClient-neovim'
rev = 'next'
# プラグインのアップデート時に呼び出されるコールバック
hook_post_update = 'call hook#post_update#language_client_neovim#load()'
# プラグインが読み込まれる際に呼び出されるコールバック
hook_add = 'call hook#add#language_client_neovim#load()'
アップデート時と読み込み時に2つの関数を呼び出して設定などを行っている。
プラグインのアップデート時は、eclipse.jdt.ls
がインストールさているか判定して未インストールの場合はインストール処理を開始するようにしている。
function! hook#post_update#language_client_neovim#load() abort
!./install.sh
" g:outher_package_pathは、`eclipse.jdt.ls`などの外部ツールのインストール先ディレクトリ。
" 省略しているが、`init.vim`で設定している。
let l:jdt_lsp_path = expand(g:outher_package_path) . "/jdt-lsp"
" 指定のディレクトリに`eclipse.jdt.ls`が存在するか確認
if !executable(l:jdt_lsp_path . "/plugins/org.eclipse.equinox.launcher_1.5.0.v20180207-1446.jar")
" `eclipse.jdt.ls`のダウンロード
!curl -o /tmp/tmp_jdt_lsp.tar.gz http://download.eclipse.org/jdtls/snapshots/jdt-language-server-0.16.0-201803280253.tar.gz
" `eclipse.jdt.ls`の保存先ディレクトリを作成
call mkdir(l:jdt_lsp_path, "p")
" ダウンロードしてきたファイルを保存先ディレクトリに解凍
execute "!tar xf /tmp/tmp_jdt_lsp.tar.gz -C " . l:jdt_lsp_path
" tar.gzファイルを削除
!rm /tmp/tmp_jdt_lsp.tar.gz
endif
endfunction
次に読み込み時の設定だ。
LSPの起動設定などを行っていて、NeoVimの起動時などに毎回呼び出される。
function! hook#add#language_client_neovim#load() abort
let g:LanguageClient_autoStart = 1 " NeoVim起動時にLSPを自動スタート
let g:LanguageClient_diagnosticsEnable = 0 " シンタックスチェックをOFF
let g:LanguageClient_serverCommands = {}
" `eclipse.jdt.ls`で利用する、データ保存先ディレクトリの存在確認
" ディレクトリが存在しない場合は作成する
let l:jdt_lsp_data_dir = expand(g:outher_package_path) . "/jdt-data"
if !isdirectory(l:jdt_lsp_data_dir)
call mkdir(l:jdt_lsp_data_dir, "p")
endif
" LSPの起動設定
" `configuration`オプションはOSごとに別の設定にする必要がある。
" `eclipse.jdt.ls`インストールディレクトリに、 `config_linux`, `config_mac`, `config_win` というディレクトリがあるので、それぞれOSに併せて設定ファイルパスを指定する。
let g:LanguageClient_serverCommands["java"] = [
\ 'java',
\ '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044',
\ '-Declipse.application=org.eclipse.jdt.ls.core.id1',
\ '-Dosgi.bundles.defaultStartLevel=4',
\ '-Declipse.product=org.eclipse.jdt.ls.core.product',
\ '-Dlog.protocol=true',
\ '-Dlog.level=ALL',
\ '-noverify',
\ '-Xmx1G',
\ '-jar',
\ expand(g:outher_package_path) . '/jdt-lsp/plugins/org.eclipse.equinox.launcher_1.5.0.v20180207-1446.jar',
\ '-configuration',
\ expand(g:outher_package_path) . '/jdt-lsp/config_mac',
\ '-data',
\ l:jdt_lsp_data_dir]
" キーマッピング
nnoremap <silent> K :call LanguageClient_textDocument_hover()<CR>
nnoremap <silent> gd :call LanguageClient_textDocument_definition()<CR>
nnoremap <silent> <F2> :call LanguageClient_textDocument_rename()<CR>
nnoremap <silent> <F3> :call LanguageClient_textDocument_references()<CR>
endfunction
ale.vim
Javaに関しては、ale.vimはデフォルト設定のまま使ってる。
シンタックスチェック時のシンボルを変えてるので、一応紹介
[[plugins]]
repo = 'w0rp/ale'
# プラグイン読み込み時のコールバック
hook_add = 'call hook#add#ale#load()'
function! hook#add#ale#load() abort
let g:ale_sign_error = '✖' " エラーシンボル
let g:ale_sign_warning = '⚠' " 警告シンボル
endfunction
vim-autoformat
LanguageClient-neovimと同じように、アップデート時にフォーマッターのインストール、読み込み時にプラグイン設定を行っている
[[plugins]]
repo = 'Chiel92/vim-autoformat'
# プラグイン読み込み時のコールバック
hook_add = 'call hook#add#vim_autoformat#load()'
# プラグインアップデート時のコールバック
hook_post_update = 'call hook#post_update#vim_autoformat#load()'
アップデート時のコールバック処理はこんな感じ
function! hook#post_update#vim_autoformat#load() abort
" `google_java_formatter`がインストールさてれているか確認
let l:google_java_formatter = expand(g:outher_package_path) . "/google-java-format-1.5-all-deps.jar"
if !executable(l:google_java_formatter)
" 未インストールの場合はjarをダウンロード
execute "!wget https://github.com/google/google-java-format/releases/download/google-java-format-1.5/google-java-format-1.5-all-deps.jar -P " . expand(g:outher_package_path)
endif
endfunction
読み込み時の設定はこんなかんじ。
フォーマッタの設定と、保存時の自動フォーマットを設定している。
function! hook#add#vim_autoformat#load() abort
let g:autoformat_remove_trailing_spaces = 1 " 末尾スペースの除去
" google_java_formatterの起動コマンド設定
let g:formatdef_google_java_formatter = '"java -jar ' . g:outher_package_path . '/' . g:google_java_formatter . ' - --aosp"'
" Javaのフォーマッタの設定
let g:formatters_java = ['google_java_formatter']
" 保存時に自動でコードフォーマットされる用に設定
call s:set_autoformat("java")
endfunction
function! s:set_autoformat(...) abort
augroup AutoIndentPreWrite
autocmd!
augroup End
for var in a:000
let l:cmd = 'autocmd AutoIndentPreWrite BufWrite *.' . var . ' :Autoformat'
execute l:cmd
endfor
endfunction
わーい、うごいたぁ(小並感)
上記の用に設定すると、なんとなくそれっぽく動く用になる。
こんな感じで、Lambda式もしっかりコード補完してくれる。
標準パッケージとか外部ライブラリへの定義元ジャンプはできなかった。
ほーん。で、何がつらいの?
- オートインポートがない
- インターフェース実装時に、抽象メソッドを自動で実装してくれない
Gradleプロジェクトでうまく動かない。
主にここらへん。
1, 2はGoとかだとそんな気にならなかったからJavaも行けるやろと思ったけど意外と辛かった。
3に関してはMavenプロジェクトだと外部パッケージのコード補完もドキュメント参照もちゃんと動いてるから、なんか設定とかミスってる可能性あるかも…?
追記 - Gradleプロジェクトでもちゃんと動いた
追記
## Gradleプロジェクトでもちゃんと動いた Gradleプロジェクトの場合、 `eclipse plugin` が必要だった。plugins {
id 'java'
id 'application'
id 'eclipse'
}
mainClassName = 'App'
dependencies {
compile 'com.google.guava:guava:23.0'
testCompile 'junit:junit:4.12'
}
repositories {
jcenter()
}
eclipse plugin
を適用したらプロジェクトルートで eclipseJdt
タスクを実行してやる。
$ gradle eclipseJdt
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
これでGradleプロジェクトでもLSPの機能が使えるようになる。
まとめ
そんなわけで、やっぱりIDEには勝てなかったよ…
とはいえ、IDEよりも手軽にコード編集できて、コード補完もかなり気の利いたもん出してくれるので、eclimのように全く使わずに削除ってことはなさそう。
今つらいと思ってるとこも、慣れてくると割りとなんとかなったりするかもしれないし、もしかしたら知らないだけでなんか便利なツールあるかもしんないので、また暇な時にでも環境見直して見ようと思う。
参考サイト
- language server protocolについて (前編) - Qiita
- dein.vimを使ってみる - Qiita
- neovimをインストールしてプラグイン管理をdein.tomlでやってみた - Qiita
- Vimでの補完ツールプラグインをneocompleteからdeopleteへ - rcmdnk's blog
-
google-java-format
でのチェックは同ツールがインストール済みの場合のみ実行される。 ↩