├── README.md ├── align-cljlet-test.el └── align-cljlet.el /README.md: -------------------------------------------------------------------------------- 1 | # Notice: 2 | 3 | This project is now deprecated in favour of the alignment features in 4 | clojure-mode 5.1. 5 | 6 | See: https://github.com/clojure-emacs/clojure-mode#vertical-alignment 7 | 8 | # Description: 9 | 10 | 'align-cljlet' is an emacs addin for aligning let-like forms. This 11 | program exists because I was tired of manually aligning let statements 12 | in Clojure. This program is designed to quickly and easily allow 13 | let-like forms to be aligned. This is my first emacs lisp program and 14 | as a result if probably less than optimal. Feel free to suggest 15 | improvements or send in patches. 16 | 17 | This program was inspired by align-let.el although only borrows one 18 | function from that code. I had considered altering align-let.el to 19 | work correctly with Clojure however it was easiler to simply start 20 | from scratch. 21 | 22 | Forms currently handled: 23 | 24 | * let 25 | * when-let 26 | * if-let 27 | * binding 28 | * loop 29 | * with-open 30 | * literal hashes {} 31 | * defroute 32 | * cond 33 | * condp (except :>> subforms) 34 | 35 | If there are let pairs together on the same line the code will refuse 36 | to align them. For example, the following will not align: 37 | 38 | (let [apple 2 pair 3 39 | peach 23] ...) 40 | 41 | will not be aligned. 42 | 43 | # Custom variables: 44 | 45 | `defroute-columns` The number of columns to align in a defroute form. 46 | 47 | # Known limitations: 48 | 49 | * This program requires clojure mode to be running in order to 50 | function correctly. 51 | * Does not currently align condp forms that use the :>> symbol. 52 | 53 | # Installation: 54 | 55 | To use align-cljlet.el, add it to your load-path and include the 56 | following to your .emacs configuration. 57 | 58 | (require 'align-cljlet) 59 | 60 | Alternatively you may install this package from Marmalade. 61 | 62 | # Usage: 63 | 64 | To invoke simply position anywhere inside an alignable form and 65 | invoke: 66 | 67 | M-x align-cljlet 68 | 69 | You may wish to bind this to a specific key. 70 | 71 | # Contributors 72 | 73 | - Glen Stampoultzis 74 | - Reid D McKenzie 75 | -------------------------------------------------------------------------------- /align-cljlet-test.el: -------------------------------------------------------------------------------- 1 | (require 'align-cljlet) 2 | 3 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 4 | ;;; Unit Tests 5 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 6 | 7 | 8 | (defun acl-forward-sexp-should-move-to (initial-buffer expected-buffer) 9 | (with-temp-buffer 10 | (clojure-mode) 11 | (insert initial-buffer) 12 | (goto-char (point-min)) 13 | (search-forward "|" nil nil 1) 14 | (delete-backward-char 1) 15 | (acl-forward-sexp) 16 | (insert "|") 17 | (should (equal (buffer-substring-no-properties 1 (point-max)) 18 | expected-buffer)))) 19 | 20 | (ert-deftest acl-foward-sexp-test () 21 | (acl-forward-sexp-should-move-to "|a b" 22 | "a| b") 23 | (acl-forward-sexp-should-move-to "a| b" 24 | "a b|") 25 | (acl-forward-sexp-should-move-to "a b|" 26 | "a b|") 27 | (acl-forward-sexp-should-move-to "|#foo/bar [1 2 3] [1 2 3]" 28 | "#foo/bar [1 2 3]| [1 2 3]") 29 | (acl-forward-sexp-should-move-to "|^int 123 999" 30 | "^int 123| 999") 31 | (acl-forward-sexp-should-move-to "|#_(ignore me) 123 999" 32 | "#_(ignore me) 123| 999") 33 | (acl-forward-sexp-should-move-to "|123 #_(ignore me) 999" 34 | "123| #_(ignore me) 999") 35 | (acl-forward-sexp-should-move-to "123| #_(ignore me) 999" 36 | "123 #_(ignore me) 999|") 37 | ) 38 | 39 | (defun acl-goto-next-pair-should-move-to (initial-buffer expected-buffer) 40 | (with-temp-buffer 41 | (clojure-mode) 42 | (insert initial-buffer) 43 | (goto-char (point-min)) 44 | (search-forward "|" nil nil 1) 45 | (delete-backward-char 1) 46 | (acl-goto-next-pair) 47 | (let ((expected-position (1+ (string-match "|" expected-buffer)))) 48 | (should (equal (point) 49 | expected-position))))) 50 | 51 | (ert-deftest acl-goto-next-pair-test () 52 | (acl-goto-next-pair-should-move-to "|a b c d" "a b |c d") 53 | (acl-goto-next-pair-should-move-to "a b |c d e f" "a b c d |e f") 54 | (acl-goto-next-pair-should-move-to "a b c d |e f" "a b c d e |f") ;; slightly odd behaviour here 55 | (acl-goto-next-pair-should-move-to "|(1 2 3) b c d" "(1 2 3) b |c d") 56 | (acl-goto-next-pair-should-move-to "|a #_(1 2 3) b c d" "a #_(1 2 3) b |c d") 57 | ) 58 | 59 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 60 | ;;; Alignment tests 61 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 62 | 63 | (defun acl-should-error (buffer) 64 | (with-temp-buffer 65 | (clojure-mode) 66 | (insert buffer) 67 | (goto-char (point-min)) 68 | (down-list) 69 | 70 | (should-error (align-cljlet)))) 71 | 72 | (defun acl-should-align (buffer expected) 73 | (with-temp-buffer 74 | (clojure-mode) 75 | (insert buffer) 76 | (goto-char (point-min)) 77 | (down-list) 78 | (align-cljlet) 79 | (should (equal (buffer-substring-no-properties (point-min) (point-max)) 80 | expected)))) 81 | 82 | (ert-deftest align-let () 83 | (acl-should-align " 84 | (let [apple 1 85 | orange 2])" 86 | " 87 | \(let [apple 1 88 | orange 2])") 89 | ) 90 | 91 | (ert-deftest align-let-with-tagged-type () 92 | (acl-should-align " 93 | (let [^Long apple 1 94 | orange 2])" 95 | " 96 | (let [^Long apple 1 97 | orange 2])")) 98 | 99 | (ert-deftest align-hash () 100 | (acl-should-align 101 | " 102 | {:foo 234 103 | :foobar (list 1 2 3) 104 | }" 105 | " 106 | {:foo 234 107 | :foobar (list 1 2 3) 108 | }") 109 | 110 | (acl-should-error 111 | " {:a 1 :b 2}")) 112 | 113 | (ert-deftest align-hash-with-reader-macro () 114 | (acl-should-align 115 | " 116 | {#foo/bar [1 2 3] 234 117 | :foobar (list 1 2 3) 118 | }" 119 | " 120 | {#foo/bar [1 2 3] 234 121 | :foobar (list 1 2 3) 122 | }")) 123 | 124 | (ert-deftest align-hash-with-commented-form () 125 | (acl-should-align 126 | " 127 | (let [variable-a #_(slow-database-call) 1 128 | some-other-variable 2] body) 129 | " 130 | " 131 | (let [variable-a #_(slow-database-call) 1 132 | some-other-variable 2] body) 133 | ")) 134 | 135 | (ert-deftest align-hash-with-commented-form-at-front () 136 | (acl-should-align 137 | " 138 | (let [#_(slow-database-call) variable-a 1 139 | some-other-variable 2] body) 140 | " 141 | " 142 | (let [#_(slow-database-call) variable-a 1 143 | some-other-variable 2] body) 144 | ")) 145 | 146 | (ert-deftest align-hash-with-all-sorts-of-commented-forms () 147 | (acl-should-align 148 | " 149 | (let [#_(slow-database-call) variable-a #_(1 2 3) 1 150 | some-other-variable 2] body)" 151 | " 152 | (let [#_(slow-database-call) variable-a #_(1 2 3) 1 153 | some-other-variable 2] body)")) 154 | 155 | (ert-deftest align-cond () 156 | (acl-should-align 157 | "(cond 158 | (> grade 90) \"A\" 159 | (> grade 80) \"B\" 160 | (> grade 70) \"C\" 161 | (> grade 60) \"D\" 162 | :else \"F\")" 163 | "(cond 164 | (> grade 90) \"A\" 165 | (> grade 80) \"B\" 166 | (> grade 70) \"C\" 167 | (> grade 60) \"D\" 168 | :else \"F\")") 169 | 170 | ;; TODO: Currently does not handle :>> forms 171 | ) 172 | 173 | (ert-deftest align-for () 174 | (acl-should-align 175 | " 176 | (for [apple [1 2] 177 | orange [3 4]] 178 | [apple orange])" 179 | " 180 | (for [apple [1 2] 181 | orange [3 4]] 182 | [apple orange])")) 183 | 184 | (ert-deftest align-condp () 185 | (acl-should-align 186 | "(condp = value 187 | 1 \"one\" 188 | 2 \"two\" 189 | 3 \"three\" 190 | :else (str \"unexpected value\"))" 191 | "(condp = value 192 | 1 \"one\" 193 | 2 \"two\" 194 | 3 \"three\" 195 | :else (str \"unexpected value\"))")) 196 | 197 | (ert-deftest align-cond () 198 | (acl-should-align 199 | "(cond 200 | (> grade 90) \"A\" 201 | (> grade 80) \"B\" 202 | (> grade 70) \"C\" 203 | (> grade 60) \"D\" 204 | :else \"F\")" 205 | "(cond 206 | (> grade 90) \"A\" 207 | (> grade 80) \"B\" 208 | (> grade 70) \"C\" 209 | (> grade 60) \"D\" 210 | :else \"F\")")) 211 | 212 | (ert-deftest calc-route-widths () 213 | (with-temp-buffer 214 | (clojure-mode) 215 | (insert "(defroutes test (GET \"/\" [] (render-home-page))\n(DELETE \"/delete/:id\" [id] (handle-delete-library id)))") 216 | (goto-char (point-min)) 217 | (down-list) 218 | (should (equal (list 6 13 4) 219 | (acl-calc-route-widths))))) 220 | 221 | (ert-deftest respace-defroute-subform () 222 | (with-temp-buffer 223 | (clojure-mode) 224 | (insert "(GET \"/\" [] (render-home-page))") 225 | (goto-char (point-min)) 226 | (down-list) 227 | (acl-respace-subform (list 8 4 6)) 228 | (should (equal "(GET \"/\" [] (render-home-page))" 229 | (buffer-substring-no-properties (point-min) (point-max)))))) 230 | 231 | (ert-deftest respace-defroute-form () 232 | (with-temp-buffer 233 | (clojure-mode) 234 | (insert "(defroutes test\n(GET \"/\" [] (render-home-page))\n(DELETE \"/delete/:id\" [id] (handle-delete-library id)))") 235 | (goto-char (point-min)) 236 | (down-list) 237 | (acl-respace-defroute-form (list 8 12 6)) 238 | (should (equal "(defroutes test\n (GET \"/\" [] (render-home-page))\n (DELETE \"/delete/:id\"[id] (handle-delete-library id)))" 239 | (buffer-substring-no-properties (point-min) (point-max))))) 240 | ) 241 | -------------------------------------------------------------------------------- /align-cljlet.el: -------------------------------------------------------------------------------- 1 | ;;; align-cljlet.el --- Space align various Clojure forms 2 | 3 | ;; Copyrigth (C) 2011 Glen Stampoultzis 4 | 5 | ;; Authors: Glen Stampoultzis , Reid D McKenzie 6 | ;; Version: 0.6 7 | ;; Package-Requires: ((clojure-mode "1.11.5")) 8 | ;; Keywords; clojure, align, let 9 | ;; URL: https://github.com/gstamp/align-cljlet 10 | ;; 11 | 12 | ;; This file is *NOT* part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or 15 | ;; modify it under the terms of the GNU General Public License as 16 | ;; published by the Free Software Foundation; either version 2, or (at 17 | ;; your option) any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, but 20 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | ;; General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 26 | ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 27 | 28 | ;;; Commentary: 29 | 30 | ;;; Description: 31 | ;; 32 | ;; This program exists because I was tired of manually aligning let 33 | ;; statements in clojure. This program is designed to quickly and 34 | ;; easily allow let forms to be aligned. This is my first Emacs 35 | ;; Lisp program and as a result if probably less than optimal. Feel 36 | ;; free to suggest improvements or send in patches. 37 | ;; 38 | ;; This program was inspired by align-let.el although does not share 39 | ;; any of it's code. I had considered altering align-let.el to 40 | ;; work correctly with Clojure however it was easiler to simply 41 | ;; start from scratch. 42 | ;; 43 | ;;; Changes: 44 | ;; 45 | ;; 14-Jan-2011 - Initial release 46 | ;; 23-Jan-2011 - Bug fixes and code cleanup. 47 | ;; 02-Apr-2012 - Package up for Marmalade 48 | ;; 30-Aug-2012 - Support for aligning defroute. 49 | ;; 04-Nov-2015 - Support for metadata when calculating widths 50 | ;; 04-Nov-2015 - Support for aligning for 51 | ;; 01-Jan-2016 - Support for reader macros 52 | ;; 11-Jan-2016 - Better handle ignore form reader macro 53 | ;; 54 | ;;; Known limitations: 55 | ;; 56 | ;; * This program requires clojure mode to be running in order to 57 | ;; function correctly. 58 | ;; 59 | ;;; Installation: 60 | ;; 61 | ;; To use align-cljlet.el, put it in your load-path and add 62 | ;; the following to your .emacs 63 | ;; 64 | ;; (require 'align-cljlet) 65 | ;; 66 | ;;; Usage: 67 | ;; 68 | ;; To invoke simply position anywhere inside the let statement and 69 | ;; invoke: 70 | ;; 71 | ;; M-x align-cljlet 72 | ;; 73 | ;; You may wish to bind this to a specific key. 74 | ;; 75 | ;; Contains one custom variable called defroute-columns which is 76 | ;; used to determine how many columns to align in a defroute call. 77 | ;; Defaults to 1. 78 | ;; 79 | ;;; Code: 80 | 81 | (defcustom defroute-columns 1 82 | "The number of columns to align in a defroute call" 83 | :type 'integer 84 | :group 'align-cljlet) 85 | 86 | (defun acl-found-alignable-form () 87 | "Check if we are currently looking at a let form" 88 | (save-excursion 89 | (if (looking-at "(") 90 | (progn 91 | (down-list) 92 | (let ((start (point)) 93 | name) 94 | (forward-sexp) 95 | (setq name (buffer-substring-no-properties start (point))) 96 | (or 97 | (string-match " *let" name) 98 | (string-match " *for" name) 99 | (string-match " *when-let" name) 100 | (string-match " *if-let" name) 101 | (string-match " *binding" name) 102 | (string-match " *loop" name) 103 | (string-match " *with-open" name) 104 | (string-match " *cond" name) 105 | (string-match " *condp" name) 106 | (string-match " *defroutes" name) 107 | (string-match " *case" name) 108 | (string-match " *alt" name)))) 109 | (if (looking-at "{") 110 | t)))) 111 | 112 | (defun acl-try-go-up () 113 | "Go upwards if possible. If we can't then we're obviously not in an 114 | alignable form." 115 | (condition-case nil 116 | (up-list -1) 117 | (error 118 | (error "Not in a \"let\" form"))) 119 | t) 120 | 121 | (defun acl-find-alignable-form () 122 | "Find the let form by moving looking upwards until nowhere to go" 123 | (while 124 | (if (acl-found-alignable-form) 125 | nil 126 | (acl-try-go-up) 127 | )) 128 | t) 129 | 130 | (defun acl-skip-commented () 131 | (while (acl-is-commented?) 132 | (call-interactively 'clojure-forward-logical-sexp))) 133 | 134 | (defun acl-is-commented? () 135 | (or (looking-at "#_") 136 | (looking-back "#_") 137 | (and (looking-at "\\s-") 138 | (save-excursion 139 | (call-interactively 'clojure-backward-logical-sexp) 140 | (or (looking-back "#_") 141 | (looking-at "#_")))))) 142 | 143 | (defun acl-forward-sexp (&optional dont-skip-comments) 144 | "Jumps the cursor forward to the end of the current sexp or to 145 | the end of the next sexp if already positioned at the 146 | end. Commented forms are skipped by default unless 147 | dont-skip-comments is true." 148 | (if (and (not dont-skip-comments) (looking-at "#_(")) 149 | (acl-skip-commented) 150 | (call-interactively 'clojure-forward-logical-sexp)) 151 | (unless dont-skip-comments (acl-skip-commented)) 152 | ) 153 | 154 | (defun acl-goto-next-pair () 155 | "Skip ahead to the next definition" 156 | (condition-case nil 157 | (progn 158 | (acl-forward-sexp) 159 | (acl-forward-sexp) 160 | (acl-forward-sexp) 161 | (call-interactively 'clojure-backward-logical-sexp) 162 | t) 163 | (error nil))) 164 | 165 | (defun acl-get-width () 166 | "Get the width of the current definition" 167 | (save-excursion 168 | (let ((col (current-column))) 169 | (acl-forward-sexp) 170 | (- (current-column) col)))) 171 | 172 | (defun acl-has-next-sexp () 173 | "Checks if there is another sexp after the point" 174 | (save-excursion 175 | (condition-case nil 176 | (progn 177 | (acl-forward-sexp) 178 | 't) 179 | ('error nil)))) 180 | 181 | (defun acl-next-sexp () 182 | "Goes to the next sexp, returning true or false if there is no next" 183 | (condition-case nil 184 | (progn 185 | (acl-forward-sexp) 186 | (acl-forward-sexp t) 187 | (call-interactively 'clojure-backward-logical-sexp) 188 | 't) 189 | ('error nil))) 190 | 191 | (defun acl-calc-route-widths () 192 | "Calculate the widths required to align a defroutes macro" 193 | (save-excursion 194 | (let ((width1 0) 195 | (width2 0) 196 | (width3 0)) 197 | (while (progn 198 | (down-list) 199 | (if (> (acl-get-width) width1) 200 | (setq width1 (acl-get-width))) 201 | ;; next column 202 | (if (and (acl-next-sexp) (> (acl-get-width) width2)) 203 | (setq width2 (acl-get-width))) 204 | ;; next column 205 | (if (and (acl-next-sexp) (> (acl-get-width) width3)) 206 | (setq width3 (acl-get-width))) 207 | 208 | (up-list) 209 | (acl-has-next-sexp))) 210 | (list width1 width2 width3)))) 211 | 212 | (defun acl-check-for-another-sexp () 213 | "Is there another sexp after this" 214 | (save-excursion 215 | (condition-case nil 216 | (progn 217 | (acl-forward-sexp) 218 | (acl-forward-sexp) 219 | t) 220 | (error nil)))) 221 | 222 | (defun acl-calc-width () 223 | "Calculate the width needed for all the definitions in the form" 224 | (save-excursion 225 | (let ((width 0)) 226 | (while (progn 227 | (if (and (acl-check-for-another-sexp) 228 | (> (acl-get-width) width)) 229 | (setq width (acl-get-width))) 230 | (acl-goto-next-pair))) 231 | width))) 232 | 233 | (defun acl-lines-correctly-paired () 234 | "Determine if all the pairs are on different lines" 235 | (save-excursion 236 | (let ((current-line (line-number-at-pos))) 237 | (while (progn 238 | (acl-goto-next-pair)) 239 | (if (= current-line (line-number-at-pos)) 240 | (error "multiple pairs on one line") 241 | (setq current-line (line-number-at-pos)))))) 242 | t) 243 | 244 | (defun acl-respace-single-let (max-width) 245 | "Respace the current definition" 246 | (acl-respace-subform (list max-width))) 247 | 248 | (defun acl-respace-subform (widths) 249 | "Respace a subform using the widths given. Point must 250 | be positioned on the first s-exp in the subform." 251 | (save-excursion 252 | (while widths 253 | (let (col 254 | current-width 255 | difference 256 | (max-width (car widths))) 257 | (setq col (current-column)) 258 | (if (acl-next-sexp) 259 | (progn 260 | (setq current-width (- (- (current-column) col) 1) 261 | difference (- max-width current-width)) 262 | (cond ((> difference 0) 263 | (insert (make-string difference ? ))) 264 | ((< difference 0) 265 | (delete-char difference)))))) 266 | 267 | (setq widths (cdr widths))))) 268 | 269 | (defun acl-respace-defroute-form (widths) 270 | "Respace the entire defroute definition. Point must be 271 | positioned on the defroute form." 272 | (let ((begin (point))) 273 | (while (progn 274 | (down-list) 275 | (acl-respace-subform widths) 276 | (up-list) 277 | (acl-has-next-sexp))) 278 | (indent-region begin (point)))) 279 | 280 | (defun acl-respace-form (width) 281 | "Respace the entire definition" 282 | (let ((begin (point))) 283 | (while (progn 284 | (acl-respace-single-let width) 285 | (acl-goto-next-pair))) 286 | (indent-region begin (point)))) 287 | 288 | (defun acl-take-n (n xs) 289 | "Take n elements from a list returning a new list" 290 | (butlast xs (- (length xs) n))) 291 | 292 | (defun acl-start-align-defroute () 293 | (progn 294 | (down-list 1) 295 | (forward-sexp 3) 296 | (backward-sexp) ; this position's us back at the start of the 297 | ; first form. 298 | (acl-respace-defroute-form (acl-take-n defroute-columns (acl-calc-route-widths))))) 299 | 300 | (defun acl-position-to-start () 301 | (if (looking-at "( *cond\\b") 302 | (progn 303 | (down-list 1) 304 | (forward-sexp 2) 305 | (backward-sexp)) 306 | (if (looking-at "( *condp\\b") 307 | (progn 308 | (down-list 1) 309 | (forward-sexp 4) 310 | (backward-sexp)) 311 | (if (looking-at "( *case\\b") 312 | (progn 313 | (down-list 1) 314 | (forward-sexp 3) 315 | (backward-sexp)) 316 | (if (looking-at "( *alt!") 317 | (progn 318 | (down-list 1) 319 | (forward-sexp 2) 320 | (backward-sexp)) 321 | (if (not (looking-at "{")) 322 | ;; move to start of [ 323 | (down-list 2) 324 | (down-list 1))))))) 325 | 326 | (defun acl-align-form () 327 | "Determine what type of form we are currently positioned at and align it" 328 | (if (looking-at "( *defroutes") 329 | (acl-start-align-defroute) 330 | (progn 331 | (acl-position-to-start) 332 | (if (acl-lines-correctly-paired) 333 | (let ((w (acl-calc-width))) 334 | (acl-respace-form w) 335 | ))))) 336 | 337 | ;; Borrowed from align-let.el: 338 | (defun acl-backward-to-code () 339 | "Move point back to the start of a preceding sexp form. 340 | This gets out of strings, comments, backslash quotes, etc, to a 341 | place where it makes sense to start examining sexp code forms. 342 | 343 | The preceding form is found by a `parse-partial-sexp' starting 344 | from `beginning-of-defun'. If it finds nothing then just go to 345 | `beginning-of-defun'." 346 | 347 | (let ((old (point))) 348 | (beginning-of-defun) 349 | (let ((parse (parse-partial-sexp (point) old))) 350 | (cond ((nth 2 parse) ;; innermost sexp 351 | (goto-char (nth 2 parse)) 352 | (forward-sexp)) 353 | ((nth 8 parse) ;; outside of comment or string 354 | (goto-char (nth 8 parse))) 355 | ((nth 5 parse) ;; after a quote char 356 | (goto-char (1- (point)))))))) 357 | 358 | 359 | ;;;###autoload 360 | (defun align-cljlet () 361 | "Align a let form so that the bindings neatly align into columns" 362 | (interactive) 363 | (save-excursion 364 | (acl-backward-to-code) 365 | (if (acl-find-alignable-form) 366 | (acl-align-form)))) 367 | 368 | (provide 'align-cljlet) 369 | 370 | ;;; align-cljlet.el ends here 371 | --------------------------------------------------------------------------------