Configurando Emacs como Java IDE
Table of Contents
IntelliJ, Android Studio e outras IDEs são extremamente poderosas, isso é fato. Mas muitas vezes queremos leveza, um ambiente produtivo sem dezenas de features que levam minutos para carregar.
Um ambiente onde nos permite abrir o editor o mais rápido possível e apenas começar a programar!
No Emacs, podemos usar um servidor de linguagem (LSP) para ter o mínimo de IntelliSense disponível no editor. Assim como o IntelliJ, Eclipse e outras IDEs fazem.
Ele funciona graças ao frontend eglot que já vem junto com o Emacs.
Vou mostrar um exemplo de configuração no MacOS, contudo vai funcionar também em outros sistemas operacionais, desde que você tenha o jdtls em seu computador e apontando corretamente o caminho dele no script que irei mostrar no decorrer deste post.
Instalando o Java Language Server
Com o Homebrew, instale com o jdtls com o comando
brew install jdtls.
Encontre o caminho de instalação com o brew --prefix jdtls.
Se estiver em outro sistema operacional, baixe o language server diretamente do repositório oficial do jdtls.
Script jdtls
Dentro do dotfiles você encontra o script jdtls responsável por inicializar o servidor de linguagem Java a partir do workspace definido como argumento.
Os parâmetros neste script está configurado para o ambiente macOS. Logo, adapte o JDTLS_HOME para o seu sistema operacional e o -configuration se necessário.
Ainda não deu tempo de deixar o script cross-platform, uso macOS 90% do tempo. Sorry!
Coloque o script jdtls em algum lugar no PATH da sua máquina. Aqui eu copio para ~/bin e configuro ele como PATH no meu .zshrc.
# .zshrc export PATH="~/bin:$PATH"
E os arquivos do emacs estão como links simbólicos para a HOME.
bin/jdtls # copied from dotfiles .emacs -> /Users/tiagoaguiar/dotfiles/.emacs #symlink .emacs.d -> /Users/tiagoaguiar/dotfiles/.emacs.d #symlink .emacs-custom.el # (empty file for Emacs)
O workspace do projeto Java será encontrado automaticamente pelo Emacs a partir do diretório do projeto que contenha a estrutura base com um dos arquivos na raiz: .project, .classpath, pom.xml ou build.gradle.
Ou seja, se o projeto estiver na estrutura correta ele irá carregar automaticamente e iniciar o E-glot com LSP.
Dot emacs
Caso não utilize o meu .emacs, você pode configurar no seu próprio. Eu extrai somente as partes relevantes para o ambiente Java IDE no Emacs.
O gerenciador de pacotes que utilizo é o use-package.
Instale também o company para o autocomplete.
Garanta que o java esteja instalado corretamente e que o Emacs possa encontrá-lo em seu JAVA_HOME.
Por aqui eu utilizo o java 21.
;;
;; .emacs (coloque no ~/.emacs)
;;
(use-package eglot
:config
(setq eldoc-echo-area-use-multiline-p nil)
(setq eglot-events-buffer-size 0)
(add-hook 'eglot-managed-mode-hook (lambda () (eglot-inlay-hints-mode -1))) ;; disable param hint
:config
(add-to-list 'eglot-server-programs
`(java-mode . ,#'my-jdtls-command)))
;; auto complete com company
(add-hook 'java-mode-hook 'global-company-mode)
(defun my-java-project-root (&optional dir)
(let ((start-dir (file-name-as-directory
(or dir
(when buffer-file-name
(file-name-directory buffer-file-name))
default-directory))))
(or
(locate-dominating-file start-dir ".project")
(locate-dominating-file start-dir ".classpath")
(locate-dominating-file start-dir "pom.xml")
(locate-dominating-file start-dir "build.gradle")
start-dir)))
(defun my-jdtls-command (&rest _)
(let* ((root (my-java-project-root))
(project-name (file-name-nondirectory
(directory-file-name root)))
(workspace (expand-file-name
(concat "~/.jdtls-workspace/" project-name))))
(message "------------------------------")
(message "[JDTLS] Root: %s" root)
(message "[JDTLS] Project: %s" project-name)
(message "[JDTLS] Workspace: %s" workspace)
(message "[JDTLS] Default dir (before): %s" default-directory)
(make-directory workspace t)
(message "[JDTLS] Starting server...")
(list "jdtls" workspace)))
;; NOTE(tiago): the jdtls only works when default-directory is set to root project
;; however, if we want to keep find-file opening at current buffer dir
;; we need to restore this variable to "default" default-directory
(defun my-eglot-java-init ()
(let ((buffer-dir default-directory)
(project-root (my-java-project-root)))
(setq-local my-eglot-java-buffer-directory buffer-dir)
(setq-local default-directory project-root)
(add-hook 'eglot-managed-mode-hook #'my-eglot-java-restore-directory nil t)
(eglot-ensure)))
(defun my-eglot-java-restore-directory ()
(when (bound-and-true-p my-eglot-java-buffer-directory)
(setq-local default-directory my-eglot-java-buffer-directory)
(kill-local-variable 'my-eglot-java-buffer-directory)
(remove-hook 'eglot-managed-mode-hook #'my-eglot-java-restore-directory t)))
(add-hook 'java-mode-hook #'my-eglot-java-init)
(add-hook 'java-mode-hook
(lambda ()
(add-hook 'before-save-hook #'eglot-format-buffer nil t)))
Estrutura Java
Veja um exemplo de projeto Java com a estrutura básica (usando maven ou gradle o lsp encontrará automaticamente também).
.classpath:
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>
.project:
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>tetris-java</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1775684946935</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
A estrutura final do seu projeto deve ser algo parecido com o modelo abaixo:
root
├── .classpath
├── .project
└── src
└── dev
└── tiagoaguiar
└── projeto
└── App.java
Será preciso abrir o Emacs a partir do terminal para ter o
JAVA_HOMEdisponível na GUI do Emacs.
Meu Acesso Rápido
- Baixar o jdtls com homebrew
- Clonar o dotfiles
- Criar links simbólicos para ~/.emacs, ~/.emacs.d
- Copiar o script jdtls para ~/bin
- Garantir que ~/bin esteja no PATH
- Estruturar projeto java com .project e .classpath na raiz