Introduction
ob-ipython is "an Emacs library that allows Org mode to evaluate code blocks using a Jupyter kernel (Python by default)."
It's a great tool for you to make note and literal programming.
It's easy to use comparing with ein
.
And you can make a matplot inline image in the org file comparing with ob-python
.
Problem
However it's not working properly when using a virtual environment.
When you don't have a system level jupyter installed and use org-mode, you will get a strange error like below.
It can be fixed easily. When there is no jupyter installed, we just don't configure the kernel. When the time we use a virtual environment, and there will be a jupyter installed!
(advice-add 'ob-ipython-auto-configure-kernels :around
(lambda (orig-fun &rest args)
"Configure the kernels when found jupyter."
(when (executable-find ob-ipython-command)
(apply orig-fun args))))
Notice: We will using a virtualenv environment called scientific, which has numpy
and jupyter
installed.
Completion
The company backend in ob-ipython
is extremely slow, which means it basic can't complete anything.
ob-ipython
will start a backend process ob-ipython-kernel
, sending the code to the process, and get the result. You can't have any interaction with the process for preventing you modify the "environment". But sometimes it's not convenient, we want to test some function. So we can start a inferior-python
process, which is the process when we call run-python
. When we evaluate our code using C-c C-c
we also send the code to the process we started. Now we can even use the inferior-python
process to complete the code!
(defun run-python-first (&rest args)
"Start a inferior python if there isn't one."
(or (comint-check-proc "*Python*") (run-python)))
(advice-add 'org-babel-execute:ipython :after
(lambda (body params)
"Send body to `inferior-python'."
(run-python-first)
(python-shell-send-string body)))
(add-hook 'org-mode-hook
(lambda ()
(setq-local completion-at-point-functions
'(pcomplete-completions-at-point python-completion-at-point))))
We configure the completion-at-point-function
, now you can use any completion backend you like.
However, we need to evaluate the code very ofen, which will send the code to a ob-ipython-kernel
to display the result, and meantime send the code to a inferior-python
process to get the code completion done.
Eldoc
Now we have an inferior-python
process for testing our code and code completion. It also can be used to display the eldoc
, too!
(defun ob-ipython-eldoc-function ()
(when (org-babel-where-is-src-block-head)
(python-eldoc-function)))
(add-hook 'org-mode-hook
(lambda ()
(setq-default eldoc-documentation-function 'ob-ipython-eldoc-function)))
Help
Sometimes we want a full description of the package or function.
Here's the idea: we send our code to the ob-ipython-kernel
to get the result, we can also send help(package or function name) (e.g. help(os.path.join)) to the ob-ipython-kernel
to get the help information, and display it in a separate buffer, just like what we type in a inferior-python
process!
(defun ob-ipython-help (symbol)
(interactive (list (read-string "Symbol: " (python-eldoc--get-symbol-at-point))))
(unless (org-babel-where-is-src-block-head)
(error "Symbol is not in src block."))
(unless (ob-ipython--get-kernel-processes)
(error "There is no ob-ipython-kernal running."))
(when-let* ((processes (ob-ipython--get-kernel-processes))
(session (caar processes))
(ret (ob-ipython--eval
(ob-ipython--execute-request (format "help(%s)" symbol) session))))
(let ((result (cdr (assoc :result ret)))
(output (cdr (assoc :output ret))))
(let ((buf (get-buffer-create "*ob-ipython-doc*")))
(with-current-buffer buf
(let ((inhibit-read-only t))
(erase-buffer)
(insert output)
(goto-char (point-min))
(read-only-mode t)
(pop-to-buffer buf)))))))
Conclusion
We evaluate our code and get the full doc using the ob-ipython-kernel
.
We start a inferior-python
process to test out code, get the completion and eldoc done.
You can get all the code here. Thanks for your time.