$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Scalaの開発環境をセットアップした

Scalaを書くことになったので、Scalaの開発環境をセットアップした。Emacsで編集できるように

  • scala-modeを使って編集
  • ensimeを導入し、できる限り動作が重くならないように
  • tagsファイルを使って定義ジャンプをできるように
  • IntelliJEmacsを行き来できるように

あたりを行った。

scala-modeを使って編集

昔はscala-mode2を使っていたみたいだけど、最近はscala-mode を使うみたい。

(require 'scala-mode)

ensimeを導入し、できる限り動作が重くならないように

EmacsでもScalaシンタックスチェックや定義ジャンプなどを使えるように ensime を導入する。ensimeを使えるように Emacs + ENSIME でScalaの開発環境を作る - Qiita とかに書いてあるとおり、

  • Emacs上でensime packageのインストール
  • sbtでプロジェクトごとにensimeの設定を吐き出す

の二つをやれば導入できる。


ただし普通に導入すると、大きめなプロジェクトを編集する時に非常に重くなってしまうので、できる限りミニマムに利用するために以下の設定を入れた。

(require 'ensime)
;; この設定を入れないと毎回ensimeを起動するたびにメッセージが表示される。
(setq ensime-startup-snapshot-notification nil)
(setq ensime-startup-notification nil)
;; デフォルトではカーソル移動のたびに型の探索が走るため、
;; カーソル移動自体が重くなる。編集困難になるので、
;; カーソル位置の型をミニバッファに表示しないようにする
(setq ensime-eldoc-hints nil)
;; 補完機能が走り出した時に、固まるときがあったので一旦オフ
(setq ensime-completion-style nil)
;; シンタックスチェックを自動で走らせないようにする。
;; この設定を入れてもファイル保存時にはシンタックスチェックが走るので問題ない。
(setq ensime-typecheck-when-idle nil)
;; semantic highlightを有効にすると、CPUが100%に張り付いたので
;; 無効にする。
(setq ensime-sem-high-enabled-p nil)

;; シンタックスエラーの時の表示をflycheckと同じものにしておく
(custom-set-faces
 '(ensime-errline-highlight ((t (:inherit flycheck-error))))
 '(ensime-warnline-highlight ((t (:inherit flycheck-warning)))))

;; 便利なやつだけ自分の好きなkeybindを設定
(define-key scala-mode-map (kbd "C-@") 'ensime-edit-definition)
(define-key scala-mode-map (kbd "M-@") 'ensime-pop-find-definition-stack)
(define-key scala-mode-map (kbd "M-t") 'ensime-type-at-point-full-name)

いろんな機能をオフにしたが、これでも型チェックや定義元ジャンプなど、便利な機能が幾つか使えるので良い。

tagsファイルを使って定義ジャンプをできるように

小さいプロジェクトではensimeの定義ジャンプが使えるが、大きいプロジェクトで非常に重くなる時もあった。そこで、ctagsでtagsファイルを作り、tagsファイルを使った定義ジャンプも併用できるようにした。

scalaはctagsに定義がないので、以下のように~/.ctagsファイルに書いておく。valを入れている人も多いが、valを入れると定義する先が非常に多くなってしまうこともあるので、今は入れていない。

--langdef=scala
--langmap=scala:.scala
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\4/c,classes/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*object[ \t]+([a-zA-Z0-9_]+)/\4/c,objects/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*case class[ \t]+([a-zA-Z0-9_]+)/\6/c,case classes/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*case object[ \t]+([a-zA-Z0-9_]+)/\4/c,case objects/
--regex-scala=/^[ \t]*((abstract|final|sealed|implicit|lazy)[ \t]*)*(private[^ ]*|protected)?[ \t]*trait[ \t]+([a-zA-Z0-9_]+)/\4/t,traits/
--regex-scala=/^[ \t]*((@inline|@noinline|abstract|final|sealed|implicit|lazy|private[^ ]*(\[[a-z]*\])*|protected)[ \t]*)*def[ \t]+([a-zA-Z0-9_]+)/\4/m,methods/
--regex-scala=/^[ \t]*package[ \t]+([a-zA-Z0-9_.]+)/\1/p,packages/

あとはScalaのプロジェクトルートでctagsを使ってtagsファイルを作る。

$ ctags --verbose -R -e

Emacs側では helm-etags-plus を使う。

(require 'helm-etags-plus)
(setq helm-etags-plus-use-absolute-path nil)
(set-face-foreground 'helm-etags-plus-file-face "green")
(global-set-key (kbd "C-@") 'helm-etags-plus-select)

これでScalaでもtagsファイルを使った定義ジャンプが出来るようになった。基本はensimeの定義ジャンプを使った方が便利だが、なんか重いと感じたらtagsを使ったジャンプに切り替えるようにしている。簡単に切り替えられるように以下のようなユーティリティを定義している。

;;; ensimeのタグジャンプを使うようにする
(defun scala/use-ensime-definition-jump ()
  (interactive)
  (define-key scala-mode-map (kbd "C-@") 'ensime-edit-definition))

;;; ctagsのタグジャンプを使うようにする
;;; グローバルのキーバインドにhelm-etags-plusを設定しているので、
;;; scala-mode-mapの方のキーバインドを消せば良い。
(defun scala/use-ctags-definition-jump ()
  (interactive)
  (define-key scala-mode-map (kbd "C-@") nil))

IntelliJEmacsを行き来できるように

以上でEmacsでとりあえず編集できるようになったが、たまにリファクタリングとかでIntelliJを使いたいということもあるので、EmacsIntelliJを行き来できるようにしておく。

これは以前ブログに書いた設定をしておくと良い。

blog.shibayu36.org