Skip to content

Commit edf0d32

Browse files
rrudakovbbatsov
authored andcommitted
Introduce commands to convert collection type
1 parent c7a3555 commit edf0d32

File tree

5 files changed

+242
-9
lines changed

5 files changed

+242
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
`clojure-ts-thread-last-all`.
1616
- [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce `clojure-ts-cycle-privacy`.
1717
- [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`.
18+
- [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types.
1819

1920
## 0.3.0 (2025-04-15)
2021

README.md

+25-9
Original file line numberDiff line numberDiff line change
@@ -400,17 +400,33 @@ vice versa.
400400
explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for
401401
`defn`s too.
402402

403+
### Convert collection
404+
405+
Convert any given collection at point to list, quoted list, map, vector or
406+
set. The following commands are available:
407+
408+
- `clojure-ts-convert-collection-to-list`
409+
- `clojure-ts-convert-collection-to-quoted-list`
410+
- `clojure-ts-convert-collection-to-map`
411+
- `clojure-ts-convert-collection-to-vector`
412+
- `clojure-ts-convert-collection-to-set`
413+
403414
### Default keybindings
404415

405-
| Keybinding | Command |
406-
|:----------------------------|:----------------------------------|
407-
| `C-:` | `clojure-ts-cycle-keyword-string` |
408-
| `C-c SPC` | `clojure-ts-align` |
409-
| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` |
410-
| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` |
411-
| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` |
412-
| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` |
413-
| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` |
416+
| Keybinding | Command |
417+
|:----------------------------|:-----------------------------------------------|
418+
| `C-:` | `clojure-ts-cycle-keyword-string` |
419+
| `C-c SPC` | `clojure-ts-align` |
420+
| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` |
421+
| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` |
422+
| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` |
423+
| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` |
424+
| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` |
425+
| `C-c C-r (` / `C-c C-r C-(` | `clojure-ts-convert-collection-to-list` |
426+
| `C-c C-r '` / `C-c C-r C-'` | `clojure-ts-convert-collection-to-quoted-list` |
427+
| `C-c C-r {` / `C-c C-r C-{` | `clojure-ts-convert-collection-to-map` |
428+
| `C-c C-r [` / `C-c C-r C-[` | `clojure-ts-convert-collection-to-vector` |
429+
| `C-c C-r #` / `C-c C-r C-#` | `clojure-ts-convert-collection-to-set` |
414430

415431
### Customize refactoring commands prefix
416432

clojure-ts-mode.el

+87
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,77 @@ value is `clojure-ts-thread-all-but-last'."
20482048
(user-error "No string or keyword at point")))
20492049
(goto-char pos)))
20502050

2051+
(defun clojure-ts--collection-node-at-point ()
2052+
"Return node at point that represent a collection."
2053+
(when-let* ((node (thread-first (point)
2054+
(treesit-node-at 'clojure)
2055+
(treesit-parent-until (rx bol
2056+
(or "map_lit"
2057+
"vec_lit"
2058+
"set_lit"
2059+
"list_lit"
2060+
"quoting_lit")
2061+
eol)))))
2062+
(cond
2063+
;; If node is a list, check if it's quoted.
2064+
((string= (treesit-node-type node) "list_lit")
2065+
(if-let* ((parent (treesit-node-parent node))
2066+
((string= (treesit-node-type parent) "quoting_lit")))
2067+
parent
2068+
node))
2069+
;; If the point is at the quote character, check if the child node is a
2070+
;; list.
2071+
((string= (treesit-node-type node) "quoting_lit")
2072+
(when-let* ((first-child (clojure-ts--node-child-skip-metadata node 0))
2073+
((string= (treesit-node-type first-child) "list_lit")))
2074+
node))
2075+
(t node))))
2076+
2077+
(defun clojure-ts--convert-collection (delim-open &optional prefix)
2078+
"Convert collection at point to another collection type.
2079+
2080+
The original collection is being unwrapped and wrapped between
2081+
DELIM-OPEN and its matching paren. If PREFIX is non-nil it's inserted
2082+
before DELIM-OPEN."
2083+
(if-let* ((coll-node (clojure-ts--collection-node-at-point)))
2084+
(save-excursion
2085+
(goto-char (treesit-node-start coll-node))
2086+
(when (string-match-p (rx (or "set_lit" "quoting_lit"))
2087+
(treesit-node-type coll-node))
2088+
(delete-char 1))
2089+
(let ((parens-require-spaces nil)
2090+
(delete-pair-blink-delay 0))
2091+
(when prefix
2092+
(insert-char prefix))
2093+
(insert-pair 1 delim-open (matching-paren delim-open))
2094+
(delete-pair 1)))
2095+
(user-error "No collection at point to convert")))
2096+
2097+
(defun clojure-ts-convert-collection-to-list ()
2098+
"Convert collection at point to list."
2099+
(interactive)
2100+
(clojure-ts--convert-collection ?\())
2101+
2102+
(defun clojure-ts-convert-collection-to-quoted-list ()
2103+
"Convert collection at point to quoted list."
2104+
(interactive)
2105+
(clojure-ts--convert-collection ?\( ?'))
2106+
2107+
(defun clojure-ts-convert-collection-to-map ()
2108+
"Convert collection at point to map."
2109+
(interactive)
2110+
(clojure-ts--convert-collection ?{))
2111+
2112+
(defun clojure-ts-convert-collection-to-vector ()
2113+
"Convert collection at point to vector."
2114+
(interactive)
2115+
(clojure-ts--convert-collection ?\[))
2116+
2117+
(defun clojure-ts-convert-collection-to-set ()
2118+
"Convert collection at point to set."
2119+
(interactive)
2120+
(clojure-ts--convert-collection ?{ ?#))
2121+
20512122
(defvar clojure-ts-refactor-map
20522123
(let ((map (make-sparse-keymap)))
20532124
(keymap-set map "C-t" #'clojure-ts-thread)
@@ -2060,6 +2131,16 @@ value is `clojure-ts-thread-all-but-last'."
20602131
(keymap-set map "l" #'clojure-ts-thread-last-all)
20612132
(keymap-set map "C-p" #'clojure-ts-cycle-privacy)
20622133
(keymap-set map "p" #'clojure-ts-cycle-privacy)
2134+
(keymap-set map "C-(" #'clojure-ts-convert-collection-to-list)
2135+
(keymap-set map "(" #'clojure-ts-convert-collection-to-list)
2136+
(keymap-set map "C-'" #'clojure-ts-convert-collection-to-quoted-list)
2137+
(keymap-set map "'" #'clojure-ts-convert-collection-to-quoted-list)
2138+
(keymap-set map "C-{" #'clojure-ts-convert-collection-to-map)
2139+
(keymap-set map "{" #'clojure-ts-convert-collection-to-map)
2140+
(keymap-set map "C-[" #'clojure-ts-convert-collection-to-vector)
2141+
(keymap-set map "[" #'clojure-ts-convert-collection-to-vector)
2142+
(keymap-set map "C-#" #'clojure-ts-convert-collection-to-set)
2143+
(keymap-set map "#" #'clojure-ts-convert-collection-to-set)
20632144
map)
20642145
"Keymap for `clojure-ts-mode' refactoring commands.")
20652146

@@ -2074,6 +2155,12 @@ value is `clojure-ts-thread-all-but-last'."
20742155
["Toggle between string & keyword" clojure-ts-cycle-keyword-string]
20752156
["Align expression" clojure-ts-align]
20762157
["Cycle privacy" clojure-ts-cycle-privacy]
2158+
("Convert collection"
2159+
["Convert to list" clojure-ts-convert-collection-to-list]
2160+
["Convert to quoted list" clojure-ts-convert-collection-to-quoted-list]
2161+
["Convert to map" clojure-ts-convert-collection-to-map]
2162+
["Convert to vector" clojure-ts-convert-collection-to-vector]
2163+
["Convert to set" clojure-ts-convert-collection-to-set])
20772164
("Refactor -> and ->>"
20782165
["Thread once more" clojure-ts-thread]
20792166
["Fully thread a form with ->" clojure-ts-thread-first-all]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
;;; clojure-ts-mode-convert-collection-test.el --- Clojure[TS] Mode convert collection type. -*- lexical-binding: t; -*-
2+
3+
;; Copyright (C) 2025 Roman Rudakov
4+
5+
;; Author: Roman Rudakov <[email protected]>
6+
7+
;; This program is free software; you can redistribute it and/or modify
8+
;; it under the terms of the GNU General Public License as published by
9+
;; the Free Software Foundation, either version 3 of the License, or
10+
;; (at your option) any later version.
11+
12+
;; This program is distributed in the hope that it will be useful,
13+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
;; GNU General Public License for more details.
16+
17+
;; You should have received a copy of the GNU General Public License
18+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
;;; Commentary:
21+
22+
;; Adapted from `clojure-mode'.
23+
24+
;;; Code:
25+
26+
(require 'clojure-ts-mode)
27+
(require 'buttercup)
28+
(require 'test-helper "test/test-helper")
29+
30+
(describe "clojure-ts-convert-collection-to-map"
31+
(when-refactoring-it "should convert a list to a map"
32+
"(:a 1 :b 2)"
33+
"{:a 1 :b 2}"
34+
(backward-sexp)
35+
(down-list)
36+
(clojure-ts-convert-collection-to-map))
37+
38+
(it "should signal a user error when there is no collection at point"
39+
(with-clojure-ts-buffer "false"
40+
(backward-sexp)
41+
(expect (clojure-ts-convert-collection-to-map)
42+
:to-throw
43+
'user-error
44+
'("No collection at point to convert")))))
45+
46+
(describe "clojure-ts-convert-collection-to-vector"
47+
(when-refactoring-it "should convert a map to a vector"
48+
"{:a 1 :b 2}"
49+
"[:a 1 :b 2]"
50+
(backward-sexp)
51+
(down-list)
52+
(clojure-ts-convert-collection-to-vector))
53+
54+
(it "should signal a user error when there is no collection at point"
55+
(with-clojure-ts-buffer "false"
56+
(backward-sexp)
57+
(expect (clojure-ts-convert-collection-to-vector)
58+
:to-throw
59+
'user-error
60+
'("No collection at point to convert")))))
61+
62+
(describe "clojure-ts-convert-collection-to-set"
63+
(when-refactoring-it "should convert a vector to a set"
64+
"[1 2 3]"
65+
"#{1 2 3}"
66+
(backward-sexp)
67+
(down-list)
68+
(clojure-ts-convert-collection-to-set))
69+
70+
(when-refactoring-it "should convert a quoted list to a set"
71+
"'(1 2 3)"
72+
"#{1 2 3}"
73+
(backward-sexp)
74+
(down-list)
75+
(clojure-ts-convert-collection-to-set))
76+
77+
(it "should signal a user error when there is no collection at point"
78+
(with-clojure-ts-buffer "false"
79+
(backward-sexp)
80+
(expect (clojure-ts-convert-collection-to-set)
81+
:to-throw
82+
'user-error
83+
'("No collection at point to convert")))))
84+
85+
(describe "clojure-ts-convert-collection-to-list"
86+
(when-refactoring-it "should convert a set to a list"
87+
"#{1 2 3}"
88+
"(1 2 3)"
89+
(backward-sexp)
90+
(down-list)
91+
(clojure-ts-convert-collection-to-list))
92+
93+
(it "should signal a user error when there is no collection at point"
94+
(with-clojure-ts-buffer "false"
95+
(backward-sexp)
96+
(expect (clojure-ts-convert-collection-to-list)
97+
:to-throw
98+
'user-error
99+
'("No collection at point to convert")))))
100+
101+
(describe "clojure-ts-convert-collection-to-quoted-list"
102+
(when-refactoring-it "should convert a set to a quoted list"
103+
"#{1 2 3}"
104+
"'(1 2 3)"
105+
(backward-sexp)
106+
(down-list)
107+
(clojure-ts-convert-collection-to-quoted-list))
108+
109+
(it "should signal a user error when there is no collection at point"
110+
(with-clojure-ts-buffer "false"
111+
(backward-sexp)
112+
(expect (clojure-ts-convert-collection-to-quoted-list)
113+
:to-throw
114+
'user-error
115+
'("No collection at point to convert")))))
116+
117+
118+
(provide 'clojure-ts-mode-convert-collection-test)
119+
;;; clojure-ts-mode-convert-collection-test.el ends here

test/samples/refactoring.clj

+10
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,13 @@
8282
(definline bad-sqr [x] `(* ~x ~x))
8383

8484
(defmulti service-charge (juxt account-level :tag))
85+
86+
;; Convert collections.
87+
88+
#{1 2 3}
89+
90+
[1 2 3]
91+
92+
;; TODO: Define indentation rule for `ns_map_lit`
93+
#:hello{:name "Roma"
94+
:world true}

0 commit comments

Comments
 (0)