emacs-ipython-notebook/lisp/ob-ein.el

410 lines
17 KiB
EmacsLisp
Raw Normal View History

;; -*- lexical-binding: t -*-
;;; ob-ein.el --- org-babel functions for template evaluation
;; Copyright (C) John M. Miller
;; Author: John M. Miller <millejoh at mac.com>
;;
;;; License:
;; This file is NOT part of GNU Emacs.
;; ob-ein.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ob-ein.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ob-ein.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Support executing org-babel source blocks using EIN worksheets.
;;; Credits:
;; Uses code from https://github.com/gregsexton/ob-ipython (MIT License)
;;; Code:
(require 'org-element)
(require 'ein-utils)
(require 'ein-notebooklist)
(require 'ein-process)
(defvar *ob-ein-sentinel* "[....]"
"Placeholder string replaced after async cell execution")
2019-08-10 18:00:03 -04:00
(defcustom ob-ein-timeout-seconds 600
"Maximum seconds to wait for block to finish (for synchronous operations)."
:type 'integer
:group 'ein)
(defcustom ob-ein-languages
'(("ein" . python)
("ein-python" . python)
("ein-R" . R)
("ein-r" . R)
("ein-julia" . julia)
("ein-hy" . hy))
"ob-ein has knowledge of these (ein-LANG . LANG-MODE) pairs."
:type '(repeat (cons string symbol))
:group 'ein)
(defcustom ob-ein-anonymous-path ".%s.ipynb"
"When session header doesn't specify ipynb, prosecute all interactions for a given language in this throwaway notebook (substitute %s with language)."
:type '(string)
:group 'ein)
(defsubst ob-ein-anonymous-p (path)
"Return t if PATH looks like ob-ein-anonymous-path. Fragile"
(string-match (replace-regexp-in-string "%s" ".+"
(replace-regexp-in-string "\\." "\\\\." ob-ein-anonymous-path))
path))
(defcustom ob-ein-inline-image-directory "ein-images"
"Store ob-ein images here."
:group 'ein
:type '(directory))
(defcustom ob-ein-default-header-args:ein nil
"No documentation."
:group 'ein
:type '(repeat string))
2018-11-22 11:22:43 -08:00
(defun ob-ein--inline-image-info (value)
(let* ((f (md5 value))
(d ob-ein-inline-image-directory)
(tf (concat d "/ob-ein-" f ".png")))
(unless (file-directory-p d)
(make-directory d 'parents))
tf))
(defun ob-ein--write-base64-image (img-string file)
(with-temp-file file
(let ((buffer-read-only nil)
(buffer-file-coding-system 'binary)
(require-final-newline nil)
(file-precious-flag t))
(insert img-string)
(base64-decode-region (point-min) (point-max)))))
(defun ob-ein--return-mime-type (json file)
(loop
for key in ein:output-types-text-preferred
for type = (intern (format ":%s" key)) ; something like `:text'
for value = (plist-get json type) ; FIXME: optimize
when (plist-member json type)
return
(case key
((svg image/svg)
(let ((file (or file (ob-ein--inline-image-info value))))
(ob-ein--write-base64-image value file)
(format "[[file:%s]]" file)))
((png image/png jpeg image/jpeg)
(let ((file (or file (ob-ein--inline-image-info value))))
(ob-ein--write-base64-image value file)
(format "[[file:%s]]" file)))
(t (plist-get json type)))))
(defun ob-ein--process-outputs (outputs params)
(let ((file (cdr (assoc :image params))))
(ein:join-str "\n"
(loop for o in outputs
collecting (ob-ein--return-mime-type o file)))))
Appveyor iterating (WIP) (#573) * ob-ein: Bring back old functionality. Bring back some old features to babel edit buffers while trying to respect recent addition of polymode support. * Override polymode if the user really wants. Polymode is really for notebook buffers in any case, but this will override whatever completion backmode a user has configured for python-mode. * Install cask using python2. For now python2 is the easiest option for testing on Windows since cask does not properly support python3 when in Windows. * Let's throw in the ert-runner, see what happens. * Can I use my fork of cask? Work around smartrep weirdness, try to live without command line wildcard expansion. * Get the url for the fork right. * Experiment with python37, use test_script. * Unstick appveyor, I hope. * Fix parsing error. * test_script is not executing. Why? * Add ert testing. But why are the test_script commands not executing? * tasks: Automate building and testing using invoke. Invoke leverages Python, which I hope will allow us to abstract out differences in platforms when it comes to building and testing ein. * Use invoke on appveyor. * appveyor: Use the environment python. So we can test versions other than python 2.7. * Parsing error. Is https://packaging.python.org/guides/supporting-windows-using-appveyor/#setting-up lying to me? * Quote commands, just in case. * Get python onto the path. * Appveyor is catching up to travis. * Parsing error. * Update pip, try to get quoted syntax right. * Still not liking my pip call. Last try, next step we go to a requirements.txt file. * Go to using a requirements file for pip. * ecukes needs bash to work. * Cleaning up and fiddling. Seems like the emacs-jupyter guy has his act together - maybe we can take some inspiration for our appveyor config. * Syntax error in environment. * More syntax errros. * Maybe we need quoting. * I give up. * Formatting and cleanup. * Add customization, yet another syntax error. New customizable variable `ob-ein-babel-edit-polymode-ignore' to override keybinding for \C-c\C-c in an org source code edit buffer. * John learned some Powershell today. * Fix the executable path. Sometimes there is more than one curl installed on the system, make sure we can account for that in testing. * Handle updating the path inside invoke. * Report which curl we are using before starting functional tests. * Enable RDP so we can see error logs. * Keep the build alive even when it finishes. * Fix #568. Apparently we need to specify the user agent when on windows, otherwise tornado will start throwing 403 responses. Currently using Mozilla/4.0 as the agent, but might be a good idea to make this value customizable. * Clean up emacs config. * Why is appveyor dropping the xsrf token? * xsrf cookie found, what does the header look like? * Try different user-agent header, reenable rdp. * JSON encoding issues on Python side, it appears. Let's try an older Python. Login works, contents query to get notebooklist works (i.e. GET on /contents/api), but creating a notebook (i.e. POST on /contents/api) fails with invalid JSON. ein and emacs-request appear to be generating the proper json, but jupyter notebook does not see the same thing that is being written. Could be bytes vs. text issue with modern v3.x python, so let us see how this all works with Python 2.7. * Python27 does not have pathlib out of the box. * Make amends with Python27 * Back to python37. Tornado/notebook still isn't reading the POST'ed json correctly. * Do we need to specify content type? * Must be selective in specifying application/json content. * Re-enable rdp. * Let's try a different curl. * Ensure most recent curl is on path * Try a different path. * Try to warn user if suspicious curl detected. * Remove debugging statements. * EVM depends on trusty for 26.x See issue #125 (https://github.com/rejeep/evm/issues/125). Let's hope I got the travis.yml syntax right. * Minimal support for ecukes from invoke. * Cleaner server shutdowns, better ecukes support from invoke. Use the /api/shutdown REST API call now to shutdown running server. Also support more command line options for ecukes from invoke. * Almost full support of ecukes using invoke. But! Also disabling integration testing for the time being until I understand why ecukes fails even though everything else is working. * Just do integration and functional testing on appveyor. Better than nothing while I work out what is breaking the integration tests.
2019-07-28 18:20:13 -06:00
(defun ob-ein--get-name-create (src-block-info)
"Get the name of a src block or add a uuid as the name."
(if-let ((name (fifth src-block-info)))
name
(save-excursion
(let ((el (org-element-context))
(id (org-id-new 'none)))
(goto-char (org-element-property :begin el))
2019-04-01 21:13:18 +11:00
(back-to-indentation)
(split-line)
(insert (format "#+NAME: %s" id))
id))))
(defun ob-ein--babelize-lang (lang-name lang-mode)
"Stand-up LANG-NAME as a babelized language with LANG-MODE syntax table.
Based on ob-ipython--configure-kernel."
(add-to-list 'org-src-lang-modes `(,lang-name . ,lang-mode))
(defvaralias (intern (concat "org-babel-default-header-args:" lang-name))
'ob-ein-default-header-args:ein)
(fset (intern (concat "org-babel-execute:" lang-name))
`(lambda (body params)
(require (quote ,(intern (format "ob-%s" lang-mode))) nil t)
(if (boundp 'python-indent-guess-indent-offset-verbose)
(setq python-indent-guess-indent-offset-verbose nil))
(let* ((parser
(quote
,(intern
(format "org-babel-variable-assignments:%s" lang-mode))))
(assignments (if (fboundp parser)
(funcall (symbol-function parser) params)
(ein:log 'verbose "%s: No suitable ob-%s module"
(concat "org-babel-execute:" ,lang-name)
(quote ,lang-mode))
nil)))
(ob-ein--execute-body body params assignments)))))
(defun ob-ein--execute-body (body params assignments)
(let* ((buffer (current-buffer))
(processed-params (org-babel-process-params params))
(result-params (cdr (assq :result-params params)))
(session (or (ein:aand (cdr (assoc :session processed-params))
(unless (string= "none" it)
(format "%s" it)))
ein:url-localhost))
(lang (nth 0 (org-babel-get-src-block-info)))
(kernelspec (or (cdr (assoc :kernelspec processed-params))
(ein:aif (cdr (assoc lang org-src-lang-modes))
(cons 'language (format "%s" it))
(error "ob-ein--execute-body: %s not among %s"
lang (mapcar #'car org-src-lang-modes)))))
(name (ob-ein--get-name-create (org-babel-get-src-block-info)))
(full-body (org-babel-expand-body:generic
(encode-coding-string body 'utf-8)
params
assignments))
(callback (lambda (notebook)
(ob-ein--execute-async
buffer
full-body
(ein:$notebook-kernel notebook)
processed-params
result-params
name))))
2019-08-10 18:00:03 -04:00
(save-excursion
(cl-assert (not (stringp (org-babel-goto-named-src-block name))))
(org-babel-insert-result *ob-ein-sentinel* result-params))
(ob-ein--initiate-session session kernelspec callback)
(if (ein:eval-if-bound 'org-current-export-file)
(save-excursion
(loop with interval = 2000
with pending = t
repeat (/ (* ob-ein-timeout-seconds 1000) interval)
do (progn
(org-babel-goto-named-result name)
(forward-line 1)
(setq pending (re-search-forward
(regexp-quote *ob-ein-sentinel*)
(org-babel-result-end) t)))
until (not pending)
do (sleep-for 0 interval)
finally return
(if pending
(progn
(ein:log 'error "ob-ein--execute-body: %s timed out" name)
"")
(ob-ein--process-outputs
(ein:oref-safe (ein:shared-output-get-cell) 'outputs)
processed-params))))
(org-babel-remove-result)
*ob-ein-sentinel*)))
(defsubst ob-ein--execute-async-callback (buffer params result-params name)
"Callback of 1-arity (the shared output cell) to update org buffer when
`ein:shared-output-eval-string' completes."
(apply-partially
(lambda (buffer* params* result-params* name* cell)
(let* ((raw (ein:aif (ein:oref-safe cell 'traceback)
(ansi-color-apply (ein:join-str "\n" it))
(ob-ein--process-outputs
(ein:oref-safe cell 'outputs) params*)))
(result
(let ((tmp-file (org-babel-temp-file "ein-")))
(with-temp-file tmp-file raw)
(org-babel-result-cond result-params*
raw (org-babel-import-elisp-from-file tmp-file '(16)))))
(info (org-babel-get-src-block-info 'light)))
2019-08-10 18:00:03 -04:00
(ein:log 'debug "ob-ein--execute-async-callback %s \"%s\" %s" name* result buffer*)
(save-excursion
(save-restriction
(with-current-buffer buffer*
2019-08-10 18:00:03 -04:00
(unless (stringp (org-babel-goto-named-src-block name*)) ;; stringp=error
(when info ;; kill #+RESULTS: (no-name)
(setf (nth 4 info) nil)
(org-babel-remove-result info))
(org-babel-remove-result) ;; kill #+RESULTS: name
(org-babel-insert-result
result
(cdr (assoc :result-params
(third (org-babel-get-src-block-info)))))
(org-redisplay-inline-images)))))))
buffer params result-params name))
(defun ob-ein--execute-async (buffer body kernel params result-params name)
Appveyor iterating (WIP) (#573) * ob-ein: Bring back old functionality. Bring back some old features to babel edit buffers while trying to respect recent addition of polymode support. * Override polymode if the user really wants. Polymode is really for notebook buffers in any case, but this will override whatever completion backmode a user has configured for python-mode. * Install cask using python2. For now python2 is the easiest option for testing on Windows since cask does not properly support python3 when in Windows. * Let's throw in the ert-runner, see what happens. * Can I use my fork of cask? Work around smartrep weirdness, try to live without command line wildcard expansion. * Get the url for the fork right. * Experiment with python37, use test_script. * Unstick appveyor, I hope. * Fix parsing error. * test_script is not executing. Why? * Add ert testing. But why are the test_script commands not executing? * tasks: Automate building and testing using invoke. Invoke leverages Python, which I hope will allow us to abstract out differences in platforms when it comes to building and testing ein. * Use invoke on appveyor. * appveyor: Use the environment python. So we can test versions other than python 2.7. * Parsing error. Is https://packaging.python.org/guides/supporting-windows-using-appveyor/#setting-up lying to me? * Quote commands, just in case. * Get python onto the path. * Appveyor is catching up to travis. * Parsing error. * Update pip, try to get quoted syntax right. * Still not liking my pip call. Last try, next step we go to a requirements.txt file. * Go to using a requirements file for pip. * ecukes needs bash to work. * Cleaning up and fiddling. Seems like the emacs-jupyter guy has his act together - maybe we can take some inspiration for our appveyor config. * Syntax error in environment. * More syntax errros. * Maybe we need quoting. * I give up. * Formatting and cleanup. * Add customization, yet another syntax error. New customizable variable `ob-ein-babel-edit-polymode-ignore' to override keybinding for \C-c\C-c in an org source code edit buffer. * John learned some Powershell today. * Fix the executable path. Sometimes there is more than one curl installed on the system, make sure we can account for that in testing. * Handle updating the path inside invoke. * Report which curl we are using before starting functional tests. * Enable RDP so we can see error logs. * Keep the build alive even when it finishes. * Fix #568. Apparently we need to specify the user agent when on windows, otherwise tornado will start throwing 403 responses. Currently using Mozilla/4.0 as the agent, but might be a good idea to make this value customizable. * Clean up emacs config. * Why is appveyor dropping the xsrf token? * xsrf cookie found, what does the header look like? * Try different user-agent header, reenable rdp. * JSON encoding issues on Python side, it appears. Let's try an older Python. Login works, contents query to get notebooklist works (i.e. GET on /contents/api), but creating a notebook (i.e. POST on /contents/api) fails with invalid JSON. ein and emacs-request appear to be generating the proper json, but jupyter notebook does not see the same thing that is being written. Could be bytes vs. text issue with modern v3.x python, so let us see how this all works with Python 2.7. * Python27 does not have pathlib out of the box. * Make amends with Python27 * Back to python37. Tornado/notebook still isn't reading the POST'ed json correctly. * Do we need to specify content type? * Must be selective in specifying application/json content. * Re-enable rdp. * Let's try a different curl. * Ensure most recent curl is on path * Try a different path. * Try to warn user if suspicious curl detected. * Remove debugging statements. * EVM depends on trusty for 26.x See issue #125 (https://github.com/rejeep/evm/issues/125). Let's hope I got the travis.yml syntax right. * Minimal support for ecukes from invoke. * Cleaner server shutdowns, better ecukes support from invoke. Use the /api/shutdown REST API call now to shutdown running server. Also support more command line options for ecukes from invoke. * Almost full support of ecukes using invoke. But! Also disabling integration testing for the time being until I understand why ecukes fails even though everything else is working. * Just do integration and functional testing on appveyor. Better than nothing while I work out what is breaking the integration tests.
2019-07-28 18:20:13 -06:00
"As `ein:shared-output-get-cell' is a singleton, ob-ein can only execute blocks
one at a time. Further, we do not order the queued up blocks!"
(deferred:$
(deferred:next
(deferred:lambda ()
(let ((cell (ein:shared-output-get-cell)))
(if (eq (slot-value cell 'callback) #'ignore)
(let ((callback
(ob-ein--execute-async-callback buffer params
result-params name)))
(setf (slot-value cell 'callback) callback))
2019-08-10 18:00:03 -04:00
;; still pending previous callback
(deferred:nextc (deferred:wait 1200) self)))))
(deferred:nextc it
(lambda (_x)
(ein:shared-output-eval-string kernel body nil)))))
(defun ob-ein--parse-session (session)
(multiple-value-bind (url-or-port _password) (ein:jupyter-server-conn-info)
(let ((tokens (split-string session "/"))
(parsed-url (url-generic-parse-url session)))
(cond ((null (url-host parsed-url))
(let* ((candidate (apply #'ein:url (car tokens) (cdr tokens)))
(parsed-candidate (url-generic-parse-url candidate))
(missing (url-scheme-get-property
(url-type parsed-candidate)
'default-port)))
(if (and url-or-port
(= (url-port parsed-candidate) missing))
(apply #'ein:url url-or-port (cdr tokens))
candidate)))
(t (ein:url session))))))
(defun ob-ein--initiate-session (session kernelspec callback)
"Retrieve notebook based on SESSION path and KERNELSPEC, starting jupyter instance
if necessary. Install CALLBACK (i.e., cell execution) upon notebook retrieval."
(let* ((nbpath (ob-ein--parse-session session))
(info (org-babel-get-src-block-info))
(anonymous-path (format ob-ein-anonymous-path (nth 0 info)))
(parsed-url (url-generic-parse-url nbpath))
(slash-path (car (url-path-and-query parsed-url)))
(path (if (string= slash-path "") anonymous-path
(substring slash-path 1)))
(url-or-port (if (string= slash-path "")
nbpath
(substring nbpath 0 (- (length slash-path)))))
(notebook (ein:notebook-get-opened-notebook url-or-port path))
(callback-nbopen (lambda (nb _created)
(loop repeat 50
for live-p = (ein:kernel-live-p (ein:$notebook-kernel nb))
until live-p
do (sleep-for 0 300)
finally
do (if (not live-p)
(ein:log 'error
"Kernel for %s failed to launch"
(ein:$notebook-notebook-name nb))
(funcall callback nb)))))
(errback-nbopen (lambda (url-or-port status-code)
(if (eq status-code 404)
(ein:notebooklist-new-notebook-with-name
url-or-port kernelspec path callback-nbopen t))))
(callback-login (lambda (_buffer url-or-port)
(ein:notebook-open url-or-port path kernelspec
callback-nbopen errback-nbopen t))))
(cond ((and notebook
(string= path anonymous-path)
(stringp kernelspec)
(not (equal (ein:$kernelspec-name (ein:$notebook-kernelspec notebook))
kernelspec)))
(ein:log 'debug "ob-ein--initiate-session: switching %s from %s to %s"
path (ein:$kernelspec-name (ein:$notebook-kernelspec notebook))
kernelspec)
(cl-letf (((symbol-function 'y-or-n-p) #'ignore))
(ein:notebook-close notebook)
(ein:query-singleton-ajax
(list 'ob-ein--initiate-session (ein:url url-or-port path))
(ein:notebook-url notebook)
:type "DELETE"))
(loop repeat 8
for extant = (file-exists-p path)
until (not extant)
do (sleep-for 0 500)
finally do (if extant
(ein:display-warning (format "cannot del %s" path))
(ob-ein--initiate-session session kernelspec callback))))
(notebook (funcall callback notebook))
((string= (url-host parsed-url) ein:url-localhost)
(ein:process-refresh-processes)
(ein:aif (ein:process-url-match nbpath)
(ein:notebooklist-login (ein:process-url-or-port it) callback-login)
(ein:jupyter-server-start
(executable-find ein:jupyter-default-server-command)
(read-directory-name "Notebook directory: " default-directory)
nil
callback-login
(let* ((port (url-port parsed-url))
(avoid (url-scheme-get-property (url-type parsed-url) 'default-port)))
(cond ((= port avoid) nil)
(t (url-port parsed-url)))))))
(t (ein:notebooklist-login url-or-port callback-login)))))
Appveyor iterating (WIP) (#573) * ob-ein: Bring back old functionality. Bring back some old features to babel edit buffers while trying to respect recent addition of polymode support. * Override polymode if the user really wants. Polymode is really for notebook buffers in any case, but this will override whatever completion backmode a user has configured for python-mode. * Install cask using python2. For now python2 is the easiest option for testing on Windows since cask does not properly support python3 when in Windows. * Let's throw in the ert-runner, see what happens. * Can I use my fork of cask? Work around smartrep weirdness, try to live without command line wildcard expansion. * Get the url for the fork right. * Experiment with python37, use test_script. * Unstick appveyor, I hope. * Fix parsing error. * test_script is not executing. Why? * Add ert testing. But why are the test_script commands not executing? * tasks: Automate building and testing using invoke. Invoke leverages Python, which I hope will allow us to abstract out differences in platforms when it comes to building and testing ein. * Use invoke on appveyor. * appveyor: Use the environment python. So we can test versions other than python 2.7. * Parsing error. Is https://packaging.python.org/guides/supporting-windows-using-appveyor/#setting-up lying to me? * Quote commands, just in case. * Get python onto the path. * Appveyor is catching up to travis. * Parsing error. * Update pip, try to get quoted syntax right. * Still not liking my pip call. Last try, next step we go to a requirements.txt file. * Go to using a requirements file for pip. * ecukes needs bash to work. * Cleaning up and fiddling. Seems like the emacs-jupyter guy has his act together - maybe we can take some inspiration for our appveyor config. * Syntax error in environment. * More syntax errros. * Maybe we need quoting. * I give up. * Formatting and cleanup. * Add customization, yet another syntax error. New customizable variable `ob-ein-babel-edit-polymode-ignore' to override keybinding for \C-c\C-c in an org source code edit buffer. * John learned some Powershell today. * Fix the executable path. Sometimes there is more than one curl installed on the system, make sure we can account for that in testing. * Handle updating the path inside invoke. * Report which curl we are using before starting functional tests. * Enable RDP so we can see error logs. * Keep the build alive even when it finishes. * Fix #568. Apparently we need to specify the user agent when on windows, otherwise tornado will start throwing 403 responses. Currently using Mozilla/4.0 as the agent, but might be a good idea to make this value customizable. * Clean up emacs config. * Why is appveyor dropping the xsrf token? * xsrf cookie found, what does the header look like? * Try different user-agent header, reenable rdp. * JSON encoding issues on Python side, it appears. Let's try an older Python. Login works, contents query to get notebooklist works (i.e. GET on /contents/api), but creating a notebook (i.e. POST on /contents/api) fails with invalid JSON. ein and emacs-request appear to be generating the proper json, but jupyter notebook does not see the same thing that is being written. Could be bytes vs. text issue with modern v3.x python, so let us see how this all works with Python 2.7. * Python27 does not have pathlib out of the box. * Make amends with Python27 * Back to python37. Tornado/notebook still isn't reading the POST'ed json correctly. * Do we need to specify content type? * Must be selective in specifying application/json content. * Re-enable rdp. * Let's try a different curl. * Ensure most recent curl is on path * Try a different path. * Try to warn user if suspicious curl detected. * Remove debugging statements. * EVM depends on trusty for 26.x See issue #125 (https://github.com/rejeep/evm/issues/125). Let's hope I got the travis.yml syntax right. * Minimal support for ecukes from invoke. * Cleaner server shutdowns, better ecukes support from invoke. Use the /api/shutdown REST API call now to shutdown running server. Also support more command line options for ecukes from invoke. * Almost full support of ecukes using invoke. But! Also disabling integration testing for the time being until I understand why ecukes fails even though everything else is working. * Just do integration and functional testing on appveyor. Better than nothing while I work out what is breaking the integration tests.
2019-07-28 18:20:13 -06:00
(defun ob-ein--edit-ctrl-c-ctrl-c ()
"C-c C-c mapping in ein:connect-mode-map."
(interactive)
(if (not (org-src-edit-buffer-p))
(ein:connect-run-buffer)
(org-edit-src-save)
(when (boundp 'org-src--beg-marker)
(let* ((beg org-src--beg-marker)
(buf (marker-buffer beg)))
(with-current-buffer buf
(save-excursion
(goto-char beg)
(org-ctrl-c-ctrl-c)))))))
(defcustom ob-ein-babel-edit-polymode-ignore nil
"When true override default python mode key mapping for `\C-c\C-c' while inside a babel edit buffer.
Instead the binding will be to `ob-ein--edit-ctrl-c-ctrl-c', which will execute the code block being edited."
:group 'ein
:type '(boolean))
(defun org-babel-edit-prep:ein (babel-info)
(if (and ein:polymode (not ob-ein-babel-edit-polymode-ignore))
(progn
(use-local-map (copy-keymap python-mode-map))
(local-set-key "\C-c\C-c" 'ob-ein--edit-ctrl-c-ctrl-c))
(let* ((buffer (current-buffer))
(processed-parameters (nth 2 babel-info))
(session (or (ein:aand (cdr (assoc :session processed-parameters))
(unless (string= "none" it)
(format "%s" it)))
ein:url-localhost))
(lang "ein-python")
(kernelspec (or (cdr (assoc :kernelspec processed-parameters))
(ein:aif (cdr (assoc lang org-src-lang-modes))
(cons 'language (format "%s" it))
(error "ob-ein--execute-body: %s not among %s"
lang (mapcar #'car org-src-lang-modes))))))
(ob-ein--initiate-session
session
kernelspec
(lambda (notebook)
(ein:connect-buffer-to-notebook notebook buffer t)
(define-key ein:connect-mode-map "\C-c\C-c" 'ob-ein--edit-ctrl-c-ctrl-c))))))
(defun org-babel-edit-prep:ein-python (babel-info)
(org-babel-edit-prep:ein babel-info))
(loop for (lang . mode) in ob-ein-languages
do (ob-ein--babelize-lang lang mode))
;;;###autoload
(if (featurep 'org)
(let* ((orig (get 'org-babel-load-languages 'custom-type))
(orig-cdr (cdr orig))
(choices (plist-get orig-cdr :key-type)))
(push '(const :tag "Ein" ein) (nthcdr 1 choices))
(put 'org-babel-load-languages 'custom-type
(cons (car orig) (plist-put orig-cdr :key-type choices)))))
(provide 'ob-ein)