Skip to content

Commit 1e1d1df

Browse files
committed
Retain zipper options after zipper operations
The internal rewrite-clj.custom-zipper.core down operation no longer discards zipper metadata for the down operation, which means it is now no longer discarding zipper options. Also: - zipper options moved to own internal namespace and refactored for clarity - custom zipper internal comments/docs reviewed/refreshed Fixes #159
1 parent 748eab9 commit 1e1d1df

File tree

8 files changed

+119
-36
lines changed

8 files changed

+119
-36
lines changed

CHANGELOG.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ For a list of breaking changes see link:#v1-breaking[breaking changes].
1616

1717
=== Unreleased
1818

19-
* Internal rewrite-clj developer facing changes only
19+
* position tracking zippers now retain zipper options correctly https://github.com/clj-commons/rewrite-clj/issues/159[#159]
2020

2121
=== v1.0.644-alpha
2222

src/rewrite_clj/custom_zipper/core.cljc

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
; Interface and algorithms based on code in clojure.zip with is bundled with Clojure itself:
2+
13
; Copyright (c) Rich Hickey. All rights reserved.
24
; The use and distribution terms for this software are covered by the
35
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
@@ -9,11 +11,14 @@
911
;functional hierarchical zipper, with navigation, editing and enumeration
1012
;see Huet
1113

12-
(ns ^:no-doc ^{:doc "Functional hierarchical zipper, with navigation, editing,
13-
and enumeration. See Huet.
14-
Modified to optionally support row col position tracking."
15-
:author "Rich Hickey"}
16-
rewrite-clj.custom-zipper.core
14+
(ns ^:no-doc rewrite-clj.custom-zipper.core
15+
"This is the underlying zipper that rewrite-clj uses.
16+
17+
It delegates to two different zipper implementations:
18+
1. the code herein under defn-switchable bodies when the user has created a `:position-tracking` zipper
19+
2. otherwise clojure.zip, with some small exceptions:
20+
- `edit` explicitly handles the delegation - I assume because defn-switchable could not handle the argument delegation
21+
- there are fns exclusive to the position trakcing zipper, `position`, `position-span`"
1722
(:refer-clojure :exclude (replace remove next))
1823
(:require [clojure.zip :as clj-zip]
1924
[rewrite-clj.custom-zipper.switchable :refer [defn-switchable]]
@@ -22,10 +27,7 @@
2227

2328
#?(:clj (set! *warn-on-reflection* true))
2429

25-
;; ## Switch
26-
;;
27-
;; To not force users into using this custom zipper, the following flag
28-
;; is used to dispatch to `clojure.zip` when set to `false`.
30+
;; the custom zipper is used to support position-tracking, otherwise clojure.zip is used
2931

3032
(defn custom-zipper
3133
[root]
@@ -107,12 +109,14 @@
107109
(let [{:keys [node] [row col] :position} zloc
108110
[c & cnext :as cs] (children zloc)]
109111
(when cs
110-
{::custom? true
111-
:node c
112-
:position [row (+ col (node/leader-length node))]
113-
:parent zloc
114-
:left []
115-
:right cnext}))))
112+
(with-meta
113+
{::custom? true
114+
:node c
115+
:position [row (+ col (node/leader-length node))]
116+
:parent zloc
117+
:left []
118+
:right cnext}
119+
(meta zloc))))))
116120

117121
(defn-switchable up
118122
"Returns zipper with the location at the parent of current node in `zloc`, or nil if at

src/rewrite_clj/zip/base.cljc

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,11 @@
44
[rewrite-clj.node.forms :as nforms]
55
[rewrite-clj.node.protocols :as node]
66
[rewrite-clj.parser :as p]
7+
[rewrite-clj.zip.options :as options]
78
[rewrite-clj.zip.whitespace :as ws]))
89

910
#?(:clj (set! *warn-on-reflection* true))
1011

11-
(defn get-opts [zloc]
12-
(:rewrite-clj.zip/opts (meta zloc)))
13-
14-
(defn set-opts [zloc opts]
15-
(with-meta zloc
16-
(merge (meta zloc)
17-
{:rewrite-clj.zip/opts (merge {:auto-resolve node/default-auto-resolve}
18-
opts)})))
19-
2012
;; ## Zipper
2113

2214
(defn edn*
@@ -31,7 +23,7 @@
3123
(-> (if (:track-position? opts)
3224
(zraw/custom-zipper node)
3325
(zraw/zipper node))
34-
(set-opts opts))))
26+
(options/set-opts opts))))
3527

3628
(defn edn
3729
"Create and return zipper from Clojure/ClojureScript/EDN `node` (likely parsed by [[rewrite-clj.parse]]),
@@ -69,14 +61,14 @@
6961
7062
See docs for [sexpr nuances](/doc/01-user-guide.adoc#sexpr-nuances)."
7163
([zloc]
72-
(some-> zloc zraw/node (node/sexpr (get-opts zloc)))))
64+
(some-> zloc zraw/node (node/sexpr (options/get-opts zloc)))))
7365

