EOS: Java Development Module
Table of Contents
(provide 'eos-java)
Java
To start, decide between using meghanada, eclim, both, or neither
(setq eos/use-meghanada t) (setq eos/use-eclim nil)
Java uses eclim and/or malabar to make life at least a little bit livable.
intellij-java-style
is a copy of our Intellij indentation rules for
Elasticsearch, which are a little weird in some cases, but needed in order to
work with the ES codebase.
;; via http://emacs.stackexchange.com/questions/17327/how-to-have-c-offset-style-correctly-detect-a-java-constructor-and-change-indent (defun my/point-in-defun-declaration-p () (let ((bod (save-excursion (c-beginning-of-defun) (point)))) (<= bod (point) (save-excursion (goto-char bod) (re-search-forward "{") (point))))) (defun my/is-string-concatenation-p () "Returns true if the previous line is a string concatenation" (save-excursion (let ((start (point))) (forward-line -1) (if (re-search-forward " \\\+$" start t) t nil)))) (defun my/inside-java-lambda-p () "Returns true if point is the first statement inside of a lambda" (save-excursion (c-beginning-of-statement-1) (let ((start (point))) (forward-line -1) (if (search-forward " -> {" start t) t nil)))) (defun my/trailing-paren-p () "Returns true if point is a training paren and semicolon" (save-excursion (end-of-line) (let ((endpoint (point))) (beginning-of-line) (if (re-search-forward "[ ]*);$" endpoint t) t nil)))) (defun my/prev-line-call-with-no-args-p () "Return true if the previous line is a function call with no arguments" (save-excursion (let ((start (point))) (forward-line -1) (if (re-search-forward ".($" start t) t nil)))) (defun my/arglist-cont-nonempty-indentation (arg) (if (my/inside-java-lambda-p) '+ (if (my/is-string-concatenation-p) 16 ;; TODO don't hard-code (unless (my/point-in-defun-declaration-p) '++)))) (defun my/statement-block-intro (arg) (if (and (c-at-statement-start-p) (my/inside-java-lambda-p)) 0 '+)) (defun my/block-close (arg) (if (my/inside-java-lambda-p) '- 0)) (defun my/arglist-close (arg) (if (my/trailing-paren-p) 0 '--)) (defun my/arglist-intro (arg) (if (my/prev-line-call-with-no-args-p) '++ 0)) (defconst intellij-java-style '((c-basic-offset . 4) (c-comment-only-line-offset . (0 . 0)) ;; the following preserves Javadoc starter lines (c-offsets-alist . ((inline-open . 0) (topmost-intro-cont . +) (statement-block-intro . my/statement-block-intro) (block-close . my/block-close) (knr-argdecl-intro . +) (substatement-open . +) (substatement-label . +) (case-label . +) (label . +) (statement-case-open . +) (statement-cont . +) (arglist-intro . my/arglist-intro) (arglist-cont-nonempty . (my/arglist-cont-nonempty-indentation c-lineup-arglist)) (arglist-close . my/arglist-close) (inexpr-class . 0) (access-label . 0) (inher-intro . ++) (inher-cont . ++) (brace-list-intro . +) (func-decl-cont . ++)))) "Elasticsearch's Intellij Java Programming Style") (c-add-style "intellij" intellij-java-style) (customize-set-variable 'c-default-style '((java-mode . "intellij") (awk-mode . "awk") (other . "gnu"))) ;; Used for eos/setup-java (use-package shut-up :ensure t) (defun eos/setup-java () (interactive) (define-key java-mode-map (kbd "M-,") 'pop-tag-mark) (define-key java-mode-map (kbd "C-c M-i") 'java-imports-add-import-dwim) (c-set-style "intellij" t) (subword-mode 1) (shut-up (toggle-truncate-lines 1)) ;; Generic java stuff things (setq-local fci-rule-column 99) (setq-local fill-column 100) (when (fboundp 'eos/turn-on-whitespace-mode) (whitespace-mode -1) (eos/turn-on-whitespace-mode)) ;; hide the initial comment in the file (usually a license) if hs-minor-mode ;; is enabled (when (boundp' hs-minor-mode) (hs-hide-initial-comment-block))) (add-hook 'java-mode-hook #'eos/setup-java) ;; Make emacs' compile recognize broken gradle output (require 'compile) (add-to-list 'compilation-error-regexp-alist '("^:[^/.\n]+\\(/.+\\):\\([[:digit:]]+\\):" 1 2))
Managing Java Imports with the java-imports
Package
I also have a custom package, java-imports, which I use to quickly add imports for things.
(use-package java-imports :ensure t :config ;; Elasticsearch's import style (setq java-imports-find-block-function 'java-imports-find-place-sorted-block) (add-hook 'java-mode-hook 'java-imports-scan-file))
Connecting Emacs and Eclipse with Eclim
Eclim is decent for emacs-java integration, but isn't quite there for showing errors or things like that. Unfortunately I still have to jump back into Intellij all the time for things.
Right now I have this disabled, I'm doing Java dev in Emacs 100% of the time, but I don't need this. Crazy, I know. I've been meaning to write a blog post about it, I just need to find the time.
(use-package eclim :ensure t :disabled t :init ;; only show errors (setq-default eclim--problems-filter "e") (when eos/use-eclim (add-hook 'java-mode-hook #'eclim-mode) (use-package company-emacs-eclim :ensure t :init (company-emacs-eclim-setup))))
Configuring SDKMan for Groovy/Gradle
This will have to be downloaded from http://sdkman.io, there is no package for it
Let's also add it to the tramp remote path
(add-to-list 'tramp-remote-path "/home/hinmanm/.sdkman/candidates/gradle/current/bin") (add-to-list 'tramp-remote-path "/home/hinmanm/.sdkman/candidates/groovy/current/bin")
Configure GNU Global and Gtags
See: https://github.com/leoliu/ggtags
If on OSX, you'll need to:
brew install ctags wget -c http://tamacom.com/global/global-6.3.1.tar.gz tar zxvf global-6.3.1.tar.gz cd global-6.3.1 ./configure --prefix=/usr/local --with-exuberant-ctags=/usr/local/bin/ctags make install
I also add this to my shell configuration:
export GTAGSCONF=/usr/local/share/gtags/gtags.conf export GTAGSLABEL=ctags
I actually choose to do this because the fedora/debian/ubuntu version of
global
is so older it doesn't work well.
(defun eos/setup-helm-gtags () (interactive) ;; this variables must be set before load helm-gtags ;; you can change to any prefix key of your choice (setq helm-gtags-prefix-key "\C-cg") (setq helm-gtags-ignore-case t helm-gtags-auto-update t helm-gtags-use-input-at-cursor t helm-gtags-pulse-at-cursor t helm-gtags-suggested-key-mapping t) (use-package helm-gtags :ensure t :init (helm-gtags-mode t) :diminish "") ;; key bindings (define-key helm-gtags-mode-map (kbd "M-S") 'helm-gtags-select) (define-key helm-gtags-mode-map (kbd "M-.") 'helm-gtags-dwim) (define-key helm-gtags-mode-map (kbd "M-,") 'helm-gtags-pop-stack) (define-key helm-gtags-mode-map (kbd "C-c <") 'helm-gtags-previous-history) (define-key helm-gtags-mode-map (kbd "C-c >") 'helm-gtags-next-history)) (defun eos/setup-ggtags () (interactive) (ggtags-mode 1) ;; turn on eldoc with ggtags (setq-local eldoc-documentation-function #'ggtags-eldoc-function) ;; add ggtags to the hippie completion (setq-local hippie-expand-try-functions-list (cons 'ggtags-try-complete-tag hippie-expand-try-functions-list)) ;; use helm for completion (setq ggtags-completing-read-function nil)) (use-package ggtags :ensure t :defer t :init (progn (add-hook 'c-mode-common-hook (lambda () (when (derived-mode-p 'c-mode 'c++-mode 'java-mode 'asm-mode) (eos/setup-semantic-mode) ;; helm-gtags (eos/setup-helm-gtags) ;; regular gtags ;;(my/setup-ggtags) )))))
Connecting to a Java debugger
Strangely enough (I didn't think it would actually exist), there does exist a
command-line debugger for Java, called jdb
. Even better, there's a packaged
called "Realgud" that combines a bunch of different debuggers and the plumbing
to hook them up to Emacs. So I'll install that.
For Elasticsearch in particular, this means running gradle run --debug-jvm
which starts up ES listening for events, and then M-x realgud:jdb
and using
jdb -attach 8000
to attach to the JVM.
(use-package realgud :ensure t)
Java development with meghanada
Meghanada-mode is a new Java development mode that is supposed to be under active development, so I'm trying it out.
(use-package meghanada :ensure t :init ;; Don't auto-start (setq meghanada-auto-start nil) (when eos/use-meghanada (add-hook 'java-mode-hook #'meghanada-mode) (add-hook 'java-mode-hook 'flycheck-mode) (bind-key "C-c M-." 'meghanada-jump-declaration java-mode-map)))