#+title: Clojure Programming #+author: Howard X. Abrams #+date: 2022-04-05 #+tags: emacs clojure programming lisp A literate programming file for programming in Clojure. #+begin_src emacs-lisp :exports none ;;; ha-programming-clojure --- programming in Clojure. -*- lexical-binding: t; -*- ;; ;; © 2022-2023 Howard X. Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard X. Abrams ;; 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 I like [[http://clojure.org][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 [[http://brew.sh][Homebrew]], try the following: #+begin_src sh brew install clojure/tools/clojure brew install leiningen #+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 Install and configure [[https://github.com/clojure-emacs/clojure-mode/][clojure-mode]]: #+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 Need the IDE features associated with [[https://github.com/clojure-emacs/cider][Cider]]: #+begin_src emacs-lisp (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))) #+end_src Read the entire [[https://docs.cider.mx/][CIDER manual]], specifically the [[https://docs.cider.mx/cider/usage/cider_mode.html][Usage document]]. ** Linting *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 (use-package flycheck-clojure :after flycheck :config (flycheck-clojure-setup)) #+end_src 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 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 (ha-local-leader :keymaps clojure-mode-map "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 (ha-local-leader :keymaps clojure-mode-map "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: #+begin_src elisp (use-package clojure-snippets) #+end_src ** Refactoring The [[https://github.com/clojure-emacs/clj-refactor.el][clj-refactor]] project: #+begin_src elisp (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) #+end_src 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 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. #+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 #+PROPERTY: header-args:emacs-lisp :tangle yes #+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes #+OPTIONS: num:nil toc:t todo:nil tasks:nil tags:nil date:nil #+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil #+INFOJS_OPT: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js