7466
(defn child-sexprs
7567
"Return s-expression (the Clojure forms) of children of current node in `zloc`.
7668
7769
See docs for [sexpr nuances](/doc/01-user-guide.adoc#sexpr-nuances)."
7870
([zloc]
79-
(some-> zloc zraw/node (node/child-sexprs (get-opts zloc)))))
71+
(some-> zloc zraw/node (node/child-sexprs (options/get-opts zloc)))))
8072

8173
(defn length
8274
"Return length of printable [[string]] of current node in `zloc`."

src/rewrite_clj/zip/editz.cljc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[rewrite-clj.node.token :as ntoken]
77
[rewrite-clj.node.whitespace :as nwhitespace]
88
[rewrite-clj.zip.base :as base]
9+
[rewrite-clj.zip.options :as options]
910
[rewrite-clj.zip.removez :as r]
1011
[rewrite-clj.zip.whitespace :as ws]))
1112

@@ -37,7 +38,7 @@
3738
3839
See docs for [sexpr nuances](/doc/01-user-guide.adoc#sexpr-nuances)."
3940
[zloc f & args]
40-
(zraw/edit zloc (node-editor (base/get-opts zloc)) #(apply f % args)))
41+
(zraw/edit zloc (node-editor (options/get-opts zloc)) #(apply f % args)))
4142

4243
;; ## Splice
4344

src/rewrite_clj/zip/options.cljc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(ns ^:no-doc rewrite-clj.zip.options
2+
(:require [rewrite-clj.node.protocols :as protocols]))
3+
4+
#?(:clj (set! *warn-on-reflection* true))
5+
6+
(def default-zipper-opts
7+
{:track-position? false
8+
:auto-resolve protocols/default-auto-resolve})
9+
10+
(defn get-opts [zloc]
11+
(:rewrite-clj.zip/opts (meta zloc)))
12+
13+
(defn set-opts [zloc opts]
14+
(vary-meta zloc assoc :rewrite-clj.zip/opts (merge default-zipper-opts opts)))

src/rewrite_clj/zip/subedit.cljc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns ^:no-doc rewrite-clj.zip.subedit
22
(:require [rewrite-clj.custom-zipper.core :as zraw]
3-
[rewrite-clj.zip.base :as base])
3+
[rewrite-clj.zip.base :as base]
4+
[rewrite-clj.zip.options :as options])
45
#?(:cljs (:require-macros [rewrite-clj.zip.subedit])) )
56

67
#?(:clj (set! *warn-on-reflection* true))
@@ -30,7 +31,7 @@
3031
[zloc path]
3132
(let [root (-> zloc
3233
zraw/root
33-
(base/edn* (base/get-opts zloc)))]
34+
(base/edn* (options/get-opts zloc)))]
3435
(reduce move-step root path)))
3536

3637
(defn edit-node
@@ -71,7 +72,7 @@
7172
[zloc]
7273
(let [zloc' (some-> zloc
7374
zraw/node
74-
(base/edn* (base/get-opts zloc)))]
75+
(base/edn* (options/get-opts zloc)))]
7576
(assert zloc' "could not create subzipper.")
7677
zloc'))
7778

test/rewrite_clj/zip/subedit_test.cljc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(ns rewrite-clj.zip.subedit-test
22
(:require [clojure.test :refer [deftest testing is]]
33
[rewrite-clj.zip :as z]
4-
[rewrite-clj.zip.base :as zbase]))
4+
[rewrite-clj.zip.options :as options]))
55

