-
Notifications
You must be signed in to change notification settings - Fork 48
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
Use nrepl pretty printing support #108
Conversation
I see this breaks most of the tests, so this may not be a viable approach at all. Could someone give a bit of feedback please? |
I was under the impression the new middleware in nREPL was supposed to handle the responses from piggieback automatically. @cichli Can you enlighten us? :-) |
A bit of background information: I started encountering this issue with the figwheel-main repl. I've seen that I've also seen the comment about I hope this helps! |
I've noticed another issue: user defined tagged literals don't work with this change. Since the mechanism for defining them is different for Clojure and ClojureScript, I imagine that this would only work if both the Clojure and the ClojureScript environments had compatible reader and printer functions defined. I'm not sure how this was handled previously either. |
You're right, the edn reader is not appropriate for use in this context. edn is a strict subset of clojure, so can't parse a lot of things that will potentially be in that value (like functions) |
I decided to check what clojurescript's browser env returns from evaluations, it looks like it does this: https://github.com/clojure/clojurescript/blob/r1.10.520-1-g230e46ae/src/main/clojure/cljs/repl/browser.clj#L297-L313 note the The delegating repl env is, well, delegating. So that should be fine! I had a poke, and found this in repl: https://github.com/clojure/clojurescript/blob/b38ded99dc0967a48824d55ea644bee86b4eae5b/src/main/clojure/cljs/repl.cljc#L500-L502
So we could attempt a read and then set it to the string otherwise. |
@SevereOverfl0w: thank you for your feedback, I've implemented falling back on strings when the reader fails. Sadly this happens whenever there is a value anywhere in the result which the reader can't handle. At least it's much better than not having pretty printing at all or getting errors for unsupported values. Could we maybe leverage |
I don't think you should catch that specific exception, and rethrow the rest. In older versions of clojure, the exception did not look like that. I think it's fine just to fallback whenever there's an exception and never rethrow. reworking things to have a clojurescript version of the pprint is beyond my knowledge for how the pprint was intended to work. |
This is just an idea, but what if we always returned a string from the ClojureScript repl? We would be guaranteed to be able to read it in Clojure and we could return the pretty-printed results through it when pretty printing is enabled. For example: (defn eval-cljs [repl-env env form opts]
(read-cljs-string
(cljs.repl/evaluate-form repl-env
env
"<cljs repl>"
`(cljs.core/with-out-str
(cljs.pprint/pprint ~form))
((:wrap opts #'cljs.repl/wrap-fn) form)
opts))) With this my CIDER repl is handling every value I've thrown at it: c.user> (repeat 10 {:a (fn [])})
({:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}
{:a #object[Function]}) I'm not sure how much of the nREPL printing configuration One thing to potentially worry about is the wrapping code affecting stack traces. I can already see a lot of tooling-related namespaces there, so I guess it doesn't make it that much worse than it already is. Also I'm not sure if we need to do `(let [form# ~form]
(cljs.core/with-out-str (cljs.pprint/pprint form#))) instead to avoid capturing output while evaluating the form via |
Forgot to mention that the above also breaks repl history (*1, *e, etc.) since it saves the printed strings instead of the original values. I'm guessing this could be fixed by first allowing the |
I suspect a println in the code you send will create broken output |
@SevereOverfl0w: yes, that's why I suggested `(let [form# ~form]
(cljs.core/with-out-str (cljs.pprint/pprint form#))) This evaluates the form before wrapping the results in `(let [sb# (goog.string.StringBuffer.)
sbw# (cljs.core/StringBufferWriter. sb#)
form# ~form]
(cljs.pprint/pprint form# sbw#)
(cljs.core/str sb#)) |
We're getting closer to a solution. I'll try to find some time for this soon, as I'd really love for us to fix it. |
@bbatsov: please let me know if I can help with anything. |
Will do. I've been extremely busy the past couple of weeks, but hopefully my schedule will be back to normal after the middle of next week. |
14e1144
to
8ac72e6
Compare
Hi, I've made another attempt at fixing this and rebased onto the I'll look into why the tests are failing, but could you please take a c.user> (for [x (range 10)]
(do (println "Testing")
(list x x (fn [x] x))))
Testing
Testing
Testing
Testing
Testing
Testing
Testing
Testing
Testing
Testing
((0 0 #object[Function])
(1 1 #object[Function])
(2 2 #object[Function])
(3 3 #object[Function])
(4 4 #object[Function])
(5 5 #object[Function])
(6 6 #object[Function])
(7 7 #object[Function])
(8 8 #object[Function])
(9 9 #object[Function]))
c.user> *1
((0 0 #object[Function])
(1 1 #object[Function])
(2 2 #object[Function])
(3 3 #object[Function])
(4 4 #object[Function])
(5 5 #object[Function])
(6 6 #object[Function])
(7 7 #object[Function])
(8 8 #object[Function])
(9 9 #object[Function]))
c.user> (throw (js/Error. "Test"))
#object[Error Error: Test]
c.user> *e
#object[Error Error: Test]
c.user> nil
nil
c.user> false
nil |
I think false->nil is probably indicative of something going on. Also, why is there special handling of ns, import, etc? Is that because they must be top level forms? |
@SevereOverfl0w: thank you for your feedback.
I've tested it with the upstream version of piggieback (0.4.1) and c.user> false
nil
I'm not sure myself: I've based this directly on |
8ac72e6
to
0e4f942
Compare
The last force-push is for rebasing on top of the latest master version. |
`(let [sb# (goog.string.StringBuffer.) | ||
sbw# (cljs.core/StringBufferWriter. sb#) | ||
form# ~form] | ||
(cljs.pprint/pprint form# sbw#) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with this approach is that we're effectively hardcoding pretty-printing, and nREPL goes to great lengths to ensure that printing is flexible and you can control what exactly do you want to print and how. I think it would be better if we made piggieback respect the nREPL infrastructure instead of forcing one particular way of pretty printing it in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the downside of reading on the clojure side and allowing nrepl to print it?
Hey there, @ak-coram! Sorry for the radio silence - I was super busy most of September and I finally got to looking into this. In general any solution that we adopt should respect how nREPL when it comes to pretty printing, otherwise that'd be super weird. It seems to me that something's wrong with the middleware ordering for piggieback if the print-keys don't get properly set for the evaluations performed by it. |
I'm also wondering if piggieback shouldn't leverage the print middleware infrastructure directly the same way Unfortunately I'm not quite sure how @cichli planned for this to work with ClojureScript and he hasn't been around lately so we can't really ask him. |
Hi @bbatsov! Thank you for the feedback. I agree that pretty-printing should be configurable, but I also think As far as I can tell the current nREPL pretty-printing infrastructure There's already some control you have over the current version, EDIT: I just realized that reading built-in data structures doesn't |
Well, that certainly comes as a surprise to me, but then again - I don't do much about ClojureScript. I always assumed that reading values was something cross-platform, but I guess it's not. Can you give me some concrete examples so I have a better idea about the nature of the problem?
Yeah, the the printing logic is running completely on the Clojure side, which seemed reasonable given the expectation for compatibility that I stated earlier.
Yeah, I get this, but I really hate having to provide different interfaces for Clojure and ClojureScript everywhere. You've got no idea how much I hate ClojureScript already because almost everything is slightly different there and half the code has to be duplicated because of this. |
It's not just ClojureScript, some Clojure values can be pretty-printed
user> (fn [x] x)
#function[user/eval23287/fn--23288]
user> (read-string "#function[user/eval23287/fn--23288]")
Execution error at user/eval23291 (REPL:27).
No reader function for tag function
user> (java.time.LocalDate/now)
#object[java.time.LocalDate 0x2647cbb8 "2019-10-05"]
user> (read-string "#object[java.time.LocalDate 0x2647cbb8 \"2019-10-05\"]")
Execution error at user/eval23295 (REPL:35).
No reader function for tag object
user> (deftype MyType [])
user.MyType
user> (->MyType)
#object[user.MyType 0x3e15eb62 "user.MyType@3e15eb62"]
user> (read-string "#object[user.MyType 0x3e15eb62 \"user.MyType@3e15eb62\"]")
Execution error at user/eval23324 (REPL:48).
No reader function for tag object For ClojureScript there's also the aforementioned
c.user> (deftype Cheese [])
cljs.user/Cheese
c.user> (Cheese.)
#object[cljs.user.Cheese]
user> (read-string "#object[cljs.user.Cheese]")
Execution error at user/eval23285 (REPL:22).
No reader function for tag object
c.user> (fn [x] x)
#object[Function]
user> (read-string "#object[Function]")
Execution error at user/eval23346 (REPL:62).
No reader function for tag object And of course it's up to the users to define any number of custom
I don't think so: the interface looks different from the Clojure REPL, Essentially this is similar to what my workaround in this PR does: it Or writing a pretty-printer which works on printed CLJS values |
We also can't be sure that the types implementing I understand your frustration with having to support CLJS in this way, guess one day we could have an independent nREPL implementation in it and not "piggieback" on the current nREPL. I'm guessing that would alleviate at least some of these issues. |
Just create a PR for adding nREPL 0.7 and Java 13, and the tests look healthy, so it's some changes from the PR that's causing the issues. I'll have a poke later to see if I can spot anything. |
(edn-reader/read-string | ||
{:default ->UnknownTaggedLiteral} | ||
result)) | ||
:nrepl.middleware.print/keys #{:value} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to update piggieback to :require
print/wrap-print`
A good theory of what's broken:
shen-tian@3750bf4 <- this commit made all the tests go green in my fork. |
Thank you @bbatsov and @shen-tian for your help! It really seems we're
Yes, I agree this PR is a bit lacking in terms of documentation. Would
Thanks, I've merged it. The implicit dependency on |
Codecov Report
@@ Coverage Diff @@
## master #108 +/- ##
=======================================
Coverage 83.33% 83.33%
=======================================
Files 1 1
Lines 12 12
Branches 1 1
=======================================
Hits 10 10
Misses 1 1
Partials 1 1
Continue to review full report at Codecov.
|
Let's add it to this PR. |
You'll also have to rebase on top of the latest |
@@ -32,8 +32,6 @@ | |||
:dependencies [[org.clojure/clojure "1.11.0-master-SNAPSHOT"] | |||
[org.clojure/clojurescript "1.10.439"]]} | |||
|
|||
:nrepl-0.4 {:dependencies [[nrepl/nrepl "0.4.5"]]} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll have to mention in the README/Changelog that Piggieback now requires nREPL 0.6+.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll have to mention in the README/Changelog that Piggieback now requires nREPL 0.6+.
Updated the README & CHANGELOG with new nREPL version requirement changes.
src/cider/piggieback_impl.clj
Outdated
@@ -203,12 +211,45 @@ | |||
(readers/source-logging-push-back-reader | |||
(java.io.StringReader. form-str)))))) | |||
|
|||
(defn- wrap-pprint [form] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add some explanation about the naming here, as wrap-something
is the standard convention for naming middleware functions in nREPL and this might confuse some people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add some explanation about the naming here, as
wrap-something
is the standard convention for naming middleware functions in nREPL and this might confuse some people.
Added a docstring to clarify that this wraps an s-expression with cljs.pprint/pprint
. We can also change the name to something else if you prefer.
Try to read the CLJS evaluation result in CLJ and rely on the nREPL printing middleware to print it if reading succeeds. The type UnknownTaggedLiteral is introduced to be able to read and print tagged literals which are specific to CLJS such as #queue (or any user-defined ones). The output may vary based on the printing function used (for example fipp and puget don't rely on the supplied print-method for UnknownTaggedLiteral). Reading can still fail in some cases, so a fallback is also included by trying to detect if pretty printing is enabled (nrepl.middleware.print/print not set to "cider.nrepl.pprint/pr") and if that is the case, wrapping the CLJS form with cljs.pprint/pprint (instead of the default cljs.core.pr-str). This way pretty printing can still be turned on or off, even when reading fails.
3750bf4
to
601ff3d
Compare
Rebased on top of the latest master. |
Also remove mention of version compatibility with nREPL 0.4
We only need some coverage of the print functionality in the README and the changelog and we're good to go. |
Sounds great, I'll try to find time on the weekend to do a write-up for the README. |
f549fa0
to
e7cbfb4
Compare
@bbatsov: I've tried to summarize the parts of this discussion which are relevant to the current solution. Please let me know what you think. Thanks! |
e7cbfb4
to
a574d42
Compare
Looks good. I'll tweak the docs a bit before cutting a release, but overall you've done great! I'm happy that we finally managed to wrap this up! |
Hi,
I'm trying to fix CIDER issue #2667 and believe it could be resolved by also relying on the pretty-printing support of the newer nREPL versions in piggieback. These changes work for me, so please let me know if there are issues with this approach. Thanks!