Skip to content

Latest commit

 

History

History
484 lines (385 loc) · 16.5 KB

emacs-clojure.org

File metadata and controls

484 lines (385 loc) · 16.5 KB

Emacs Settings for Clojure

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.

Introduction

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.

Regarding Leiningen

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.

Required Packages

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:

Each of these packages will be configured below.

Snippets

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)

Clojure Mode

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

Compojure

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

Paredit

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.

REPL

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

Cider

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

Linting

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

Refactoring

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

4Clojure

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

Endless Questions

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

Question Saving?

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...

Technical Artifacts

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