From c1bff794d0e2b96ac37d312fcd13c9af8c15d7bd Mon Sep 17 00:00:00 2001 From: Nathaniel Nicandro Date: Sun, 22 Nov 2020 17:19:23 -0600 Subject: [PATCH] Add `jupyter-reply-message` --- jupyter-client.el | 112 +++++++++++++++++++++---------------------- jupyter-julia.el | 17 ++++--- jupyter-monads.el | 15 +++--- jupyter-repl.el | 43 ++++++++--------- test/jupyter-test.el | 65 +++++++++++-------------- 5 files changed, 121 insertions(+), 131 deletions(-) diff --git a/jupyter-client.el b/jupyter-client.el index 3283c08..5909cf6 100644 --- a/jupyter-client.el +++ b/jupyter-client.el @@ -1182,52 +1182,49 @@ DETAIL is the detail level to use for the request and defaults to (object-of-class-p jupyter-current-client 'jupyter-kernel-client)) (error "Need a valid `jupyter-current-client'")) (condition-case nil - (jupyter-mlet* ((msgs - (jupyter-messages - (jupyter-inspect-request - :code code - :pos pos - :detail detail) - jupyter-long-timeout))) - (when-let* ((msg (jupyter-find-message "inspect_reply" msgs))) - (jupyter-with-message-content msg - (status found) - (if (and (equal status "ok") (eq found t)) - (let ((inhibit-read-only t)) - (if (buffer-live-p buffer) - (with-current-buffer buffer - ;; Insert MSG here so that `jupyter-insert' - ;; has access to the message type. This is - ;; needed since the python kernel and others - ;; may use this information. - (jupyter-insert msg) - (current-buffer)) - (with-help-window (help-buffer) - (with-current-buffer standard-output - (setq other-window-scroll-buffer (current-buffer)) - (setq jupyter-current-client client) - (help-setup-xref - (list - ;; Don't capture a strong reference to the - ;; client object since we don't know when - ;; this reference will be cleaned up. - (let ((ref (jupyter-weak-ref client))) - (lambda () - (let ((jupyter-current-client - (jupyter-weak-ref-resolve ref))) - (if jupyter-current-client - (jupyter-inspect code pos nil detail) - ;; TODO: Skip over this xref, need - ;; to figure out if going forward or - ;; backward first. - (error "Client has been removed")))))) - nil) - (jupyter-insert msg))))) - (message "Nothing found for %s" - (with-temp-buffer - (insert code) - (goto-char pos) - (symbol-at-point))))))) + (jupyter-mlet* ((msg (jupyter-reply-message + (jupyter-inspect-request + :code code + :pos pos + :detail detail)))) + (jupyter-with-message-content msg + (status found) + (if (and (equal status "ok") (eq found t)) + (let ((inhibit-read-only t)) + (if (buffer-live-p buffer) + (with-current-buffer buffer + ;; Insert MSG here so that `jupyter-insert' + ;; has access to the message type. This is + ;; needed since the python kernel and others + ;; may use this information. + (jupyter-insert msg) + (current-buffer)) + (with-help-window (help-buffer) + (with-current-buffer standard-output + (setq other-window-scroll-buffer (current-buffer)) + (setq jupyter-current-client client) + (help-setup-xref + (list + ;; Don't capture a strong reference to the + ;; client object since we don't know when + ;; this reference will be cleaned up. + (let ((ref (jupyter-weak-ref client))) + (lambda () + (let ((jupyter-current-client + (jupyter-weak-ref-resolve ref))) + (if jupyter-current-client + (jupyter-inspect code pos nil detail) + ;; TODO: Skip over this xref, need + ;; to figure out if going forward or + ;; backward first. + (error "Client has been removed")))))) + nil) + (jupyter-insert msg))))) + (message "Nothing found for %s" + (with-temp-buffer + (insert code) + (goto-char pos) + (symbol-at-point)))))) (jupyter-timeout-before-idle (message "Inspect timed out")))) @@ -1686,19 +1683,18 @@ name are changed to \"-\" and all uppercase characters lowered." (progn (message "Requesting kernel info...") (let ((jupyter-current-client client)) - (jupyter-mlet* ((msgs (jupyter-messages - (jupyter-kernel-info-request - :handlers nil) - (* 3 jupyter-long-timeout)))) + (jupyter-mlet* ((msg (jupyter-reply-message + (jupyter-kernel-info-request + :handlers nil) + (* 3 jupyter-long-timeout)))) (message "Requesting kernel info...done") - (let ((msg (jupyter-find-message "kernel_info_reply" msgs))) - (oset client kernel-info (jupyter-message-content msg)) - ;; Canonicalize language name to a language symbol for - ;; method dispatching - (let* ((info (plist-get (oref client kernel-info) :language_info)) - (lang (plist-get info :name)) - (name (jupyter-canonicalize-language-string lang))) - (plist-put info :name (intern name)))))) + (oset client kernel-info (jupyter-message-content msg)) + ;; Canonicalize language name to a language symbol for + ;; method dispatching + (let* ((info (plist-get (oref client kernel-info) :language_info)) + (lang (plist-get info :name)) + (name (jupyter-canonicalize-language-string lang))) + (plist-put info :name (intern name))))) (oref client kernel-info)))) (defun jupyter-kernel-language-mode-properties (client) diff --git a/jupyter-julia.el b/jupyter-julia.el index e1f8d08..ffd2e2b 100644 --- a/jupyter-julia.el +++ b/jupyter-julia.el @@ -122,19 +122,18 @@ Make the character after `point' invisible." If the Pkg prompt can't be retrieved from the kernel, return nil." (let ((prompt-code "import Pkg; Pkg.REPLMode.promptf()")) - (jupyter-mlet* ((msgs - (jupyter-messages + (jupyter-mlet* ((msg + (jupyter-reply-message (jupyter-execute-request :code "" :silent t :user-expressions (list :prompt prompt-code))))) - (when-let* ((msg (jupyter-find-message "execute_reply" msgs))) - (cl-destructuring-bind (&key prompt &allow-other-keys) - (jupyter-message-get msg :user_expressions) - (cl-destructuring-bind (&key status data &allow-other-keys) - prompt - (when (equal status "ok") - (plist-get data :text/plain)))))))) + (cl-destructuring-bind (&key prompt &allow-other-keys) + (jupyter-message-get msg :user_expressions) + (cl-destructuring-bind (&key status data &allow-other-keys) + prompt + (when (equal status "ok") + (plist-get data :text/plain))))))) (cl-defmethod jupyter-repl-after-change ((_type (eql insert)) beg _end &context (jupyter-lang julia)) diff --git a/jupyter-monads.el b/jupyter-monads.el index 9cee9b8..c7211a4 100644 --- a/jupyter-monads.el +++ b/jupyter-monads.el @@ -415,12 +415,15 @@ when it is idle." (string= type msg-type))) msgs)) -(defun jupyter-reply-message (msgs) - (cl-find-if - (lambda (msg) - (let ((type (jupyter-message-type msg))) - (string-suffix-p "_reply" type))) - msgs)) +(defun jupyter-reply-message (io-req &optional timeout) + (make-jupyter-delayed + :value (lambda () + (jupyter-mlet* ((msgs (jupyter-messages io-req timeout))) + (cl-find-if + (lambda (msg) + (let ((type (jupyter-message-type msg))) + (string-suffix-p "_reply" type))) + msgs))))) (defun jupyter-message-subscribed (io-req cbs) (make-jupyter-delayed diff --git a/jupyter-repl.el b/jupyter-repl.el index 5efaca7..652fe8b 100644 --- a/jupyter-repl.el +++ b/jupyter-repl.el @@ -1792,28 +1792,27 @@ it." "Synchronize the `jupyter-current-client's kernel state. Also update the cell count of the current REPL input prompt using the updated state." - (jupyter-mlet* ((msgs (jupyter-messages - (jupyter-execute-request - :code "" - :silent t - :handlers nil) - ;; Waiting longer here to account for initial - ;; startup of the Jupyter kernel. Sometimes - ;; the idle message won't be received if - ;; another long running execute request is - ;; sent right after. - jupyter-long-timeout))) - (when-let* ((msg (jupyter-find-message "execute_reply" msgs))) - (jupyter-with-message-content msg (execution_count) - (let ((client jupyter-current-client)) - (oset client execution-count (1+ execution_count)) - (unless (equal (jupyter-execution-state client) "busy") - ;; Set the cell count and update the prompt - (jupyter-with-repl-buffer client - (save-excursion - (goto-char (point-max)) - (jupyter-repl-update-cell-count - (oref client execution-count)))))))))) + (jupyter-mlet* ((msg (jupyter-reply-message + (jupyter-execute-request + :code "" + :silent t + :handlers nil) + ;; Waiting longer here to account for initial + ;; startup of the Jupyter kernel. Sometimes + ;; the idle message won't be received if + ;; another long running execute request is + ;; sent right after. + jupyter-long-timeout))) + (jupyter-with-message-content msg (execution_count) + (let ((client jupyter-current-client)) + (oset client execution-count (1+ execution_count)) + (unless (equal (jupyter-execution-state client) "busy") + ;; Set the cell count and update the prompt + (jupyter-with-repl-buffer client + (save-excursion + (goto-char (point-max)) + (jupyter-repl-update-cell-count + (oref client execution-count))))))))) ;;; `jupyter-repl-interaction-mode' diff --git a/test/jupyter-test.el b/test/jupyter-test.el index b3e0e48..f5bf871 100644 --- a/test/jupyter-test.el +++ b/test/jupyter-test.el @@ -382,26 +382,23 @@ :tags '(client messages) (jupyter-test-with-python-client client (ert-info ("Kernel info") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((res (jupyter-reply-message (jupyter-kernel-info-request)))) - (let ((res (jupyter-reply-message msgs))) - (should res) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "kernel_info_reply"))))) + (should res) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "kernel_info_reply")))) (ert-info ("Comm info") - (jupyter-mlet* ((msgs (jupyter-messages - (jupyter-comm-info-request)))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "comm_info_reply"))))) + (jupyter-mlet* ((res (jupyter-reply-message + (jupyter-comm-info-request)))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "comm_info_reply")))) (ert-info ("Execute") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((res (jupyter-reply-message (jupyter-execute-request :code "y = 1 + 2")))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "execute_reply"))))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "execute_reply")))) (ert-info ("Input") (cl-letf (((symbol-function 'read-from-minibuffer) (lambda (_prompt &rest _args) "foo"))) @@ -414,40 +411,36 @@ (should (string= (jupyter-message-type res) "execute_result")) (should (equal (jupyter-message-data res :text/plain) "'foo'")))))) (ert-info ("Inspect") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((res (jupyter-reply-message (jupyter-inspect-request :code "list((1, 2, 3))" :pos 2 :detail 0)))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "inspect_reply"))))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "inspect_reply")))) (ert-info ("Complete") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((res (jupyter-reply-message (jupyter-complete-request :code "foo = lis" :pos 8)))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "complete_reply"))))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "complete_reply")))) (ert-info ("History") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((msgs (jupyter-reply-message (jupyter-history-request :hist-access-type "tail" :n 2)))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "history_reply"))))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "history_reply")))) (ert-info ("Is Complete") - (jupyter-mlet* ((msgs (jupyter-messages + (jupyter-mlet* ((msgs (jupyter-reply-message (jupyter-is-complete-request :code "for i in range(5):")))) - (let ((res (jupyter-reply-message msgs))) - (should-not (null res)) - (should (json-plist-p res)) - (should (string= (jupyter-message-type res) "is_complete_reply"))))) + (should-not (null res)) + (should (json-plist-p res)) + (should (string= (jupyter-message-type res) "is_complete_reply")))) ;; TODO: Why does this fail now? ;; (ert-info ("Shutdown") ;; (let ((res (jupyter-wait-until-received "shutdown_reply"