A collection of simple clojure refactoring functions. Please send help.
I highly recommend installing clj-refactor through elpa.
It's available on marmalade and melpa:
M-x package-install clj-refactor
You can also install the dependencies on your own, and just dump clj-refactor in your path somewhere:
(require 'clj-refactor)
(add-hook 'clojure-mode-hook (lambda ()
(clj-refactor-mode 1)
;; insert keybinding setup here
))You'll also have to set up the keybindings in the lambda. Read on.
All functions in clj-refactor have a two-letter mnemonic shortcut. For
instance, rename-file is rf. You get to choose how those are bound.
Here's how:
(cljr-add-keybindings-with-prefix "C-c C-m")
;; eg. rename files with `C-c C-m rf`.If you would rather have a modifier key, instead of a prefix, do:
(cljr-add-keybindings-with-modifier "C-s-")
;; eg. rename files with `C-s-r C-s-f`.If neither of these appeal to your sense of keyboard layout aesthetics, feel free to pick and choose your own keybindings with a smattering of:
(define-key clj-refactor-map (kbd "C-x C-r") 'cljr-rename-file)This is it so far:
th: thread another expressionuw: unwind a threaded expressionua: fully unwind a threaded expressiontf: wrap in thread-first (->) and fully threadtl: wrap in thread-last (->>) and fully threadil: introduce letel: expand letml: move to letrf: rename file, update ns-declaration, and then query-replace new ns in project.ar: add require to namespace declaration, then jump back (see optional setup)au: add "use" (ie require refer all) to namespace declaration, then jump backai: add import to namespace declaration, then jump backru: replace all:usein namespace with:refer :allsn: sort :use, :require and :import in the ns formrr: remove unused requirespc: run project cleaner functions on the whole projectsr: stop referring (removes:refer []from current require, fixing references)cc: cycle surrounding collection typecp: cycle privacy ofdefns anddefscs: cycle between "string" -> :string -> "string"ci: refactoring betweenifandif-notad: add declaration for current top-level formdk: destructure keysmf: move one or more forms to another namespace,:referany functionssp: Sort all dependency vectors in project.clj
Combine with your keybinding prefix/modifier.
Given this:
(map square (filter even? [1 2 3 4 5]))Start by wrapping it in a threading macro:
(->> (map square (filter even? [1 2 3 4 5])))And start threading away, using cljr-thread:
(->> (filter even? [1 2 3 4 5])
(map square))And again:
(->> [1 2 3 4 5]
(filter even?)
(map square))You can also do all of these steps in one go.
Start again with:
(map square (filter even? [1 2 3 4 5]))Put your cursor in front of the s-exp, and call cljr-thread-last-all:
(->> [1 2 3 4 5]
(filter even?)
(map square))There is a corresponding cljr-thread-first-all as well.
To revert this, there's cljr-unwind to unwind one step at a time. Or
there's cljr-unwind-all to unwind the entire expression at once.
To see how that works, just read the examples in the other direction.
Given this:
(defn handle-request
{:status 200
:body (find-body abc)})With the cursor in front of (find-body abc), I do cljr-introduce-let:
(defn handle-request
{:status 200
:body (let [X (find-body abc)]
X)})Now I have two cursors where the Xes are. Just type out the name,
and press enter. Of course, that's not where I wanted the let
statement. So I do cljr-expand-let:
(defn handle-request
(let [body (find-body abc)]
{:status 200
:body body}))Yay.
Next with the cursor in front of 200, I do cljr-move-to-let:
(defn handle-request
(let [body (find-body abc)
X 200]
{:status X
:body body}))Again I have two cursors where the Xes are, so I type out the name,
and press enter:
(defn handle-request
(let [body (find-body abc)
status 200]
{:status status
:body body}))Pretty handy. And it works with if-let and when-let too.
Given this function:
(defn add [a b]
(+ a b))I do cljr-cycle-privacy:
(defn- add [a b]
(+ a b))I do cljr-cycle-privacy again to return to the original:
(defn add [a b]
(+ a b))Given this def:
(def config
"docs"
{:env "staging"})I do cljr-cycle-privacy:
(def ^:private config
"docs"
{:env "staging"})I do cljr-cycle-privacy again to return to the original:
(def config
"docs"
{:env "staging"})Given this collection:
(:a 1 :b 2)I do cljr-cycle-coll to return:
{:a 1 :b 2}... and then 3 more times:
[:a 1 :b 2]
#{:a 1 :b 2}
(:a 1 :b 2)Given this string:
"refactor"I do cljr-cycle-stringlike to return:
:refactor... and then 3 more times:
"refactor"
:refactor
"refactor"Thanks to Jay Fields and emacs-live for these cycling features. Good idea!
Given this:
(defn- render-recommendation [rec]
(list [:h3 (:title rec)]
[:p (:by rec)]
[:p (:blurb rec) " "
(render-link (:link rec))]))I place the cursor on rec inside [rec] and do cljr-destructure-keys:
(defn- render-recommendation [{:keys [title by blurb link]}]
(list [:h3 title]
[:p by]
[:p blurb " "
(render-link link)]))If rec had still been in use, it would have added an :as clause.
For now this feature is limited to top-level symbols in a let form. PR welcome.
Given this:
(ns cljr.core
(:require [my.lib :as lib :refer [a b]]))
(+ (a 1) (b 2))I place cursor on my.lib and do cljr-stop-referring:
(ns cljr.core
(:require [my.lib :as lib]))
(+ (lib/a 1) (lib/b 2))If you're not using yasnippet, then the "jumping back"-part of adding to namespace won't work. To remedy that, enable the mode with either:
(yas/global-mode 1)or
(add-hook 'clojure-mode-hook (lambda () (yas/minor-mode 1)))It is an excellent package, so I recommend looking into it. Here are some snippet packages for Clojure:
- David Nolen has created some clojure-snippets
- I've made some datomic-snippets
- Max Penet has also created some clojure-snippets, early fork of dnolens' with tons of additions and MELPA compatible
By default sort ns sn will sort your ns declaration alphabetically. You can change this by setting cljr-sort-comparator in your clj-refactor configuration.
Sort it longer first:
(setq cljr-sort-comparator 'cljr--string-length-comparator)Or you can use the semantic comparator:
(setq cljr-sort-comparator 'cljr--semantic-comparator)The semantic comparator sorts used and required namespaces closer to the namespace of the current buffer before the rest. When this is not applicable it falls back to alphabetical sorting.
For example the following namespace:
(ns foo.bar.baz.goo
(:require [clj-time.bla :as bla]
[foo.bar.baz.bam :refer :all]
[foo.bar.async :refer :all]
[foo [bar.goo :refer :all] [baz :refer :all]]
[async.funkage.core :as afc]
[clj-time.core :as clj-time]
[foo.async :refer :all])
(:import (java.security MessageDigest)
java.util.Calendar
[org.joda.time DateTime]
(java.nio.charset Charset)))will be sorted like this:
(ns foo.bar.baz.goo
(:require [foo.bar.baz.bam :refer :all]
[foo.bar.async :refer :all]
[foo.async :refer :all]
[foo [bar.goo :refer :all] [baz :refer :all]]
[async.funkage.core :as afc]
[clj-time.bla :as bla]
[clj-time.core :as clj-time])
(:import (java.nio.charset Charset)
(java.security MessageDigest)
java.util.Calendar
[org.joda.time DateTime]))The cljr-sort-comparator variable also enables you to write your own comparator function if you prefer. Comparator is called with two elements of the sub section of the ns declaration, and should return non-nil if the first element should sort before the second.
When you open a blank .clj-file, clj-refactor inserts the namespace
declaration for you.
It will also add the relevant :use clauses in test files, normally
using clojure.test, but if you're depending on midje in your
project.clj it uses that instead.
Like clojure-mode, clj-refactor presumes that you are postfixing your
test files with _test.
Prefer to insert your own ns-declarations? Then:
(setq clj-add-ns-to-blank-clj-files nil)Common namespace shorthands are automatically required when you type them:
For instance, typing (io/) adds [clojure.java.io :as io] to the requires.
ioisclojure.java.iosetisclojure.setstrisclojure.stringwalkisclojure.walkzipisclojure.zip
You can turn this off with:
(setq cljr-magic-requires nil)or set it to :prompt if you want to confirm before it inserts.
cljr-project-clean runs some clean up functions on all clj files in a project in bulk. By default these are cljr-remove-unused-requires and cljr-sort-ns. Additionally, cljr-sort-project-dependencies is called to put the project.clj file in order. Before any changes are made, the user is prompted for confirmation because this function can touch a large number of files.
This promting can be switched off by setting cljr-project-clean-prompt nil:
(setq cljr-project-clean-prompt nil)The list of functions to run with cljr-project-clean is also configurable via cljr-project-clean-functions. You can add more functions defined in clj-refactor or remove some or even write your own.
cljr-project-clean will only work with leiningen managed projects with a project.clj in their root directory. This limitation will very likely be fixed when #27 is done.
With clj-refactor enabled, any keybindings for paredit-raise-sexp is
replaced by cljr-raise-sexp which does the same thing - except it
also removes any # in front of function literals and sets.
You might also like
- align-cljlet - which is an Emacs package for aligning let-like forms.
- Add
cljr-cycle-ifAlexBaranosky - Common namespace shorthands are (optionally) automatically required when you type it.
- Comparator for sort require, use and import is configurable, add optional lenght based comparator to sort longer first Benedek Fazekas
- Add semantic comparator to sort items closer to the current namespace first Benedek Fazekas
- Add
cljr-project-cleanwith configurable clean functions Benedek Fazekas - Add
cljr-sort-project-dependenciesLars Andersen
- When expanding let, or moving expressions to let, it now replaces duplicates in the let body with the bound name. Benedek Fazekas
- Add
cljr-raise-sexp - Add
cljr-remove-unused-requiresBenedek Fazekas - Add
cljr-move-formLars Andersen
- Add
cljr-stop-referring - Add
cljr-destructure-keys - Add
cljr-sort-nsAlexBaranosky
- Add
cljr-replace-useLars Andersen - Add
cljr-add-declarationLars Andersen
- Add
cljr-cycle-stringlikeAlexBaranosky - Add
cljr-cycle-collAlexBaranosky - Add
cljr-cycle-privacyAlexBaranosky
- Add
cljr-thread-first-all,cljr-thread-last-allandcljr-unwind-allAlexBaranosky
- Add
cljr-move-to-letAlexBaranosky
Yes, please do. There's a suite of tests, so remember to add tests for your specific feature, or I might break it later.
You'll find the repo at:
https://github.com/magnars/clj-refactor.el
To fetch the test dependencies, install cask if you haven't already, then:
$ cd /path/to/clj-refactor
$ cask
Run the tests with:
$ ./run-tests.sh
- AlexBaranosky added a bunch of features. See the Changelog for details.
- Lars Andersen added
cljr-replace-use,cljr-add-declarationandcljr-move-form,cljr-sort-project-dependencies. - Benedek Fazekas added
cljr-remove-unused-requiresand improved on the let-expanding functions.
Thanks!
Copyright © 2012-2014 Magnar Sveen
Authors: Magnar Sveen [email protected] Keywords: clojure convenience
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
