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" "=>")

Reply via email to