Skip to content

Commit 7d82f81

Browse files
committed
Add :max-column-anchor-gap option
Add a new :max-column-anchor-gap option for column alignment that selects the anchor by cascade demotion: the largest key for which every shorter key fits within N spaces. Keys longer than the chosen anchor are not padded.
1 parent 609d570 commit 7d82f81

3 files changed

Lines changed: 106 additions & 18 deletions

File tree

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,38 @@ In order to load the standard configuration file from Leiningen, add the
316316
:as everything} ;; 27 spaces, falls back to 1 space
317317
```
318318

319+
* `:max-column-anchor-gap` -
320+
a positive integer that controls anchor selection during column
321+
alignment. Instead of using the longest key as the alignment anchor,
322+
cljfmt selects the largest key for which every shorter key would need
323+
no more than N spaces to align. Keys longer than the chosen anchor
324+
are not padded. Defaults to nil (no limit). **Experimental.**
325+
326+
This differs from `:max-column-alignment-gap` in where the limit is
327+
applied: `:max-column-alignment-gap` checks each row individually
328+
after the anchor is determined; `:max-column-anchor-gap` demotes the
329+
anchor itself so that all shorter keys fit within the limit.
330+
331+
```clojure
332+
;; Input:
333+
{:a 1
334+
:abcde 2
335+
:abcdefg 3
336+
:long-name 4}
337+
338+
;; With :align-map-columns? true and :max-column-anchor-gap 5:
339+
;; long-name (gap 7) and abcdefg (gap 5) exceed the limit from :a;
340+
;; anchor demotes to :abcde (gap for :a = 4 ≤ 5)
341+
{:a 1 ;; 4-space gap, aligns
342+
:abcde 2 ;; anchor, 1 space
343+
:abcdefg 3 ;; longer than anchor, 1 space
344+
:long-name 4} ;; longer than anchor, 1 space
345+
```
346+
347+
Both flags can be used together. `:max-column-anchor-gap` runs first
348+
to select the anchor; `:max-column-alignment-gap` then trims any
349+
individual rows whose gap still exceeds its limit.
350+
319351
* `:blank-lines-separate-alignment?` -
320352
true if cljfmt should treat blank lines as separators when aligning
321353
columns. When enabled, alignment groups are separated by blank lines,

cljfmt/src/cljfmt/core.cljc

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@
383383
:align-single-column-lines? false
384384
:aligned-forms default-aligned-forms
385385
:max-column-alignment-gap nil
386+
:max-column-anchor-gap nil
386387
:blank-line-forms blank-line-forms
387388
:blank-lines-separate-alignment? false
388389
:extra-aligned-forms {}
@@ -670,14 +671,18 @@
670671
(defn- count-spaces [zloc]
671672
(if (space? zloc) (node-str-length zloc) 0))
672673

673-
(defn- pad-to-position [zloc start-position {max-gap :max-column-alignment-gap}]
674-
{:pre [(or (nil? max-gap) (pos-int? max-gap))]}
675-
(let [delta (- start-position (margin zloc))]
676-
(if max-gap
677-
(let [old-gap (count-spaces (z/left* zloc))
678-
new-gap (+ old-gap delta)]
679-
(pad-node zloc (if (> new-gap max-gap) (- 1 old-gap) delta)))
680-
(pad-node zloc delta))))
674+
(defn- pad-to-position [zloc start-position {max-gap :max-column-alignment-gap
675+
anchor-gap :max-column-anchor-gap}]
676+
{:pre [(or (nil? max-gap) (pos-int? max-gap))
677+
(or (nil? anchor-gap) (pos-int? anchor-gap))]}
678+
(let [old-gap (count-spaces (z/left* zloc))
679+
delta (- start-position (margin zloc))
680+
new-gap (+ old-gap delta)]
681+
(cond
682+
(< new-gap 1) (pad-node zloc (- 1 old-gap))
683+
(and max-gap
684+
(> new-gap max-gap)) (pad-node zloc (- 1 old-gap))
685+
:else (pad-node zloc delta))))
681686

682687
(defn- edit-column [zloc column f]
683688
(loop [zloc zloc, col 0]
@@ -708,16 +713,26 @@
708713
(recur (z/right* zloc) (inc col) (f zloc col acc)))))))
709714

710715
(defn- column-start-position [zloc col opts]
711-
(let [reduce-fn (if (:blank-lines-separate-alignment? opts)
712-
reduce-column-group
713-
reduce-columns)
714-
maximizer (fn [zloc c max-pos]
715-
(if (and (= c (dec col))
716-
(or (:align-single-column-lines? opts)
717-
(not (single-column-line? zloc))))
718-
(max max-pos (node-end-position zloc))
719-
max-pos))]
720-
(inc (reduce-fn zloc maximizer 0))))
716+
(let [reduce-fn (if (:blank-lines-separate-alignment? opts)
717+
reduce-column-group
718+
reduce-columns)
719+
anchor-gap (:max-column-anchor-gap opts)
720+
collector (fn [zloc c positions]
721+
(if (and (= c (dec col))
722+
(or (:align-single-column-lines? opts)
723+
(not (single-column-line? zloc))))
724+
(conj positions (node-end-position zloc))
725+
positions))
726+
positions (reduce-fn zloc collector [])
727+
anchor (when (seq positions)
728+
(if anchor-gap
729+
(let [min-pos (apply min positions)
730+
threshold (+ min-pos anchor-gap)]
731+
(->> positions
732+
(filter #(<= % threshold))
733+
(apply max)))
734+
(apply max positions)))]
735+
(inc (or anchor 0))))
721736

722737
(defn- align-one-column
723738
[zloc col {:keys [blank-lines-separate-alignment?] :as opts}]

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3193,6 +3193,47 @@
31933193
{:align-map-columns? true
31943194
:max-column-alignment-gap 5}))))
31953195

3196+
(deftest test-max-column-anchor-gap
3197+
(testing "form: anchor demoted until all keys fit within gap"
3198+
(is (reformats-to?
3199+
["(let [a 1"
3200+
" abcde 2"
3201+
" abcdefg 3"
3202+
" long-name 4]"
3203+
" [a abcde abcdefg long-name])"]
3204+
["(let [a 1"
3205+
" abcde 2"
3206+
" abcdefg 3"
3207+
" long-name 4]"
3208+
" [a abcde abcdefg long-name])"]
3209+
{:align-form-columns? true
3210+
:max-column-anchor-gap 5})))
3211+
(testing "map: anchor demoted until all keys fit within gap"
3212+
(is (reformats-to?
3213+
["{:a \"x\""
3214+
" :abcde \"y\""
3215+
" :abcdefg \"z\""
3216+
" :long-key \"w\"}"]
3217+
["{:a \"x\""
3218+
" :abcde \"y\""
3219+
" :abcdefg \"z\""
3220+
" :long-key \"w\"}"]
3221+
{:align-map-columns? true
3222+
:max-column-anchor-gap 5})))
3223+
(testing "composing both flags refines alignment further"
3224+
(is (reformats-to?
3225+
["{:x \"a\""
3226+
" :abc \"b\""
3227+
" :abcde \"c\""
3228+
" :long-key \"d\"}"]
3229+
["{:x \"a\""
3230+
" :abc \"b\""
3231+
" :abcde \"c\""
3232+
" :long-key \"d\"}"]
3233+
{:align-map-columns? true
3234+
:max-column-anchor-gap 5
3235+
:max-column-alignment-gap 3}))))
3236+
31963237
(deftest test-realign-form
31973238
(is (= "
31983239
{:x 1

0 commit comments

Comments
 (0)