Skip to content

Latest commit

 

History

History
504 lines (397 loc) · 16.2 KB

emacs-clojure.org

File metadata and controls

504 lines (397 loc) · 16.2 KB

;#+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.

Supporting Packages

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

Compojure

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

Keystrokes

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)

Code Highlighting

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?
      ("."    . ?•)
      ("__"   . ?⁈))))

Rainbow Ponies

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

Clojure Docs

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

Auto Completion

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

ElDoc

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)

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.

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)

Refactoring

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.

New Key Bindings

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

4Clojure

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

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