There is LSP and I tried to create an environment to write Java with Vim (NeoVim), but after all I could not beat the IDE ...

Probably about a year ago, I used eclim to create a Java development environment with NeoVim.

This eclim is quite high performance because it starts Eclipse in the background and provides a Java development environment, but it requires a workspace like Eclipse, and Eclipse starts up in the background, so it is heavy even though it is Vim, installation is There were many painful parts such as annoying and difficult to automate, so I erased them with almost no use.

So, after all, I used the IDE when writing Java, but recently there is a Java LSP called eclipse.jdt.ls. I found it.

For me during the campaign to replace the development environment with LSP

"It looks like the official eclipse plugin, and it seems that the environment construction can be automated, and this won (idiot)"

I tried to build an environment with a similar feeling.

After all, I couldn't beat the IDE, but I was quite satisfied with it, so I'll introduce it.

I'm using NeoVim, but I should be able to build an environment with normal Vim in the same way.

In the first place ... What is LSP?

LSP is an abbreviation for Language Server Protocel, which is a specification released by Microsoft in June 2016.

What exactly is this? It defines common specifications for providing functions such as code completion and error analysis required for the IDE as a service.

By standardizing the specifications in this way, the IDE function can be used in any environment as long as there is an LSP language implementation and the editor used has an LSP client.

LSP is described in detail in this article, and the specifications are published on the official website and GitHub, so if you are interested, please have a look.

In addition, the implementation for each language and the client for each editor are summarized below.

In this environment construction, I tried to create a Java development environment using ʻeclipse.jdt.ls, which is a Java implementation of this LSP, and LanguageClient-neovim`, which is NeoVim's LSP client.

Here are some of the coolest tools I've used this time!

dein.vim The one who doesn't know this person is said to be Moguri (self-examination). A plug-in management tool developed by Mr. Shougo, also known as Dark Minou. It is introduced in various places and the explanation is omitted.

Please refer to here for details.

deoplete.nvim A code completion plugin also developed by Shougo. I don't need to talk about it now either. (suitable)

I will throw the details in another article.

eclipse.jdt.ls Java implementation of LSP mentioned above. The leading role of this time, part 1. When NeoVim starts up, it starts up automatically, and by exchanging messages with LanguageClient-neovim, which will be described later, it is a hateful guy who blows the wind of IDE into NeoVim.

The startup speed and memory usage are much better than eclim, and the installation is easy enough to automate for the time being.

LanguageClient-neovim The LSP NeoVim client mentioned above. The leading role of this time, part 2.

Code completion, code format, syntax check, document display, definition source jump, etc., which seems to be the minimum necessary for coding can be done by preparing this guy and LSP for each language, the best squid guy ..

In my case, I don't use syntax check and code format because I have another plugin do it.

There is also vim-lsp developed by an MS employee (I think I heard it somewhere), but it supports deoplete. I'm using this. Many of the stars on GitHub are Language Client-neovim.

By the way, the name says neovim, but it seems that it can be used with ordinary vim. I haven't tried it.

ale.vim A plugin that performs syntax checking asynchronously during coding.

** Image Image ** ale.vim

LanguageClient-neovim has a similar function, but I'm using a language that doesn't support LSP, so I'm using this plugin for the purpose of unifying.

You can also customize the check tool to your liking.

In the case of Java, it seems that syntax check is performed using javac andgoogle-java-format (described later)[^ 1] as standard. [^ 1]: The check with google-java-format is executed only when the tool is installed.

vim-autoformat A plug-in that allows you to specify the formatter tool and format the code. Some default formatters are set depending on the file type, and they will be executed unless the formatter is specified.

There is a similar feature in ʻale.vim`, but I've been using this plugin for quite some time and it's still in use.

When saving the code, set it so that it will be formatted with google-java-format described later.

google-java-format A CLI tool that can format code according to Google Java Style.

By default, it is formatted with 2-space indentation, which many Java uncles may not be familiar with. Please be assured that you can make it 4 space indent by specifying the --aosp option (Android Open Source Project).

