hamacs/ha-programming-clojure.org
2023-04-01 16:32:16 -07:00

301 lines
12 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+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; -*-
;;
;; © 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 <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
I like [[http://clojure.org][Clojure]] as a /modern Lisp/, but I dont 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
Lets create a keybinding menu of Clojure-related commands:
#+begin_src emacs-lisp
(general-create-definer ha-clojure-leader
:states '(normal visual motion)
:keymaps 'clojure-mode-map
:prefix ","
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+end_src
Next, 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
;; Predefine a "help" sequence used later on:
(ha-clojure-leader "h" '(:ignore t :which-key "help"))
(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-clojure-leader
"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)))
#+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-clojure-leader
"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-clojure-leader
"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-clojure-leader
;; 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. Havent done this yet, and to be honest, Im 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: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