ein-kernel.el 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. ;;; ein-kernel.el --- Communicate with IPython notebook server
  2. ;; Copyright (C) 2012- Takafumi Arakaki
  3. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; This file is NOT part of GNU Emacs.
  5. ;; ein-kernel.el is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; ein-kernel.el is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with ein-kernel.el. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;;; Code:
  18. (eval-when-compile (require 'cl))
  19. (require 'ansi-color)
  20. (require 'ein-core)
  21. (require 'ein-log)
  22. ;; FIXME: use websocket.el directly once v1.0 is released.
  23. (require 'ein-websocket)
  24. (require 'ein-events)
  25. (require 'ein-query)
  26. ;; FIXME: Rewrite `ein:$kernel' using `defclass'. It should ease
  27. ;; testing since I can mock I/O using method overriding.
  28. (defstruct ein:$kernel
  29. "Hold kernel variables.
  30. `ein:$kernel-url-or-port'
  31. URL or port of IPython server.
  32. "
  33. url-or-port
  34. events
  35. kernel-id
  36. shell-channel
  37. iopub-channel
  38. base-url ; /kernels/
  39. kernel-url ; /kernels/<KERNEL-ID>
  40. ws-url ; ws://<URL>[:<PORT>]
  41. running
  42. username
  43. session-id
  44. msg-callbacks
  45. ;; FIXME: Use event instead of hook.
  46. after-start-hook
  47. after-execute-hook)
  48. ;; "Public" getters. Use them outside of this package.
  49. ;;;###autoload
  50. (defalias 'ein:kernel-url-or-port 'ein:$kernel-url-or-port)
  51. ;;;###autoload
  52. (defalias 'ein:kernel-id 'ein:$kernel-kernel-id)
  53. ;;; Initialization and connection.
  54. (defun ein:kernel-new (url-or-port base-url events)
  55. (make-ein:$kernel
  56. :url-or-port url-or-port
  57. :events events
  58. :kernel-id nil
  59. :shell-channel nil
  60. :iopub-channel nil
  61. :base-url base-url
  62. :running nil
  63. :username "username"
  64. :session-id (ein:utils-uuid)
  65. :msg-callbacks (make-hash-table :test 'equal)))
  66. (defun ein:kernel-del (kernel)
  67. "Destructor for `ein:$kernel'."
  68. (ein:kernel-stop-channels kernel))
  69. (defun ein:kernel--get-msg (kernel msg-type content)
  70. (list
  71. :header (list
  72. :msg_id (ein:utils-uuid)
  73. :username (ein:$kernel-username kernel)
  74. :session (ein:$kernel-session-id kernel)
  75. :msg_type msg-type)
  76. :metadata (make-hash-table)
  77. :content content
  78. :parent_header (make-hash-table)))
  79. (defun ein:kernel-start (kernel notebook-id)
  80. "Start kernel of the notebook whose id is NOTEBOOK-ID."
  81. (unless (ein:$kernel-running kernel)
  82. (ein:query-singleton-ajax
  83. (list 'kernel-start (ein:$kernel-kernel-id kernel))
  84. (concat (ein:url (ein:$kernel-url-or-port kernel)
  85. (ein:$kernel-base-url kernel))
  86. "?" (format "notebook=%s" notebook-id))
  87. :type "POST"
  88. :parser #'ein:json-read
  89. :success (apply-partially #'ein:kernel--kernel-started kernel))))
  90. (defun ein:kernel-restart (kernel)
  91. (ein:events-trigger (ein:$kernel-events kernel)
  92. 'status_restarting.Kernel)
  93. (ein:log 'info "Restarting kernel")
  94. (when (ein:$kernel-running kernel)
  95. (ein:kernel-stop-channels kernel)
  96. (ein:query-singleton-ajax
  97. (list 'kernel-restart (ein:$kernel-kernel-id kernel))
  98. (ein:url (ein:$kernel-url-or-port kernel)
  99. (ein:$kernel-kernel-url kernel)
  100. "restart")
  101. :type "POST"
  102. :parser #'ein:json-read
  103. :success (apply-partially #'ein:kernel--kernel-started kernel))))
  104. (defun* ein:kernel--kernel-started (kernel &key data &allow-other-keys)
  105. (destructuring-bind (&key kernel_id ws_url &allow-other-keys) data
  106. (unless (and kernel_id ws_url)
  107. (error "Failed to start kernel. No `kernel_id' or `ws_url'. Got %S."
  108. data))
  109. (ein:log 'info "Kernel started: %s" kernel_id)
  110. (setf (ein:$kernel-running kernel) t)
  111. (setf (ein:$kernel-kernel-id kernel) kernel_id)
  112. (setf (ein:$kernel-ws-url kernel) (ein:kernel--ws-url kernel ws_url))
  113. (setf (ein:$kernel-kernel-url kernel)
  114. (concat (ein:$kernel-base-url kernel) "/" kernel_id)))
  115. (ein:kernel-start-channels kernel)
  116. (let ((shell-channel (ein:$kernel-shell-channel kernel))
  117. (iopub-channel (ein:$kernel-iopub-channel kernel)))
  118. ;; FIXME: get rid of lexical-let
  119. (lexical-let ((kernel kernel))
  120. (setf (ein:$websocket-onmessage shell-channel)
  121. (lambda (packet)
  122. (ein:kernel--handle-shell-reply kernel packet)))
  123. (setf (ein:$websocket-onmessage iopub-channel)
  124. (lambda (packet)
  125. (ein:kernel--handle-iopub-reply kernel packet))))))
  126. (defun ein:kernel--ws-url (kernel ws_url)
  127. "Use `ein:$kernel-url-or-port' if WS_URL is an empty string.
  128. See: https://github.com/ipython/ipython/pull/3307"
  129. (if (string-match-p "^wss?://" ws_url)
  130. ws_url
  131. (let ((ein:url-localhost-template "ws://127.0.0.1:%s"))
  132. (ein:url (ein:$kernel-url-or-port kernel)))))
  133. (defun ein:kernel--websocket-closed (kernel ws-url early)
  134. (if early
  135. (ein:display-warning
  136. "Websocket connection to %s could not be established.
  137. You will NOT be able to run code. Your websocket.el may not be
  138. compatible with the websocket version in the server, or if the
  139. url does not look right, there could be an error in the
  140. server's configuration." ws-url)
  141. (ein:display-warning "Websocket connection closed unexpectedly.
  142. The kernel will no longer be responsive.")))
  143. (defun ein:kernel-send-cookie (channel host)
  144. ;; cookie can be an empty string for IPython server with no password,
  145. ;; but something must be sent to start channel.
  146. (let ((cookie (ein:query-get-cookie host "/")))
  147. (ein:websocket-send channel cookie)))
  148. (defun ein:kernel--ws-closed-callback (websocket kernel arg)
  149. ;; NOTE: The argument ARG should not be "unpacked" using `&rest'.
  150. ;; It must be given as a list to hold state `already-called-onclose'
  151. ;; so it can be modified in this function.
  152. (destructuring-bind (&key already-called-onclose ws-url early)
  153. arg
  154. (unless already-called-onclose
  155. (plist-put arg :already-called-onclose t)
  156. (unless (ein:$websocket-closed-by-client websocket)
  157. ;; Use "event-was-clean" when it is implemented in websocket.el.
  158. (ein:kernel--websocket-closed kernel ws-url early)))))
  159. (defun ein:kernel-start-channels (kernel)
  160. (ein:kernel-stop-channels kernel)
  161. (let* ((ws-url (concat (ein:$kernel-ws-url kernel)
  162. (ein:$kernel-kernel-url kernel)))
  163. (onclose-arg (list :ws-url ws-url
  164. :already-called-onclose nil
  165. :early t)))
  166. (ein:log 'info "Starting WS: %S" ws-url)
  167. (setf (ein:$kernel-shell-channel kernel)
  168. (ein:websocket (concat ws-url "/shell")))
  169. (setf (ein:$kernel-iopub-channel kernel)
  170. (ein:websocket (concat ws-url "/iopub")))
  171. (loop for c in (list (ein:$kernel-shell-channel kernel)
  172. (ein:$kernel-iopub-channel kernel))
  173. do (setf (ein:$websocket-onclose-args c) (list kernel onclose-arg))
  174. do (setf (ein:$websocket-onopen c)
  175. (lexical-let ((channel c)
  176. (kernel kernel)
  177. (host (let (url-or-port
  178. (ein:$kernel-url-or-port kernel))
  179. (if (stringp url-or-port)
  180. url-or-port
  181. ein:url-localhost))))
  182. (lambda ()
  183. (ein:kernel-send-cookie channel host)
  184. ;; run `ein:$kernel-after-start-hook' if both
  185. ;; channels are ready.
  186. (when (ein:kernel-live-p kernel)
  187. (ein:kernel-run-after-start-hook kernel)))))
  188. do (setf (ein:$websocket-onclose c)
  189. #'ein:kernel--ws-closed-callback))
  190. ;; switch from early-close to late-close message after 1s
  191. (run-at-time
  192. 1 nil
  193. (lambda (onclose-arg)
  194. (plist-put onclose-arg :early nil)
  195. (ein:log 'debug "(via run-at-time) onclose-arg changed to: %S"
  196. onclose-arg))
  197. onclose-arg)))
  198. ;; NOTE: `onclose-arg' can be accessed as:
  199. ;; (nth 1 (ein:$websocket-onclose-args (ein:$kernel-shell-channel (ein:$notebook-kernel ein:notebook))))
  200. (defun ein:kernel-run-after-start-hook (kernel)
  201. (ein:log 'debug "EIN:KERNEL-RUN-AFTER-START-HOOK")
  202. (mapc #'ein:funcall-packed
  203. (ein:$kernel-after-start-hook kernel)))
  204. (defun ein:kernel-stop-channels (kernel)
  205. (when (ein:$kernel-shell-channel kernel)
  206. (setf (ein:$websocket-onclose (ein:$kernel-shell-channel kernel)) nil)
  207. (ein:websocket-close (ein:$kernel-shell-channel kernel))
  208. (setf (ein:$kernel-shell-channel kernel) nil))
  209. (when (ein:$kernel-iopub-channel kernel)
  210. (setf (ein:$websocket-onclose (ein:$kernel-iopub-channel kernel)) nil)
  211. (ein:websocket-close (ein:$kernel-iopub-channel kernel))
  212. (setf (ein:$kernel-iopub-channel kernel) nil)))
  213. (defun ein:kernel-live-p (kernel)
  214. (and
  215. (ein:$kernel-p kernel)
  216. (ein:aand (ein:$kernel-shell-channel kernel) (ein:websocket-open-p it))
  217. (ein:aand (ein:$kernel-iopub-channel kernel) (ein:websocket-open-p it))))
  218. (defmacro ein:kernel-if-ready (kernel &rest body)
  219. "Execute BODY if KERNEL is ready. Warn user otherwise."
  220. (declare (indent 1))
  221. `(if (ein:kernel-live-p ,kernel)
  222. (progn ,@body)
  223. (ein:log 'warn "Kernel is not ready yet! (or closed already.)")))
  224. ;;; Main public methods
  225. ;; NOTE: The argument CALLBACKS for the following functions is almost
  226. ;; same as the JS implementation in IPython. However, as Emacs
  227. ;; lisp does not support closure, value is "packed" using
  228. ;; `cons': `car' is the actual callback function and `cdr' is
  229. ;; its first argument. It's like using `cons' instead of
  230. ;; `$.proxy'.
  231. (defun ein:kernel-object-info-request (kernel objname callbacks)
  232. "Send object info request of OBJNAME to KERNEL.
  233. When calling this method pass a CALLBACKS structure of the form:
  234. (:object_info_reply (FUNCTION . ARGUMENT))
  235. Call signature::
  236. (`funcall' FUNCTION ARGUMENT CONTENT METADATA)
  237. CONTENT and METADATA are given by `object_into_reply' message.
  238. `object_into_reply' message is documented here:
  239. http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
  240. "
  241. (assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
  242. (when objname
  243. (let* ((content (list :oname (format "%s" objname)))
  244. (msg (ein:kernel--get-msg kernel "object_info_request" content))
  245. (msg-id (plist-get (plist-get msg :header) :msg_id)))
  246. (ein:websocket-send
  247. (ein:$kernel-shell-channel kernel)
  248. (json-encode msg))
  249. (ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
  250. msg-id)))
  251. (defun* ein:kernel-execute (kernel code &optional callbacks
  252. &key
  253. (silent t)
  254. (user-variables [])
  255. (user-expressions (make-hash-table))
  256. (allow-stdin json-false))
  257. "Execute CODE on KERNEL.
  258. When calling this method pass a CALLBACKS structure of the form:
  259. (:execute_reply EXECUTE-REPLY-CALLBACK
  260. :output OUTPUT-CALLBACK
  261. :clear_output CLEAR-OUTPUT-CALLBACK
  262. :set_next_input SET-NEXT-INPUT)
  263. Objects end with -CALLBACK above must pack a FUNCTION and its
  264. first ARGUMENT in a `cons'::
  265. (FUNCTION . ARGUMENT)
  266. Call signature
  267. --------------
  268. ::
  269. (`funcall' EXECUTE-REPLY-CALLBACK ARGUMENT CONTENT METADATA)
  270. (`funcall' OUTPUT-CALLBACK ARGUMENT MSG-TYPE CONTENT METADATA)
  271. (`funcall' CLEAR-OUTPUT-CALLBACK ARGUMENT CONTENT METADATA)
  272. (`funcall' SET-NEXT-INPUT ARGUMENT TEXT)
  273. * Both CONTENT and METADATA objects are plist.
  274. * The MSG-TYPE argument for OUTPUT-CALLBACK is a string
  275. (one of `stream', `display_data', `pyout' and `pyerr').
  276. * The CONTENT object for CLEAR-OUTPUT-CALLBACK has
  277. `stdout', `stderr' and `other' fields that are booleans.
  278. * The SET-NEXT-INPUT callback will be passed the `set_next_input' payload,
  279. which is a string.
  280. See `ein:kernel--handle-shell-reply' for how the callbacks are called.
  281. Links
  282. -----
  283. * For general description of CONTENT and METADATA:
  284. http://ipython.org/ipython-doc/dev/development/messaging.html#general-message-format
  285. * `execute_reply' message is documented here:
  286. http://ipython.org/ipython-doc/dev/development/messaging.html#execute
  287. * Output type messages is documented here:
  288. http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
  289. Sample implementations
  290. ----------------------
  291. * `ein:cell--handle-execute-reply'
  292. * `ein:cell--handle-output'
  293. * `ein:cell--handle-clear-output'
  294. * `ein:cell--handle-set-next-input'
  295. "
  296. ;; FIXME: Consider changing callback to use `&key'.
  297. ;; Otherwise, adding new arguments to callback requires
  298. ;; backward incompatible changes (hence a big diff), unlike
  299. ;; Javascript. Downside of this is that there is no short way
  300. ;; to write anonymous callback because there is no `lambda*'.
  301. ;; You can use `function*', but that's bit long...
  302. ;; FIXME: Consider allowing a list of fixed argument so that the
  303. ;; call signature becomes something like:
  304. ;; (funcall FUNCTION [ARG ...] CONTENT METADATA)
  305. (assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
  306. (let* ((content (list
  307. :code code
  308. :silent (or silent json-false)
  309. :user_variables user-variables
  310. :user_expressions user-expressions
  311. :allow_stdin allow-stdin))
  312. (msg (ein:kernel--get-msg kernel "execute_request" content))
  313. (msg-id (plist-get (plist-get msg :header) :msg_id)))
  314. (ein:websocket-send
  315. (ein:$kernel-shell-channel kernel)
  316. (json-encode msg))
  317. (ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
  318. (unless silent
  319. (mapc #'ein:funcall-packed
  320. (ein:$kernel-after-execute-hook kernel)))
  321. msg-id))
  322. (defun ein:kernel-complete (kernel line cursor-pos callbacks)
  323. "Complete code at CURSOR-POS in a string LINE on KERNEL.
  324. CURSOR-POS is the position in the string LINE, not in the buffer.
  325. When calling this method pass a CALLBACKS structure of the form:
  326. (:complete_reply (FUNCTION . ARGUMENT))
  327. Call signature::
  328. (`funcall' FUNCTION ARGUMENT CONTENT METADATA)
  329. CONTENT and METADATA are given by `complete_reply' message.
  330. `complete_reply' message is documented here:
  331. http://ipython.org/ipython-doc/dev/development/messaging.html#complete
  332. "
  333. (assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
  334. (let* ((content (list
  335. :text ""
  336. :line line
  337. :cursor_pos cursor-pos))
  338. (msg (ein:kernel--get-msg kernel "complete_request" content))
  339. (msg-id (plist-get (plist-get msg :header) :msg_id)))
  340. (ein:websocket-send
  341. (ein:$kernel-shell-channel kernel)
  342. (json-encode msg))
  343. (ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
  344. msg-id))
  345. (defun* ein:kernel-history-request (kernel callbacks
  346. &key
  347. (output nil)
  348. (raw t)
  349. (hist-access-type "tail")
  350. session
  351. start
  352. stop
  353. (n 10)
  354. pattern
  355. unique)
  356. "Request execution history to KERNEL.
  357. When calling this method pass a CALLBACKS structure of the form:
  358. (:history_reply (FUNCTION . ARGUMENT))
  359. Call signature::
  360. (`funcall' FUNCTION ARGUMENT CONTENT METADATA)
  361. CONTENT and METADATA are given by `history_reply' message.
  362. `history_reply' message is documented here:
  363. http://ipython.org/ipython-doc/dev/development/messaging.html#history
  364. Relevant Python code:
  365. * :py:method:`IPython.zmq.ipkernel.Kernel.history_request`
  366. * :py:class:`IPython.core.history.HistoryAccessor`
  367. "
  368. (assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
  369. (let* ((content (list
  370. :output (ein:json-any-to-bool output)
  371. :raw (ein:json-any-to-bool raw)
  372. :hist_access_type hist-access-type
  373. :session session
  374. :start start
  375. :stop stop
  376. :n n
  377. :pattern pattern
  378. :unique unique))
  379. (msg (ein:kernel--get-msg kernel "history_request" content))
  380. (msg-id (plist-get (plist-get msg :header) :msg_id)))
  381. (ein:websocket-send
  382. (ein:$kernel-shell-channel kernel)
  383. (json-encode msg))
  384. (ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
  385. msg-id))
  386. (defun ein:kernel-kernel-info-request (kernel callbacks)
  387. "Request core information of KERNEL.
  388. When calling this method pass a CALLBACKS structure of the form::
  389. (:kernel_info_reply (FUNCTION . ARGUMENT))
  390. Call signature::
  391. (`funcall' FUNCTION ARGUMENT CONTENT METADATA)
  392. CONTENT and METADATA are given by `kernel_info_reply' message.
  393. `kernel_info_reply' message is documented here:
  394. http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info
  395. Example::
  396. (ein:kernel-kernel-info-request
  397. (ein:get-kernel)
  398. '(:kernel_info_reply (message . \"CONTENT: %S\\nMETADATA: %S\")))
  399. "
  400. (assert (ein:kernel-live-p kernel) nil "Kernel is not active.")
  401. (let* ((msg (ein:kernel--get-msg kernel "kernel_info_request" nil))
  402. (msg-id (plist-get (plist-get msg :header) :msg_id)))
  403. (ein:websocket-send
  404. (ein:$kernel-shell-channel kernel)
  405. (json-encode msg))
  406. (ein:kernel-set-callbacks-for-msg kernel msg-id callbacks)
  407. msg-id))
  408. (defun ein:kernel-interrupt (kernel)
  409. (when (ein:$kernel-running kernel)
  410. (ein:log 'info "Interrupting kernel")
  411. (ein:query-singleton-ajax
  412. (list 'kernel-interrupt (ein:$kernel-kernel-id kernel))
  413. (ein:url (ein:$kernel-url-or-port kernel)
  414. (ein:$kernel-kernel-url kernel)
  415. "interrupt")
  416. :type "POST"
  417. :success (lambda (&rest ignore)
  418. (ein:log 'info "Sent interruption command.")))))
  419. (defun ein:kernel-kill (kernel &optional callback cbargs)
  420. (when (ein:$kernel-running kernel)
  421. (ein:query-singleton-ajax
  422. (list 'kernel-kill (ein:$kernel-kernel-id kernel))
  423. (ein:url (ein:$kernel-url-or-port kernel)
  424. (ein:$kernel-kernel-url kernel))
  425. :type "DELETE"
  426. :success (apply-partially
  427. (lambda (kernel callback cbargs &rest ignore)
  428. (ein:log 'info "Notebook kernel is killed")
  429. (setf (ein:$kernel-running kernel) nil)
  430. (when callback (apply callback cbargs)))
  431. kernel callback cbargs))))
  432. ;; Reply handlers.
  433. (defun ein:kernel-get-callbacks-for-msg (kernel msg-id)
  434. (gethash msg-id (ein:$kernel-msg-callbacks kernel)))
  435. (defun ein:kernel-set-callbacks-for-msg (kernel msg-id callbacks)
  436. (puthash msg-id callbacks (ein:$kernel-msg-callbacks kernel)))
  437. (defun ein:kernel--handle-shell-reply (kernel packet)
  438. (ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY")
  439. (destructuring-bind
  440. (&key header content metadata parent_header &allow-other-keys)
  441. (ein:json-read-from-string packet)
  442. (let* ((msg-type (plist-get header :msg_type))
  443. (msg-id (plist-get parent_header :msg_id))
  444. (callbacks (ein:kernel-get-callbacks-for-msg kernel msg-id))
  445. (cb (plist-get callbacks (intern (format ":%s" msg-type)))))
  446. (ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY: msg_type = %s" msg-type)
  447. (if cb
  448. (ein:funcall-packed cb content metadata)
  449. (ein:log 'debug "no callback for: msg_type=%s msg_id=%s"
  450. msg-type msg-id))
  451. (ein:aif (plist-get content :payload)
  452. (ein:kernel--handle-payload kernel callbacks it))
  453. (let ((events (ein:$kernel-events kernel)))
  454. (ein:case-equal msg-type
  455. (("execute_reply")
  456. (ein:aif (plist-get content :execution_count)
  457. ;; It can be `nil' for silent execution
  458. (ein:events-trigger events 'execution_count.Kernel it)))))))
  459. (ein:log 'debug "KERNEL--HANDLE-SHELL-REPLY: finished"))
  460. (defun ein:kernel--handle-payload (kernel callbacks payload)
  461. (loop with events = (ein:$kernel-events kernel)
  462. for p in payload
  463. for text = (plist-get p :text)
  464. for source = (plist-get p :source)
  465. if (member source '("IPython.kernel.zmq.page.page"
  466. "IPython.zmq.page.page"))
  467. do (when (not (equal (ein:trim text) ""))
  468. (ein:events-trigger
  469. events 'open_with_text.Pager (list :text text)))
  470. else if
  471. (member
  472. source
  473. '("IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input"
  474. "IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input"))
  475. do (let ((cb (plist-get callbacks :set_next_input)))
  476. (when cb (ein:funcall-packed cb text)))))
  477. (defun ein:kernel--handle-iopub-reply (kernel packet)
  478. (ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY")
  479. (destructuring-bind
  480. (&key content metadata parent_header header &allow-other-keys)
  481. (ein:json-read-from-string packet)
  482. (let* ((msg-type (plist-get header :msg_type))
  483. (callbacks (ein:kernel-get-callbacks-for-msg
  484. kernel (plist-get parent_header :msg_id)))
  485. (events (ein:$kernel-events kernel)))
  486. (ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY: msg_type = %s" msg-type)
  487. (if (and (not (equal msg-type "status")) (null callbacks))
  488. (ein:log 'verbose "Got message not from this notebook.")
  489. (ein:case-equal msg-type
  490. (("stream" "display_data" "pyout" "pyerr")
  491. (ein:aif (plist-get callbacks :output)
  492. (ein:funcall-packed it msg-type content metadata)))
  493. (("status")
  494. (ein:case-equal (plist-get content :execution_state)
  495. (("busy")
  496. (ein:events-trigger events 'status_busy.Kernel))
  497. (("idle")
  498. (ein:events-trigger events 'status_idle.Kernel))
  499. (("dead")
  500. (ein:kernel-stop-channels kernel)
  501. (ein:events-trigger events 'status_dead.Kernel))))
  502. (("clear_output")
  503. (ein:aif (plist-get callbacks :clear_output)
  504. (ein:funcall-packed it content metadata)))))))
  505. (ein:log 'debug "KERNEL--HANDLE-IOPUB-REPLY: finished"))
  506. ;;; Utility functions
  507. (defun ein:kernel-filename-to-python (kernel filename)
  508. "See: `ein:filename-to-python'."
  509. (ein:filename-to-python (ein:$kernel-url-or-port kernel) filename))
  510. (defun ein:kernel-filename-from-python (kernel filename)
  511. "See: `ein:filename-from-python'."
  512. (ein:filename-from-python (ein:$kernel-url-or-port kernel) filename))
  513. (defun ein:kernel-construct-defstring (content)
  514. "Construct call signature from CONTENT of ``:object_info_reply``.
  515. Used in `ein:pytools-finish-tooltip', etc."
  516. (or (plist-get content :call_def)
  517. (plist-get content :init_definition)
  518. (plist-get content :definition)))
  519. (defun ein:kernel-construct-help-string (content)
  520. "Construct help string from CONTENT of ``:object_info_reply``.
  521. Used in `ein:pytools-finish-tooltip', etc."
  522. (ein:log 'debug "KERNEL-CONSTRUCT-HELP-STRING")
  523. (let* ((defstring (ein:aand
  524. (ein:kernel-construct-defstring content)
  525. (ansi-color-apply it)
  526. (ein:string-fill-paragraph it)))
  527. (docstring (ein:aand
  528. (or (plist-get content :call_docstring)
  529. (plist-get content :init_docstring)
  530. (plist-get content :docstring)
  531. ;; "<empty docstring>"
  532. )
  533. (ansi-color-apply it)))
  534. (help (ein:aand
  535. (ein:filter 'identity (list defstring docstring))
  536. (ein:join-str "\n" it))))
  537. (ein:log 'debug "KERNEL-CONSTRUCT-HELP-STRING: help=%s" help)
  538. help))
  539. (defun ein:kernel-request-stream (kernel code func &optional args)
  540. "Run lisp callback FUNC with the output stream returned by Python CODE.
  541. The first argument to the lisp function FUNC is the stream output
  542. as a string and the rest of the argument is the optional ARGS."
  543. (ein:kernel-execute
  544. kernel
  545. code
  546. (list :output (cons (lambda (packed msg-type content -metadata-not-used-)
  547. (let ((func (car packed))
  548. (args (cdr packed)))
  549. (when (equal msg-type "stream")
  550. (ein:aif (plist-get content :data)
  551. (apply func it args)))))
  552. (cons func args)))))
  553. (defun* ein:kernel-history-request-synchronously
  554. (kernel &rest args &key (timeout 0.5) (tick-time 0.05) &allow-other-keys)
  555. "Send the history request and wait TIMEOUT seconds.
  556. Return a list (CONTENT METADATA).
  557. This function checks the request reply every TICK-TIME seconds.
  558. See `ein:kernel-history-request' for other usable options."
  559. ;; As `result' and `finished' are set in callback, make sure they
  560. ;; won't be trapped in other let-bindings.
  561. (lexical-let (result finished)
  562. (apply
  563. #'ein:kernel-history-request
  564. kernel
  565. (list :history_reply
  566. (cons (lambda (-ignore- content metadata)
  567. (setq result (list content metadata))
  568. (setq finished t))
  569. nil))
  570. args)
  571. (loop repeat (floor (/ timeout tick-time))
  572. do (sit-for tick-time)
  573. when finished
  574. return t
  575. finally (error "Timeout"))
  576. result))
  577. (defun ein:kernel-history-search-synchronously (kernel pattern &rest args)
  578. "Search execution history in KERNEL using PATTERN.
  579. Return matched history as a list of strings.
  580. See `ein:kernel-history-request-synchronously' and
  581. `ein:kernel-history-request' for usable options."
  582. (let ((reply
  583. (apply #'ein:kernel-history-request-synchronously
  584. kernel
  585. :hist-access-type "search"
  586. :pattern pattern
  587. args)))
  588. (mapcar #'caddr (plist-get (car reply) :history))))
  589. (provide 'ein-kernel)
  590. ;;; ein-kernel.el ends here