Settings for that ... and automation for that ...?

That's why the explanation of plug-in settings and so on.

The settings for dein.vim and deoplete.nvim are not relevant this time, so they are omitted.

By the way, the directory structure looks like this.

$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-Settings around neovim

The first is the setting of the main feature, LanguageClient-neovim. It is defined in dein.toml as follows.

dein/dein.toml


[[plugins]]
repo             = 'autozimu/LanguageClient-neovim'
rev              = 'next'
#Callback called when updating plugin
hook_post_update = 'call hook#post_update#language_client_neovim#load()'
#Callback called when the plugin is loaded
hook_add         = 'call hook#add#language_client_neovim#load()'

Two functions are called at the time of update and at the time of loading to set.

When updating the plug-in, it is determined whether ʻeclipse.jdt.ls` is installed and if it is not installed, the installation process is started.

autoload/hook/post_update/language_client_neovim.vim


function! hook#post_update#language_client_neovim#load() abort
  !./install.sh
  " g:outher_package_path is`eclipse.jdt.ls`The directory where external tools such as are installed.
  "Although omitted,`init.vim`It is set in.
  let l:jdt_lsp_path = expand(g:outher_package_path) . "/jdt-lsp"
  "In the specified directory`eclipse.jdt.ls`Check if exists
  if !executable(l:jdt_lsp_path . "/plugins/org.eclipse.equinox.launcher_1.5.0.v20180207-1446.jar")
    " `eclipse.jdt.ls`Download
    !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`Create a destination directory for
    call mkdir(l:jdt_lsp_path, "p")
    "Unzip the downloaded file to the save destination directory
    execute "!tar xf /tmp/tmp_jdt_lsp.tar.gz -C " . l:jdt_lsp_path
    " tar.Delete gz file
    !rm /tmp/tmp_jdt_lsp.tar.gz
  endif
endfunction

Next is the setting at the time of reading. The LSP startup settings are set, and it is called every time NeoVim is started.

autoload/hook/add/language_client_neovim.vim


function! hook#add#language_client_neovim#load() abort
  let g:LanguageClient_autoStart         = 1 "Automatically start LSP when NeoVim starts
  let g:LanguageClient_diagnosticsEnable = 0 "Syntax check OFF

  let g:LanguageClient_serverCommands = {}
  " `eclipse.jdt.ls`Confirmation of the existence of the data storage destination directory used in
  "Create the directory if it does not exist
  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 startup settings
  " `configuration`Options need to be set differently for each OS.
  " `eclipse.jdt.ls`In the installation directory`config_linux`, `config_mac`, `config_win`Since there is a directory called, specify the setting file path according to each 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]

  "Key mapping
  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 For Java, ale.vim uses the default settings. Since the symbol at the time of syntax check is changed, I will introduce it for the time being

dein/dein.toml


[[plugins]]
repo     = 'w0rp/ale'
#Callback when loading plugin
hook_add = 'call hook#add#ale#load()'

autoload/hook/add/ale.vim


function! hook#add#ale#load() abort
  let g:ale_sign_error      = '✖' "Error symbol
  let g:ale_sign_warning    = '⚠' "Warning symbol
endfunction

vim-autoformat Similar to LanguageClient-neovim, the formatter is installed at the time of update and the plugin is set at the time of loading.

dein/dein.toml


[[plugins]]
repo             = 'Chiel92/vim-autoformat'
#Callback when loading plugin
hook_add         = 'call hook#add#vim_autoformat#load()'
#Callback when updating plugins
hook_post_update = 'call hook#post_update#vim_autoformat#load()'

Callback processing at the time of update looks like this

autoload/hook/post_update/vim_autoformat.vim


function! hook#post_update#vim_autoformat#load() abort
  " `google_java_formatter`Check if is installed
  let l:google_java_formatter = expand(g:outher_package_path) . "/google-java-format-1.5-all-deps.jar"
  if !executable(l:google_java_formatter)
    "Download jar if not installed
    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

The settings at the time of reading are like this. The formatter settings and the automatic format when saving are set.

