diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 29933240..73d3a7bc 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -26,6 +26,9 @@ A release with known breaking changes is marked with: * `rewrite-clj.zip/insert-right` and `rewrite-clj.zip/append-child` no longer insert a space when inserting/appending after a comment node. {issue}346[#346] ({lread}) +* `rewrite.clj.paredit` +** `pos` arguments now accepts vector `[row col]` in addition to map `{:row :col}` +{issue}344[#344] ({lread}) === v1.1.49 - 2024-11-18 [[v1.1.49]] diff --git a/src/rewrite_clj/paredit.cljc b/src/rewrite_clj/paredit.cljc index 68d39b27..fe32994a 100644 --- a/src/rewrite_clj/paredit.cljc +++ b/src/rewrite_clj/paredit.cljc @@ -5,6 +5,7 @@ (:require [rewrite-clj.custom-zipper.utils :as u] [rewrite-clj.node :as nd] [rewrite-clj.zip :as z] + [rewrite-clj.zip.findz :as fz] [rewrite-clj.zip.whitespace :as ws])) #?(:clj (set! *warn-on-reflection* true)) @@ -130,16 +131,20 @@ - if inside string kills to end of string and stops there - If inside comment kills to end of line (not including linebreak) - `pos` should provide `{:row :col }` which are relative to the start of the given form the zipper represents - `zloc` must be positioned at a node previous (given depth first) to the node at given pos" + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are + 1-based and relative to the start of the source code the zipper represents. + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking)." [zloc pos] (if-let [candidate (z/find-last-by-pos zloc pos)] - (cond - (string-node? candidate) (kill-in-string-node candidate pos) - (ws/comment? candidate) (kill-in-comment-node candidate pos) - (and (empty-seq? candidate) - (> (:col pos) (-> candidate z/node meta :col))) (z/remove candidate) - :else (kill candidate)) + (let [pos (fz/pos-as-map pos)] + (cond + (string-node? candidate) (kill-in-string-node candidate pos) + (ws/comment? candidate) (kill-in-comment-node candidate pos) + (and (empty-seq? candidate) + (> (:col pos) (-> candidate z/node meta :col))) (z/remove candidate) + :else (kill candidate))) zloc)) @@ -194,6 +199,12 @@ (defn kill-one-at-pos "In string and comment aware kill for one node/word at `pos` in `zloc`. + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are + 1-based and relative to the start of the source code the zipper represents. + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + - `(+ |100 100) => (+ |100)` - `(for |(bar do)) => (foo)` - `\"|hello world\" => \"| world\"` @@ -201,7 +212,8 @@ [zloc pos] (if-let [candidate (->> (z/find-last-by-pos zloc pos) (ws/skip z/right* ws/whitespace?))] - (let [[bounds-row bounds-col] (z/position candidate) + (let [pos (fz/pos-as-map pos) + [bounds-row bounds-col] (z/position candidate) kill-in-node? (not (and (= (:row pos) bounds-row) (<= (:col pos) bounds-col)))] (cond @@ -457,17 +469,19 @@ (defn split-at-pos "In string aware split - Perform split at given position `pos` Like split, but: + Perform split at given position `pos` Like split, but if inside string splits string into two strings. - - if inside string splits string into two strings + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are + 1-based and relative to the start of the source code the zipper represents. - `pos` should provide `{:row :col }` which are relative to the start of the given form the zipper represents - `zloc` must be positioned at a node previous (given depth first) to the node at given pos" + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking)." [zloc pos] (if-let [candidate (z/find-last-by-pos zloc pos)] - (if (string-node? candidate) - (split-string candidate pos) - (split candidate)) + (let [pos (fz/pos-as-map pos)] + (if (string-node? candidate) + (split-string candidate pos) + (split candidate))) zloc)) (defn- join-seqs [left right] diff --git a/src/rewrite_clj/zip.cljc b/src/rewrite_clj/zip.cljc index 82deb1bf..8c8c188e 100644 --- a/src/rewrite_clj/zip.cljc +++ b/src/rewrite_clj/zip.cljc @@ -486,17 +486,30 @@ ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.findz (defn find-last-by-pos - "Return `zloc` located to the last node spanning position `pos` that satisfies predicate `p?` else `nil`. - Search is depth-first from the current node. + "Return `zloc` located at the last node spanning position `pos` that satisfies the predicate `p?`, else `nil`. - NOTE: Does not ignore whitespace/comment nodes." + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + - `p?` is optional and defaults to `(constantly true)` + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + + NOTE: Whitespace and comment nodes are included in the search." ([zloc pos] (rewrite-clj.zip.findz/find-last-by-pos zloc pos)) ([zloc pos p?] (rewrite-clj.zip.findz/find-last-by-pos zloc pos p?))) ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.findz (defn find-tag-by-pos - "Return `zloc` located to the last node spanning position `pos` with tag `t` else `nil`. - Search is depth-first from the current node." + "Return `zloc` located at the last node spanning position `pos` with tag `t`, else `nil`. + + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + + NOTE: Whitespace and comment nodes are included in the search." [zloc pos t] (rewrite-clj.zip.findz/find-tag-by-pos zloc pos t)) ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.insert diff --git a/src/rewrite_clj/zip/find.clj b/src/rewrite_clj/zip/find.clj index a9a60220..95da6691 100644 --- a/src/rewrite_clj/zip/find.clj +++ b/src/rewrite_clj/zip/find.clj @@ -19,10 +19,16 @@ ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.findz (defn find-last-by-pos - "Return `zloc` located to the last node spanning position `pos` that satisfies predicate `p?` else `nil`. - Search is depth-first from the current node. + "Return `zloc` located at the last node spanning position `pos` that satisfies the predicate `p?`, else `nil`. - NOTE: Does not ignore whitespace/comment nodes." + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + - `p?` is optional and defaults to `(constantly true)` + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + + NOTE: Whitespace and comment nodes are included in the search." ([zloc pos] (rewrite-clj.zip.findz/find-last-by-pos zloc pos)) ([zloc pos p?] (rewrite-clj.zip.findz/find-last-by-pos zloc pos p?))) @@ -67,8 +73,15 @@ ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.findz (defn find-tag-by-pos - "Return `zloc` located to the last node spanning position `pos` with tag `t` else `nil`. - Search is depth-first from the current node." + "Return `zloc` located at the last node spanning position `pos` with tag `t`, else `nil`. + + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + + NOTE: Whitespace and comment nodes are included in the search." [zloc pos t] (rewrite-clj.zip.findz/find-tag-by-pos zloc pos t)) ;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.findz diff --git a/src/rewrite_clj/zip/findz.cljc b/src/rewrite_clj/zip/findz.cljc index 0fef3fa9..1a454f54 100644 --- a/src/rewrite_clj/zip/findz.cljc +++ b/src/rewrite_clj/zip/findz.cljc @@ -16,8 +16,14 @@ (additional node))) #(= (base/tag %) t))) +(defn pos-as-vec [pos] + (if (map? pos) [(:row pos) (:col pos)] pos)) + +(defn pos-as-map [pos] + (if (map? pos) pos (zipmap [:row :col] pos))) + (defn- position-in-range? [zloc pos] - (let [[r c] (if (map? pos) [(:row pos) (:col pos)] pos)] + (let [[r c] (pos-as-vec pos)] (when (or (<= r 0) (<= c 0)) (throw (ex-info "zipper row and col positions are ones-based" {:pos pos}))) (let [[[zstart-row zstart-col][zend-row zend-col]] (zraw/position-span zloc)] @@ -44,10 +50,16 @@ (first)))) (defn find-last-by-pos - "Return `zloc` located to the last node spanning position `pos` that satisfies predicate `p?` else `nil`. - Search is depth-first from the current node. + "Return `zloc` located at the last node spanning position `pos` that satisfies the predicate `p?`, else `nil`. + + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + - `p?` is optional and defaults to `(constantly true)` + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). - NOTE: Does not ignore whitespace/comment nodes." + NOTE: Whitespace and comment nodes are included in the search." ([zloc pos] (find-last-by-pos zloc pos (constantly true))) ([zloc pos p?] (->> zloc @@ -102,8 +114,15 @@ (find-next zloc f)))) (defn find-tag-by-pos - "Return `zloc` located to the last node spanning position `pos` with tag `t` else `nil`. - Search is depth-first from the current node." + "Return `zloc` located at the last node spanning position `pos` with tag `t`, else `nil`. + + - `zloc` location is (inclusive) starting point for `pos` depth-first search + - `pos` can be a `{:row :col}` map or a `[row col]` vector. The `row` and `col` values are 1-based and relative to the + start of the form represented by the zipper. + + Throws if `zloc` was not created with [position tracking](/doc/01-user-guide.adoc#position-tracking). + + NOTE: Whitespace and comment nodes are included in the search." ([zloc pos t] (find-last-by-pos zloc pos #(= (base/tag %) t)))) diff --git a/test/rewrite_clj/paredit_test.cljc b/test/rewrite_clj/paredit_test.cljc index 37172fea..3258f570 100644 --- a/test/rewrite_clj/paredit_test.cljc +++ b/test/rewrite_clj/paredit_test.cljc @@ -50,7 +50,9 @@ ["⊚\"\"" "◬"]]]] (let [{:keys [pos s]} (th/pos-and-s s) zloc (z/of-string* s {:track-position? true})] - (is (= expected (-> zloc (pe/kill-at-pos pos) th/root-locmarked-string)))))) + (doseq [pos [pos [(:row pos) (:col pos)]]] + (testing (str s " @pos " pos) + (is (= expected (-> zloc (pe/kill-at-pos pos) th/root-locmarked-string)))))))) (deftest kill-one-at-pos-test ;; for this pos fn test, ⊚ in `s` represents character row/col the the `pos` @@ -78,7 +80,9 @@ ["\"foo bar ⊚do\n lorem\"" "⊚\"foo bar \n lorem\""]]] (let [{:keys [pos s]} (th/pos-and-s s) zloc (z/of-string* s {:track-position? true})] - (is (= expected (-> zloc (pe/kill-one-at-pos pos) th/root-locmarked-string)))))) + (doseq [pos [pos [(:row pos) (:col pos)]]] + (testing (str s " @pos " pos) + (is (= expected (-> zloc (pe/kill-one-at-pos pos) th/root-locmarked-string)))))))) (deftest slurp-forward-test (doseq [opts zipper-opts] @@ -207,10 +211,15 @@ (is (= expected (-> zloc pe/split th/root-locmarked-string)) "string after")))))) (deftest split-at-pos-test - (is (= "(⊚\"Hello \" \"World\")" - (-> (th/of-locmarked-string "⊚(\"Hello World\")" {:track-position? true}) - (pe/split-at-pos {:row 1 :col 9}) - th/root-locmarked-string)))) + ;; for this pos fn test, ⊚ in `s` represents character row/col the the `pos` + ;; ⊚ in `expected` is at zipper node granularity + (doseq [[s expected] + [["(\"Hello ⊚World\")" "(⊚\"Hello \" \"World\")" ]]] + (let [{:keys [pos s]} (th/pos-and-s s) + zloc (z/of-string* s {:track-position? true})] + (doseq [pos [pos [(:row pos) (:col pos)]]] + (testing (str s " @pos " pos) + (is (= expected (-> zloc (pe/split-at-pos pos) th/root-locmarked-string)))))))) (deftest join-test (doseq [opts zipper-opts]