EOS: Java Development Module

Table of Contents

(provide 'eos-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)
    (<= bod
        (save-excursion (goto-char bod)
                        (re-search-forward "{")

(defun my/is-string-concatenation-p ()
  "Returns true if the previous line is a string concatenation"
    (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"
    (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"
    (let ((endpoint (point)))
      (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"
    (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
     ((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 ()
  (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)
  ;; hide the initial comment in the file (usually a license) if hs-minor-mode
  ;; is enabled
  (when (boundp' hs-minor-mode)

(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
  ;; 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
  ;; 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 ()
  ;; 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 ()
  (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
  ;; use helm for completion
  (setq ggtags-completing-read-function nil))

(use-package ggtags
  :ensure t
  :defer t
    (add-hook 'c-mode-common-hook
              (lambda ()
                (when (derived-mode-p 'c-mode 'c++-mode 'java-mode 'asm-mode)
                  ;; helm-gtags
                  ;; regular gtags

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
  ;; 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)))

Author: Lee Hinman

Created: 2017-08-21 Mon 14:27