Bloop integration in Emacs

Some quick and hacky integration of Scala bloop with Emacs, using ammonite as the console and projectile for compilation and testing.

;;; bloop --- bloop minor mode
;; Author: Philipp Fehre <>
;; Keywords: scala, bloop, tools, convenience
;;; Commentary:
;; Helpers to integrate better with bloop, inspired by emacs-bloop
;; C-c M-j jack-in a bloop project running a new Ammonite REPL buffer
;; C-c M-z switch to an active Ammonite REPL
;; C-c b c Compile a bloop project backed by projectile-compile-project
;; C-c b t Test a bloop project backed by projectile-test-project
;; C-c b r Run a bloop project
;; Changelog:
;; - 25/8/2020 - Added run command mapping bloop-run (C-c b r)
;; - 1/5/2020 - initial working version
;;; Code:
(require 'projectile)
(require 'scala-mode)
(require 'ammonite-term-repl)
(require 's)
(defgroup bloop nil
"Bloop integration for emacs"
:group 'tools
:group 'convenience
:link '(url-link :tag "Gist" ""))
(defcustom bloop-program-name "bloop"
"Program used to run bloop commands, default to whatever is in the path."
:type 'string
:group 'bloop)
(defcustom bloop-reporter "scalac"
"Either bloop or scalac.
The main difference is that bloop shows errors in reverse order.
Emacs generally assumes the first error in the output is the most
relevant so the scalac reporter will most likely be preferred.
This is used for test and compile."
:type 'string
:group 'bloop)
(defun bloop--command (&rest args)
"Build a bloop command for ARGS."
(s-join " " (cons bloop-program-name args)))
(defun bloop--available-projects ()
"Get a list of currently available projects from bloop."
(projectile-with-default-dir (projectile-project-root)
(let ((projects-string (shell-command-to-string (bloop--command "projects"))))
(split-string projects-string))))
(defun bloop-switch-to-ammonite ()
"Switch to the running Ammonite REPL."
(if-let ((ammonite-buffer (get-buffer ammonite-term-repl-buffer-name)))
(switch-to-buffer ammonite-buffer)
(message "Ammonite is not running try C-c M-j to start an Ammonite REPL for bloop.")))
(defun bloop-run-ammonite (project)
"Run Ammonite for a bloop PROJECT."
(interactive (list (completing-read "Run Ammonite REPL for project: " (bloop--available-projects))))
(projectile-with-default-dir (projectile-project-root)
(let ((ammonite-term-repl-program bloop-program-name)
(ammonite-term-repl-program-args (list "console" project)))
(defun bloop-compile (project)
"Compile a bloop PROJECT."
(interactive (list (completing-read "Compile bloop project: " (bloop--available-projects))))
(let ((command (bloop--command "compile" project)))
(projectile--run-project-cmd command projectile-compilation-cmd-map
:show-prompt 't
:prompt-prefix "Compile command: "
:save-buffers t)))
(defun bloop-test (project)
"Test a bloop PROJECT."
(interactive (list (completing-read "Test bloop project: " (bloop--available-projects))))
(let ((test-command (bloop--command "test" "--reporter" bloop-reporter project)))
(projectile--run-project-cmd test-command projectile-test-cmd-map
:show-prompt 't
:prompt-prefix "Test command: "
:save-buffers t)))
(defun bloop-run (project)
"Run a bloop PROJECT."
(interactive (list (completing-read "Run bloop project: " (bloop--available-projects))))
(let ((run-command (bloop--command "run" project)))
(projectile--run-project-cmd run-command projectile-run-cmd-map
:show-prompt 't
:prompt-prefix "Run command: "
:save-buffers t)))
(define-minor-mode bloop-mode
"Bloop integration for emacs."
:lighter " bloop"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c M-j") #'bloop-run-ammonite)
(define-key map (kbd "C-c M-z") #'bloop-switch-to-ammonite)
(define-key map (kbd "C-c b c") #'bloop-compile)
(define-key map (kbd "C-c b t") #'bloop-test)
(define-key map (kbd "C-c b r") #'bloop-run)
(add-hook 'scala-mode-hook 'bloop-mode)
(provide 'bloop)
;;; bloop.el ends here
view raw bloop.el hosted with ❤ by GitHub