autoload/hook/add/vim_autoformat.vim


function! hook#add#vim_autoformat#load() abort
  let g:autoformat_remove_trailing_spaces = 1 "Removal of trailing spaces

  " google_java_formatter startup command settings
  let g:formatdef_google_java_formatter = '"java -jar ' . g:outher_package_path . '/' . g:google_java_formatter . ' - --aosp"'

  "Java formatter settings
  let g:formatters_java = ['google_java_formatter']

  "Set for automatic code formatting when saving
  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

Wow, it's moving (small average feeling)

If you set it for the above, it will work like that.

** Code completion and auto-formatting **

Like this, the Lambda expression also completes the code firmly.

** Document reference and definition source jump **

It was not possible to jump the definition source to a standard package or an external library.

Hmm. So what's the pain?

  1. No auto import
  2. When implementing the interface, the abstract method is not implemented automatically
  3. It doesn't work well in ~~ Gradle project. ~~

Mainly here. I didn't feel like Go for 1 and 2, so I thought I could go to Java, but it was unexpectedly painful.

Regarding ~~ 3, in the Maven project, the code completion of the external package and the document reference are working properly, so there may be some mistakes in the settings ...? ~~ [Addition-It worked fine in the Gradle project](It worked fine in the #gradle project)

Postscript

## It worked fine in the Gradle project For the Gradle project, I needed the ʻeclipse plugin`.

build.gradle


plugins {
    id 'java'
    id 'application'
    id 'eclipse'
}

mainClassName = 'App'

dependencies {
    compile 'com.google.guava:guava:23.0'

    testCompile 'junit:junit:4.12'
}

repositories {
    jcenter()
}

After applying the ʻeclipse plugin, execute the ʻeclipseJdt task in the project root.

$ gradle eclipseJdt
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Now you can use the LSP function in your Gradle project.

Summary

That's why I couldn't beat the IDE ... However, it's easier to edit the code than the IDE, and the code completion is also quite nice, so it seems unlikely that you will delete it without using it at all like eclim.

Even if you think it's hard right now, it may be easier to get used to it, and maybe there is a useful tool just because you don't know it, so I'll review the environment again even in my spare time.

Reference site

Recommended Posts

There is LSP and I tried to create an environment to write Java with Vim (NeoVim), but after all I could not beat the IDE ...
I tried to create a java8 development environment with Chocolatey
[Java] I installed JDBC and tried to connect with servlet + MySQL. (There is a version using DAO / Bean)
I built a CentOS 8 environment with Vagrant and tried to sync_folder, but I got an error, so I solved it.
I tried to create a shopping site administrator function / screen with Java and Spring
I tried to solve the Ruby karaoke machine problem (there is an example of the answer)
I tried to solve the Ruby bonus drink problem (there is an example of the answer)
[Rails 6.0, Docker] I tried to summarize the Docker environment construction and commands necessary to create a portfolio
I tried to summarize the basics of kotlin and java
What do you need after all to create a web application using java? Explain the mechanism and what is necessary for learning
I tried to create a padrino development environment with Docker
I tried to solve the Ruby bingo card creation problem (there is an example of the answer)
After learning Progate, I tried to make an SNS application using Rails in the local environment
I tried to build the environment of PlantUML Server with Docker
I tried to build an http2 development environment with Eclipse + Tomcat
I tried to make an Android application with MVC now (Java)
I tried to summarize the methods of Java String and StringBuilder
I tried to solve the past 10 questions that should be solved after registering with AtCoder in Java
I tried to interact with Java
[Introduction to Java] I tried to summarize the knowledge that I think is essential
01. I tried to build an environment with SpringBoot + IntelliJ + MySQL (MyBatis) (Windows10)
I tried to measure and compare the speed of GraalVM with JMH
After all I wanted to preview the contents of mysql with Docker ...
After verifying the Monty Hall problem with Ruby, a story that I could understand well and did not understand well
I tried to create a method to apply multiple filters at once with Java Stream API. Is this okay?
I tried to make a program that searches for the target class from the process that is overloaded with Java