Configurando Neovim para Java
Tags: java, neovim
Published: 2023-07-16 20:58:56

Engana-se muito quem acha que é necessário toda uma infraestrutura dedicada para programação Java. A cultura atual para programação com esta linguagem, bem como C# é que é necessário todo um ambiente de IDE para trabalhar com ela.

Eu não vou negar, utilizar um IntelliJ junto com o Java é muito bom! Porém, eu acredito firmemente que é possível sim trabalhar com esta linguagem utilizando um simples editor de texto como o neovim.

## Mas cara, porque não utilizar plugins para ativar modo neovim no intellij?

Eu já testei estes plugins em editores de código e IDE's com GUI's poderosas e várias opções, porém o que me decepciona neles são os mapeamentos padrão das IDE's. Para utilizar todo o poder da IDE, é necessário configurar todos os mapeamentos padrão para trabalhar junto com o neovim, fora também as features que não são cobertas pelo mapeamento padrão.

Como gosto muito do neovim, decidi configurá-lo melhor para que atenda a necessidade de utilizar ele para programar Java.

## O que você vai precisar

* Neovim 0.7+
* jdtls (language server padrão do Eclipse)

## Instalando os plugins

Para começar, primeiro de tudo é necessário instalar o Packer. Eu utilizo Lua na maior parte da minha configuração:

```sh
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
 ~/.local/share/nvim/site/pack/packer/start/packer.nvim
```

Com o Packer instalado, vamos instalar primeiramente os plugins. Na minha configuração eu crio um arquivo `lua/plugins.lua` e executo ele no meu `init.lua`:

```lua
require("plugins")
```

No arquivo `plugins.lua`, vou instanciar o Packer e colocar todas as declarações necessárias de plugins para a configuração do lsp para o java.

```lua

-- instancia o Packer
local status, packer = pcall(require, "packer")
if (not status) then
  print("Packer is not installed")
  return
end

vim.cmd [[packadd packer.nvim]]

-- aqui eu declaro todos os meus plugins
packer.startup(function(use)
  use 'wbthomason/packer.nvim'  -- necessário
  use 'nvim-treesitter/nvim-treesitter' -- syntax highlighting
  use 'neovim/nvim-lspconfig' -- provê configurações padrão de lsp
  use 'hrsh7th/cmp-nvim-lsp' -- auto complete
  use 'hrsh7th/nvim-cmp' -- auto complete
  use 'hrsh7th/cmp-buffer' -- auto complete
  use 'L3MON4D3/LuaSnip' -- provê snippets 
  use 'onsails/lspkind.nvim' -- provê ícones para o lsp
  use 'mfussenegger/nvim-jdtls' -- plugin com todas as features do jdtls
end)
```

Ok! Feita esta declaração, basta salvar o arquivo, reiniciar o neovim e rodar o comando `:PackerSync`. Ele vai instalar e compilar os plugins declarados.

## Configurando o neovim

A próxima parte é configurar esses plugins para rodar de maneira correta.
Como o jdtls será necessário somente quando abrir um arquivo `.java`, a recomendação é utilizar o FileType Plugin do neovim para isso.

Este plugin é nativo, então basta criar uma pasta de nome `ftplugin` onde fica localizado seu `init.lua` e dentro dele iremos criar o arquivo `java.lua`:

```sh
$ mkdir ftplugin
$ cd ftplugin
$ touch java.lua
```

Abra esse arquivo e iremos iniciar a configuração.

A primeira coisa que iremos configurar são as fontes de autocomplete. Nesse caso, iremos apenas invocar as default capabilities do `cmp_nvim_lsp` e do próprio lsp client do neovim.

Outra coisa importante é ativar o registro dinâmico de duas notificações: `didChangeWatchedFiles` e `didChangeConfiguration`.

```lua
local capabilities = require('cmp_nvim_lsp').default_capabilities(vim.lsp.protocol.make_client_capabilities())

capabilities.workspace = {
  configuration = true,
  didChangeWatchedFiles = {
    dynamicRegistration = true
  },
  didChangeConfiguration = {
    dynamicRegistration = true
  }
}
```

A próxima coisa que iremos configurar é a feature de "format on save" e também os keymaps. Tudo isto é definido dentro da função on_attach.

```lua
local on_attach = function(client, bufnr)
  -- Aqui define <Ctrl-x> + <Ctrl-o> para ativar o autocomplete, mas ele irá se ativar quando você digita também
  vim.api.nvim_buf_set_option(bufnr, 'omnifunc', "v:lua.vim.lsp.omnifunc")
  vim.api.nvim_create_autocmd("BufWritePre", {
    group = vim.api.nvim_create_augroup('Format', { clear = true }),
    buffer = bufnr,
    callback = function()
      vim.lsp.buf.format()
    end
  })
  local bufopts = { noremap = true, silent = true, buffer = bufnr }
  local opts = { noremap = true, silent = true }
  vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
  vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
  vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
  vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
  vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts)
  vim.keymap.set('n', '<space>K', vim.lsp.buf.signature_help, bufopts)
  vim.keymap.set('n', 'gt', vim.lsp.buf.type_definition, bufopts)
  vim.keymap.set('n', '<F2>', vim.lsp.buf.rename, bufopts)
  vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts)
  vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts)
  vim.keymap.set('n', '<space>f', vim.lsp.buf.format, bufopts)
  vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts)
  vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
  vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
end
```

