Skip to content

A collection of simple clojure refactoring functions for Emacs

Notifications You must be signed in to change notification settings

lsnape/clj-refactor.el

 
 

Repository files navigation

clj-refactor.el Build Status

A collection of simple clojure refactoring functions. Please send help.

Installation

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:

Setup

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

Setup keybindings

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)

Usage

This is it so far:

  • th: thread another expression
  • uw: unwind a threaded expression
  • ua: fully unwind a threaded expression
  • tf: wrap in thread-first (->) and fully thread
  • tl: wrap in thread-last (->>) and fully thread
  • il: introduce let
  • el: expand let
  • ml: move to let
  • rf: 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 back
  • ai: add import to namespace declaration, then jump back
  • ru: replace all :use in namespace with :refer :all
  • sn: sort :use, :require and :import in the ns form
  • rr: remove unused requires
  • pc: run project cleaner functions on the whole project
  • sr: stop referring (removes :refer [] from current require, fixing references)
  • cc: cycle surrounding collection type
  • cp: cycle privacy of defns and defs
  • cs: cycle between "string" -> :string -> "string"
  • ci: refactoring between if and if-not
  • ad: add declaration for current top-level form
  • dk: destructure keys
  • mf: move one or more forms to another namespace, :refer any functions
  • sp: Sort all dependency vectors in project.clj

Combine with your keybinding prefix/modifier.

Thread / unwind example

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.

Introduce / expand / move to let example

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.

Cycling Privacy

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

Cycling Collection Type

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)

Cycling Between Strings and Keywords

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!

Destructuring keys

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.

Stop referring

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

Optional setup

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:

Changing the way how the ns declaration is sorted

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.

Automatic insertion of namespace declaration

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)

Magic requires

Common namespace shorthands are automatically required when you type them:

For instance, typing (io/) adds [clojure.java.io :as io] to the requires.

  • io is clojure.java.io
  • set is clojure.set
  • str is clojure.string
  • walk is clojure.walk
  • zip is clojure.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.

Project clean up

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.

Miscellaneous

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.

More stuff to check out

You might also like

  • align-cljlet - which is an Emacs package for aligning let-like forms.

Changelog

  • Add cljr-cycle-if AlexBaranosky
  • 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-clean with configurable clean functions Benedek Fazekas
  • Add cljr-sort-project-dependencies Lars Andersen

From 0.11 to 0.12

  • When expanding let, or moving expressions to let, it now replaces duplicates in the let body with the bound name. Benedek Fazekas

From 0.10 to 0.11

From 0.9 to 0.10

  • Add cljr-stop-referring
  • Add cljr-destructure-keys
  • Add cljr-sort-ns AlexBaranosky

From 0.8 to 0.9

From 0.7 to 0.8

From 0.6 to 0.7

  • Add cljr-thread-first-all, cljr-thread-last-all and cljr-unwind-all AlexBaranosky

From 0.5 to 0.6

Contribute

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

Contributors

  • AlexBaranosky added a bunch of features. See the Changelog for details.
  • Lars Andersen added cljr-replace-use, cljr-add-declaration and cljr-move-form, cljr-sort-project-dependencies.
  • Benedek Fazekas added cljr-remove-unused-requires and improved on the let-expanding functions.

Thanks!

License

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

About

A collection of simple clojure refactoring functions for Emacs

Resources

Stars

Watchers

Forks

Packages

No packages published