branch: elpa/inf-clojure
commit 4af135f85fc1182b95bf3cf86c66d56c2077dc9a
Author: Bozhidar Batsov <[email protected]>
Commit: GitHub <[email protected]>
Add namespace-aware eval via inf-clojure-eval-ns-aware (#221)
* Add namespace-aware eval via inf-clojure-eval-ns-aware
When enabled, eval commands wrap code in a binding form that sets
*ns* to the namespace declared in the source buffer. This avoids
"Unable to resolve symbol" errors when the REPL is in a different
namespace than the file being evaluated.
Off by default to preserve existing behavior.
Fixes #205
* Document namespace-aware evaluation in README
---
README.md | 17 +++++++++++++++++
inf-clojure.el | 28 +++++++++++++++++++++++++---
test/inf-clojure-tests.el | 21 +++++++++++++++++++++
3 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 64f97ce9ea..3f7604b903 100644
--- a/README.md
+++ b/README.md
@@ -225,6 +225,23 @@ There are two important configuration variables here:
If these are set and you wish to prevent inf-clojure from using them,
use a prefix arg when invoking `inf-clojure` (`C-u M-x inf-clojure`).
+### Namespace-aware Evaluation
+
+By default, eval commands (`C-x C-e`, `C-M-x`, `C-c C-r`, etc.) send code
+to the REPL as-is, so it runs in whatever namespace the REPL is currently in.
+If you're evaluating code from a file with a different `ns` declaration, you
+may get "Unable to resolve symbol" errors.
+
+Setting `inf-clojure-eval-ns-aware` to non-nil makes eval commands
+automatically wrap code so it executes in the buffer's namespace:
+
+```emacs-lisp
+(setopt inf-clojure-eval-ns-aware t)
+```
+
+Alternatively, you can switch the REPL to the buffer's namespace
+explicitly with `C-c M-n` (`inf-clojure-set-ns`).
+
### REPL Features
The supported REPL-features are in an alist called
diff --git a/inf-clojure.el b/inf-clojure.el
index 1859f5ee8e..b94d92e67d 100644
--- a/inf-clojure.el
+++ b/inf-clojure.el
@@ -556,6 +556,15 @@ All buffers in `clojure-mode' will automatically be in
:safe #'booleanp
:package-version '(inf-clojure . "3.1.0"))
+(defcustom inf-clojure-eval-ns-aware nil
+ "When non-nil, evaluate code in the context of the buffer's namespace.
+Eval commands will wrap the code so that it executes in the
+namespace declared in the current buffer's `ns' form, rather than
+whatever namespace the REPL is currently in."
+ :type 'boolean
+ :safe #'booleanp
+ :package-version '(inf-clojure . "3.4.0"))
+
(defun inf-clojure--get-preferred-major-modes ()
"Return list of preferred major modes that are actually available."
(cl-remove-if-not (lambda (mode) (featurep mode))
@@ -973,17 +982,30 @@ of forms."
(buffer-substring-no-properties (point-min) (point-max))))
(scan-error str)))
+(defun inf-clojure--wrap-for-ns (code)
+ "Wrap CODE to evaluate in the buffer's namespace when appropriate.
+When `inf-clojure-eval-ns-aware' is non-nil and a namespace can be
+detected in the current buffer, wrap CODE in a `binding' form that
+sets `*ns*' to that namespace. Otherwise return CODE unchanged."
+ (let ((ns (when inf-clojure-eval-ns-aware (inf-clojure--find-ns))))
+ (if ns
+ (format "(binding [*ns* (find-ns '%s)] (eval '(do %s)))" ns code)
+ code)))
+
(defun inf-clojure-eval-region (start end &optional and-go)
"Send the current region to the inferior Clojure process.
Sends substring between START and END. Prefix argument AND-GO
-means switch to the Clojure buffer afterwards."
+means switch to the Clojure buffer afterwards.
+When `inf-clojure-eval-ns-aware' is non-nil, the code is evaluated
+in the context of the buffer's namespace."
(interactive "r\nP")
(let* ((str (buffer-substring-no-properties start end))
;; newlines over a socket repl between top level forms cause
;; a prompt to be returned. so here we dump the region into a
;; temp buffer, and delete all newlines between the forms
- (formatted (inf-clojure--forms-without-newlines str)))
- (inf-clojure--send-string (inf-clojure-proc) formatted))
+ (formatted (inf-clojure--forms-without-newlines str))
+ (wrapped (inf-clojure--wrap-for-ns formatted)))
+ (inf-clojure--send-string (inf-clojure-proc) wrapped))
(when and-go (inf-clojure-switch-to-repl t)))
(defun inf-clojure-eval-string (code)
diff --git a/test/inf-clojure-tests.el b/test/inf-clojure-tests.el
index 796a08bd0e..8fcf77a817 100644
--- a/test/inf-clojure-tests.el
+++ b/test/inf-clojure-tests.el
@@ -300,6 +300,27 @@ is a string\")
(it "returns nil for non-list response"
(expect (inf-clojure-list-completions "42") :to-be nil)))
+(describe "inf-clojure--wrap-for-ns"
+ (it "returns code unchanged when inf-clojure-eval-ns-aware is nil"
+ (let ((inf-clojure-eval-ns-aware nil))
+ (ict-with-assess-buffers
+ ((a (insert "(ns my.app)\n(+ 1 2)")))
+ (with-current-buffer a
+ (expect (inf-clojure--wrap-for-ns "(+ 1 2)") :to-equal "(+ 1 2)")))))
+ (it "wraps code with binding when inf-clojure-eval-ns-aware is non-nil"
+ (let ((inf-clojure-eval-ns-aware t))
+ (ict-with-assess-buffers
+ ((a (insert "(ns myapp)\n(+ 1 2)")))
+ (with-current-buffer a
+ (expect (inf-clojure--wrap-for-ns "(+ 1 2)")
+ :to-equal "(binding [*ns* (find-ns 'myapp)] (eval '(do (+ 1
2))))")))))
+ (it "returns code unchanged when no namespace is detected"
+ (let ((inf-clojure-eval-ns-aware t))
+ (ict-with-assess-buffers
+ ((a (insert "(+ 1 2)")))
+ (with-current-buffer a
+ (expect (inf-clojure--wrap-for-ns "(+ 1 2)") :to-equal "(+ 1 2)"))))))
+
(describe "inf-clojure--string-boundaries"
(it "returns full string bounds when no regexps given"
(expect (inf-clojure--string-boundaries "hello" "=>")