2022-08-24 21:37:29 +00:00
#+TITLE : Clojure Programming
#+AUTHOR : Howard X. Abrams
#+DATE : 2022-04-05
#+FILETAGS : :emacs:
A literate programming file for programming in Clojure.
#+begin_src emacs-lisp :exports none
;;; ha-programming-clojure --- programming in Clojure. -*- lexical-binding: t; -* -
;;
2023-02-23 17:35:36 +00:00
;; © 2022-2023 Howard X. Abrams
2022-08-24 21:37:29 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
;; Maintainer: Howard X. Abrams
;; Created: April 5, 2022
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/ha-programming-clojure.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
2022-09-12 05:29:25 +00:00
I like [[http://clojure.org ][Clojure ]] as a /modern Lisp/ , but I don’ t get to play with it much anymore. 😢
2022-08-24 21:37:29 +00:00
The following instructions create a fully blinged-out Emacs-Clojure setup.
* Introduction
2022-09-13 05:09:26 +00:00
To get Clojure working on a new MacOS system using [[http://brew.sh ][Homebrew ]], try the following:
2022-08-24 21:37:29 +00:00
#+begin_src sh
brew install clojure/tools/clojure
2022-09-12 05:29:25 +00:00
brew install leiningen
2022-08-24 21:37:29 +00:00
#+end_src
And make sure it works:
#+begin_src sh
clojure --help
#+end_src
Or by starting a REPL:
#+begin_src sh
clj
#+end_src
Then for each project, create the project directory with this command:
#+begin_src sh
lein new app fresh-app
#+end_src
* Emacs Support
2023-05-01 18:49:33 +00:00
Install and configure [[https://github.com/clojure-emacs/clojure-mode/ ][clojure-mode ]]:
2022-08-24 21:37:29 +00:00
#+begin_src emacs-lisp
(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))
#+end_src
2022-09-13 05:09:26 +00:00
Need the IDE features associated with [[https://github.com/clojure-emacs/cider ][Cider ]]:
2022-08-24 21:37:29 +00:00
#+begin_src emacs-lisp
(use-package cider
2022-09-12 05:29:25 +00:00
:after clojure-mode
:commands (cider-connect cider-jack-in cider)
2022-08-24 21:37:29 +00:00
:init
2022-09-12 05:29:25 +00:00
(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.
2022-08-24 21:37:29 +00:00
cider-repl-pop-to-buffer-on-connect nil
2022-09-12 05:29:25 +00:00
2022-08-24 21:37:29 +00:00
cider-repl-use-clojure-font-lock t
2022-09-12 05:29:25 +00:00
;; T to wrap history around when the end is reached.
2022-08-24 21:37:29 +00:00
cider-repl-wrap-history t
2022-09-12 05:29:25 +00:00
2022-08-24 21:37:29 +00:00
cider-repl-history-size 1000
2022-09-12 05:29:25 +00:00
;; Hide `*nrepl-connection* ' and `*nrepl-server* ' buffers from appearing
;; in some buffer switching commands like switch-to-buffer
2022-08-24 21:37:29 +00:00
nrepl-hide-special-buffers t
2022-09-12 05:29:25 +00:00
2022-08-24 21:37:29 +00:00
;; Stop error buffer from popping up while working in buffers other than the REPL:
2022-09-12 05:29:25 +00:00
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
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps clojure-mode-map
2022-09-13 05:09:26 +00:00
"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)
"t T" '("toggle test/file" . projectile-toggle-between-implementation-and-test)
"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)))
2022-08-24 21:37:29 +00:00
#+end_src
2022-09-12 05:29:25 +00:00
Read the entire [[https://docs.cider.mx/ ][CIDER manual ]], specifically the [[https://docs.cider.mx/cider/usage/cider_mode.html ][Usage document ]].
2022-08-24 21:37:29 +00:00
** Linting
2022-09-12 05:29:25 +00:00
*Note:* The [[https://develop.spacemacs.org/layers/+lang/clojure/README.html ][Spacemacs community ]] recommends using [[https://github.com/borkdude/clj-kondo ][clj-kondo ]] in combination with [[https://github.com/candid82/joker ][joker ]].
Add lint warnings to [[file:emacs.org::*Flycheck ][Flycheck ]]:
#+begin_src elisp
2022-08-24 21:37:29 +00:00
(use-package flycheck-clojure
:after flycheck
:config
(flycheck-clojure-setup))
2022-09-12 05:29:25 +00:00
#+end_src
2022-08-24 21:37:29 +00:00
2022-09-12 05:29:25 +00:00
To install the =joker= binary:
#+begin_src sh
brew install candid82/brew/joker
#+end_src
And the [[https://github.com/candid82/flycheck-joker ][flycheck-joker ]] package should do the trick:
#+begin_src emacs-lisp
(use-package flycheck-joker)
#+end_src
2022-08-24 21:37:29 +00:00
2022-09-12 05:29:25 +00:00
To install the =clj-kondo= binary is a bit more involved:
#+begin_src sh
curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
chmod +x install-clj-kondo
./install-clj-kondo
#+end_src
And the [[https://github.com/borkdude/flycheck-clj-kondo ][flycheck-clj-kondo ]] project should do the integration:
#+begin_src emacs-lisp
(use-package flycheck-clj-kondo)
#+end_src
Search on Clojars more easily
This [[https://github.com/joshuamiller/clojars.el ][clojars ]] extension allows you to search for projects on [[www.clojars.org ][clojars.org ]] and copies your selection to the kill ring in a format suitable for your =project.clj= .
#+begin_src emacs-lisp
(use-package clojars
:after clojure-mode
:config
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps clojure-mode-map
2022-09-12 05:29:25 +00:00
"h j" '("clojars" . clojars)))
#+end_src
** Clojure Cheatsheet
The [[https://github.com/clojure-emacs/clojure-cheatsheet ][clojure-cheatsheet ]]
#+begin_src emacs-lisp
(use-package clojure-cheatsheet
:after clojure-mode
:config
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps clojure-mode-map
2022-09-12 05:29:25 +00:00
"h c" '("cheatsheet" . clojure-cheatsheet)))
#+end_src
** Snippets
For clojure-specific templates for [[https://github.com/capitaomorte/yasnippet ][yasnippets ]], we use David Nolen's [[http://github.com/swannodette/clojure-snippets ][clojure-snippets ]] repository:
2022-08-24 21:37:29 +00:00
#+begin_src elisp
(use-package clojure-snippets)
#+end_src
** Refactoring
The [[https://github.com/clojure-emacs/clj-refactor.el ][clj-refactor ]] project:
2022-09-12 05:29:25 +00:00
#+begin_src elisp
2022-08-24 21:37:29 +00:00
(use-package clj-refactor
2022-09-12 05:29:25 +00:00
:after clojure-mode
2022-08-24 21:37:29 +00:00
:hook
(clojure-mode . clj-refactor-mode)
2022-09-12 05:29:25 +00:00
2022-08-24 21:37:29 +00:00
:config
2022-09-12 05:29:25 +00:00
;; Configure the Clojure Refactoring prefix.
2022-08-24 21:37:29 +00:00
(cljr-add-keybindings-with-prefix "C-c .")
2022-09-12 05:29:25 +00:00
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps clojure-mode-map
2023-03-20 21:01:40 +00:00
;; Would really like to have this on the , prefix:
2022-09-12 05:29:25 +00:00
"r" '("refactoring" . hydra-cljr-help-menu/body)
"h d" '("describe refactoring" . cljr-describe-refactoring)
"h r" '("refactoring" . hydra-cljr-toplevel-form-menu/body))
2022-08-24 21:37:29 +00:00
:diminish clj-refactor-mode)
2022-09-12 05:29:25 +00:00
#+end_src
2022-08-24 21:37:29 +00:00
The advanced refactorings require the [[https://github.com/clojure-emacs/refactor-nrepl ][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 [[https://github.com/clojure-emacs/clj-refactor.el/wiki ][refactoring options ]]. Remember: =C-c . h h=
** Org Babel
And of course, we want to put this with org blocks:
#+begin_src emacs-lisp
(use-package ob-clojure
:straight (:type built-in)
:custom
(org-babel-clojure-backend 'cider)
:config
(add-to-list 'org-babel-load-languages '(clojure . t)))
#+end_src
Does this now work?
#+begin_src clojure :results raw replace
(format "The answer is %d" (+ (* 4 10) 2))
#+end_src
#+RESULTS :
"The answer is 42"
* LSP, a WIP
2022-09-13 05:09:26 +00:00
Check out the goodies in [[https://emacs-lsp.github.io/lsp-mode/tutorials/clojure-guide/ ][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.
2022-08-24 21:37:29 +00:00
#+begin_src emacs-lisp :tangle no
(add-hook 'clojure-mode-hook 'lsp)
(add-hook 'clojurescript-mode-hook 'lsp)
(add-hook 'clojurec-mode-hook 'lsp)
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-programming-clojure)
;;; ha-programming-clojure.el ends here
#+end_src
#+DESCRIPTION : programming in Clojure.
#+PROPERTY : header-args:sh :tangle no
2023-07-05 16:22:41 +00:00
#+PROPERTY : header-args:emacs-lisp :tangle ~/.emacs.d/elisp/ha-programming-clojure.el
2022-08-24 21:37:29 +00:00
#+PROPERTY : header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS : num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS : skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT : view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js