Skip to content

Commit 33dd39d

Browse files
committed
[Fix #215] Add middleware op for magic requires
- Without middleware everything works as before. - `cljr-magic-require-namespaces` is now more of a seed value and if the middleware is active only plays a role in new projects. - On ambiguous aliases we prompt for resolution. Hopefully this will drive users to pick unique aliases which improves readability. - We keep a cache of aliases around, to keep things snappy. On larger projects the aliases are probably fairly stable. - The cache is updated,async, on repl init and everytime `clean-ns` is called. If the caching proves to be annoying we can change this easily to be a sync request for fresh data. If *that* proves too slow we can also easily cache the alias data in the middleware so we don't gather alias data from files that are unchanged since last scan.
1 parent 88c6b4d commit 33dd39d

File tree

4 files changed

+96
-16
lines changed

4 files changed

+96
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Up next
44

5+
- [#215](https://github.com/clojure-emacs/clj-refactor.el/issues/215)Improve the magic requires feature (when you hit `/`) by asking the middleware for all available namespace aliases.
56
- Add `cljr-extract-def` which extracts the form at, or around, point as a def.
67
- Add `cljr-change-function-signature` to re-order or re-name function parameters.
78
- Keep pressing `l` after `cljr-expand-let` to expand further.

clj-refactor.el

+62-16
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ namespace in the project."
253253
(defvar cljr--change-signature-buffer "*cljr-change-signature*")
254254
(defvar cljr--manual-intervention-buffer "*cljr-manual-intervention*")
255255
(defvar cljr--find-symbol-buffer "*cljr-find-usages*")
256+
(defvar cljr--namespace-aliases-cache nil)
256257

257258
;;; Buffer Local Declarations
258259

@@ -2109,8 +2110,35 @@ front of function literals and sets."
21092110

21102111
;; ------ magic requires -------
21112112

2113+
(defun cljr--clj-file? (&optional buf)
2114+
"Is BUF, or the current buffer, visiting a clj file?"
2115+
(s-ends-with? ".clj" (buffer-file-name (or buf (current-buffer)))))
2116+
21122117
(defun cljr--magic-requires-re ()
2113-
(concat "(\\(" (regexp-opt (-map 'car cljr-magic-require-namespaces)) "\\)/"))
2118+
(regexp-opt (-map 'car cljr-magic-require-namespaces)))
2119+
2120+
(defun cljr--clj-context? ()
2121+
"Is point in a cljs context?"
2122+
(or (cljr--clj-file?)
2123+
;; TODO check for context within a reader conditional
2124+
;; perhaps these class of functions belong in `clojure-mode'
2125+
))
2126+
2127+
(defun cljr--magic-requires-lookup-alias ()
2128+
"Return (alias (ns.candidate candidate.ns)) if we recognize
2129+
the alias in the project."
2130+
(let ((short (buffer-substring-no-properties
2131+
(cljr--point-after 'paredit-backward)
2132+
(1- (point)))))
2133+
(if (s-matches? (cljr--magic-requires-re) short)
2134+
(list short
2135+
(list (aget cljr-magic-require-namespaces short)))
2136+
(-when-let (aliases (and cljr--namespace-aliases-cache
2137+
(if (cljr--clj-context?)
2138+
(gethash :clj cljr--namespace-aliases-cache)
2139+
(gethash :cljs cljr--namespace-aliases-cache))))
2140+
(-when-let (candidates (gethash (intern short) aliases))
2141+
(list short candidates))))))
21142142

21152143
;;;###autoload
21162144
(defun cljr-slash ()
@@ -2121,16 +2149,19 @@ command will add the corresponding require statement to the ns
21212149
form."
21222150
(interactive)
21232151
(insert "/")
2124-
(when (and cljr-magic-requires
2125-
(looking-back (cljr--magic-requires-re) (point-at-bol)))
2126-
(let* ((short (match-string-no-properties 1))
2127-
(long (aget cljr-magic-require-namespaces short)))
2152+
(-when-let (aliases (and cljr-magic-requires
2153+
(cljr--magic-requires-lookup-alias)))
2154+
(let* ((short (first aliases))
2155+
(long (cljr--prompt-user-for "Require " (second aliases))))
21282156
(if (and (not (cljr--in-namespace-declaration? (concat ":as " short)))
21292157
(or (not (eq :prompt cljr-magic-requires))
2158+
(not (> (length (second aliases)) 1)) ; already prompted
21302159
(yes-or-no-p (format "Add %s :as %s to requires?" long short))))
21312160
(save-excursion
21322161
(cljr--insert-in-ns ":require")
2133-
(insert (format "[%s :as %s]" long short))
2162+
(let ((libspec (format "[%s :as %s]" long short)))
2163+
(insert libspec)
2164+
(message "Required %s" libspec))
21342165
(cljr--maybe-sort-ns))))))
21352166

21362167
(defun aget (map key)
@@ -2367,8 +2398,14 @@ before non-empty. This lets 1.7.0 be sorted above 1.7.0-RC1."
23672398
(error "Empty version list received from middleware!"))))
23682399

23692400
(defun cljr--prompt-user-for (prompt &optional choices)
2401+
"Prompt the user with PROMPT.
2402+
2403+
If CHOICES is provided provide a completed read among the
2404+
possible choices. If the choice is trivial, return it."
23702405
(if choices
2371-
(completing-read prompt choices)
2406+
(if (= (length choices) 1)
2407+
(first choices)
2408+
(completing-read prompt choices))
23722409
(read-from-minibuffer prompt)))
23732410

23742411
(defun cljr--add-project-dependency (artifact version)
@@ -2763,6 +2800,16 @@ See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-rename-symbol"
27632800
(paredit-forward)
27642801
(cljr--just-one-blank-line)))
27652802

2803+
(defun cljr--update-namespace-aliases-cache-async ()
2804+
"Update the contents of `cljr--namespace-aliases-cache'."
2805+
(cljr--call-middleware-async
2806+
(list "op" "namespace-aliases")
2807+
(lambda (response)
2808+
(ignore-errors
2809+
(cljr--maybe-rethrow-error response) ; abort; best effort
2810+
(setq cljr--namespace-aliases-cache
2811+
(edn-read (nrepl-dict-get response "namespace-aliases")))))))
2812+
27662813
;;;###autoload
27672814
(defun cljr-clean-ns ()
27682815
"Clean the ns form for the current buffer.
@@ -2786,16 +2833,14 @@ See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-clean-ns"
27862833
"path" path-to-file))))
27872834
(cljr--maybe-rethrow-error result)
27882835
(-when-let (new-ns (nrepl-dict-get result "ns"))
2789-
(cljr--replace-ns new-ns))))
2836+
(cljr--replace-ns new-ns))
2837+
(cljr--update-namespace-aliases-cache-async)))
27902838

27912839
(defun cljr--narrow-candidates (candidates symbol)
2792-
(cond ((= (length candidates) 0)
2793-
(message "Couldn't find any symbols matching %s on classpath."
2794-
(cljr--symbol-suffix symbol)))
2795-
((= (length candidates) 1)
2796-
(car candidates))
2797-
(t
2798-
(cljr--prompt-user-for "Require: " candidates))))
2840+
(if (= (length candidates) 0)
2841+
(error "Couldn't find any symbols matching %s on classpath."
2842+
(cljr--symbol-suffix symbol))
2843+
(cljr--prompt-user-for "Require: " candidates)))
27992844

