hamacs/ha-programming-clojure.org
Howard Abrams a8e5fec161 Expanded Clojure to really work
Lots of leader keys to tie into Cider.
2022-09-11 22:29:25 -07:00

11 KiB
Raw Blame History

Clojure Programming

A literate programming file for programming in Clojure.

I like 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 system, try the following on a Mac:

  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

Lets create a keybinding menu of Clojure-related commands:

  (general-create-definer ha-clojure-leader
        :states '(normal visual motion)
        :keymaps 'clojure-mode-map
        :prefix "SPC m"
        :global-prefix "<f17>"
        :non-normal-prefix "S-SPC")

Next, install and configure clojure-mode:

  (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))

Need the IDE feature of 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-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)))

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 Flycheck:

  (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 clojars.org and copies your selection to the kill ring in a format suitable for your project.clj.

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

Clojure Cheatsheet

The clojure-cheatsheet

  (use-package clojure-cheatsheet
    :after clojure-mode
    :config
    (ha-clojure-leader
     "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-clojure-leader
     ;; Would really like to have this on the SPC m 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. Havent done this yet.

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