A próxima parte é definir configurações mais gerais do LSP. 

```lua
local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t')
local workspace_dir = "/home/eronads/workspaces/" .. project_name


local config = {
  flags = {
    allow_incremental_sync = true
  },
  -- O comando para invocar o lsp. Eu recomendo seguir o caminho abaixo, através do executável em python
  cmd = {
    '/caminho/para/jdtls',
    
    -- Na pasta do jdtls terá algumas pastas com configurações específicas do OS. Indique este caminho de acordo com seu OS
    '-configuration', '/caminho/para/pasta-do-jtdls/config_SEU-OS',
   
    -- Para cada projeto, o lsp cria uma pasta com um workspace. Aqui você irá indicar onde irão ficar essas pastas.  
    '-data', workspace_dir
  },

-- A raiz do seu projeto
  root_dir = require("jdtls.setup").find_root({ 'gradlew', '.git', 'mvnw' }),
  on_attach = on_attach,
  capabilities = capabilities,

-- Outras configurações, recomendável repetir
  settings = {
    java = {
      signatureHelp = { enabled = true },
      contentProvider = { preferred = 'fernflower' },
      completion = {


-- Se precisar adicionar uma classe para import estático
        favoriteStaticMembers = {
          "org.hamcrest.MatcherAssert.assertThat",
          "org.hamcrest.Matchers.*",
          "org.hamcrest.CoreMatchers.*",
          "org.junit.jupiter.api.Assertions.*",
          "java.util.Objects.requireNonNull",
          "java.util.Objects.requireNonNullElse",
          "org.mockito.Mockito.*"
        },
        filteredTypes = {
          "com.sun.*",
          "io.micrometer.shaded.*",
          "java.awt.*",
          "jdk.*",
          "sun.*",
        },
      },
      sources = {
        organizeImports = {
          starThreshold = 9999,
          staticStarThreshold = 9999,
        },
      },
      codeGeneration = {

   -- Instrução para geração de métodos populares
        toString = {
          template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
        },
        hashCodeEquals = {
          useJava7Objects = true,
        },
        useBlocks = true,
      },
      configuration = {

-- Indique aqui as versões de java e as pastas onde se encontram
        runtimes = {
          {
            name = "JavaSE-17",
            path = "/caminho/para/pasta/do/java/17.0.7-tem/",
            default = true
          },
        }
      },
    },
  }
}
```

O restante da configuração contém o cmp, lspkind e a execução do lsp, que são padrão:

```lua
local cmp = require 'cmp';
local lspkind = require 'lspkind'

cmp.setup({
  snippet = {
    expand = function(args)
      require('luasnip').lsp_expand(args.body)
    end
  },
  mapping = cmp.mapping.preset.insert({
    ['C-Space'] = cmp.mapping.complete(),
    ['<CR>'] = cmp.mapping.confirm({ behavior = cmp.ConfirmBehavior.Replace, select = true }),
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'buffer' },
  }),
  formatting = {
    format = lspkind.cmp_format({
      mode = 'symbol',       -- show only symbol annotations
      maxwidth = 50,         -- prevent the popup from showing more than provided characters (e.g 50 will not show more than 50 characters)
      ellipsis_char = '...', -- when popup menu exceed maxwidth, the truncated part would show ellipsis_char instead (must define maxwidth first)

      -- The function below will be called before any actual modifications from lspkind
      -- so that you can provide more controls on popup customization. (See [#30](https://github.com/onsails/lspkind-nvim/pull/30))
      before = function(entry, vim_item)
        return vim_item
      end
    })
  }
})


require('jdtls').start_or_attach(config)
```

Salve o arquivo. A partir daqui seu neovim já está configurado para Java.

## Resultado Final

Iniciando o serviço:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bsfegdxtofr7x9b71hg5.png)

Auto Complete funcionando:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0chknchp2ygeibyucno.png)

Ele já vem com alguns snippets que facilitam criação de classes e interfaces:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uzms6gsof6h84n6vwfqz.png)

Diagnósticos:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iftzfyos3sswymjjgtev.png)


Code Actions (inclusive os queridinhos dos javeiros):


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k02uq31wns0d5mov9vhu.png)


## Configuração completa

A minha configuração completa está no [github](https://github.com/EronAlves1996/neovim-dotfiles).

Além da configuração para Java, ele já está configurado para:

- Typescript;
- Javascript;
- Vue;
- React;
- Go;
- PHP.

Além disso, ele conta com alguns outros plugins que me atendem bem com uma configuração leve e essencial para a edição do dia-a-dia.