├── eastwood.clj ├── env ├── test │ └── hello_world │ │ └── foo.cljs └── repl │ └── user.clj ├── .dir-locals.el ├── .gitignore ├── src └── cider │ ├── piggieback_shim.clj │ ├── piggieback.clj │ └── piggieback_impl.clj ├── Makefile ├── project.clj ├── test └── cider │ └── piggieback_test.clj ├── .circleci └── config.yml ├── CHANGES.md ├── epl-v10.html └── README.md /eastwood.clj: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /env/test/hello_world/foo.cljs: -------------------------------------------------------------------------------- 1 | (ns hello-world.foo) 2 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((clojure-mode 5 | (clojure-indent-style . :always-align) 6 | (indent-tabs-mode . nil) 7 | (fill-column . 80))) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .classpath 13 | .project 14 | .settings 15 | .repl 16 | .lein-repl-history 17 | .externalToolBuilders 18 | .cljs_* 19 | nashorn* 20 | .nrepl-port 21 | \.\#* 22 | -------------------------------------------------------------------------------- /src/cider/piggieback_shim.clj: -------------------------------------------------------------------------------- 1 | (in-ns 'cider.piggieback) 2 | 3 | (def ^:private 4 | fail-to-call 5 | (fn [& args] 6 | (throw (ex-info "Unable to load ClojureScript, did you forget a dependency?" 7 | {})))) 8 | 9 | (def cljs-repl fail-to-call) 10 | (def repl-caught fail-to-call) 11 | (def read-cljs-string fail-to-call) 12 | (def eval-cljs fail-to-call) 13 | (def do-eval fail-to-call) 14 | 15 | (defn wrap-cljs-repl [handler] 16 | (fn [msg] 17 | (handler msg))) 18 | -------------------------------------------------------------------------------- /env/repl/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [cider.piggieback :as pback])) 4 | 5 | (defmacro ^:private when-ns 6 | [ns & body] 7 | (if (try 8 | (require ns) 9 | true 10 | (catch java.io.FileNotFoundException e 11 | false)) 12 | `(do ~@body) 13 | `(do))) 14 | 15 | (when-ns cljs.repl 16 | (require '[cljs.repl :as repl] 17 | '[cljs.repl.node :as node]) 18 | 19 | 20 | (defn cljs-node [] 21 | (pback/cljs-repl (node/repl-env))) 22 | 23 | (defn cljs* [] 24 | (repl/repl (node/repl-env)))) 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test eastwood cljfmt release deploy clean 2 | 3 | VERSION ?= 1.10 4 | NREPL_VERSION ?= nrepl-0.6 5 | 6 | test: 7 | lein with-profile -user,+$(VERSION),+$(NREPL_VERSION) test; 8 | 9 | eastwood: 10 | lein with-profile -user,+$(VERSION),+eastwood eastwood 11 | 12 | cljfmt: 13 | lein with-profile -user,+$(VERSION),+cljfmt cljfmt check 14 | 15 | cljfmt-fix: 16 | lein with-profile -user,+$(VERSION),+cljfmt cljfmt fix 17 | 18 | 19 | # When releasing, the BUMP variable controls which field in the 20 | # version string will be incremented in the *next* snapshot 21 | # version. Typically this is either "major", "minor", or "patch". 22 | 23 | BUMP ?= patch 24 | 25 | release: 26 | lein with-profile -user,+$(VERSION) release $(BUMP) 27 | 28 | # Deploying requires the caller to set environment variables as 29 | # specified in project.clj to provide a login and password to the 30 | # artifact repository. 31 | 32 | deploy: 33 | lein with-profile -user,+$(VERSION) deploy clojars 34 | 35 | clean: 36 | lein clean 37 | -------------------------------------------------------------------------------- /src/cider/piggieback.clj: -------------------------------------------------------------------------------- 1 | (ns cider.piggieback 2 | "nREPL middleware enabling the transparent use of a ClojureScript REPL with nREPL tooling." 3 | {:author "Chas Emerick"} 4 | (:refer-clojure :exclude [load-file]) 5 | (:require 6 | [nrepl.middleware :refer [set-descriptor!]] 7 | [nrepl.middleware.print :as print])) 8 | 9 | (defmacro ^:private if-ns 10 | "Evaluate some code conditionally based on the presence of `ns`." 11 | [ns body else] 12 | (if (try 13 | (require ns) 14 | true 15 | (catch java.io.FileNotFoundException e 16 | false)) 17 | `~body 18 | `~else)) 19 | 20 | ;; Depending on whether ClojureScript is present we load either the real implementation 21 | ;; or a no-op shim. 22 | (if-ns cljs.repl 23 | (load "piggieback_impl") 24 | (load "piggieback_shim")) 25 | 26 | (set-descriptor! #'wrap-cljs-repl 27 | {:requires #{"clone" #'print/wrap-print} 28 | ;; piggieback unconditionally hijacks eval and load-file 29 | :expects #{"eval" "load-file"} 30 | :handles {}}) 31 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cider/piggieback "0.6.1" 2 | :description "Middleware adding support for running ClojureScript REPLs over nREPL." 3 | :url "https://github.com/nrepl/piggieback" 4 | :license {:name "Eclipse Public License" 5 | :url "https://www.eclipse.org/legal/epl-v10.html"} 6 | :scm {:name "git" :url "https://github.com/nrepl/piggieback"} 7 | :min-lein-version "2.0.0" 8 | 9 | :source-paths ["src"] 10 | 11 | :deploy-repositories [["clojars" {:url "https://clojars.org/repo" 12 | :username :env/clojars_username 13 | :password :env/clojars_password 14 | :sign-releases false}]] 15 | 16 | :profiles {:provided {:dependencies [[org.clojure/clojure "1.12.0"] 17 | [org.clojure/clojurescript "1.11.132"] 18 | [nrepl/nrepl "1.0.0"]]} 19 | 20 | :test {:source-paths ["env/test"]} 21 | 22 | :1.10 {:dependencies [[org.clojure/clojure "1.10.3"] 23 | [org.clojure/clojurescript "1.10.914"]]} 24 | 25 | :1.11 {:dependencies [[org.clojure/clojure "1.11.4"] 26 | [org.clojure/clojurescript "1.11.132"]]} 27 | 28 | :1.12 {:dependencies [[org.clojure/clojure "1.12.0"] 29 | [org.clojure/clojurescript "1.11.132"]]} 30 | 31 | :nrepl-1.0 {:dependencies [[nrepl/nrepl "1.0.0"]]} 32 | :nrepl-1.1 {:dependencies [[nrepl/nrepl "1.1.1"]]} 33 | :nrepl-1.2 {:dependencies [[nrepl/nrepl "1.2.0"]]} 34 | :nrepl-1.3 {:dependencies [[nrepl/nrepl "1.3.0"]]} 35 | 36 | ;; Need ^:repl because of: https://github.com/technomancy/leiningen/issues/2132 37 | :repl ^:repl [:test 38 | {:source-paths ["env/repl"] 39 | :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}}] 40 | 41 | :cljfmt {:plugins [[lein-cljfmt "0.6.1"]]} 42 | 43 | :eastwood {:plugins [[jonase/eastwood "0.9.9"]] 44 | :eastwood {:config-files ["eastwood.clj"] 45 | :exclude-linters [:no-ns-form-found]}}}) 46 | -------------------------------------------------------------------------------- /test/cider/piggieback_test.clj: -------------------------------------------------------------------------------- 1 | (ns cider.piggieback-test 2 | (:require 3 | [clojure.java.shell] 4 | [clojure.test :refer [deftest is use-fixtures testing]] 5 | [nrepl.core :as nrepl] 6 | [nrepl.server :as server])) 7 | 8 | (require '[cider.piggieback :as pb]) 9 | 10 | (def ^:dynamic *server-port* nil) 11 | (def ^:dynamic *session*) 12 | 13 | (def ^:private cljs-repl-start-code 14 | (do (require 'cljs.repl.node) 15 | (nrepl/code 16 | (cider.piggieback/cljs-repl 17 | (cljs.repl.node/repl-env))))) 18 | 19 | (defn repl-server-fixture 20 | [f] 21 | (let [{:keys [exit] 22 | :as v} (clojure.java.shell/sh "node" "--version")] 23 | (assert (zero? exit) 24 | (pr-str v))) 25 | 26 | (with-open [^nrepl.server.Server 27 | server (server/start-server 28 | :bind "127.0.0.1" 29 | :handler (server/default-handler #'cider.piggieback/wrap-cljs-repl))] 30 | (let [port (.getLocalPort ^java.net.ServerSocket (:server-socket server)) 31 | conn (nrepl/connect :port port) 32 | session (nrepl/client-session (nrepl/client conn Long/MAX_VALUE))] 33 | ;; need to let the dynamic bindings get in place before trying to eval anything that 34 | ;; depends upon those bindings being set 35 | (dorun (nrepl/message session {:op "eval" :code cljs-repl-start-code})) 36 | (try 37 | (binding [*server-port* port 38 | *session* session] 39 | (f)) 40 | (finally 41 | (dorun (nrepl/message session {:op "eval" :code ":cljs/quit"}))))))) 42 | 43 | (use-fixtures :once repl-server-fixture) 44 | 45 | (deftest default-sanity 46 | (dorun (nrepl/message *session* {:op "eval" :code "(defn x [] (into [] (js/Array 1 2 3)))"})) 47 | (is (= [1 2 3] (->> {:op "eval" :code "(x)"} 48 | (nrepl/message *session*) 49 | nrepl/response-values 50 | first)))) 51 | 52 | (deftest proper-ns-tracking 53 | (let [response (-> (nrepl/message *session* {:op "eval" :code "5"}) 54 | nrepl/combine-responses)] 55 | (testing (pr-str response) 56 | (some-> response :err println) 57 | (is (= ["5"] (:value response))) 58 | (is (= "cljs.user" (:ns response))))) 59 | 60 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(ns foo.bar)"}) 61 | nrepl/combine-responses)] 62 | (testing (pr-str response) 63 | (some-> response :err println) 64 | (is (= ["nil"] (:value response))) 65 | (is (= "foo.bar" (:ns response))))) 66 | 67 | (dorun (nrepl/message *session* {:op "eval" :code "(defn ns-tracking [] (into [] (js/Array 1 2 3)))"})) 68 | 69 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(ns-tracking)"}) 70 | nrepl/combine-responses)] 71 | (testing (pr-str response) 72 | (some-> response :err println) 73 | (is (= ["[1 2 3]"] (:value response))) 74 | (is (= "foo.bar" (:ns response))))) 75 | 76 | ;; TODO emit a response message to in-ns, doesn't seem to hit eval.... 77 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(in-ns 'cljs.user)"}) 78 | nrepl/combine-responses)] 79 | (testing (pr-str response) 80 | (some-> response :err println) 81 | (is (= "cljs.user" (:ns response))))) 82 | 83 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(ns cljs.user)"}) 84 | nrepl/combine-responses)] 85 | (testing (pr-str response) 86 | (some-> response :err println) 87 | (is (= "cljs.user" (:ns response))))) 88 | 89 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(ns-tracking)" :ns "foo.bar"}) 90 | nrepl/combine-responses)] 91 | (testing (pr-str response) 92 | (some-> response :err println) 93 | (is (= ["[1 2 3]"] (:value response))) 94 | (is (= "foo.bar" (:ns response))))) 95 | 96 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(ns foo.bar)" :ns "cljs.user"}) 97 | nrepl/combine-responses)] 98 | (testing (pr-str response) 99 | (some-> response :err println) 100 | (is (= "foo.bar" (:ns response))))) 101 | 102 | ;; verifying that this doesn't throw 103 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(require 'hello-world.foo :reload)" :ns "foo.bar"}) 104 | nrepl/combine-responses)] 105 | (testing (pr-str response) 106 | (some-> response :err println) 107 | (is (:value response)) 108 | (is (= "foo.bar" (:ns response))))) 109 | 110 | (let [response (-> (nrepl/message *session* {:op "eval" :code "(in-ns 'cljs.user)"}) 111 | nrepl/combine-responses)] 112 | (testing (pr-str response) 113 | (is (= "cljs.user" (:ns response)))))) 114 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | ###################################################################### 4 | # 5 | # Start of general purpose config. These can plausibly go into orbs 6 | # 7 | ###################################################################### 8 | 9 | # Default settings for executors 10 | 11 | defaults: &defaults 12 | working_directory: ~/repo 13 | 14 | env_defaults: &env_defaults 15 | LEIN_ROOT: "true" # we intended to run lein as root 16 | 17 | jdk_env_defaults: &jdk_env_defaults 18 | JVM_OPTS: -Xmx3200m 19 | 20 | # Sets a flag that didn't exist in prior JDKs and that is deprecated in later JDKs. 21 | jdk11_env_defaults: &jdk11_env_defaults 22 | JVM_OPTS: -Xmx3200m --illegal-access=deny 23 | 24 | executors: 25 | openjdk8: 26 | docker: 27 | - image: circleci/clojure:openjdk-8-lein-2.9.1-node 28 | environment: 29 | <<: *env_defaults 30 | <<: *jdk_env_defaults 31 | <<: *defaults 32 | openjdk11: 33 | docker: 34 | - image: circleci/clojure:openjdk-11-lein-2.9.1-node 35 | environment: 36 | <<: *env_defaults 37 | <<: *jdk11_env_defaults 38 | <<: *defaults 39 | openjdk17: 40 | docker: 41 | - image: circleci/clojure:openjdk-17-lein-2.9.5-buster-node 42 | environment: 43 | <<: *env_defaults 44 | <<: *jdk_env_defaults 45 | <<: *defaults 46 | 47 | # Runs a given set of steps, with some standard pre- and post- 48 | # steps, including restoring of cache, saving of cache. 49 | # 50 | # we also install `make` here. 51 | # 52 | # Adapted from https://github.com/lambdaisland/meta/blob/master/circleci/clojure_orb.yml 53 | 54 | commands: 55 | with_cache: 56 | description: | 57 | Run a set of steps with Maven dependencies and Clojure classpath cache 58 | files cached. 59 | This command restores ~/.m2 and .cpcache if they were previously cached, 60 | then runs the provided steps, and finally saves the cache. 61 | The cache-key is generated based on the contents of `deps.edn` present in 62 | the `working_directory`. 63 | parameters: 64 | steps: 65 | type: steps 66 | files: 67 | description: Files to consider when creating the cache key 68 | type: string 69 | default: "deps.edn project.clj build.boot" 70 | cache_version: 71 | type: string 72 | description: "Change this value to force a cache update" 73 | default: "1" 74 | steps: 75 | - run: 76 | name: Install make 77 | command: | 78 | sudo apt-get install make 79 | - run: 80 | name: Generate Cache Checksum 81 | command: | 82 | for file in << parameters.files >> 83 | do 84 | find . -name $file -exec cat {} + 85 | done | shasum | awk '{print $1}' > /tmp/clojure_cache_seed 86 | - restore_cache: 87 | key: clojure-<< parameters.cache_version >>-{{ checksum "/tmp/clojure_cache_seed" }} 88 | - steps: << parameters.steps >> 89 | - save_cache: 90 | paths: 91 | - ~/.m2 92 | - .cpcache 93 | key: clojure-<< parameters.cache_version >>-{{ checksum "/tmp/clojure_cache_seed" }} 94 | 95 | # The jobs are relatively simple. One runs utility commands against 96 | # latest stable JDK + Clojure, the other against specified versions 97 | 98 | jobs: 99 | 100 | util_job: 101 | description: | 102 | Running utility commands/checks (linter etc.) 103 | Always uses the latest JDK and Clojure versions 104 | parameters: 105 | steps: 106 | type: steps 107 | executor: openjdk17 108 | environment: 109 | VERSION: "1.10" 110 | steps: 111 | - checkout 112 | - with_cache: 113 | cache_version: "1.10" 114 | steps: << parameters.steps >> 115 | 116 | test_code: 117 | description: | 118 | Run tests against given version of JDK and Clojure 119 | parameters: 120 | jdk_version: 121 | description: Version of JDK to test against 122 | type: string 123 | clojure_version: 124 | description: Version of Clojure to test against 125 | type: string 126 | nrepl_version: 127 | description: Version of nrepl to test against 128 | type: string 129 | executor: << parameters.jdk_version >> 130 | environment: 131 | VERSION: << parameters.clojure_version >> 132 | NREPL_VERSION: << parameters.nrepl_version >> 133 | steps: 134 | - checkout 135 | - with_cache: 136 | cache_version: << parameters.clojure_version >>|<< parameters.jdk_version >> 137 | steps: 138 | - run: 139 | name: Running tests 140 | command: make test 141 | 142 | ###################################################################### 143 | # 144 | # End general purpose configs 145 | # 146 | ###################################################################### 147 | 148 | 149 | # The ci-test-matrix does the following: 150 | # 151 | # - run tests against the target matrix 152 | # - Java 8, 11 and 17 153 | # - Clojure 1.8, 1.9, 1.10, master 154 | # - linters: eastwood and cljfmt 155 | 156 | workflows: 157 | version: 2.1 158 | ci-test-matrix: 159 | jobs: 160 | - test_code: 161 | matrix: 162 | parameters: 163 | jdk_version: [openjdk8, openjdk11, openjdk17] 164 | clojure_version: ["1.10", "1.11", "1.12"] 165 | nrepl_version: ["nrepl-1.0", "nrepl-1.1", "nrepl-1.2", "nrepl-1.3"] 166 | - util_job: 167 | name: Code Linting 168 | steps: 169 | - run: 170 | name: Running cljfmt 171 | command: | 172 | make cljfmt 173 | - run: 174 | name: Running Eastwood 175 | command: | 176 | make eastwood 177 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## unreleased 4 | 5 | ## 0.6.1 (2025-12-31) 6 | 7 | * [132](https://github.com/nrepl/piggieback/pull/132): Fix current namespace dropping back to previous after in-ns. 8 | 9 | ## 0.6.0 (2024-12-31) 10 | 11 | * Bump minimal supported nREPL version to 1.0. 12 | * Bump minimal supported Clojure and ClojureScript versions to 1.10. 13 | * Restore compatibility with nREPL 1.3. 14 | * [#127](https://github.com/nrepl/piggieback/issues/127): Respect :file in msg when evaluating forms. 15 | 16 | ## 0.5.3 (2021-10-26) 17 | 18 | * Address reflection warnings. 19 | 20 | ## 0.5.2 (2020-11-02) 21 | 22 | * [#118](https://github.com/nrepl/piggieback/issues/118): Fix bad dependencies in release. 23 | 24 | ### Bugs fixed 25 | 26 | * [#117](https://github.com/nrepl/piggieback/pull/117): Require `cljs.repl` and `cljs.pprint` into `cljs.user` in the case that the repl-env specifies `:repl-requires`. 27 | 28 | ## 0.5.1 (2020-05-11) 29 | 30 | ### Bugs fixed 31 | 32 | * [#117](https://github.com/nrepl/piggieback/pull/117): Require `cljs.repl` and `cljs.pprint` into `cljs.user` in the case that the repl-env specifies `:repl-requires`. 33 | 34 | ## 0.5.0 (2020-05-11) 35 | 36 | * [#108](https://github.com/nrepl/piggieback/pull/108): **(Breaking)** Dropped support for nREPL versions 0.4 and 0.5, `[nrepl "0.6.0"]` is now the minimum required version. 37 | * [#108](https://github.com/nrepl/piggieback/pull/108): Added support for nREPL print middleware introduced in nREPL 0.6.0. 38 | * Moved away from Nashorn: changed tests and example code to Node. 39 | 40 | ## 0.4.2 (2019-10-08) 41 | 42 | * [#107](https://github.com/nrepl/piggieback/pull/107): Make piggieback a no-op when ClojureScript is not loaded. 43 | 44 | ## 0.4.1 (2019-05-15) 45 | 46 | * Fix a bug affecting nREPL 0.6 where `*out*` and `*err` were not reliably bound after session init. 47 | 48 | ## 0.4.0 (2019-02-05) 49 | 50 | * **(Breaking)** Dropped support for `clojure.tools.nrepl`. `[nrepl "0.4.0"]` is 51 | now the minimum required version. 52 | * Add compatibility with nREPL 0.6+. 53 | 54 | ## 0.3.10 (2018-10-21) 55 | 56 | * [#95](https://github.com/nrepl/piggieback/issues/95): Bind `*cljs-warnings*`. 57 | * [#97](https://github.com/nrepl/piggieback/pulls/97): Establish a binding to `cljs.analyzer/*unchecked-if*`. 58 | 59 | ## 0.3.9 60 | 61 | * Honor `:repl-requires` CLJS repl option. 62 | * Bind `cljs.repl/*repl-env*` to better support CLJS versions newer than 1.10.126. 63 | 64 | ## 0.3.8 65 | 66 | * Fix the `tools.nrepl` support. (a silly typo had broken it) 67 | 68 | ## 0.3.7 69 | 70 | * Add compatibility with nREPL 0.4+. 71 | 72 | ## 0.3.6 73 | 74 | * Allow repl-options to flow through. 75 | 76 | ## 0.3.5 77 | 78 | * Fix loss of `compiler-env`. 79 | 80 | ## 0.3.4 81 | 82 | * Fix REPL teardown problem with ClojureScript 1.10. 83 | 84 | ## 0.3.3 85 | 86 | * Fix REPL teardown problem and bind *out* and *err* for initialization (this affected the node repl). 87 | 88 | ## 0.3.2 89 | 90 | * Enable `:wrap` repl-option. 91 | * Enable `:caught` repl-option. 92 | * Capture `cljs-warning-handlers` so consumers can bind them. 93 | 94 | ## 0.3.1 95 | 96 | * [#87](https://github.com/nrepl/piggieback/issues/87): Fix a Nashorn regression introduced in 0.2.3. 97 | 98 | ## 0.3.0 99 | 100 | * Drop support for Rhino. 101 | * Change the namespace prefix from `cemerick` to `cider`. 102 | 103 | ## 0.2.3 104 | 105 | * Changed the artefact coordinates to `cider/piggieback`. It's now being deployed 106 | to Clojars, instead of to Maven Central. 107 | * [#80](https://github.com/nrepl/piggieback/pull/80): Make eval just eval, instead of creating a new REPL for each evaluation. 108 | * Piggieback now requires ClojureScript 1.9 and Java 8. 109 | 110 | ## 0.2.2 111 | 112 | * Removed superfluous Clojure 1.6.0 dependency (gh-70) 113 | * The current nREPL's session's `*e` binding is now set properly when an 114 | uncaught exception occurs. 115 | 116 | ## 0.2.1 117 | 118 | Fixes nREPL load-file support, implementing it in terms of evaluation of the 119 | `load-file` `cljs.repl` special function. 120 | 121 | ## 0.2.0 122 | 123 | This release is essentially a rewrite to accommodate the significant changes to 124 | the upstream ClojureScript REPL infrastructure. Using piggieback is effectively 125 | unchanged, things just work a lot better now (and many outstanding issues are no 126 | longer relevant due to a change in how Piggieback is implemented). 127 | 128 | Note that `cemerick.piggieback/cljs-repl` has been changed to match the signature 129 | provided by `cljs.repl/repl`, i.e. the REPL environment is always the first 130 | argument. 131 | 132 | There are no breaking changes AFAICT w.r.t. other nREPL middlewares that might use 133 | Piggieback to access e.g. the current session's ClojureScript REPL environment, etc. 134 | 135 | ## [`0.1.5`](https://github.com/cemerick/piggieback/issues?q=milestone%3A0.1.5+is%3Aclosed) 136 | 137 | * Add support for "new style" ClojureScript special REPL functions. Piggieback 138 | is now completely compatible with ClojureScript >= 2665. (gh-38) 139 | * Fix to support ClojureScript-provided node.js REPL environment (gh-39) 140 | 141 | ## [`0.1.4`](https://github.com/cemerick/piggieback/issues?q=milestone%3A0.1.4+is%3Aclosed) 142 | 143 | * Change to support updated `cljs.repl` API, per 144 | https://github.com/clojure/clojurescript/wiki/Custom-REPLs. Piggieback now 145 | requires ClojureScript >= 2665. 146 | 147 | ## [`0.1.3`](https://github.com/cemerick/piggieback/issues?milestone=3&state=closed) 148 | 149 | * Piggieback now uses tools.reader to read expressions sent for evaluation when 150 | a ClojureScript REPL environment is active. This preserves proper source 151 | information (useful for source maps) and allows Piggieback to participate in 152 | the aliasing mechanism used in ClojureScript to support namespace 153 | alias-qualified keywords e.g. `::alias/keyword` (gh-19) 154 | * The ClojureScript support for the `load-file` nREPL operation now correctly 155 | provides the source file's path instead of its name to the compiler. (gh-24) 156 | 157 | ## `0.1.2` 158 | 159 | Released to fix a derp in `0.1.1`. 160 | 161 | ## `0.1.1` 162 | 163 | * Adds support for ClojureScript compiler environments introduced in `0.0-2014`. 164 | Now requires that version of ClojureScript or higher. 165 | 166 | ## `0.1.0` 167 | 168 | * _Breaking change_: ClojureScript REPL environments no longer need to / should 169 | be explicitly `-setup` prior to use with `cemerick.piggieback/cljs-repl`. 170 | i.e. this: 171 | 172 | ``` 173 | (cemerick.piggieback/cljs-repl 174 | :repl-env (doto (create-some-cljs-repl-env) 175 | cljs.repl/-setup)) 176 | ``` 177 | should be replaced with this: 178 | ``` 179 | (cemerick.piggieback/cljs-repl :repl-env (create-some-cljs-repl-env)) 180 | ``` 181 | Fixes gh-10. 182 | * Deprecated `cemerick.piggieback/rhino-repl-env`, which will be removed 183 | ~`0.2.0`; it now simply calls through 184 | to `cljs.repl.rhino/repl-env`. Any usage of the former should be replaced 185 | with the latter. Fixes gh-9. 186 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/nrepl/piggieback/tree/master.svg?style=svg)](https://circleci.com/gh/nrepl/piggieback/tree/master) 2 | [![Clojars Project](https://img.shields.io/clojars/v/cider/piggieback.svg)](https://clojars.org/cider/piggieback) 3 | 4 | # Piggieback 5 | 6 | [nREPL](http://github.com/nrepl/nrepl) middleware that enables the 7 | use of a ClojureScript REPL on top of an nREPL session. 8 | 9 | ## Why? 10 | 11 | Two reasons: 12 | 13 | * The default ClojureScript REPL (as described in the 14 | ["quick start"](https://clojurescript.org/guides/quick-start) 15 | tutorial) assumes that it is running in a teletype environment. This works fine 16 | with nREPL tools in that environment (e.g. `lein repl` in `Terminal.app` or 17 | `gnome-terminal`, etc), but isn't suitable for development environments that 18 | have richer interaction models (including editors like vim ([vim-fireplace][]) and Emacs 19 | ([CIDER][]), and IDEs like Intellij ([Cursive][]) and Eclipse ([Counterclockwise][CCW])). 20 | 21 | * Most of the more advanced tool support for Clojure and ClojureScript (code 22 | completion, introspection and inspector utilities, refactoring tools, etc) is 23 | packaged and delivered as nREPL extensions (e.g. [cider-nrepl][] and [refactor-nrepl][]). 24 | 25 | Piggieback provides an alternative ClojureScript REPL entry point 26 | (`cider.piggieback/cljs-repl`) that changes an nREPL session into a 27 | ClojureScript REPL for `eval` and `load-file` operations, while accepting all 28 | the same options as `cljs.repl/repl`. When the ClojureScript REPL is terminated 29 | (by sending `:cljs/quit` for evaluation), the nREPL session is restored to it 30 | original state. 31 | 32 | ## Installation 33 | 34 | Piggieback is compatible with Clojure 1.10.0+, and _requires_ ClojureScript 35 | `1.10` or later and nREPL `1.0.0` or later. 36 | 37 | To use the default Node.js REPL (`cljs.repl.node`) you'll also need to install a recent version of Node.js. 38 | 39 | ### Leiningen 40 | 41 | These instructions are for Leiningen. Translating them for use in Boot should be 42 | straightforward. 43 | 44 | Modify your `project.clj` to include the following `:dependencies` and 45 | `:repl-options`: 46 | 47 | ```clojure 48 | :profiles {:dev {:dependencies [[cider/piggieback "0.6.1"]] 49 | :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}}} 50 | ``` 51 | 52 | The `:repl-options` bit causes `lein repl` to automagically mix the Piggieback 53 | nREPL middleware into its default stack. 54 | 55 | _If you're using Leiningen directly, or as the basis for the REPLs in your local 56 | development environment (e.g. CIDER, fireplace, counterclockwise, etc), you're 57 | done._ [Skip to starting a ClojureScript REPL](#usage). 58 | 59 | ### Boot 60 | 61 | Contributions welcome! 62 | 63 | ### Clojure CLI (aka `tools.deps`) 64 | 65 | **The instructions below require nREPL 1.0.0 or newer** 66 | 67 | Add this alias to `~/.clojure/deps.edn`: 68 | 69 | ``` clojure 70 | { 71 | ;; ... 72 | :aliases {:nrepl 73 | {:extra-deps 74 | {nrepl/nrepl {:mvn/version "1.3.0"} 75 | cider/piggieback {:mvn/version "0.6.1"}}}} 76 | } 77 | ``` 78 | 79 | Then you can simply run a ClojureScript-capable nREPL server like this: 80 | 81 | ``` shell 82 | clj -R:nrepl -m nrepl.cmdline --middleware "[cider.piggieback/wrap-cljs-repl]" 83 | ``` 84 | 85 | When you connect to the running server with your favourite nREPL client 86 | (e.g. CIDER), you will be greeted by a Clojure REPL. Within this Clojure REPL, 87 | you can now [start a ClojureScript REPL](#usage). 88 | 89 | ### Embedded 90 | 91 | If you're not starting nREPL through a build tool (e.g. maybe you're starting up 92 | an nREPL server from within an application), you can achieve the same thing by 93 | specifying that the `wrap-cljs-repl` middleware be mixed into nREPL's default 94 | handler: 95 | 96 | ```clojure 97 | (require '[nrepl.server :as server] 98 | '[cider.piggieback :as pback]) 99 | 100 | (server/start-server 101 | :handler (server/default-handler #'pback/wrap-cljs-repl) 102 | ; ...additional `start-server` options as desired 103 | ) 104 | ``` 105 | 106 | Alternatively, you can add `wrap-cljs-repl` to your application's hand-tweaked 107 | nREPL handler. Keep two things in mind when doing so: 108 | 109 | * Piggieback needs to be "above" nREPL's 110 | `nrepl.middleware.interruptible-eval/interruptible-eval`; it 111 | doesn't use `interruptible-eval`'s evaluation machinery, but it does reuse its 112 | execution queue and thus inherits its interrupt capability. 113 | * Piggieback depends upon persistent REPL sessions, like those provided by 114 | `nrepl.middleware.session/session`.) 115 | 116 | 117 | ## Usage 118 | 119 | Before you run the following, you must have gone through the [setup 120 | steps](#installation). Instead of using `lein repl`, you might also connect to a 121 | headless nREPL using your development environment. 122 | 123 | ``` 124 | $ lein repl 125 | .... 126 | user=> (require 'cljs.repl.node) 127 | nil 128 | user=> (cider.piggieback/cljs-repl (cljs.repl.node/repl-env)) 129 | To quit, type: :cljs/quit 130 | nil 131 | cljs.user=> (defn <3 [a b] (str a " <3 " b "!")) 132 | #< 133 | function cljs$user$_LT_3(a, b) { 134 | return [cljs.core.str(a), cljs.core.str(" <3 "), cljs.core.str(b), cljs.core.str("!")].join(""); 135 | } 136 | > 137 | cljs.user=> (<3 "nREPL" "ClojureScript") 138 | "nREPL <3 ClojureScript!" 139 | ``` 140 | 141 | See how the REPL prompt changed after invoking 142 | `cider.piggieback/cljs-repl`? After that point, all expressions sent to the 143 | REPL are evaluated within the ClojureScript environment. 144 | `cider.piggieback/cljs-repl`'s passes along all of its options to 145 | `cljs.repl/repl`, so all of the tutorials and documentation related to it hold. 146 | 147 | *Important Notes* 148 | 149 | 1. When using Piggieback to enable a browser REPL: the ClojureScript compiler 150 | defaults to putting compilation output in `out`, which is probably not where 151 | your ring app is serving resources from (`resources`, 152 | `target/classes/public`, etc). Either configure your ring app to serve 153 | resources from `out`, or pass a `cljs-repl` `:output-dir` option so that a 154 | reasonable correspondence is established. 155 | 2. The `load-file` nREPL operation will only load the state of files from disk. 156 | This is in contrast to "regular" Clojure nREPL operation, where the current 157 | state of a file's buffer is loaded without regard to its saved state on disk. 158 | 159 | Of course, you can concurrently take advantage of all of nREPL's other 160 | facilities, including connecting to the same nREPL server with other clients (so 161 | as to easily modify Clojure and ClojureScript code via the same JVM), and 162 | interrupting hung ClojureScript invocations: 163 | 164 | ```clojure 165 | cljs.user=> (iterate inc 0) 166 | ^C 167 | cljs.user=> "Error evaluating:" (iterate inc 0) :as "cljs.core.iterate.call(null,cljs.core.inc,0);\n" 168 | java.lang.ThreadDeath 169 | java.lang.Thread.stop(Thread.java:776) 170 | .... 171 | cljs.user=> (<3 "nREPL still" "ClojureScript") 172 | "nREPL still <3 ClojureScript!" 173 | ``` 174 | 175 | (The ugly `ThreadDeath` exception will be eliminated eventually.) 176 | 177 | Piggieback works well with all known ClojureScript REPL environments, including 178 | Node and browser REPLs. 179 | 180 | *Support for Rhino was dropped in version 0.3, and Nashorn support 181 | was dropped from ClojureScript in 1.10.741.* 182 | 183 | ## Design 184 | 185 | This section documents some of the main design decisions in Piggieback 186 | and the differences between similar functionality in nREPL and Piggieback. 187 | 188 | Perhaps the most important thing to remember is that Piggieback is written in 189 | Clojure and runs on Clojure. It drives ClojureScript evaluation by using 190 | ClojureScript's Clojure API (`cljs.repl/IJavaScriptEnv`). This allows you to 191 | host both Clojure and ClojureScript evaluation sessions on the same nREPL 192 | server, which is pretty cool. On the other hand it also means that you can't use 193 | Piggieback with self-hosted ClojureScript REPLs (e.g. Lumo). 194 | 195 | **Note:** For self-hosted ClojureScript you'll need an nREPL implementation that can run 196 | natively on it (e.g. [nrepl-cljs](https://github.com/djblue/nrepl-cljs)). 197 | 198 | ### No hard dependency on ClojureScript 199 | 200 | Piggieback doesn't have a hard dependency on ClojureScript, as users are 201 | expected to provide the necessary ClojureScript dependency themselves. If 202 | ClojureScript is not present, Piggieback simply won't do anything (see 203 | `piggieback_shim.clj` for details). 204 | 205 | This allows tools to safely load Piggieback without 206 | having to consider whether something would blow up. 207 | 208 | ### Session type based dispatch 209 | 210 | Clients don't have to specify explicitly whether they are doing a ClojureScript eval 211 | operation (e.g. by passing some `:env :cljs` request params). As Piggieback operates 212 | at the nREPL session level all clients need to do is to pass a Piggieback session 213 | to ops like `eval` and that would trigger the Piggieback version of those ops. 214 | 215 | ### Evaluation 216 | 217 | As noted above Piggieback provides alternative versions of the standard nREPL 218 | ops `eval` and `load-file` for ClojureScript evaluation. Due to some differences 219 | between Clojure and ClojureScript they don't behave exactly the same. 220 | 221 | Most notably - for performance reasons we don't spin separate instances of `cljs.repl` 222 | for each evaluation, as nREPL does for Clojure. In practice this means that if you try 223 | to evaluate multiple forms together only the first of them would be evaluated: 224 | 225 | ```clojure 226 | ;; standard ClojureScript REPL behaviour 227 | cljs.user> 228 | (declare is-odd?) 229 | (defn is-even? [n] (if (= n 0) true (is-odd? (dec n)))) 230 | (defn is-odd? [n] (if (= n 0) false (is-even? (dec n)))) 231 | #'cljs.user/is-odd? 232 | #'cljs.user/is-even? 233 | #'cljs.user/is-odd? 234 | cljs.user> (is-even? 4) 235 | true 236 | ``` 237 | 238 | Let's compare this to a REPL powered by Piggieback: 239 | 240 | ```clojure 241 | cljs.user> 242 | (declare is-odd?) 243 | (defn is-even? [n] (if (= n 0) true (is-odd? (dec n)))) 244 | (defn is-odd? [n] (if (= n 0) false (is-even? (dec n)))) 245 | #'cljs.user/is-odd? 246 | cljs.user> (is-even? 4) 247 | Compile Warning line:1 column:2 248 | 249 | Use of undeclared Var cljs.user/is-even? 250 | 251 | 1 (is-even? 4) 252 | ^--- 253 | 254 | #object[TypeError TypeError: Cannot read property 'call' of undefined] 255 | () 256 | cljs.user> 257 | ``` 258 | 259 | Normally that's not a big deal in practice, as you'd rarely want to evaluate multiple expressions together, but it's 260 | something to be kept in mind. 261 | 262 | **Note:** Check out [this discussion](https://github.com/nrepl/piggieback/pull/98) for more details on the subject. 263 | 264 | ### Pretty-printing 265 | 266 | **Note:** Piggieback introduced support for nREPL's pretty-printing interface 267 | in version 0.5. 268 | 269 | Support for pretty printing ClojureScript evaluation results is not 270 | entirely straightforward. This is because Piggieback mostly relies on 271 | the underlying nREPL server implementation to support the features of 272 | the nREPL protocol and on the `cljs.repl/IJavaScriptEnv` interface for 273 | ClojureScript evaluation. 274 | 275 | nREPL 0.6 introduced `nrepl.middleware.print` to facilitate printing 276 | evaluation results in a configurable way. Since nREPL is implemented 277 | in Clojure and runs on the JVM, the middleware relies on receiving 278 | Clojure values for printing them. Conversely when evaluating a 279 | ClojureScript expression in a JavaScript environment, the resulting 280 | Clojure value of the evaluation is always a string. If this value 281 | would simply be passed on as is to the middleware, only the string 282 | itself could be printed by it instead of the evaluation result within 283 | the string. 284 | 285 | There are multiple approaches for working around this issue with 286 | various trade-offs. The current implementation has the following main 287 | considerations: 288 | 289 | 1. `nrepl.middleware.print` is used to print ClojureScript evaluation 290 | results whenever possible, so that the same nREPL (pretty) printing 291 | configuration is applied to both Clojure and ClojureScript. 292 | 293 | 2. For cases where the above is not possible (see below), there is a 294 | fallback to support basic pretty printing. 295 | 296 | In order to support `nrepl.middleware.print` for ClojureScript 297 | evaluation results, they first need to be _read_. The resulting 298 | Clojure values can then be normally printed by the middleware. However 299 | there are various cases where ClojureScript evaluation results can not 300 | be read by the default Clojure reader. Some examples: 301 | 302 | - Functions: `#object[Function]` 303 | - Objects: `#object[cljs.user.Cheese]`, `#object[Window [object Window]]` 304 | - `#js` literals: `#js {:foo 1, :bar 2}` 305 | - `#queue` literals: `#queue [1 2 3]` 306 | - Custom tagged literals: `#user/cheese "Pálpusztai"` 307 | - Types implementing `IPrintWithWriter` in a way that is incompatible 308 | with the Clojure reader 309 | 310 | To work around some of these cases Piggieback provides its own 311 | `UnknownTaggedLiteral` type. It is used as the default tag reader when 312 | reading ClojureScript evaluation results. It doesn't parse the 313 | contents of the literal and has `print-method` defined to simply print 314 | the original. 315 | 316 | **Note:** When a pretty-printer which doesn't rely on `print-method` to 317 | serialize values (such as fipp, puget, etc.) is used, 318 | `UnknownTaggedLiteral` will be serialized in the output instead of the 319 | original literal. 320 | 321 | There are still cases left which can prevent the Clojure reader from 322 | successfully reading ClojureScript evaluation results (mostly custom 323 | `IPrintWithWriter` implementations). In order to support pretty 324 | printing these results as well, the ClojureScript expression to be 325 | evaluated is always wrapped with `cljs.pprint/pprint` (unless 326 | `:nrepl.middleware.print/print` is set to `nrepl.util.print/pr` or `cider.nrepl.pprint/pr`, in 327 | which case `cljs.core/pr` is used instead). This means that whenever 328 | the Clojure reader fails to read the value for any reason, we can 329 | safely fall back on an already (pretty) printed string, albeit 330 | disabling `nrepl.middleware.print` and hence effectively ignoring the 331 | `nrepl.middleware.print` configuration. Special care is taken that 332 | output written to `*out*` during evaluation is not affected by the 333 | wrapping. 334 | 335 | For the cases where the (pretty) printing configuration is not being 336 | applied, the reader probably failed to read the evaluation results and 337 | the above fallbacks are being used instead. 338 | 339 | **Note:** See [this pull request](https://github.com/nrepl/piggieback/pull/108) 340 | for more background and discussion on the current solution. 341 | 342 | ## FAQ 343 | 344 | ### Why "piggieback" instead of "piggyback"? 345 | 346 | That's one of life's greatest mysteries. Only Chas can answer that one. 347 | 348 | ### Why is the artifact group id "cider" instead of "nrepl"? 349 | 350 | Bozhidar took over the maintenance of Piggieback before taking over 351 | the maintenance of nREPL. That's why for a period of time Piggieback lived under 352 | CIDER's GitHub org and back then it made sense to use CIDER's group id. 353 | Eventually, it got reunited with nREPL, but we've opted to preserve 354 | the CIDER group id to avoid further breakages. 355 | 356 | For the same reason the main namespace is `cider.piggieback` instead of 357 | `nrepl.piggieback`. 358 | 359 | ### Does Piggieback work with self-hosted ClojureScript REPLs (e.g. Lumo)? 360 | 361 | No, it doesn't. Piggieback is implemented in Clojure and relies on Clojure's ClojureScript evaluation 362 | API (`cljs.repl/IJavaScriptEnv`). 363 | 364 | For self-hosted ClojureScript you'll need a native ClojureScript nREPL implementation like 365 | [nrepl-cljs](https://github.com/djblue/nrepl-cljs). 366 | 367 | ### Does shadow-cljs use Piggieback? 368 | 369 | No, it doesn't use it. It's most recommended for shadow-cljs users to avoid including the `cider.piggieback/wrap-cljs-repl` middleware. 370 | 371 | Unlike `figwheel`, which relies on Piggieback, `shadow-cljs` provides 372 | its own nREPL middleware. That's why some features of Piggieback (e.g. pretty-printing) 373 | might not be available with `shadow-cljs`. 374 | 375 | You can find `shadow-cljs`'s middleware [here](https://github.com/thheller/shadow-cljs/blob/faab284fe45b04328639718583a7d70feb613d26/src/main/shadow/cljs/devtools/server/nrepl.clj). 376 | 377 | ## Need Help? 378 | 379 | Feel free to create a Github issue or ask on `#cider` on Clojurians Slack if you 380 | have questions or would like to contribute patches. 381 | 382 | ## Acknowledgements 383 | 384 | [Nelson Morris](http://twitter.com/xeqixeqi) was instrumental in the initial 385 | development of piggieback. 386 | 387 | ## License 388 | 389 | Copyright © 2012-2023 Chas Emerick, Bruce Hauman, Bozhidar Batsov and other contributors. 390 | 391 | Distributed under the Eclipse Public License, the same as Clojure. 392 | 393 | [vim-fireplace]: https://github.com/tpope/vim-fireplace 394 | [Cursive]: https://cursive-ide.com/ 395 | [CIDER]: https://github.com/clojure-emacs/CIDER 396 | [cider-nrepl]: https://github.com/clojure-emacs/cider-nrepl 397 | [refactor-nrepl]: https://github.com/clojure-emacs/refactor-nrepl 398 | [CCW]: https://github.com/ccw-ide/ccw 399 | -------------------------------------------------------------------------------- /src/cider/piggieback_impl.clj: -------------------------------------------------------------------------------- 1 | (in-ns 'cider.piggieback) 2 | 3 | (require 4 | '[clojure.java.io :as io] 5 | '[clojure.main] 6 | '[clojure.string :as string] 7 | '[clojure.tools.reader :as reader] 8 | '[clojure.tools.reader.edn :as edn-reader] 9 | '[clojure.tools.reader.reader-types :as readers] 10 | '[cljs.closure] 11 | '[cljs.repl] 12 | '[cljs.env :as env] 13 | '[cljs.analyzer :as ana] 14 | '[cljs.tagged-literals :as tags] 15 | '[nrepl.core :as nrepl] 16 | '[nrepl.middleware :as middleware] 17 | '[nrepl.middleware.interruptible-eval :as ieval] 18 | '[nrepl.misc :as misc :refer [response-for]] 19 | '[nrepl.transport :as transport]) 20 | 21 | (import 22 | '(java.io StringReader Writer)) 23 | 24 | ;; this is the var that is checked by the middleware to determine whether an 25 | ;; active CLJS REPL is in flight 26 | (def ^:private ^:dynamic *cljs-repl-env* nil) 27 | (def ^:private ^:dynamic *cljs-compiler-env* nil) 28 | (def ^:private ^:dynamic *cljs-repl-options* nil) 29 | (def ^:private ^:dynamic *cljs-warnings* nil) 30 | (def ^:private ^:dynamic *cljs-warning-handlers* nil) 31 | (def ^:private ^:dynamic *original-clj-ns* nil) 32 | 33 | ;; --------------------------------------------------------------------------- 34 | ;; Delegating Repl Env 35 | ;; --------------------------------------------------------------------------- 36 | 37 | ;; We have to create a delegating ReplEnv to prevent the call to -tear-down 38 | ;; this could be avoided if we could override -tear-down only 39 | 40 | (defprotocol GetReplEnv 41 | (get-repl-env [this])) 42 | 43 | (def ^:private cljs-repl-protocol-impls 44 | {cljs.repl/IReplEnvOptions 45 | {:-repl-options (fn [repl-env] (cljs.repl/-repl-options (get-repl-env repl-env)))} 46 | cljs.repl/IParseError 47 | {:-parse-error (fn [repl-env err build-options] 48 | (cljs.repl/-parse-error (get-repl-env repl-env) err build-options))} 49 | cljs.repl/IGetError 50 | {:-get-error (fn [repl-env name env build-options] 51 | (cljs.repl/-get-error (get-repl-env repl-env) name env build-options))} 52 | cljs.repl/IParseStacktrace 53 | {:-parse-stacktrace (fn [repl-env stacktrace err build-options] 54 | (cljs.repl/-parse-stacktrace (get-repl-env repl-env) stacktrace err build-options))} 55 | cljs.repl/IPrintStacktrace 56 | {:-print-stacktrace (fn [repl-env stacktrace err build-options] 57 | (cljs.repl/-print-stacktrace (get-repl-env repl-env) stacktrace err build-options))}}) 58 | 59 | (deftype ^:private UnknownTaggedLiteral [tag data]) 60 | 61 | (defmethod print-method UnknownTaggedLiteral 62 | [^UnknownTaggedLiteral this ^java.io.Writer w] 63 | (.write w (str "#" (.tag this) (.data this)))) 64 | 65 | (defn- generate-delegating-repl-env [repl-env] 66 | (let [repl-env-class (class repl-env) 67 | classname (string/replace (.getName repl-env-class) \. \_) 68 | dclassname (str "Delegating" classname)] 69 | (eval 70 | (list* 71 | 'deftype (symbol dclassname) 72 | '([repl-env] 73 | cider.piggieback/GetReplEnv 74 | (get-repl-env [this] (.-repl-env this)) 75 | cljs.repl/IJavaScriptEnv 76 | (-setup [this options] (cljs.repl/-setup repl-env options)) 77 | (-evaluate [this a b c] (cljs.repl/-evaluate repl-env a b c)) 78 | (-load [this ns url] (cljs.repl/-load repl-env ns url)) 79 | ;; This is the whole reason we are creating this delegator 80 | ;; to prevent the call to tear-down 81 | (-tear-down [_]) 82 | clojure.lang.ILookup 83 | (valAt [_ k] (get repl-env k)) 84 | (valAt [_ k default] (get repl-env k default)) 85 | clojure.lang.Seqable 86 | (seq [_] (seq repl-env)) 87 | clojure.lang.Associative 88 | (containsKey [_ k] (contains? repl-env k)) 89 | (entryAt [_ k] (find repl-env k)) 90 | (assoc [_ k v] (#'cider.piggieback/delegating-repl-env (assoc repl-env k v))) 91 | clojure.lang.IPersistentCollection 92 | (count [_] (count repl-env)) 93 | (cons [_ entry] (conj repl-env entry)) 94 | ;; pretty meaningless; most REPL envs are records for the assoc'ing, but they're not values 95 | (equiv [_ other] false)))) 96 | (let [dclass (resolve (symbol dclassname)) 97 | ctor (resolve (symbol (str "->" dclassname)))] 98 | (doseq [[protocol fn-map] cljs-repl-protocol-impls] 99 | (when (satisfies? protocol repl-env) 100 | (extend dclass protocol fn-map))) 101 | @ctor))) 102 | 103 | (defn- delegating-repl-env [repl-env] 104 | (let [ctor (generate-delegating-repl-env repl-env)] 105 | (ctor repl-env))) 106 | 107 | ;; --------------------------------------------------------------------------- 108 | 109 | (defn repl-caught [session transport nrepl-msg err repl-env repl-options] 110 | (let [root-ex (#'clojure.main/root-cause err)] 111 | (when-not (instance? ThreadDeath root-ex) 112 | (set! *e err) 113 | (swap! session assoc #'*e err) 114 | (transport/send transport (response-for nrepl-msg {:status :eval-error 115 | :ex (-> err class str) 116 | :root-ex (-> root-ex class str)})) 117 | ((:caught repl-options cljs.repl/repl-caught) err repl-env repl-options)))) 118 | 119 | (defn- run-cljs-repl [{:keys [session transport ns] :as nrepl-msg} 120 | code repl-env compiler-env options] 121 | (let [initns (if ns (symbol ns) (@session #'ana/*cljs-ns*)) 122 | repl cljs.repl/repl*] 123 | (binding [ana/*cljs-ns* initns] 124 | (with-in-str (str code " :cljs/quit") 125 | (repl repl-env 126 | (merge 127 | {:compiler-env compiler-env} 128 | ;; if options has a compiler env let it override 129 | options 130 | ;; these options need to be set to the following values 131 | ;; for the repl to initialize correctly 132 | {:need-prompt (fn []) 133 | :init (fn []) 134 | :prompt (fn []) 135 | :bind-err false 136 | :quit-prompt (fn []) 137 | :print (fn [result & rest] 138 | (when (or (not ns) 139 | (not= initns ana/*cljs-ns*)) 140 | (swap! session assoc #'ana/*cljs-ns* ana/*cljs-ns*)) 141 | (set! *cljs-compiler-env* env/*compiler*))})))))) 142 | 143 | ;; This function always executes when the nREPL session is evaluating Clojure, 144 | ;; via interruptible-eval, etc. This means our dynamic environment is in place, 145 | ;; so set! and simple dereferencing is available. Contrast w/ evaluate and 146 | ;; load-file below. 147 | (defn cljs-repl 148 | "Starts a ClojureScript REPL over top an nREPL session. Accepts 149 | all options usually accepted by e.g. cljs.repl/repl." 150 | [repl-env & {:as options}] 151 | (try 152 | (let [repl-opts (cljs.repl/-repl-options repl-env) 153 | repl-env (delegating-repl-env repl-env) 154 | ;; have to initialise repl-options the same way they 155 | ;; are initilized inside of the cljs.repl/repl loop 156 | ;; because we are calling evaluate outside of the repl 157 | ;; loop. 158 | opts (merge 159 | {:def-emits-var true} 160 | (cljs.closure/add-implicit-options 161 | (merge-with (fn [a b] (if (nil? b) a b)) 162 | repl-opts options)))] 163 | (set! ana/*cljs-ns* 'cljs.user) 164 | ;; this will implicitly set! *cljs-compiler-env* 165 | (run-cljs-repl ieval/*msg* 166 | ;; this is needed to respect :repl-requires 167 | (if-let [requires (not-empty (:repl-requires opts))] 168 | (pr-str (cons 'ns `(cljs.user (:require ~@requires 169 | [~'cljs.repl :refer-macros [~'source ~'doc ~'find-doc 170 | ~'apropos ~'dir ~'pst]] 171 | [~'cljs.pprint])))) 172 | (nrepl/code (ns cljs.user 173 | (:require [cljs.repl :refer-macros [source doc find-doc 174 | apropos dir pst]] 175 | [cljs.pprint])))) 176 | repl-env nil options) 177 | ;; (clojure.pprint/pprint (:options @*cljs-compiler-env*)) 178 | (set! *cljs-repl-env* repl-env) 179 | (set! *cljs-repl-options* opts) 180 | ;; interruptible-eval is in charge of emitting the final :ns response in this context 181 | (set! *original-clj-ns* *ns*) 182 | (set! *cljs-warnings* ana/*cljs-warnings*) 183 | (set! *cljs-warning-handlers* ana/*cljs-warning-handlers*) 184 | (set! *ns* (find-ns ana/*cljs-ns*)) 185 | (println "To quit, type:" :cljs/quit)) 186 | (catch Exception e 187 | (set! *cljs-repl-env* nil) 188 | (throw e)))) 189 | 190 | (defn- enqueue [{:keys [id session transport] :as msg} func] 191 | (let [{:keys [exec]} (meta session)] 192 | ;; Binding *msg* outside of :exec is the correct way to pass msg in nREPL 193 | ;; 1.3+ (actually, the correct way is to pass msg as the fourth argument, 194 | ;; but binding *msg* works too). Binding it inside of `f` is needed in nREPL 195 | ;; <1.3. We do both to be compatible with either. 196 | (binding [ieval/*msg* msg] 197 | (exec id 198 | #(binding [ieval/*msg* msg] 199 | (func)) 200 | #(transport/send transport (response-for msg :status :done)))))) 201 | 202 | (defn read-cljs-string [form-str] 203 | (when-not (string/blank? form-str) 204 | (binding [*ns* (create-ns ana/*cljs-ns*) 205 | reader/resolve-symbol ana/resolve-symbol 206 | reader/*data-readers* tags/*cljs-data-readers* 207 | reader/*alias-map* 208 | (apply merge 209 | ((juxt :requires :require-macros) 210 | (ana/get-namespace ana/*cljs-ns*)))] 211 | (reader/read {:read-cond :allow :features #{:cljs}} 212 | (readers/source-logging-push-back-reader 213 | (java.io.StringReader. form-str)))))) 214 | 215 | (defn- wrap-pprint 216 | "Wraps sexp with cljs.pprint/pprint in order for it to return a 217 | pretty-printed evaluation result as a string." 218 | [form] 219 | `(let [sb# (goog.string.StringBuffer.) 220 | sbw# (cljs.core/StringBufferWriter. sb#) 221 | form# ~form] 222 | (cljs.pprint/pprint form# sbw#) 223 | (cljs.core/str sb#))) 224 | 225 | (defn- pprint-repl-wrap-fn [form] 226 | (cond 227 | (and (seq? form) 228 | (#{'ns 'require 'require-macros 229 | 'use 'use-macros 'import 'refer-clojure} (first form))) 230 | identity 231 | 232 | ('#{*1 *2 *3 *e} form) (fn [x] 233 | (wrap-pprint x)) 234 | :else 235 | (fn [x] 236 | `(try 237 | ~(wrap-pprint 238 | `(let [ret# ~x] 239 | (set! *3 *2) 240 | (set! *2 *1) 241 | (set! *1 ret#) 242 | ret#)) 243 | (catch :default e# 244 | (set! *e e#) 245 | (throw e#)))))) 246 | 247 | (defn eval-cljs [repl-env env form file opts] 248 | (cljs.repl/evaluate-form repl-env 249 | env 250 | (or file "") 251 | form 252 | ((:wrap opts 253 | (if (contains? #{"nrepl.util.print/pr" "cider.nrepl.pprint/pr"} (::print opts)) 254 | #'cljs.repl/wrap-fn 255 | #'pprint-repl-wrap-fn)) form) 256 | opts)) 257 | 258 | (defn- output-bindings [{:keys [session] :as msg}] 259 | (when-let [replying-PrintWriter (resolve 'nrepl.middleware.print/replying-PrintWriter)] 260 | {#'*out* (replying-PrintWriter :out msg {}) 261 | #'*err* (replying-PrintWriter :err msg {})})) 262 | 263 | (def nrepl-1-3+? (some? (resolve 'ieval/evaluator))) 264 | 265 | (defn do-eval [{:keys [session transport ^String code file ns] :as msg}] 266 | (with-bindings (merge {#'ana/*cljs-warnings* ana/*cljs-warnings* 267 | #'ana/*cljs-warning-handlers* ana/*cljs-warning-handlers* 268 | #'ana/*unchecked-if* ana/*unchecked-if* 269 | #'env/*compiler* (get @session #'*cljs-compiler-env*) 270 | #'cljs.repl/*repl-env* (get @session #'*cljs-repl-env*)} 271 | ;; ieval/evaluator appeared in nREPL 1.3 where session 272 | ;; contents are already bound by session middleware and 273 | ;; should NOT be rebound here. 274 | (when-not nrepl-1-3+? 275 | @session) 276 | (when ns 277 | {#'ana/*cljs-ns* (symbol ns)}) 278 | (output-bindings msg)) 279 | (let [repl-env *cljs-repl-env* 280 | repl-options *cljs-repl-options* 281 | init-ns ana/*cljs-ns* 282 | special-fns (merge cljs.repl/default-special-fns (:special-fns repl-options)) 283 | is-special-fn? (set (keys special-fns))] 284 | (try 285 | (let [form (read-cljs-string code) 286 | env (assoc (ana/empty-env) :ns (ana/get-namespace init-ns)) 287 | result (when form 288 | (if (and (seq? form) (is-special-fn? (first form))) 289 | (do ((get special-fns (first form)) repl-env env form repl-options) 290 | nil) 291 | (eval-cljs repl-env 292 | env 293 | form 294 | file 295 | (assoc repl-options 296 | ::print 297 | (:nrepl.middleware.print/print msg)))))] 298 | (.flush ^Writer *out*) 299 | (.flush ^Writer *err*) 300 | (when (and (or (not ns) 301 | (not= init-ns ana/*cljs-ns*)) 302 | ana/*cljs-ns*) 303 | (swap! session assoc #'ana/*cljs-ns* ana/*cljs-ns*)) 304 | (transport/send 305 | transport 306 | (response-for msg 307 | (try 308 | {:value (when (some? result) 309 | (edn-reader/read-string 310 | {:default ->UnknownTaggedLiteral} 311 | result)) 312 | :nrepl.middleware.print/keys #{:value} 313 | :ns ana/*cljs-ns*} 314 | (catch Exception _ 315 | {:value (or result "nil") 316 | :ns ana/*cljs-ns*}))))) 317 | (catch Throwable t 318 | (repl-caught session transport msg t repl-env repl-options)))))) 319 | 320 | ;; only executed within the context of an nREPL session having *cljs-repl-env* 321 | ;; bound. Thus, we're not going through interruptible-eval, and the user's 322 | ;; Clojure session (dynamic environment) is not in place, so we need to go 323 | ;; through the `session` atom to access/update its vars. Same goes for load-file. 324 | (defn- evaluate [{:keys [session transport ^String code] :as msg}] 325 | (if-not (-> code string/trim (string/ends-with? ":cljs/quit")) 326 | (do-eval msg) 327 | 328 | (let [actual-repl-env (get-repl-env (@session #'*cljs-repl-env*)) 329 | orig-ns (@session #'*original-clj-ns*)] 330 | (cljs.repl/-tear-down actual-repl-env) 331 | (swap! session assoc 332 | #'*ns* orig-ns 333 | #'*cljs-repl-env* nil 334 | #'*cljs-compiler-env* nil 335 | #'*cljs-repl-options* nil 336 | #'ana/*cljs-ns* 'cljs.user) 337 | (when (thread-bound? #'*ns*) 338 | (set! *ns* orig-ns)) 339 | (transport/send transport (response-for msg 340 | :value "nil" 341 | :ns (str orig-ns)))))) 342 | 343 | ;; struggled for too long trying to interface directly with cljs.repl/load-file, 344 | ;; so just mocking a "regular" load-file call 345 | ;; this seems to work perfectly, *but* it only loads the content of the file from 346 | ;; disk, not the content of the file sent in the message (in contrast to nREPL on 347 | ;; Clojure). This is necessitated by the expectation of cljs.repl/load-file that 348 | ;; the file being loaded is on disk, in the location implied by the namespace 349 | ;; declaration. 350 | ;; TODO: Either pull in our own `load-file` that doesn't imply this, or raise the issue upstream. 351 | (defn- load-file [{:keys [session transport file-path] :as msg}] 352 | (evaluate (assoc msg :code (format "(load-file %s)" (pr-str file-path))))) 353 | 354 | (defn wrap-cljs-repl [handler] 355 | (fn [{:keys [session op] :as msg}] 356 | (let [handler (or (when-let [f (and (@session #'*cljs-repl-env*) 357 | ({"eval" #'evaluate "load-file" #'load-file} op))] 358 | (fn [msg] 359 | (enqueue msg #(f msg)))) 360 | handler)] 361 | ;; ensure that bindings exist so cljs-repl can set! 362 | (when-not (@session #'*cljs-repl-env*) 363 | (swap! session (partial merge {#'*cljs-repl-env* *cljs-repl-env* 364 | #'*cljs-compiler-env* *cljs-compiler-env* 365 | #'*cljs-repl-options* *cljs-repl-options* 366 | #'*cljs-warnings* *cljs-warnings* 367 | #'*cljs-warning-handlers* *cljs-warning-handlers* 368 | #'*original-clj-ns* *ns* 369 | #'ana/*cljs-ns* ana/*cljs-ns*}))) 370 | (handler msg)))) 371 | --------------------------------------------------------------------------------