volrath / spiral

Emacs Clojure IDE based on UNREPL
GNU General Public License v3.0
148 stars 6 forks source link

Equivalent of nrepl-sync-request:eval? #9

Closed abo-abo closed 6 years ago

abo-abo commented 6 years ago

Hi, I just happened upon your project, played around with it a bit; I really like the shorter startup time and the expandable eval results.

I'd like to add some unrepl support to lispy.el to play around with it a bit more. I need just one function for sync eval to get started; it looks like this for CIDER:

(defun nrepl-sync-request:eval (input connection &optional ns)
  "Send the INPUT to the nREPL server synchronously.
The request is dispatched via CONNECTION.
If NS is non-nil, include it in the request."
  (nrepl-send-sync-request
   (nrepl--eval-request input ns)
   connection))
volrath commented 6 years ago

Hi @abo-abo, the equivalent function is called spiral-aux-sync-request. The signature is different from nrepl-sync-request:eval, it only needs the input. The connection is taken from a buffer local variable called spiral-conn-id, which should be a symbol of the form 'localhost:5555, which should be automatically set if the buffer is already connected/has spiral-mode on.

Feel free to close this issue if this answers your question and covers what you would need. It'd be cool to know what are you planning to implement :)

abo-abo commented 6 years ago

Thanks for the pointer. Here's what I've got so far.

Getting the value of the evaluated expr works:

(let* ((r (spiral-aux-sync-request "(range 100)")))
  (with-temp-buffer
    (spiral-ast-unparse r)
    (substring-no-properties
     (buffer-string))))
;; => "(0 1 2 3 4 5 6 7 8 9  ...)"

However, the print output is nowhere to be found:

(let* ((r (spiral-aux-sync-request "(do (print \"test\") (+ 1 2))")))
  (with-temp-buffer
    (spiral-ast-unparse r)
    (substring-no-properties
     (buffer-string))))
;; => "3"

Looking at how it's handled in the REPL eval, the print output comes in chunks, which makes getting it all in a sync request harder.

With nREPL, I getting the output was as easy as (nrepl-dict-get res "out"). Is it possible with unREPL?

volrath commented 6 years ago

Right. Sorry I forgot to mention that results came as parseclj AST data structures and you'd have to unparse them, but you figure it out on your own :)

spiral-aux-sync-request only returns the evaluation result. In reality, this function is almost never used in the SPIRAL code, since we try to always adapt to the nature of a stream based REPL (different from nREPL RPC messaging system). The only place where spiral-aux-sync-request is being used is in the completion system, where Emacs needs a sync'd response to completion candidates.

That being said, implementing your own function that returns both eval result and stdout, should be a relatively simple variation of spiral-aux-sync-request:

(defun spiral-aux-sync-request-2 (str)
  (let* ((start (current-time))
         (conn-id spiral-conn-id)
         (unparse-no-properties (lambda (node) (substring-no-properties
                                           (spiral-ast-unparse-to-string node))))
         stdout
         result)
    (spiral-loop--send conn-id :aux str)
    (spiral-pending-eval-add :aux conn-id
                             :status :sent
                             :eval-callback (lambda (eval-payload)
                                              (setq result (funcall unparse-no-properties eval-payload)))
                             :stdout-callback (lambda (stdout-payload &rest _)
                                                (setq stdout
                                                      (concat stdout
                                                              (funcall unparse-no-properties stdout-payload)))))
    (while (and (not result)
                (not (input-pending-p))  ;; do not hang UI
                (or (not spiral-aux-sync-request-timeout)
                    (< (cadr (time-subtract (current-time) start))
                       spiral-aux-sync-request-timeout)))
      (accept-process-output nil 0.01))
    (list :result result
          :stdout stdout)))

(spiral-aux-sync-request-2 "(do (println \"hi\") (println \"another thing\") (+ 1 1))")

This of course will only record stdout strings that happens "synchronously" before the actual result, so for example, prints like: (future (Thread/sleep 5000) (println "delayed 5 secs")) wouldn't be recorded.

Hope this helps.

abo-abo commented 6 years ago

Hope this helps.

It does, thanks a lot.

On another topic, is there a good Clojure workflow for dynamically installing and requiring packages into a running REPL without having to restart it? Basically, like load-path and package.el for Emacs, or sys.path and pip for Python?

I've had a brief look at pomegranate for this purpose, but it's a bit cumbersome; and version 1.0.0 looks to be broken.

volrath commented 6 years ago

It's one of the top things in my TODO list.

I was thinking on using pomegranate for this, but I haven't looked too deep into it yet. clj-refactor provides this feature but it's tightly coupled to nREPL, so it works with cider but not with UNREPL. If I remember corrercly, clj-refactor uses alembic internally, which could also be an option.

bbatsov commented 6 years ago

Unfortunately it's abandonware and clj-refactor is using some forked version of it. Those dynamic loads are always painful with Java/Clojure and that's one of the reasons I didn't want this feature in CIDER initially.

abo-abo commented 6 years ago

FYI, just made some prograss on the issue. I'm kind of new to Clojure for a while now, since I don't use it at work. Takes me a while to figure out the best practices with things like class paths and package management.