Skip to content

Commit 6a2b1f2

Browse files
author
Yannick Scherer
committed
Fixed whitespace-handling in 'remove', restricted 'splice' behaviour.
1 parent c415abe commit 6a2b1f2

File tree

7 files changed

+129
-46
lines changed

7 files changed

+129
-46
lines changed

CHANGES.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
- new functions in `rewrite-clj.zip.core`:
99
- `length`
1010
- `move-to-node`
11-
- `edit->>`
12-
- added removal of a single whitespace character to `rewrite-clj.zip/remove`.
11+
- `edit->>`, `edit-node`
12+
- `subedit->`, `subedit->>`, `edit-children`
13+
- `leftmost?`, `rightmost?`
14+
- new functions in `rewrite-clj.zip.edit`:
15+
- `splice-or-remove`
16+
- `prefix`, `suffix` (formerly `rewrite-clj.zip.utils`)
17+
- `rewrite-clj.zip.edit/remove` now handles whitespace appropriately.
1318
- indentation-aware modification functions in `rewrite-clj.zip.indent`:
1419
- `indent`
1520
- `indent-children`
@@ -19,6 +24,7 @@
1924
- `insert-right
2025
- `remove`
2126
- `splice`
27+
- fast-zip utility functions in `rewrite-clj.zip.utils`
2228

2329
### 0.2.0
2430

src/rewrite_clj/zip.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
edn tag value length sexpr
2929
whitespace? linebreak?
30+
leftmost? rightmost?
3031
skip-whitespace skip-whitespace-left
3132
prepend-space append-space
3233
prepend-newline append-newline
@@ -50,7 +51,8 @@
5051

5152
insert-right insert-left
5253
insert-child append-child
53-
replace edit remove splice
54+
replace edit remove
55+
splice splice-or-remove
5456
prefix suffix]
5557

5658
[rewrite-clj.zip.seqs

src/rewrite_clj/zip/core.clj

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
;; ## Zipper
99

10-
(declare skip-whitespace)
10+
(declare skip-whitespace
11+
skip-whitespace-left)
1112

1213
(defn- z-branch?
1314
[node]
@@ -71,6 +72,16 @@
7172
[zloc]
7273
(= (tag zloc) :newline))
7374

75+
(defn leftmost?
76+
"Check if the given zipper is at the leftmost non-whitespace position."
77+
[zloc]
78+
(nil? (skip-whitespace-left (z/left zloc))))
79+
80+
(defn rightmost?
81+
"Check if the given zipper is at the rightmost non-whitespace position."
82+
[zloc]
83+
(nil? (skip-whitespace (z/right zloc))))
84+
7485
;; ## Skip
7586

7687
(defn skip

src/rewrite_clj/zip/edit.clj

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,13 @@
44
(:refer-clojure :exclude [replace remove])
55
(:require [fast-zip.core :as z]
66
[rewrite-clj.convert :as conv]
7-
[rewrite-clj.zip.core :as zc]))
7+
[rewrite-clj.zip.core :as zc]
8+
[rewrite-clj.zip.utils :as zu]))
89

910
;; ## Helpers
1011

1112
(def ^:private ^:const SPACE [:whitespace " "])
1213

13-
(defn- remove-trailing-space
14-
"Remove a single whitespace character after the given node."
15-
[zloc]
16-
(or
17-
(when-let [ws (z/right zloc)]
18-
(when (= (zc/tag ws) :whitespace)
19-
(let [w (dec (zc/length ws))]
20-
(-> ws
21-
(z/replace [:whitespace (apply str (repeat w \space))])
22-
z/left))))
23-
zloc))
24-
2514
;; ## Insert
2615

2716
(defn insert-right
@@ -73,28 +62,65 @@
7362
(let [form (zc/sexpr zloc)]
7463
(z/replace zloc (conv/->tree (apply f form args)))))
7564

65+
;; ## Remove
66+
67+
(defn- remove-trailing-space
68+
"Remove all whitespace following a given node."
69+
[zloc]
70+
(loop [zloc zloc]
71+
(if-let [rloc (z/right zloc)]
72+
(if (zc/whitespace? rloc)
73+
(recur (zu/remove-right zloc))
74+
zloc)
75+
zloc)))
76+
77+
(defn- remove-preceding-space
78+
[zloc]
79+
(loop [zloc zloc]
80+
(if-let [lloc (z/left zloc)]
81+
(if (zc/whitespace? lloc)
82+
(recur (zu/remove-left zloc))
83+
zloc)
84+
zloc)))
85+
7686
(defn remove
7787
"Remove value at the given zipper location. Returns the first non-whitespace node
78-
that would have preceded it in a depth-first walk. Will remove a single whitespace
79-
character following the current node and/or preceding it (if last element in branch)."
88+
that would have preceded it in a depth-first walk. Will remove whitespace appropriately.
89+
90+
[1 2 3] => [1 3]
91+
[1 2] => [1]
92+
[1 2] => [2]
93+
[1] => []
94+
[ 1 ] => []
95+
[1 [2 3] 4] => [1 [2 3]]
96+
[1 [2 3] 4] => [[2 3] 4]
97+
98+
If a node is located rightmost, both preceding and trailing spaces are removed, otherwise only
99+
trailing spaces are touched. This means that a following element (no matter whether on the
100+
same line or not) will end up in the same position (line/column) as the removed one."
80101
[zloc]
81-
(->> zloc
82-
remove-trailing-space
83-
;; TODO: Remove preceding space
84-
z/remove
85-
(zc/skip-whitespace z/prev)))
102+
(let [zloc (if (zc/rightmost? zloc) (remove-preceding-space zloc) zloc)
103+
zloc (-> zloc remove-trailing-space z/remove)]
104+
(zc/skip-whitespace z/prev zloc)))
105+
106+
;; ## Splice
86107

87108
(defn splice
88109
"Add the current node's children to the parent branch (in place of the current node).
89-
The resulting zipper will be positioned on the first non-whitespace \"child\"."
110+
The resulting zipper will be positioned on the first non-whitespace \"child\". If
111+
the node does not have children (or only whitespace children), `nil` is returned."
112+
[zloc]
113+
(when (and (z/branch? zloc) (pos? (count (z/children zloc))) (zc/skip-whitespace (z/down zloc)))
114+
(->> (reverse (z/children zloc))
115+
(reduce z/insert-right zloc)
116+
zu/remove-and-move-right
117+
zc/skip-whitespace)))
118+
119+
(defn splice-or-remove
120+
"Try to splice the given node. If it fails (no children, only whitespace children),
121+
remove it."
90122
[zloc]
91-
(if-not (z/branch? zloc)
92-
zloc
93-
(let [ch (z/children zloc)]
94-
(-> (reduce z/insert-right zloc (reverse ch))
95-
z/remove
96-
z/next
97-
zc/skip-whitespace))))
123+
(or (splice zloc) (remove zloc)))
98124

99125
;; ## Prefix
100126

test/rewrite_clj/indent_test.clj

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
;; ## Fixtures
1010

1111
(def data-string "{:first [1\n 2],\n :second 3}")
12-
1312
(def data (z/of-string data-string))
1413

1514
;; ## Tests
@@ -48,8 +47,7 @@
4847
(z/tag rloc) => :token
4948
(z/value rloc) => 2
5049
(z/->root-string rloc) => "[1\n 2 \"abc\" [3\n 4]]")
51-
(future-fact "about correct preceding whitespace handling."
52-
(let [rloc (-> (z/of-string "[1\n 2 [3\n 4]]") (z/find-value z/next 2) i/remove)]
53-
(z/tag rloc) => :token
54-
(z/value rloc) => 1
55-
(z/->root-string rloc) => "[1\n [3\n 4]]")))
50+
(let [rloc (-> (z/of-string "[1\n 2 [3\n 4]]") (z/find-value z/next 2) i/remove)]
51+
(z/tag rloc) => :token
52+
(z/value rloc) => 1
53+
(z/->root-string rloc) => "[1\n [3\n 4]]"))

test/rewrite_clj/transform_test.clj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,16 @@
7070
[b-suffix \"1.2.3\"]]
7171
:repositories { \"private\" \"http://private.com/repo\" })")
7272

73+
(fact "about whitespace-handling in removal"
74+
(-> data
75+
(z/find-value z/next 'defproject)
76+
(z/find-value :dependencies)
77+
z/right z/down z/remove
78+
z/->root-string)
79+
=>
80+
";; This is a Project File.
81+
(defproject my-project \"0.1.0-SNAPSHOT\"
82+
:description \"A project.
83+
Multiline!\"
84+
:dependencies [[b \"1.2.3\"]]
85+
:repositories { \"private\" \"http://private.com/repo\" })")

test/rewrite_clj/zip_test.clj

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
(-> root z/down z/remove z/root)
9191
=> [:forms
9292
[:vector
93-
[:newline "\n"] [:whitespace " "] [:token 2]
93+
[:token 2]
9494
[:newline "\n"] [:whitespace " "] [:token 3]]]
9595
(-> root z/down z/right (z/replace 5) z/root)
9696
=> [:forms
@@ -106,6 +106,11 @@
106106
[:newline "\n"] [:whitespace " "] [:token 8]]]))
107107

108108
(fact "about node removal (including trailing/preceding whitespace if necessary)"
109+
(let [root (z/of-string "[1\n2]")]
110+
(z/sexpr root) => [1 2]
111+
(let [r0 (-> root z/down z/remove)]
112+
(z/sexpr r0) => [2]
113+
(z/->root-string r0) => "[2]"))
109114
(let [root (z/of-string "[1 [2 3] 4]")]
110115
(z/sexpr root) => [1 [2 3] 4]
111116
(let [r0 (-> root z/down z/remove*)]
@@ -116,15 +121,37 @@
116121
(z/->root-string r0) => "[[2 3] 4]")
117122
(let [r0 (-> root z/down z/right z/right z/remove*)]
118123
(z/->root-string r0) => "[1 [2 3] ]")
119-
(future-fact "about removal of preceding spaces"
120-
(let [r0 (-> root z/down z/right z/right z/remove)]
121-
(z/sexpr r0) => 3
122-
(z/->root-string r0) => "[1 [2 3]]"))))
124+
(let [r0 (-> root z/down z/right z/right z/remove)]
125+
(z/sexpr r0) => 3
126+
(z/->root-string r0) => "[1 [2 3]]")))
123127

124128
(fact "about zipper splice"
125-
(let [root (z/of-string "[1 [2 3] 4]")]
126-
(z/sexpr root) => [1 [2 3] 4]
127-
(-> root z/down z/right z/splice z/up z/sexpr) => [1 2 3 4]))
129+
(let [r0 (z/of-string "[1 [2 3] 4]")
130+
r1 (-> r0 z/down z/right z/splice)]
131+
(z/sexpr r0) => [1 [2 3] 4]
132+
(z/sexpr r1) => 2
133+
(z/sexpr (z/up r1)) => [1 2 3 4])
134+
(let [r0 (z/of-string "[1 [] 4]")
135+
r1 (-> r0 z/down z/right z/splice)
136+
r2 (-> r0 z/down z/right z/splice-or-remove)]
137+
(z/sexpr r0) => [1 [] 4]
138+
r1 => falsey
139+
(z/sexpr r2) => 1
140+
(z/->root-string r2) => "[1 4]")
141+
(let [r0 (z/of-string "[1 [ ] 4]")
142+
r1 (-> r0 z/down z/right z/splice)
143+
r2 (-> r0 z/down z/right z/splice-or-remove)]
144+
(z/sexpr r0) => [1 [] 4]
145+
r1 => falsey
146+
(z/sexpr r2) => 1
147+
(z/->root-string r2) => "[1 4]")
148+
(let [r0 (z/of-string "[1 []]")
149+
r1 (-> r0 z/down z/right z/splice)
150+
r2 (-> r0 z/down z/right z/splice-or-remove)]
151+
(z/sexpr r0) => [1 []]
152+
r1 => falsey
153+
(z/sexpr r2) => 1
154+
(z/->root-string r2) => "[1]"))
128155

129156
(fact "about zipper search/find traversal"
130157
(-> root z/down (z/find-value :description) z/right z/node) => [:token "A project."]

0 commit comments

Comments
 (0)