Skip to content

Commit b9c00f8

Browse files
authored
Merge pull request #371 from weavejester/align-maps
Add option to align columns in maps
2 parents d56ac42 + 20733bc commit b9c00f8

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ In order to load the standard configuration file from Leiningen, add the
225225

226226
### Formatting Options
227227

228+
* `:align-map-columns?` -
229+
true if cljfmt should align the keys and values of maps such that they
230+
line up in columns. Defaults to false.
231+
228232
* `:indentation?` -
229233
true if cljfmt should correct the indentation of your code.
230234
Defaults to true.

cljfmt/src/cljfmt/core.cljc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@
363363
:split-keypairs-over-multiple-lines? false
364364
:sort-ns-references? false
365365
:function-arguments-indentation :community
366+
:align-map-columns? false
366367
:indents default-indents
367368
:extra-indents {}
368369
:alias-map {}})
@@ -572,6 +573,90 @@
572573
(defn sort-ns-references [form]
573574
(transform form edit-all ns-reference? sort-arguments))
574575

576+
(defn- skip-to-linebreak-or-element [zloc]
577+
(z/skip z/right* (some-fn space? comma?) zloc))
578+
579+
(defn- reduce-columns [zloc f init]
580+
(loop [zloc zloc, col 0, acc init]
581+
(if-some [zloc (skip-to-linebreak-or-element zloc)]
582+
(if (line-break? zloc)
583+
(recur (z/right* zloc) 0 acc)
584+
(recur (z/right* zloc) (inc col) (f zloc col acc)))
585+
acc)))
586+
587+
(defn- count-columns [zloc]
588+
(inc (reduce-columns zloc #(max %2 %3) 0)))
589+
590+
(defn- trailing-commas [zloc]
591+
(let [right (z/right* zloc)]
592+
(if (and right (comma? right))
593+
(-> right z/node n/string)
594+
"")))
595+
596+
(defn- node-end-position [zloc]
597+
(let [lines (str (prior-line-string zloc)
598+
(n/string (z/node zloc))
599+
(trailing-commas zloc))]
600+
(transduce (comp (remove #(str/starts-with? % ";"))
601+
(map count))
602+
max 0 (str/split lines #"\r?\n"))))
603+
604+
(defn- max-column-end-position [zloc col]
605+
(reduce-columns zloc
606+
(fn [zloc c max-pos]
607+
(if (= c col)
608+
(max max-pos (node-end-position zloc))
609+
max-pos))
610+
0))
611+
612+
(defn- update-space-left [zloc delta]
613+
(let [left (z/left* zloc)]
614+
(cond
615+
(space? left) (let [n (-> left z/node n/string count)]
616+
(z/right* (z/replace* left (n/spaces (+ n delta)))))
617+
(pos? delta) (z/insert-space-left zloc delta)
618+
:else zloc)))
619+
620+
(defn- skip-to-next-line-in-form [zloc]
621+
(z/right (z/skip z/right* (complement line-break?) (z/right* zloc))))
622+
623+
(defn- pad-node [zloc start-position]
624+
(let [padding (- start-position (margin zloc))
625+
zloc (update-space-left zloc padding)]
626+
(if-some [zloc (z/down zloc)]
627+
(loop [zloc zloc]
628+
(if-some [zloc (skip-to-next-line-in-form zloc)]
629+
(let [zloc (update-space-left zloc padding)]
630+
(if-some [zloc (z/right* zloc)]
631+
(recur zloc)
632+
(z/up zloc)))
633+
(z/up zloc)))
634+
zloc)))
635+
636+
(defn- edit-column [zloc column f]
637+
(loop [zloc zloc, col 0]
638+
(if-some [zloc (skip-to-linebreak-or-element zloc)]
639+
(let [zloc (if (and (= col column) (not (line-break? zloc)))
640+
(f zloc)
641+
zloc)
642+
col (if (line-break? zloc) 0 (inc col))]
643+
(if-some [zloc (z/right* zloc)]
644+
(recur zloc col)
645+
zloc))
646+
zloc)))
647+
648+
(defn- align-column [zloc col]
649+
(if-some [zloc (z/down zloc)]
650+
(let [start-position (inc (max-column-end-position zloc (dec col)))]
651+
(z/up (edit-column zloc col #(pad-node % start-position))))
652+
zloc))
653+
654+
(defn- align-form-columns [zloc]
655+
(reduce align-column zloc (-> zloc z/down count-columns range rest)))
656+
657+
(defn align-map-columns [form]
658+
(transform form edit-all z/map? align-form-columns))
659+
575660
(defn reformat-form
576661
([form]
577662
(reformat-form form {}))
@@ -594,6 +679,8 @@
594679
(reindent (merge (:indents opts) (:extra-indents opts))
595680
(:alias-map opts)
596681
opts))
682+
(cond-> (:align-map-columns? opts)
683+
align-map-columns)
597684
(cond-> (:remove-trailing-whitespace? opts)
598685
remove-trailing-whitespace)))))
599686

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,3 +2032,152 @@
20322032
" \"Help\"))"
20332033
" (str \"🔢 \" (str \"email\""
20342034
" \"Leverage\"))]"])))
2035+
2036+
(deftest test-align-map-columns
2037+
(testing "empty maps"
2038+
(is (reformats-to?
2039+
["{}"]
2040+
["{}"]
2041+
{:align-map-columns? true})))
2042+
(testing "basic aligning"
2043+
(is (reformats-to?
2044+
["{:x 1"
2045+
" :longer 2}"]
2046+
["{:x 1"
2047+
" :longer 2}"]
2048+
{:align-map-columns? true}))
2049+
(is (reformats-to?
2050+
["{:longer 1"
2051+
" :x 2}"]
2052+
["{:longer 1"
2053+
" :x 2}"]
2054+
{:align-map-columns? true}))
2055+
(is (reformats-to?
2056+
["{:x 1 :longer 2}"]
2057+
["{:x 1 :longer 2}"]
2058+
{:align-map-columns? true}))
2059+
(is (reformats-to?
2060+
["{:x 1 :y 2"
2061+
" :longer 2}"]
2062+
["{:x 1 :y 2"
2063+
" :longer 2}"]
2064+
{:align-map-columns? true}))
2065+
(is (reformats-to?
2066+
["{:a 1 :b 2 :cc 3"
2067+
":dd 4 :eee 5 :f 6"
2068+
":ggg 7 :hh 8 :iii 9}"]
2069+
["{:a 1 :b 2 :cc 3"
2070+
" :dd 4 :eee 5 :f 6"
2071+
" :ggg 7 :hh 8 :iii 9}"]
2072+
{:align-map-columns? true})))
2073+
(testing "wrong alignment"
2074+
(is (reformats-to?
2075+
["{:f 1"
2076+
" :bar 2}"]
2077+
["{:f 1"
2078+
" :bar 2}"]
2079+
{:align-map-columns? true}))
2080+
(is (reformats-to?
2081+
["{:foo 1"
2082+
" :b 2}"]
2083+
["{:foo 1"
2084+
" :b 2}"]
2085+
{:align-map-columns? true}))
2086+
(is (reformats-to?
2087+
["{ :foo 1"
2088+
":b 2}"]
2089+
["{:foo 1"
2090+
" :b 2}"]
2091+
{:align-map-columns? true})))
2092+
(testing "commas"
2093+
(is (reformats-to?
2094+
["{:a 1, :b 2, :cc 3"
2095+
":dd 4, :eee 5, :f 6"
2096+
":ggg 7, :hh 8, :iii 9}"]
2097+
["{:a 1, :b 2, :cc 3"
2098+
" :dd 4, :eee 5, :f 6"
2099+
" :ggg 7, :hh 8, :iii 9}"]
2100+
{:align-map-columns? true})))
2101+
(testing "nested maps"
2102+
(is (reformats-to?
2103+
["{:a {:b 1"
2104+
" :c 2}"
2105+
" :ddd {:e 3}}"]
2106+
["{:a {:b 1"
2107+
" :c 2}"
2108+
" :ddd {:e 3}}"]
2109+
{:align-map-columns? true}))
2110+
(is (reformats-to?
2111+
["{:aaa {:b 1"
2112+
" :c 2}"
2113+
" :d {:e 3}}"]
2114+
["{:aaa {:b 1"
2115+
" :c 2}"
2116+
" :d {:e 3}}"]
2117+
{:align-map-columns? true}))
2118+
(is (reformats-to?
2119+
["{{:a 1"
2120+
" :b 2} 3"
2121+
" {:ccc 4} 5}"]
2122+
["{{:a 1"
2123+
" :b 2} 3"
2124+
" {:ccc 4} 5}"]
2125+
{:align-map-columns? true}))
2126+
(is (reformats-to?
2127+
["{{:a 1"
2128+
" :b 2} 3"
2129+
" :c 5}"]
2130+
["{{:a 1"
2131+
" :b 2} 3"
2132+
" :c 5}"]
2133+
{:align-map-columns? true}))
2134+
(is (reformats-to?
2135+
["{:a {:b 1"
2136+
" :c 2} :ddd 3"
2137+
" :eee {:ff 3} :e 4}"]
2138+
["{:a {:b 1"
2139+
" :c 2} :ddd 3"
2140+
" :eee {:ff 3} :e 4}"]
2141+
{:align-map-columns? true})))
2142+
(testing "nested forms"
2143+
(is (reformats-to?
2144+
["{:x (let [x 1]"
2145+
" (+ x 1))"
2146+
" :yyy (let [y 2]"
2147+
" (+ y 2))}"]
2148+
["{:x (let [x 1]"
2149+
" (+ x 1))"
2150+
" :yyy (let [y 2]"
2151+
" (+ y 2))}"]
2152+
{:align-map-columns? true}))
2153+
(is (reformats-to?
2154+
["(def m {:x 1"
2155+
":longer 2})"]
2156+
["(def m {:x 1"
2157+
" :longer 2})"]
2158+
{:align-map-columns? true}))
2159+
(is (reformats-to?
2160+
["(def m {{:a 1"
2161+
":b 2} 3"
2162+
":d 4})"]
2163+
["(def m {{:a 1"
2164+
" :b 2} 3"
2165+
" :d 4})"]
2166+
{:align-map-columns? true}))
2167+
(is (reformats-to?
2168+
["(def m {{:a 1"
2169+
":b 2} [x"
2170+
"y]"
2171+
":d [z]})"]
2172+
["(def m {{:a 1"
2173+
" :b 2} [x"
2174+
" y]"
2175+
" :d [z]})"]
2176+
{:align-map-columns? true})))
2177+
(testing "comments"
2178+
(is (reformats-to?
2179+
["{:x 1 ; a comment"
2180+
" :longer 2}"]
2181+
["{:x 1 ; a comment"
2182+
" :longer 2}"]
2183+
{:align-map-columns? true}))))

0 commit comments

Comments
 (0)