DEV Community

EronAlves1996
EronAlves1996

Posted on

Configurando Neovim para Java

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:

git clone --depth 1 https://github.com/wbthomason/packer.nvim\
 ~/.local/share/nvim/site/pack/packer/start/packer.nvim
Enter fullscreen mode Exit fullscreen mode

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:

require("plugins")
Enter fullscreen mode Exit fullscreen mode

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.


-- 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)
Enter fullscreen mode Exit fullscreen mode

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:

$ mkdir ftplugin
$ cd ftplugin
$ touch java.lua
Enter fullscreen mode Exit fullscreen mode

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.

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

capabilities.workspace = {
  configuration = true,
  didChangeWatchedFiles = {
    dynamicRegistration = true
  },
  didChangeConfiguration = {
    dynamicRegistration = true
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

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
          },
        }
      },
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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)
Enter fullscreen mode Exit fullscreen mode

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

Resultado Final

Iniciando o serviço:

Image description

Auto Complete funcionando:

Image description

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

Image description

Diagnósticos:

Image description

Code Actions (inclusive os queridinhos dos javeiros):

Image description

Configuração completa

A minha configuração completa está no github.

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.

Top comments (0)