Skip to content

Commit a296a77

Browse files
committed
Introduce cycle privacy refactoring command
1 parent 3569c90 commit a296a77

File tree

5 files changed

+154
-1
lines changed

5 files changed

+154
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [#88](https://github.com/clojure-emacs/clojure-ts-mode/pull/88): Introduce `clojure-ts-unwind` and `clojure-ts-unwind-all`.
1414
- [#89](https://github.com/clojure-emacs/clojure-ts-mode/pull/89): Introduce `clojure-ts-thread`, `clojure-ts-thread-first-all` and
1515
`clojure-ts-thread-last-all`.
16+
- Introduce `clojure-ts-cycle-privacy`.
1617

1718
## 0.3.0 (2025-04-15)
1819

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ entire form. With a prefix argument do not thread the last form.
391391
`clojure-unwind-all`: Fully unwind a threaded expression removing the threading
392392
macro.
393393

394+
`clojure-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata
395+
explicitly with setting `clojure-use-metadata-for-privacy` to `t` for `defn`s
396+
too.
397+
394398
### Default keybindings
395399

396400
| Keybinding | Command |
@@ -400,6 +404,7 @@ macro.
400404
| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` |
401405
| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` |
402406
| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` |
407+
| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` |
403408

404409
### Customize refactoring commands prefix
405410

clojure-ts-mode.el

+37-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ current sexp."
160160
:safe #'booleanp
161161
:type 'boolean)
162162

163+
(defcustom clojure-ts-use-metadata-for-privacy nil
164+
"If nil, `clojure-ts-cycle-privacy' will use (defn- f []).
165+
166+
If t, it will use (defn ^:private f [])."
167+
:package-version '(clojure-ts-mode . "0.4.0")
168+
:safe #'booleanp
169+
:type 'boolean)
170+
163171
(defcustom clojure-ts-align-reader-conditionals nil
164172
"Whether to align reader conditionals, as if they were maps."
165173
:package-version '(clojure-ts-mode . "0.4")
@@ -1480,6 +1488,13 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph."
14801488
"map_lit" "ns_map_lit" "vec_lit" "set_lit")
14811489
"A regular expression that matches nodes that can be treated as lists.")
14821490

1491+
(defun clojure-ts--defun-node-p (node)
1492+
"Return TRUE if NODE is a function or a var definition."
1493+
(and (clojure-ts--list-node-p node)
1494+
(let ((sym (clojure-ts--node-child-skip-metadata node 0)))
1495+
(string-match-p (rx bol (or "def" "defn" "defn-") eol)
1496+
(clojure-ts--named-node-text sym)))))
1497+
14831498
(defconst clojure-ts--markdown-inline-sexp-nodes
14841499
'("inline_link" "full_reference_link" "collapsed_reference_link"
14851500
"uri_autolink" "email_autolink" "shortcut_link" "image"
@@ -1490,7 +1505,8 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph."
14901505
`((clojure
14911506
(sexp ,(regexp-opt clojure-ts--sexp-nodes))
14921507
(list ,(regexp-opt clojure-ts--list-nodes))
1493-
(text ,(regexp-opt '("comment"))))
1508+
(text ,(regexp-opt '("comment")))
1509+
(defun ,#'clojure-ts--defun-node-p))
14941510
(when clojure-ts-use-markdown-inline
14951511
(markdown-inline
14961512
(sexp ,(regexp-opt clojure-ts--markdown-inline-sexp-nodes))))))
@@ -1991,6 +2007,23 @@ value is `clojure-ts-thread-all-but-last'."
19912007
(interactive "P")
19922008
(clojure-ts--thread-all "->> " but-last))
19932009

2010+
(defun clojure-ts-cycle-privacy ()
2011+
"Make a definition at point public or private."
2012+
(interactive)
2013+
(if-let* ((node-at-point (treesit-node-at (point) 'clojure t))
2014+
(defun-node (treesit-parent-until node-at-point 'defun t)))
2015+
(save-excursion
2016+
(goto-char (treesit-node-start defun-node))
2017+
(search-forward-regexp (rx "(def" (? "n") (? (group (or "-" " ^:private")))))
2018+
(if (match-string 1)
2019+
(replace-match "" nil nil nil 1)
2020+
(goto-char (match-end 0))
2021+
(insert (if (or clojure-ts-use-metadata-for-privacy
2022+
(equal (match-string 0) "(def"))
2023+
" ^:private"
2024+
"-"))))
2025+
(user-error "No defun at point")))
2026+
19942027
(defvar clojure-ts-refactor-map
19952028
(let ((map (make-sparse-keymap)))
19962029
(keymap-set map "C-t" #'clojure-ts-thread)
@@ -2001,6 +2034,8 @@ value is `clojure-ts-thread-all-but-last'."
20012034
(keymap-set map "f" #'clojure-ts-thread-first-all)
20022035
(keymap-set map "C-l" #'clojure-ts-thread-last-all)
20032036
(keymap-set map "l" #'clojure-ts-thread-last-all)
2037+
(keymap-set map "C-p" #'clojure-ts-cycle-privacy)
2038+
(keymap-set map "p" #'clojure-ts-cycle-privacy)
20042039
map)
20052040
"Keymap for `clojure-ts-mode' refactoring commands.")
20062041

@@ -2012,6 +2047,7 @@ value is `clojure-ts-thread-all-but-last'."
20122047
(easy-menu-define clojure-ts-mode-menu map "Clojure[TS] Mode Menu"
20132048
'("Clojure"
20142049
["Align expression" clojure-ts-align]
2050+
["Cycle privacy" clojure-ts-cycle-privacy]
20152051
("Refactor -> and ->>"
20162052
["Thread once more" clojure-ts-thread]
20172053
["Fully thread a form with ->" clojure-ts-thread-first-all]

test/clojure-ts-mode-cycling-test.el

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
;;; clojure-ts-mode-cycling-test.el --- Clojure[TS] Mode: cycling things tests -*- 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+
;; The code is 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-cycle-privacy"
31+
32+
(when-refactoring-it "should turn a public defn into a private defn"
33+
"(defn add [a b]
34+
(+ a b))"
35+
36+
"(defn- add [a b]
37+
(+ a b))"
38+
39+
(clojure-ts-cycle-privacy))
40+
41+
(when-refactoring-it "should also work from the beginning of a sexp"
42+
"(defn- add [a b]
43+
(+ a b))"
44+
45+
"(defn add [a b]
46+
(+ a b))"
47+
48+
(backward-sexp)
49+
(clojure-ts-cycle-privacy))
50+
51+
(when-refactoring-it "should use metadata when clojure-use-metadata-for-privacy is set to true"
52+
"(defn add [a b]
53+
(+ a b))"
54+
55+
"(defn ^:private add [a b]
56+
(+ a b))"
57+
58+
(let ((clojure-ts-use-metadata-for-privacy t))
59+
(clojure-ts-cycle-privacy)))
60+
61+
(when-refactoring-it "should turn a private defn into a public defn"
62+
"(defn- add [a b]
63+
(+ a b))"
64+
65+
"(defn add [a b]
66+
(+ a b))"
67+
68+
(clojure-ts-cycle-privacy))
69+
70+
(when-refactoring-it "should turn a private defn with metadata into a public defn"
71+
"(defn ^:private add [a b]
72+
(+ a b))"
73+
74+
"(defn add [a b]
75+
(+ a b))"
76+
77+
(let ((clojure-ts-use-metadata-for-privacy t))
78+
(clojure-ts-cycle-privacy)))
79+
80+
(when-refactoring-it "should also work with pre-existing metadata"
81+
"(def ^:dynamic config
82+
\"docs\"
83+
{:env \"staging\"})"
84+
85+
"(def ^:private ^:dynamic config
86+
\"docs\"
87+
{:env \"staging\"})"
88+
89+
(clojure-ts-cycle-privacy))
90+
91+
(when-refactoring-it "should turn a private def with metadata into a public def"
92+
"(def ^:private config
93+
\"docs\"
94+
{:env \"staging\"})"
95+
96+
"(def config
97+
\"docs\"
98+
{:env \"staging\"})"
99+
100+
(clojure-ts-cycle-privacy)))
101+
102+
(provide 'clojure-ts-mode-cycling-test)
103+
;;; clojure-ts-mode-cycling-test.el ends here

test/samples/refactoring.clj

+8
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@
6666

6767
(->> (map square (filter even? [1 2 3 4 5])))
6868

69+
(-> (dissoc (assoc {} :key "value") :lock))
70+
6971
(deftask dev []
7072
(comp (serve)
7173
(cljs (lala)
7274
10)))
75+
76+
(def my-name "Roma")
77+
78+
(defn say-hello
79+
[]
80+
(println "Hello" my-name))

0 commit comments

Comments
 (0)