branch: elpa/clojure-ts-mode
commit c7a355588755d35144f64b0a4a8061bdd47cae45
Author: Roman Rudakov <rruda...@fastmail.com>
Commit: Bozhidar Batsov <bozhi...@batsov.dev>

    Introduce clojure-ts-toggle-keyword-string
---
 CHANGELOG.md                         |  1 +
 README.md                            | 20 ++++++++++++--------
 clojure-ts-mode.el                   | 18 ++++++++++++++++++
 test/clojure-ts-mode-cycling-test.el | 31 +++++++++++++++++++++++++++++++
 4 files changed, 62 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8fc91be12..292cbe0afb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - [#89](https://github.com/clojure-emacs/clojure-ts-mode/pull/89): Introduce 
`clojure-ts-thread`, `clojure-ts-thread-first-all` and
   `clojure-ts-thread-last-all`.
 - [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce 
`clojure-ts-cycle-privacy`.
+- [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce 
`clojure-ts-cycle-keyword-string`.
 
 ## 0.3.0 (2025-04-15)
 
diff --git a/README.md b/README.md
index bf14a33ddf..4ef3293126 100644
--- a/README.md
+++ b/README.md
@@ -393,20 +393,24 @@ threading macro.
 
 ### Cycling things
 
+`clojure-ts-cycle-keyword-string`: Convert the string at point to a keyword and
+vice versa.
+
 `clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata
 explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for
 `defn`s too.
 
 ### Default keybindings
 
-| Keybinding                  | Command                       |
-|:----------------------------|:------------------------------|
-| `C-c SPC`                   | `clojure-ts-align`            |
-| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread`           |
-| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind`           |
-| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` |
-| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all`  |
-| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy`    |
+| Keybinding                  | Command                           |
+|:----------------------------|:----------------------------------|
+| `C-:`                       | `clojure-ts-cycle-keyword-string` |
+| `C-c SPC`                   | `clojure-ts-align`                |
+| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread`               |
+| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind`               |
+| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all`     |
+| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all`      |
+| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy`        |
 
 ### Customize refactoring commands prefix
 
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
index a110d2f78d..4ce9a29d11 100644
--- a/clojure-ts-mode.el
+++ b/clojure-ts-mode.el
@@ -2032,6 +2032,22 @@ value is `clojure-ts-thread-all-but-last'."
                     "-"))))
     (user-error "No defun at point")))
 
+(defun clojure-ts-cycle-keyword-string ()
+  "Convert the string at point to a keyword, or vice versa."
+  (interactive)
+  (let ((node (treesit-thing-at-point 'sexp 'nested))
+        (pos (point)))
+    (cond
+     ((clojure-ts--string-node-p node)
+      (if (string-match-p " " (treesit-node-text node t))
+          (user-error "Cannot convert a string containing spaces to keyword")
+        (insert ?: (substring (clojure-ts--delete-and-extract-sexp) 1 -1))))
+     ((clojure-ts--keyword-node-p node)
+      (insert ?\" (substring (clojure-ts--delete-and-extract-sexp) 1) ?\"))
+     (t
+      (user-error "No string or keyword at point")))
+    (goto-char pos)))
+
 (defvar clojure-ts-refactor-map
   (let ((map (make-sparse-keymap)))
     (keymap-set map "C-t" #'clojure-ts-thread)
@@ -2050,10 +2066,12 @@ value is `clojure-ts-thread-all-but-last'."
 (defvar clojure-ts-mode-map
   (let ((map (make-sparse-keymap)))
     ;;(set-keymap-parent map clojure-mode-map)
+    (keymap-set map "C-:" #'clojure-ts-cycle-keyword-string)
     (keymap-set map "C-c SPC" #'clojure-ts-align)
     (keymap-set map clojure-ts-refactor-map-prefix clojure-ts-refactor-map)
     (easy-menu-define clojure-ts-mode-menu map "Clojure[TS] Mode Menu"
       '("Clojure"
+        ["Toggle between string & keyword" clojure-ts-cycle-keyword-string]
         ["Align expression" clojure-ts-align]
         ["Cycle privacy" clojure-ts-cycle-privacy]
         ("Refactor -> and ->>"
diff --git a/test/clojure-ts-mode-cycling-test.el 
b/test/clojure-ts-mode-cycling-test.el
index d0e813063c..b0d83cb5df 100644
--- a/test/clojure-ts-mode-cycling-test.el
+++ b/test/clojure-ts-mode-cycling-test.el
@@ -27,6 +27,37 @@
 (require 'buttercup)
 (require 'test-helper "test/test-helper")
 
+(describe "clojure-ts-cycle-keyword-string"
+  (when-refactoring-with-point-it "should convert string to keyword"
+    "\"hel|lo\""
+
+    ":hel|lo"
+
+    (clojure-ts-cycle-keyword-string))
+
+  (when-refactoring-with-point-it "should convert keyword to string"
+    ":|hello"
+
+    "\"|hello\""
+
+    (clojure-ts-cycle-keyword-string))
+
+  (it "should signal a user error when there is nothing to convert at point"
+    (with-clojure-ts-buffer "[true false]"
+      (goto-char 2)
+      (expect (clojure-ts-cycle-keyword-string)
+              :to-throw
+              'user-error
+              '("No string or keyword at point"))))
+
+  (it "should signal a user error when string at point contains spaces"
+    (with-clojure-ts-buffer "\"Hello world\""
+      (goto-char 2)
+      (expect (clojure-ts-cycle-keyword-string)
+              :to-throw
+              'user-error
+              '("Cannot convert a string containing spaces to keyword")))))
+
 (describe "clojure-ts-cycle-privacy"
 
   (when-refactoring-it "should turn a public defn into a private defn"

Reply via email to