66
(deftest t-trees
77
(let [root (z/of-string "[1 #{2 [3 4] 5} 6]")]
@@ -23,15 +23,15 @@
2323

2424
(deftest zipper-retains-options
2525
(let [zloc (z/of-string "(1 (2 (3 4 ::my-kw)))" {:auto-resolve (fn [_x] 'custom-resolved)})
26-
orig-opts (zbase/get-opts zloc)]
26+
orig-opts (options/get-opts zloc)]
2727
(testing "sanity - without subzip"
2828
(is (= :custom-resolved/my-kw (-> zloc
2929
z/down z/right
3030
z/down z/right
3131
z/down z/rightmost z/sexpr))))
3232
(testing "subzip"
3333
(let [sub-zloc (-> zloc z/up* z/subzip z/down*)]
34-
(is (= orig-opts (zbase/get-opts sub-zloc)))
34+
(is (= orig-opts (options/get-opts sub-zloc)))
3535
(is (= :custom-resolved/my-kw (-> sub-zloc
3636
z/down z/right
3737
z/down z/right
@@ -43,7 +43,7 @@
4343
z/down z/right
4444
z/down (z/replace* 'x)))))]
4545
(is (= 'x (-> edited-zloc z/down z/right z/down z/sexpr)))
46-
(is (= orig-opts (zbase/get-opts edited-zloc)))
46+
(is (= orig-opts (options/get-opts edited-zloc)))
4747
(is (= :custom-resolved/my-kw (-> edited-zloc
4848
z/down z/right
4949
z/down z/right

test/rewrite_clj/zip_test.cljc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,74 @@
170170
;; spot check, more thorough testing done on node tests
171171
(is (= false (-> "," z/of-string z/next* z/sexpr-able?)))
172172
(is (= true (-> "heyy" z/of-string z/sexpr-able?))))
173+
174+
175+
(deftest t-zipper-options-are-retained-after-operations
176+
;; use a custom auto-resolve to verify that options are still in effect on returned zippers
177+
;; test with both track-position true (rewrite-clj.custom-zipper.core) and false (clojure.zip)
178+
(let [opts {:track-position? true
179+
:auto-resolve (constantly 'testing123)}]
180+
(doseq [opts [(assoc opts :track-position? false)
181+
opts]]
182+
(let [zloc (z/of-string "[::kw1 ::kw2 ::kw3]" opts)
183+
msg (fn [desc] (str desc " " (select-keys opts [:track-position?])))]
184+
(is (= [:testing123/kw1 :testing123/kw2 :testing123/kw3] (z/sexpr zloc))
185+
(msg "of-string"))
186+
(is (= :testing123/kw1 (-> zloc z/down z/sexpr))
187+
(msg "down"))
188+
(is (= [:testing123/kw1 :testing123/kw2 :testing123/kw3] (-> zloc z/down z/up z/sexpr))
189+
(msg "up"))
190+
(is (= :testing123/kw2 (-> zloc z/down z/right z/sexpr))
191+
(msg "right"))
192+
(is (= :testing123/kw1 (-> zloc z/down z/right z/left z/sexpr))
193+
(msg "left"))
194+
(is (= :testing123/kw3 (-> zloc z/down z/rightmost z/sexpr))
195+
(msg "rightmost"))
196+
(is (= :testing123/kw1 (-> zloc z/down z/rightmost z/leftmost z/sexpr))
197+
(msg "leftmost"))
198+
(is (= :testing123/kw1 (-> zloc z/next z/sexpr))
199+
(msg "next"))
200+
(is (= :testing123/kw1 (-> zloc z/next z/next z/prev z/sexpr))
201+
(msg "prev"))
202+
(is (= ["child" :testing123/kw1 :testing123/kw2 :testing123/kw3]
203+
(-> zloc (z/insert-child "child") z/sexpr))
204+
(msg "append-child"))
205+
(is (= [:testing123/kw1 :testing123/kw2 :testing123/kw3 "child"]
206+
(-> zloc (z/append-child "child") z/sexpr))
207+
(msg "append-child"))
208+
(is (= [:testing123/kw1 ":testing123/kw2-edited" :testing123/kw3]
209+
(-> zloc z/down z/right (z/edit str "-edited") z/up z/sexpr))
210+
(msg "edit"))
211+
(is (= ["inserted" :testing123/kw1 :testing123/kw2 :testing123/kw3]
212+
(-> zloc z/down (z/insert-left "inserted") z/up z/sexpr))
213+
(msg "insert-left"))
214+
(is (= [:testing123/kw1 "inserted" :testing123/kw2 :testing123/kw3]
215+
(-> zloc z/down (z/insert-right "inserted") z/up z/sexpr))
216+
(msg "insert-right"))
217+
(is (= ["replaced" :testing123/kw2 :testing123/kw3]
218+
(-> zloc z/down (z/replace "replaced") z/up z/sexpr))
219+
(msg "replace"))
220+
(is (= ["inserted" :testing123/kw1 :testing123/kw2 :testing123/kw3]
221+
(-> zloc (z/insert-child "inserted") z/sexpr))
222+
(msg "insert-child"))
223+
(is (= [:testing123/kw2 :testing123/kw3]
224+
(-> zloc z/down z/remove z/sexpr))
225+
(msg "remove"))
226+
(is (= ["insert-left" "insert-child" "insert-right" "replace" :testing123/kw2 "append-child-edited"]
227+
(-> zloc z/down* z/up*
228+
z/down* z/rightmost* z/leftmost* z/up*
229+
z/next* z/prev*
230+
(z/insert-child* (n/spaces 1))
231+
(z/insert-child* (n/string-node "insert-child"))
232+
(z/append-child* (n/spaces 1))
233+
(z/append-child* (n/string-node "append-child"))
234+
z/down* (z/insert-left* (n/string-node "insert-left")) (z/insert-left* (n/spaces 1))
235+
(z/insert-right* (n/string-node "insert-right")) (z/insert-right* (n/spaces 1)) z/up*
236+
z/down* z/rightmost* (z/edit* (fn [n] (n/string-node
237+
(str (n/sexpr n) "-edited")))) z/up*
238+
z/down* z/right* z/right* z/right* z/right* z/right* z/right*
239+
(z/replace* (n/string-node "replace")) z/up*
240+
z/down* z/rightmost* z/left* z/left* z/remove* z/up*
241+
z/sexpr))
242+
(msg "raw ops sanity test"))))))
243+

0 commit comments

Comments
 (0)