Skip to content

A collection of simple clojure refactoring functions for Emacs

Notifications You must be signed in to change notification settings

darth10/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
  • 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"
  • ad: add declaration for current top-level form
  • dk: destructure keys
  • mf: move one or more forms to another namespace, :refer any functions

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:

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)

More stuff to check out

You might also like

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

Changelog

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

Thanks!

License

Copyright © 2012-2013 Magnar Sveen

Authors: Magnar Sveen magnars@gmail.com 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

Languages

  • Emacs Lisp 97.3%
  • Ruby 2.0%
  • Shell 0.7%