Skip to content

Rename ns aliases in selected region #590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### New features

* [#590](https://github.com/clojure-emacs/clojure-mode/pull/590): Extend `clojure-rename-ns-alias` to work on selected regions.
* [#567](https://github.com/clojure-emacs/clojure-mode/issues/567): Add new commands `clojure-toggle-ignore`, `clojure-toggle-ignore-surrounding-form`, and `clojure-toggle-defun` for inserting/deleting #_ ignore forms.
* [#582](https://github.com/clojure-emacs/clojure-mode/pull/582): Add `clojure-special-arg-indent-factor` to control special argument indentation.

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,14 @@ form. If called with a prefix argument slurp the previous n forms.

### Rename ns alias

`clojure-rename-ns-alias`: Rename an alias inside a namespace declaration.
`clojure-rename-ns-alias`: Rename an alias inside a namespace declaration,
and all of its usages in the buffer

<img width="512" src="/doc/clojure-rename-ns-alias.gif">

If there is an active selected region, only rename usages of aliases within the region,
without affecting the namespace declaration.

### Add arity to a function

`clojure-add-arity`: Add a new arity to an existing single-arity or multi-arity function.
Expand Down
119 changes: 75 additions & 44 deletions clojure-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -2670,38 +2670,6 @@ lists up."
(insert sexp)
(clojure--replace-sexps-with-bindings-and-indent)))

(defun clojure-collect-ns-aliases (ns-form)
"Collect all namespace aliases in NS-FORM."
(with-temp-buffer
(delay-mode-hooks
(clojure-mode)
(insert ns-form)
(goto-char (point-min))
(let ((end (point-max))
(rgx (rx ":as" (+ space)
(group-n 1 (+ (not (in " ,]\n"))))))
(res ()))
(while (re-search-forward rgx end 'noerror)
(unless (or (clojure--in-string-p) (clojure--in-comment-p))
(push (match-string-no-properties 1) res)))
res))))

(defun clojure--rename-ns-alias-internal (current-alias new-alias)
"Rename a namespace alias CURRENT-ALIAS to NEW-ALIAS."
(clojure--find-ns-in-direction 'backward)
(let ((rgx (concat ":as +" (regexp-quote current-alias) "\\_>"))
(bound (save-excursion (forward-list 1) (point))))
(when (search-forward-regexp rgx bound t)
(replace-match (concat ":as " new-alias))
(save-excursion
(while (re-search-forward (concat (regexp-quote current-alias) "/") nil t)
(when (not (nth 3 (syntax-ppss)))
(replace-match (concat new-alias "/")))))
(save-excursion
(while (re-search-forward (concat "#::" (regexp-quote current-alias) "{") nil t)
(replace-match (concat "#::" new-alias "{"))))
(message "Successfully renamed alias '%s' to '%s'" current-alias new-alias))))

;;;###autoload
(defun clojure-let-backward-slurp-sexp (&optional n)
"Slurp the s-expression before the let form into the let form.
Expand Down Expand Up @@ -2745,21 +2713,84 @@ With a numeric prefix argument the let is introduced N lists up."
(interactive)
(clojure--move-to-let-internal (read-from-minibuffer "Name of bound symbol: ")))


;;; Renaming ns aliases

(defun clojure--alias-usage-regexp (alias)
"Regexp for matching usages of ALIAS in qualified symbols, keywords and maps.
When nil, match all namespace usages.
The first match-group is the alias."
(let ((alias (if alias (regexp-quote alias) clojure--sym-regexp)))
(concat "#::\\(?1:" alias "\\)[ ,\r\n\t]*{"
"\\|"
"\\_<\\(?1:" alias "\\)/")))

(defun clojure--rename-ns-alias-usages (current-alias new-alias beg end)
"Rename all usages of CURRENT-ALIAS in region BEG to END with NEW-ALIAS."
(let ((rgx (clojure--alias-usage-regexp current-alias)))
(save-mark-and-excursion
(goto-char end)
(setq end (point-marker))
(goto-char beg)
(while (re-search-forward rgx end 'noerror)
(when (not (clojure--in-string-p)) ;; replace in comments, but not strings
(goto-char (match-beginning 1))
(delete-region (point) (match-end 1))
(insert new-alias))))))

(defun clojure--collect-ns-aliases (beg end ns-form-p)
"Collect all aliases between BEG and END.
When NS-FORM-P is non-nil, treat the region as a ns form
and pick up aliases from [... :as alias] forms,
otherwise pick up alias usages from keywords / symbols."
(let ((res ()))
(save-excursion
(let ((rgx (if ns-form-p
(rx ":as" (+ space)
(group-n 1 (+ (not (in " ,]\n")))))
(clojure--alias-usage-regexp nil))))
(goto-char beg)
(while (re-search-forward rgx end 'noerror)
(unless (or (clojure--in-string-p) (clojure--in-comment-p))
(cl-pushnew (match-string-no-properties 1) res
:test #'equal)))
(reverse res)))))

(defun clojure--rename-ns-alias-internal (current-alias new-alias)
"Rename a namespace alias CURRENT-ALIAS to NEW-ALIAS.
Assume point is at the start of ns form."
(clojure--find-ns-in-direction 'backward)
(let ((rgx (concat ":as +" (regexp-quote current-alias) "\\_>"))
(bound (save-excursion (forward-list 1) (point-marker))))
(when (search-forward-regexp rgx bound t)
(replace-match (concat ":as " new-alias))
(clojure--rename-ns-alias-usages current-alias new-alias bound (point-max)))))

;;;###autoload
(defun clojure-rename-ns-alias ()
"Rename a namespace alias."
"Rename a namespace alias.
If a region is active, only pick up and rename aliases within the region."
(interactive)
(save-excursion
(clojure--find-ns-in-direction 'backward)
(let* ((current-alias (completing-read "Current alias: "
(clojure-collect-ns-aliases
(thing-at-point 'list))))
(rgx (concat ":as +" (regexp-quote current-alias) "\\_>"))
(bound (save-excursion (forward-list 1) (point))))
(if (save-excursion (search-forward-regexp rgx bound t))
(let ((new-alias (read-from-minibuffer "New alias: ")))
(clojure--rename-ns-alias-internal current-alias new-alias))
(message "Cannot find namespace alias: '%s'" current-alias)))))
(if (use-region-p)
(let ((beg (region-beginning))
(end (copy-marker (region-end)))
current-alias new-alias)
;; while loop for renaming multiple aliases in the region.
;; C-g or leave blank to break out of the loop
(while (not (string-empty-p
(setq current-alias
(completing-read "Current alias: "
(clojure--collect-ns-aliases beg end nil)))))
(setq new-alias (read-from-minibuffer (format "Replace %s with: " current-alias)))
(clojure--rename-ns-alias-usages current-alias new-alias beg end)))
(save-excursion
(clojure--find-ns-in-direction 'backward)
(let* ((bounds (bounds-of-thing-at-point 'list))
(current-alias (completing-read "Current alias: "
(clojure--collect-ns-aliases
(car bounds) (cdr bounds) t)))
(new-alias (read-from-minibuffer (format "Replace %s with: " current-alias))))
(clojure--rename-ns-alias-internal current-alias new-alias)))))

(defun clojure--add-arity-defprotocol-internal ()
"Add an arity to a signature inside a defprotocol.
Expand Down
40 changes: 35 additions & 5 deletions test/clojure-mode-refactor-rename-ns-alias-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,48 @@
(m*/operator 1 (math.-/subtract 2 3))"
(clojure--rename-ns-alias-internal "math.*" "m*"))

(it "should offer completions"
(when-refactoring-it "should replace aliases in region"
"(str/join [])

(s/with-gen #(string/includes? % \"gen/nope\")
#(gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2))
(gen/tuple (gen/string-alphanumeric) (gen/string-alphanumeric))))

(gen/different-library)"
"(string/join [])

(s/with-gen #(string/includes? % \"gen/nope\")
#(s.gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2))
(s.gen/tuple (s.gen/string-alphanumeric) (s.gen/string-alphanumeric))))

(gen/different-library)"

(clojure--rename-ns-alias-usages "str" "string" (point-min) 13)
(clojure--rename-ns-alias-usages "gen" "s.gen" (point-min) (- (point-max) 23)))

(it "should offer completions for ns forms"
(expect
(clojure-collect-ns-aliases
"(ns test.ns
(with-clojure-buffer
"(ns test.ns
(:require [my.math.subtraction :as math.-]
[my.math.multiplication :as math.*]
[clojure.spec.alpha :as s]
;; [clojure.spec.alpha2 :as s2]
[symbols :as abc123.-$#.%*+!@]))

(math.*/operator 1 (math.-/subtract 2 3))")
:to-equal '("abc123.-$#.%*+!@" "s" "math.*" "math.-"))))
(math.*/operator 1 (math.-/subtract 2 3))"
(clojure--collect-ns-aliases (point-min) (point-max) 'ns-form))
:to-equal '("math.-" "math.*" "s" "abc123.-$#.%*+!@")))

(it "should offer completions for usages in region"
(expect
(with-clojure-buffer
"(s/with-gen #(string/includes? % \"hello\")
#(gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2))
(gen/tuple (gen/string-alphanumeric) (gen/string-alphanumeric))))"
(clojure--collect-ns-aliases (point-min) (point-max) nil))
:to-equal '("s" "string" "gen"))))


(provide 'clojure-mode-refactor-rename-ns-alias-test)

Expand Down