diff --git a/CHANGELOG.md b/CHANGELOG.md index c8fc91b..292cbe0 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 bf14a33..4ef3293 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 a110d2f..4ce9a29 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 d0e8130..b0d83cb 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"