Me like Clojure, and since it is a LISP, then Emacs likes it too. The following instructions create a fully blinged-out Emacs-Clojure setup.
While one can copy and paste sections of this document, if you
download the original org-mode
document, type C-c C-v t
to
tangle it as an Emacs Lisp file: ~/.emacs.d/elisp/init-clojure.el
If the directory is added to the Emacs load path, you can then:
(require 'init-clojure)
Otherwise, you can simply call load-file
on it.
To keep from having to add the same information to every
project.clj
file, place the following in ~/.lein/profiles.clj
:
{:repl {:plugins [[cider/cider-nrepl "0.11.0-SNAPSHOT"]
[jonase/eastwood "0.2.2"]
[refactor-nrepl "2.0.0-SNAPSHOT"]
[lein-cljfmt "0.3.0"]]
:dependencies [[org.clojure/clojure "1.7.0"]
[acyclic/squiggly-clojure "0.1.3-SNAPSHOT"]
[org.clojure/tools.nrepl "0.2.12"]]}}
Note: I think the latest version of Cider removes this need.
To make installation easier, we’ll use the use-package project, so
to being, kick off a M-x install-package
with use-package
, and
install the following:
- clojure-mode
- cider
- clj-refactor
- flycheck-clojure (and flycheck of course)
- paredit
- color-identifiers-mode
- clojure-cheatsheet
Each of these packages will be configured below.
Most reliable way to add David Nolen’s clojure-snippets collection
to Yasnippets, is simply to clone the repository into my snippets
directory:
git clone http://github.com/swannodette/clojure-snippets ~/.emacs/snippets/clojure-mode
Or we could just install it as a package:
(use-package clojure-snippets
:ensure t)
The clojure-mode project seems to be the best (and works well with Cider).
(use-package clojure-mode
:ensure t
:init
(defconst clojure--prettify-symbols-alist
'(("fn" . ?λ)
("__" . ?⁈)))
:config
(add-hook 'clojure-mode-hook 'global-prettify-symbols-mode)
:bind (("C-c d f" . cider-code)
("C-c d g" . cider-grimoire)
("C-c d w" . cider-grimoire-web)
("C-c d c" . clojure-cheatsheet)
("C-c d d" . dash-at-point)))
Need to figure out how to get the color identifiers mode to work without an error:
(use-package color-identifiers-mode
:ensure t
:init
(add-hook 'clojure-mode-hook 'color-identifiers-mode))
According to the Compojure Wiki, the following code makes their macros look prettier:
(use-package clojure-mode
:config
(define-clojure-indent
(defroutes 'defun)
(GET 2)
(POST 2)
(PUT 2)
(DELETE 2)
(HEAD 2)
(ANY 2)
(context 2)))
All Lisps, including Clojure, should use paredit.
Since it’s currently possible to use something like join-lines
to pull code up from one line and stick it into the end-of-line
comment of another line, invalidating the code. The following
replacement for delete-indentation prevents this.
(defun paredit-delete-indentation (&optional arg)
"Handle joining lines that end in a comment."
(interactive "*P")
(let (comt)
(save-excursion
(move-beginning-of-line (if arg 1 0))
(when (skip-syntax-forward "^<" (point-at-eol))
(setq comt (delete-and-extract-region (point) (point-at-eol)))))
(delete-indentation arg)
(when comt
(save-excursion
(move-end-of-line 1)
(insert " ")
(insert comt)))))
While M-SPC
(especially M-0 M-SPC
) is good for cleaning up extra
white space on a single line, let’s use this function to get rid of
it all.
(defun paredit-remove-newlines ()
"Removes extras whitespace and newlines from the current point
to the next parenthesis."
(interactive)
(let ((up-to (point))
(from (re-search-forward "[])}]")))
(backward-char)
(while (> (point) up-to)
(paredit-delete-indentation))))
Bind these previous functions and add it to the clojure-mode
:
(use-package paredit
:bind ("M-^" . paredit-delete-indentation)
:bind ("C-^" . paredit-remove-newlines)
:init
(add-hook 'clojure-mode-hook 'paredit-mode))
Useful key sequences for positioning cursor on particular s-expressions:
- C-M- a d
- Move to beginning of function and inside the declaration. Good start to just about any other positioning.
- C-M- d f d
- At beginning of function, moves to first s-expression.
When demonstrating Clojure, I find it is a better approach is to send the S-Expression to the REPL and evaluate it there instead of showing the result in the mini-buffer:
(defun cider-send-and-evaluate-sexp ()
"Sends the s-expression located before the point or the active
region to the REPL and evaluates it. Then the Clojure buffer is
activated as if nothing happened."
(interactive)
(if (not (region-active-p))
(cider-insert-last-sexp-in-repl)
(cider-insert-in-repl
(buffer-substring (region-beginning) (region-end)) nil))
(cider-switch-to-repl-buffer)
(cider-repl-closing-return)
(cider-switch-to-last-clojure-buffer)
(message ""))
The Cider project is da bomb. Usage:
cider-jack-in
- For starting an nREPL server and setting everything up. Keyboard:C-c M-j
cider
to connect to an existing nREPL server.
(use-package cider
:ensure t
:commands (cider cider-connect cider-jack-in)
:init
(setq cider-auto-select-error-buffer t
cider-repl-pop-to-buffer-on-connect nil
cider-repl-use-clojure-font-lock t
cider-repl-wrap-history t
cider-repl-history-size 1000
cider-show-error-buffer t
nrepl-hide-special-buffers t
;; Stop error buffer from popping up while working in buffers other than the REPL:
nrepl-popup-stacktraces nil)
;; (add-hook 'cider-mode-hook 'cider-turn-on-eldoc-mode)
(add-hook 'cider-mode-hook 'company-mode)
(add-hook 'cider-repl-mode-hook 'paredit-mode)
(add-hook 'cider-repl-mode-hook 'superword-mode)
(add-hook 'cider-repl-mode-hook 'company-mode)
(add-hook 'cider-test-report-mode 'jcf-soft-wrap)
:bind (:map cider-mode-map
("C-c C-v C-c" . cider-send-and-evaluate-sexp)
("C-c C-p" . cider-eval-print-last-sexp))
:config
(use-package slamhound))
What about doing the evaluation but re-inserting the results as a comment at the end of the expression? Let’s create a function that will insert a comment character if we aren’t already in a comment, and we will then advice the Cider function that prints the results:
(defun ha/cider-append-comment ()
(when (null (nth 8 (syntax-ppss)))
(insert " ; ")))
(advice-add 'cider-eval-print-last-sexp :before #'ha/cider-append-comment)
While I typically use clj-refactor’s add-missing-libspec function,
I am thinking of looking into Slamhound for reconstructing the ns
namespace.
This also specifies using ElDoc working with Clojure.
To get Clojure’s Cider working with org-mode, do:
(use-package ob-clojure
:init
(setq org-babel-clojure-backend 'cider))
Using Eastwood with the Squiggly Clojure project to add lint warnings to Flycheck:
(use-package flycheck-clojure
:ensure t
:init
(add-hook 'after-init-hook 'global-flycheck-mode)
:config
(use-package flycheck
:config
(flycheck-clojure-setup)))
Seems we should also install flycheck-pos-tip as well.
(use-package flycheck-pos-tip
:ensure t
:config
(use-package flycheck
:config
(setq flycheck-display-errors-function 'flycheck-pos-tip-error-messages)))
Using the clj-refactor project:
(use-package clj-refactor
:ensure t
:init
(add-hook 'clojure-mode-hook 'clj-refactor-mode)
:config
;; Configure the Clojure Refactoring prefix:
(cljr-add-keybindings-with-prefix "C-c .")
: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.
Of course, the real problem is trying to remember all the
refactoring options. Remember: C-c . h h
Finally, if you are just learning Clojure, check out 4Clojure and then install 4clojure-mode.
(use-package 4clojure
:init
(bind-key "<f9> a" '4clojure-check-answers clojure-mode-map)
(bind-key "<f9> n" '4clojure-next-question clojure-mode-map)
(bind-key "<f9> p" '4clojure-previous-question clojure-mode-map)
:config
(defadvice 4clojure-open-question (around 4clojure-open-question-around)
"Start a cider/nREPL connection if one hasn't already been started when
opening 4clojure questions."
ad-do-it
(unless cider-current-clojure-buffer
(cider-jack-in))))
Got some good advice from Endless Parens for dealing with 4Clojure:
(defun endless/4clojure-check-and-proceed ()
"Check the answer and show the next question if it worked."
(interactive)
(unless
(save-excursion
;; Find last sexp (the answer).
(goto-char (point-max))
(forward-sexp -1)
;; Check the answer.
(cl-letf ((answer
(buffer-substring (point) (point-max)))
;; Preserve buffer contents, in case you failed.
((buffer-string)))
(goto-char (point-min))
(while (search-forward "__" nil t)
(replace-match answer))
(string-match "failed." (4clojure-check-answers))))
(4clojure-next-question)))
And:
(defadvice 4clojure/start-new-problem
(after endless/4clojure/start-new-problem-advice () activate)
;; Prettify the 4clojure buffer.
(goto-char (point-min))
(forward-line 2)
(forward-char 3)
(fill-paragraph)
;; Position point for the answer
(goto-char (point-max))
(insert "\n\n\n")
(forward-char -1)
;; Define our key.
(local-set-key (kbd "M-j") #'endless/4clojure-check-and-proceed))
I really should advice the 4clojure-next-question
to store the
current question … and then we can pop back to that and resume
where we left off.
We need a file where we can save our current question:
(defvar ha-4clojure-place-file (concat user-emacs-directory "4clojure-place.txt"))
Read a file’s contents as a buffer by specifying the file. For this, we use a temporary buffer, so that we don’t have to worry about saving it.
(defun ha-file-to-string (file)
"Read the contents of FILE and return as a string."
(with-temp-buffer
(insert-file-contents file)
(buffer-substring-no-properties (point-min) (point-max))))
Parse a file into separate lines and return a list.
(defun ha-file-to-list (file)
"Return a list of lines in FILE."
(split-string (ha-file-to-string file) "\n" t))
We create a wrapper function that reads our previous “place” question and then calls the open question function.
(defun ha-4clojure-last-project (file)
(interactive "f")
(if (file-exists-p file)
(car (ha-file-to-list file))
"1"))
(defun 4clojure-start-session ()
(interactive)
(4clojure-open-question
(ha-4clojure-last-project ha-4clojure-place-file)))
(global-set-key (kbd "<f2> s") '4clojure-start-session)
Write a value to a file. Making this interactive makes for an interesting use case…we’ll see if I use that.
(defun ha-string-to-file (string file)
(interactive "sEnter the string: \nFFile to save to: ")
(with-temp-file file
(insert string)))
Whenever we load a 4clojure project or go to the next one, we store the project number to our “place” file:
(when (package-installed-p '4clojure)
(defun ha-4clojure-store-place (num)
(ha-string-to-file (int-to-string num) ha-4clojure-place-file))
(defadvice 4clojure-next-question (after ha-4clojure-next-question)
"Save the place for each question you progress to."
(ha-4clojure-store-place (4clojure/problem-number-of-current-buffer)))
(defadvice 4clojure-open-question (after ha-4clojure-next-question)
"Save the place for each question you progress to."
(ha-4clojure-store-place (4clojure/problem-number-of-current-buffer)))
(ad-activate '4clojure-next-question)
(ad-activate '4clojure-open-question))
;; Notice that we don't advice the previous question...
Make sure that we can simply require
this library.
(provide 'init-clojure)
Before you can build this on a new system, make sure that you put
the cursor over any of these properties, and hit: C-c C-c