28002845
(defun cljr--insert-libspec-verbosely (libspec)
28012846
(insert libspec)
@@ -3294,7 +3339,8 @@ You can mute this warning by changing cljr-suppress-middleware-warnings."
32943339
(when cljr-populate-artifact-cache-on-startup
32953340
(cljr--update-artifact-cache))
32963341
(when cljr-eagerly-build-asts-on-startup
3297-
(cljr--warm-ast-cache))))
3342+
(cljr--warm-ast-cache))
3343+
(cljr--update-namespace-aliases-cache-async)))
32983344

32993345
(defvar cljr--list-fold-function-names
33003346
'("map" "mapv" "pmap" "keep" "mapcat" "filter" "remove" "take-while" "drop-while"

features/magic-requires.feature

+23
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,26 @@ Feature: Magic requires
2222
2323
(set/union)
2424
"""
25+
26+
Scenario: Require is inserted automagically after getting suggestions from middleware
27+
When I insert:
28+
"""
29+
(ns cljr.core)
30+
31+
(util)
32+
"""
33+
And the cache of namespace aliases is populated
34+
And I place the cursor after "util"
35+
And I start an action chain
36+
And I type "/"
37+
And I type "refactor-nrepl.util"
38+
And I press "RET"
39+
And I type "get-last-sexp"
40+
And I execute the action chain
41+
Then I should see:
42+
"""
43+
(ns cljr.core
44+
(:require [refactor-nrepl.util :as util]))
45+
46+
(util/get-last-sexp)
47+
"""

features/step-definitions/clj-refactor-steps.el

+10
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,16 @@
345345
(cljr--change-function-signature (list (cl-second cljr--test-occurrences))
346346
cljr--baz-renamed-to-qux))))
347347

348+
(Given "The cache of namespace aliases is populated"
349+
(lambda ()
350+
(setq cljr--namespace-aliases-cache
351+
(edn-read "{:clj {t (clojure.test)
352+
set (clojure.set)
353+
util (refactor-nrepl.util clojure.tools.analyzer.jvm.utils)
354+
readers (clojure.tools.reader.reader-types) }
355+
:cljs {set (clojure.set)
356+
pprint (cljs.pprint)}}"))))
357+
348358
(When "I kill the \"\\(.+\\)\" buffer"
349359
(lambda (buffer)
350360
(kill-buffer buffer)))

0 commit comments

Comments
 (0)