;#+TITLE: Emacs Settings for Clojure
Me like Clojure, and since it is a LISP, then Emacs likes it too.
Here are all the packages related to Clojure that I use. Note: Since
I use Cider for all projects, I place the following in
~/.lein/profiles.clj
:
{:user {:plugins [[cider/cider-nrepl "0.10.0-SNAPSHOT"]
[refactor-nrepl "1.2.0-SNAPSHOT"]]
:dependencies [[org.clojure/tools.nrepl "0.2.7"]]}}
Since I’m using the latest of these tools, the version numbers change regularly.
(packages-install '( clojure-mode
clojure-cheatsheet
clojure-snippets
clojurescript-mode
cider
ac-cider
clj-refactor
elein
paredit
popup
rainbow-delimiters ;; Mode for alternating paren colors
rainbow-mode
))
Need to add Yasnippets to Clojure mode:
(require 'clojure-mode)
(add-hook 'clojure-mode-hook
'(lambda ()
;; Two under-bars are used as "stuff to do" in the Koans
(highlight-phrase "__" 'hi-red-b)))
According to the Compojure Wiki, the following code makes their macros look prettier:
(define-clojure-indent
(defroutes 'defun)
(GET 2)
(POST 2)
(PUT 2)
(DELETE 2)
(HEAD 2)
(ANY 2)
(context 2))
I like the idea that we can evaluate an s-expression, and have the results embedded into the source code as a comment. Thanks to this blog entry for the idea.
(defun cider-eval-last-sexp-and-append ()
"Evaluate the expression preceding point and append result."
(interactive)
(let* ((last-sexp (if (region-active-p)
(buffer-substring (region-beginning) (region-end))
(cider-last-sexp)))
(last-results (cider-eval-and-get-value last-sexp)))
(with-current-buffer (current-buffer)
(comment-indent)
(insert " => ")
(insert (prin1-to-string last-results)))))
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 ""))
(defun cider-eval-expression-at-point-in-repl ()
(interactive)
(let ((form (cider-defun-at-point)))
;; Strip excess whitespace
(while (string-match "\\`\s+\\|\n+\\'" form)
(setq form (replace-match "" t t form)))
(set-buffer (cider-get-repl-buffer))
(goto-char (point-max))
(insert form)
(cider-repl-return)))
(define-key cider-mode-map
(kbd "C-;") 'cider-eval-expression-at-point-in-repl)
The M-e to go forward a sentence may be useful, but not during
coding. Let’s rebind that key sequence to forward-sexp
:
(add-hook 'clojure-mode-hook
(lambda ()
(local-set-key (kbd "M-e") 'forward-sexp)
(local-set-key (kbd "M-a") 'backward-sexp)
(local-set-key (kbd "C-c C-v") 'cider-eval-last-sexp-and-append)
(local-set-key (kbd "C-c C-S-v") 'cider-send-and-evaluate-sexp)))
(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)))))
(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))))
(define-key paredit-mode-map (kbd "C-^") 'paredit-remove-newlines)
(define-key paredit-mode-map (kbd "M-^") 'paredit-delete-indentation)
Making it easier to read some Clojure code by changing into actual symbols.
(when (fboundp 'global-prettify-symbols-mode)
(defconst clojure--prettify-symbols-alist
'(("fn" . ?λ)
("->" . ?⤷) ;; Threading for the first (into left)
("->>" . ?⤶) ;; Threading for the last item (from right)
("<=" . ?≤)
(">=" . ?≥)
("==" . ?≡) ;; Do I like this?
("not=" . ?≠) ;; Or even this?
("." . ?•)
("__" . ?⁈))))
Most LISP-based programming is better with rainbow ponies:
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
(add-hook 'cider-repl-mode-hook 'rainbow-delimiters-mode)
But the only parens I really care about are the bad ones, so let’s make all those rainbow colors disappear, leaving only the bad red ones:
(when (require 'rainbow-delimiters nil t)
(set-face-attribute 'rainbow-delimiters-unmatched-face nil
:foreground 'unspecified
:inherit 'error))
Really want to try out my new ClojureDocs functions. Note: You need to do the following steps:
git clone https://github.com/howardabrams/clojuredocs-emacs.git $work/clojure-docs-emacs
ln -s $work/clojure-docs-emacs/clojure-docs.el ~/.emacs.d/elisp
Then the following code will work:
(when (require 'clojure-docs nil t)
(add-hook 'clojure-mode-hook
(lambda ()
(local-set-key (kbd "C-c C-e") 'clojuredocs-examples))))
Basic auto completion taken from these instructions:
(require 'auto-complete-config)
(setq ac-delay 0.0)
(setq ac-quick-help-delay 0.5)
(ac-config-default)
The CIDER-specific configuration for auto completion:
(require 'ac-cider)
((and )dd-hook 'cider-mode-hook 'ac-flyspell-workaround)
(add-hook 'cider-mode-hook 'ac-cider-setup)
(add-hook 'cider-repl-mode-hook 'ac-cider-setup)
(eval-after-load "auto-complete"
'(add-to-list 'ac-modes 'cider-mode))
And we can call it with C-c C-d
:
(eval-after-load "cider"
'(define-key cider-mode-map (kbd "C-c C-d") 'ac-cider-popup-doc))
Need to get ElDoc working with Clojure (oh, and with Emacs Lisp). Do I need this EL file?
(add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
(add-hook 'clojure-mode-hook 'eldoc-mode)
(add-hook 'cider-mode-hook 'cider-turn-on-eldoc-mode)
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.
Let’s color the REPL:
(setq cider-repl-use-clojure-font-lock t)
Don’t care much for the extra buffers that show up when you start:
(setq nrepl-hide-special-buffers t)
Stop the error buffer from popping up while working in buffers other than the REPL:
(setq cider-popup-stacktraces nil)
To get Clojure’s Cider working with org-mode, do:
;; (require 'ob-clojure)
(setq org-babel-clojure-backend 'cider)
(require 'cider)
But we will evaluate in a particular cider-connection
with:
(global-set-key (kbd "C-c j") 'cider-eval-last-sexp)
Using the clj-refactor project:
(when (require 'clj-refactor nil t)
(defun my-clojure-mode-hook ()
(clj-refactor-mode 1)
(define-key clj-refactor-map (kbd "C-x C-r") 'cljr-rename-file)
(cljr-add-keybindings-with-prefix "C-c ."))
(add-hook 'clojure-mode-hook #'my-clojure-mode-hook))
The advanced refactorings require the refactor-nrepl middleware, so
add the following to either the project’s project.clj
or in the :user
profile found at ~/.lein/profiles.clj
:
:plugins [[refactor-nrepl "1.0.5"]]
The real problem is trying to remember all refactoring options.
Pulling up the documentation for a Clojure function is indispensable.
(eval-after-load "cider"
'(define-key cider-mode-map (kbd "C-c C-d") 'cider-doc))
Finally, if you are just learning Clojure, check out 4Clojure and then install 4clojure-mode.
(when (package-installed-p '4clojure)
(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)))
(global-set-key (kbd "<f9> 4") '4clojure-open-question)
(define-key clojure-mode-map (kbd "<f9> a") '4clojure-check-answers)
(define-key clojure-mode-map (kbd "<f9> n") '4clojure-next-question)
(define-key clojure-mode-map (kbd "<f9> p") '4clojure-previous-question))
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