Clojure Programming

Table of Contents

A literate programming file for programming in Clojure.

I like Clojure as a modern Lisp, but I don’t get to play with it much anymore. 😢 The following instructions create a fully blinged-out Emacs-Clojure setup.

Introduction

To get Clojure working on a new MacOS system using Homebrew, try the following:

brew install clojure/tools/clojure
brew install leiningen

And make sure it works:

clojure --help

Or by starting a REPL:

clj

Then for each project, create the project directory with this command:

lein new app fresh-app

Emacs Support

Install and configure clojure-mode:

(use-package clojure-mode
  :init
  (add-to-list 'org-babel-load-languages '(clojure . t))

  :config
  (defun ha-prettify-clojure ()
    "Make the Clojure syntax prettier."
    (push '("fn"   . ?𝝀) prettify-symbols-alist)
    (push '("__"   . ?⁈) prettify-symbols-alist)
    (prettify-symbols-mode))

  :hook (clojure-mode . ha-prettify-clojure))

Need the IDE features associated with Cider:

(use-package cider
  :after clojure-mode
  :commands (cider-connect cider-jack-in cider)
  :init
  (setq cider-show-error-buffer t
        ;; The use of paredit when editing Clojure (or any other Lisp) code is
        ;; highly recommended. You're probably using it already in your
        ;; clojure-mode buffers (if you're not you probably should). You might
        ;; also want to enable paredit in the REPL buffer as well.
        ;; (add-hook 'cider-repl-mode-hook #'paredit-mode)

        ;; Don't select the error buffer when it's displayed:
        cider-auto-select-error-buffer nil

        ;; Controls whether to pop to the REPL buffer on connect.
        cider-repl-pop-to-buffer-on-connect nil

        cider-repl-use-clojure-font-lock t

        ;; T to wrap history around when the end is reached.
        cider-repl-wrap-history t

        cider-repl-history-size 1000

        ;; Hide `*nrepl-connection*' and `*nrepl-server*' buffers from appearing
        ;; in some buffer switching commands like switch-to-buffer
        nrepl-hide-special-buffers t

        ;; Stop error buffer from popping up while working in buffers other than the REPL:
        nrepl-popup-stacktraces nil)

  ;; Enabling CamelCase support for editing commands (like forward-word,
  ;; backward-word, etc) in the REPL is quite useful since we often have
  ;; to deal with Java class and method names. The built-in Emacs minor
  ;; mode subword-mode provides such functionality
  :hook (cider-repl-mode . #'subword-mode)

  :config
  (ha-local-leader :keymaps clojure-mode-map
    "w" '(:ignore t :which-key "cider")
    "w s" '("start"            . cider-jack-in)
    "w r" '("restart"          . cider-restart)
    "w c" '("connect"          . cider-connect)
    "w b" '("switch"           . cider-switch-repl-buffer)
    "w n" '("namespace"        . cider-repl-set-ns)
    "w e" '("describe"         . cider-describe-connection)
    "w x" '("interrupt"        . cider-interrupt)
    "w q" '("quit"             . cider-quit)

    "b" '(:ignore t :which-key "load")
    "b b" '("load buffer"      . cider-load-buffer)
    "b f" '("load file"        . cider-load-file)
    "b f" '("load all"         . cider-load-all-files)
    "b r" '("refresh all"      . cider-ns-refresh)

    "e" '(:ignore t :which-key "eval")
    "e e" '("last s-expr"      . cider-eval-last-sexp)
    "e E" '("replace s-expr"   . cider-eval-last-sexp-and-replace)
    "e ." '("s-expr point"     . cider-eval-sexp-at-point)
    "e f" '("defun"            . cider-eval-defun-at-point)
    "e F" '("file"             . cider-eval-file)
    "e b" '("buffer"           . cider-eval-buffer)
    "e r" '("region"           . cider-eval-region)
    "e R" '("to repl"          . cider-eval-last-sexp-to-repl)
    "e n" '("namespace"        . cider-eval-ns-form)
    "e ;" '("expression"       . cider-read-and-eval)
    "e i" '("inspect"          . cider-inspect)

    "e p" '(:ignore t :which-key "pprint")
    "e p p" '("last s-expr"    . cider-pprint-eval-last-sexp)
    "e p f" '("defun"          . cider-pprint-eval-defun-at-point)
    "e p r" '("to repl"        . cider-pprint-eval-last-sexp-to-repl)

    ;; Overshadowing xref menu in `ha-programming':
    "s" '(:ignore t :which-key "search")
    "s d" '("definition"       . cider-find-resource)
    "s s" '("var"              . cider-find-var)
    "s f" '("file"             . cider-find-ns)
    "s o" '("other window"     . xref-find-definitions-other-window)
    "s D" '("deps"             . cider-xref-fn-deps)
    "s b" '("back"             . cider-pop-back)

    "t" '(:ignore t :which-key "test")
    "t t" '("run test"         . cider-test-run-test)
    "t r" '("rerun test"       . cider-test-rerun-test)
    "t a" '("run all"          . cider-test-run-ns-tests)
    "t p" '("run project"      . cider-test-run-project-tests)
    "t f" '("run failed"       . cider-test-rerun-failed-tests)
    "t R" '("show report"      . cider-test-show-report)

    "d" '(:ignore t :which-key "docs")
    "d d" '("documentation"    . cider-doc)
    "d s" '("search docs"      . cider-apropos-documentation)
    "d j" '("javadocs"         . cider-javadoc)
    "d c" '("clojuredocs"      . cider-clojuredocs)
    "d a" '("apropos"          . cider-apropos)))

Read the entire CIDER manual, specifically the Usage document.

Linting

Note: The Spacemacs community recommends using clj-kondo in combination with joker. Add lint warnings to :

(use-package flycheck-clojure
  :after flycheck
  :config
  (flycheck-clojure-setup))

To install the joker binary:

brew install candid82/brew/joker

And the flycheck-joker package should do the trick:

(use-package flycheck-joker)

To install the clj-kondo binary is a bit more involved:

curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
chmod +x install-clj-kondo
./install-clj-kondo

And the flycheck-clj-kondo project should do the integration:

(use-package flycheck-clj-kondo)

Search on Clojars more easily This clojars extension allows you to search for projects on and copies your selection to the kill ring in a format suitable for your project.clj.

(use-package clojars
  :after clojure-mode
  :config
  (ha-local-leader :keymaps clojure-mode-map
   "h j" '("clojars" . clojars)))

Clojure Cheatsheet

The clojure-cheatsheet

(use-package clojure-cheatsheet
  :after clojure-mode
  :config
  (ha-local-leader :keymaps clojure-mode-map
   "h c" '("cheatsheet" . clojure-cheatsheet)))

Snippets

For clojure-specific templates for yasnippets, we use David Nolen’s clojure-snippets repository:

(use-package clojure-snippets)

Refactoring

The clj-refactor project:

(use-package clj-refactor
  :after clojure-mode
  :hook
  (clojure-mode . clj-refactor-mode)

  :config
  ;; Configure the Clojure Refactoring prefix.
  (cljr-add-keybindings-with-prefix "C-c .")

  (ha-local-leader :keymaps clojure-mode-map
   ;; Would really like to have this on the , prefix:
   "r"  '("refactoring" . hydra-cljr-help-menu/body)

   "h d" '("describe refactoring" . cljr-describe-refactoring)
   "h r" '("refactoring" . hydra-cljr-toplevel-form-menu/body))

  :diminish clj-refactor-mode)

The advanced refactorings require the refactor-nrepl middleware, which should explain why we added the refactor-nrepl to the :plugins section in the ~/.lein/profiles.clj file (see below).

The real problem is trying to remember all the refactoring options. Remember: C-c . h h

Org Babel

And of course, we want to put this with org blocks:

(use-package ob-clojure
  :straight (:type built-in)
  :custom
  (org-babel-clojure-backend 'cider)
  :config
  (add-to-list 'org-babel-load-languages '(clojure . t)))

Does this now work?

(format "The answer is %d" (+ (* 4 10) 2))

“The answer is 42”

LSP, a WIP

Check out the goodies in this essay for hooking Clojure to LSP. Haven’t done this yet, and to be honest, I’m not sure what it buys us beyond what Cider offers.

(add-hook 'clojure-mode-hook 'lsp)
(add-hook 'clojurescript-mode-hook 'lsp)
(add-hook 'clojurec-mode-hook 'lsp)