mirror of
https://github.com/vale981/emacs-ipython-notebook
synced 2025-03-06 09:31:39 -05:00

After many attempts ein now supports getting object information in IPython 3.0 just like in the good ole days in IPython 2.x.
319 lines
12 KiB
EmacsLisp
319 lines
12 KiB
EmacsLisp
;;; ein-pytools.el --- Python tools build on top of kernel
|
||
|
||
;; Copyright (C) 2012- Takafumi Arakaki
|
||
|
||
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
|
||
|
||
;; This file is NOT part of GNU Emacs.
|
||
|
||
;; ein-pytools.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.
|
||
|
||
;; ein-pytools.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 ein-pytools.el. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
;;; Commentary:
|
||
|
||
;;
|
||
|
||
;;; Code:
|
||
|
||
(eval-when-compile (require 'cl))
|
||
|
||
;; for `ein:pytools-pandas-to-ses'
|
||
(declare-function ses-yank-tsf "ses")
|
||
(declare-function ses-command-hook "ses")
|
||
|
||
(require 'ein-kernel)
|
||
|
||
(defun ein:goto-file (filename lineno &optional other-window)
|
||
"Jump to file FILEAME at line LINENO.
|
||
If OTHER-WINDOW is non-`nil', open the file in the other window."
|
||
(funcall (if other-window #'find-file-other-window #'find-file) filename)
|
||
(goto-char (point-min))
|
||
(forward-line (1- lineno)))
|
||
|
||
(defun ein:goto-marker (marker &optional other-window)
|
||
(funcall (if other-window #'pop-to-buffer #'switch-to-buffer)
|
||
(marker-buffer marker))
|
||
(goto-char marker))
|
||
|
||
(defcustom ein:propagate-connect t
|
||
"Set to `t' to connect to the notebook after jumping to a buffer."
|
||
:type '(choice (const :tag "Yes" t)
|
||
(const :tag "No" nil))
|
||
:group 'ein)
|
||
|
||
(defun ein:pytools-setup-hooks (kernel)
|
||
(push (cons #'ein:pytools-add-sys-path kernel)
|
||
(ein:$kernel-after-start-hook kernel)))
|
||
|
||
(defun ein:pytools-add-sys-path (kernel)
|
||
(ein:kernel-execute
|
||
kernel
|
||
(format "__import__('sys').path.append('%s')" ein:source-dir)))
|
||
|
||
|
||
;;; Tooltip and help
|
||
|
||
(defun ein:pytools-request-tooltip (kernel func)
|
||
(interactive (list (ein:get-kernel-or-error)
|
||
(ein:object-at-point-or-error)))
|
||
(if (>= (ein:$kernel-api-version kernel) 3)
|
||
(ein:kernel-execute
|
||
kernel
|
||
(format "__import__('ein').print_object_info_for(%s)" func)
|
||
(list
|
||
:output (cons
|
||
(lambda (name msg-type content -metadata-not-used-)
|
||
(ein:case-equal msg-type
|
||
(("stream" "display_data")
|
||
(ein:pytools-finish-tooltip name (ein:json-read-from-string (plist-get content :text)) nil))))
|
||
func)))
|
||
(ein:kernel-object-info-request
|
||
kernel func (list :object_info_reply
|
||
(cons #'ein:pytools-finish-tooltip nil)))))
|
||
|
||
(declare-function pos-tip-show "pos-tip")
|
||
(declare-function popup-tip "popup")
|
||
|
||
(defun ein:pytools-finish-tooltip (-ignore- content -metadata-not-used-)
|
||
;; See: Tooltip.prototype._show (tooltip.js)
|
||
(let ((tooltip (ein:kernel-construct-help-string content))
|
||
(defstring (ein:kernel-construct-defstring content))
|
||
(name (plist-get content :name)))
|
||
(if tooltip
|
||
(cond
|
||
((and window-system (featurep 'pos-tip))
|
||
(pos-tip-show tooltip 'ein:pos-tip-face nil nil 0))
|
||
((featurep 'popup)
|
||
(popup-tip tooltip))
|
||
(t (when (stringp defstring)
|
||
(message (ein:trim (ansi-color-apply defstring))))))
|
||
(ein:log 'info "no info for %s" name))))
|
||
|
||
(defun ein:pytools-request-help (kernel func)
|
||
(interactive (list (ein:get-kernel-or-error)
|
||
(ein:object-at-point-or-error)))
|
||
(ein:kernel-execute kernel
|
||
(format "%s?" func) ; = code
|
||
nil ; = callbacks
|
||
;; It looks like that magic command does
|
||
;; not work in silent mode.
|
||
:silent nil))
|
||
|
||
(defun ein:pytools-request-tooltip-or-help (&optional pager)
|
||
"Show the help for the object at point using tooltip.
|
||
When the prefix argument ``C-u`` is given, open the help in the
|
||
pager buffer. You can explicitly specify the object by selecting it."
|
||
(interactive "P")
|
||
(call-interactively (if pager
|
||
#'ein:pytools-request-help
|
||
#'ein:pytools-request-tooltip)))
|
||
|
||
|
||
;;; Source jump
|
||
|
||
(defvar ein:pytools-jump-stack nil)
|
||
|
||
(defvar ein:pytools-jump-to-source-not-found-regexp
|
||
(ein:join-str "\\|"
|
||
(list "^WARNING: .*"
|
||
"^Traceback (most recent call last):\n"
|
||
"^.*<ipython-input-[^>\n]+>\n"
|
||
"^\n")))
|
||
|
||
(defun ein:pytools-jump-to-source (kernel object &optional
|
||
other-window notebook)
|
||
(ein:log 'info "Jumping to the source of %s..." object)
|
||
(let ((last (car ein:pytools-jump-stack)))
|
||
(if (ein:aand last (eql (current-buffer) (marker-buffer it)))
|
||
(unless (equal (point) (marker-position last))
|
||
(push (point-marker) ein:pytools-jump-stack))
|
||
(setq ein:pytools-jump-stack (list (point-marker)))))
|
||
(ein:kernel-execute
|
||
kernel
|
||
(format "__import__('ein').find_source('%s')" object)
|
||
(list
|
||
:output
|
||
(cons
|
||
(lambda (packed msg-type content -metadata-not-used-)
|
||
(destructuring-bind (kernel object other-window notebook)
|
||
packed
|
||
(ein:case-equal msg-type
|
||
(("stream")
|
||
(ein:aif (plist-get content :data)
|
||
(if (string-match ein:pytools-jump-to-source-not-found-regexp
|
||
it)
|
||
(ein:log 'info
|
||
"Jumping to the source of %s...Not found" object)
|
||
(destructuring-bind (filename &optional lineno &rest ignore)
|
||
(split-string it "\n")
|
||
(setq lineno (string-to-number lineno))
|
||
(setq filename
|
||
(ein:kernel-filename-from-python kernel filename))
|
||
(let ((ein:connect-default-notebook nil))
|
||
;; Avoid auto connection to connect to the
|
||
;; NOTEBOOK instead of the default one.
|
||
(ein:goto-file filename lineno other-window))
|
||
;; Connect current buffer to NOTEBOOK. No reconnection.
|
||
(ein:connect-buffer-to-notebook notebook nil t)
|
||
(push (point-marker) ein:pytools-jump-stack)
|
||
(ein:log 'info
|
||
"Jumping to the source of %s...Done" object)))))
|
||
(("pyerr" "error")
|
||
(ein:log 'info
|
||
"Jumping to the source of %s...Not found" object)))))
|
||
(list kernel object other-window notebook)))))
|
||
|
||
(defun ein:pytools-jump-to-source-command (&optional other-window)
|
||
"Jump to the source code of the object at point.
|
||
When the prefix argument ``C-u`` is given, open the source code
|
||
in the other window. You can explicitly specify the object by
|
||
selecting it."
|
||
(interactive "P")
|
||
(let ((kernel (ein:get-kernel))
|
||
(object (ein:object-at-point)))
|
||
(assert (ein:kernel-live-p kernel) nil "Kernel is not ready.")
|
||
(assert object nil "Object at point not found.")
|
||
(ein:pytools-jump-to-source kernel object other-window
|
||
(when ein:propagate-connect
|
||
(ein:get-notebook)))))
|
||
|
||
(defun ein:pytools-jump-back-command (&optional other-window)
|
||
"Go back to the point where `ein:pytools-jump-to-source-command'
|
||
is executed last time. When the prefix argument ``C-u`` is
|
||
given, open the last point in the other window."
|
||
(interactive "P")
|
||
(when (ein:aand (car ein:pytools-jump-stack)
|
||
(equal (point) (marker-position it)))
|
||
(setq ein:pytools-jump-stack (cdr ein:pytools-jump-stack)))
|
||
(ein:aif (car ein:pytools-jump-stack)
|
||
(ein:goto-marker it other-window)
|
||
(ein:log 'info "Nothing on stack.")))
|
||
|
||
(define-obsolete-function-alias
|
||
'ein:pytools-eval-string-internal
|
||
'ein:shared-output-eval-string "0.1.2")
|
||
|
||
(defun ein:pytools-doctest ()
|
||
"Do the doctest of the object at point."
|
||
(interactive)
|
||
(let ((object (ein:object-at-point)))
|
||
(ein:shared-output-eval-string
|
||
(format "__import__('ein').run_docstring_examples(%s)" object)
|
||
t)))
|
||
|
||
(defun ein:pytools-whos ()
|
||
"Execute ``%whos`` magic command and popup the result."
|
||
(interactive)
|
||
(ein:shared-output-eval-string "%whos" t))
|
||
|
||
(defun ein:pytools-hierarchy (&optional ask)
|
||
"Draw inheritance graph of the class at point.
|
||
hierarchymagic_ extension is needed to be installed.
|
||
You can explicitly specify the object by selecting it.
|
||
|
||
.. _hierarchymagic: https://github.com/tkf/ipython-hierarchymagic"
|
||
(interactive "P")
|
||
(let ((object (ein:object-at-point)))
|
||
(when ask
|
||
(setq object (read-from-minibuffer "class or object: " object)))
|
||
(assert (and object (not (equal object "")))
|
||
nil "Object at point not found.")
|
||
(ein:shared-output-eval-string (format "%%hierarchy %s" object) t)))
|
||
|
||
(defun ein:pytools-pandas-to-ses (dataframe)
|
||
"View pandas_ DataFrame in SES_ (Simple Emacs Spreadsheet).
|
||
Open a `ses-mode' buffer and import DataFrame object into it.
|
||
|
||
SES_ is distributed with Emacs since Emacs 22, so you don't need
|
||
to install it if you are using newer Emacs.
|
||
|
||
.. _pandas: http://pandas.pydata.org
|
||
.. _SES: http://www.gnu.org/software/emacs/manual/html_node/ses/index.html"
|
||
(interactive (list (read-from-minibuffer "pandas DataFrame "
|
||
(ein:object-at-point))))
|
||
(let ((buffer (get-buffer-create
|
||
(generate-new-buffer-name "*ein:ses pandas*"))))
|
||
;; fetch TSV (tab separated values) via stdout
|
||
(ein:kernel-request-stream
|
||
(ein:get-kernel)
|
||
(concat dataframe ".to_csv(__import__('sys').stdout, sep='\\t')")
|
||
(lambda (tsv buffer)
|
||
(with-current-buffer buffer
|
||
(cl-flet ((y-or-n-p
|
||
(prompt)
|
||
(if (string-prefix-p "Yank will insert " prompt)
|
||
t
|
||
(error "Unexpected prompt: %s" prompt))))
|
||
;; Import DataFrame as TSV
|
||
(ses-yank-tsf tsv nil))
|
||
;; Force SES to update (equivalent to run `post-command-hook').
|
||
(ses-command-hook)))
|
||
(list buffer))
|
||
;; Open `ses-mode' buffer
|
||
(with-current-buffer buffer
|
||
(ses-mode))
|
||
(pop-to-buffer buffer)))
|
||
|
||
(defun ein:pytools-export-buffer (buffer format)
|
||
"Export contents of notebook using nbconvert_ to user-specified format
|
||
\(options will depend on the version of nbconvert available\) to a new buffer.
|
||
|
||
Currently EIN/IPython supports exporting to the following formats:
|
||
|
||
- HTML
|
||
- JSON (this is basically the sames opening the ipynb file in a buffer).
|
||
- Latex
|
||
- Markdown
|
||
- Python
|
||
- RST
|
||
- Slides
|
||
|
||
.. _nbconvert: http://ipython.org/ipython-doc/stable/notebook/nbconvert.html"
|
||
(interactive (list (read-buffer "Buffer: " (current-buffer) t)
|
||
(completing-read "Export format: "
|
||
(list "html"
|
||
"json"
|
||
"latex"
|
||
"markdown"
|
||
"python"
|
||
"rst"
|
||
"slides"))))
|
||
(let* ((nb (first (ein:notebook-opened-notebooks
|
||
#'(lambda (nb)
|
||
(equal (buffer-name (ein:notebook-buffer nb))
|
||
buffer)))))
|
||
(json (ein:content-to-json (ein:content-from-notebook nb)))
|
||
(name (format "*ein %s export: %s*" format (ein:$notebook-notebook-name nb)))
|
||
(buffer (get-buffer-create name)))
|
||
(if (equal format "json")
|
||
(with-current-buffer buffer
|
||
(erase-buffer)
|
||
(insert json)
|
||
(json-pretty-print (point-min) (point-max)))
|
||
(ein:kernel-request-stream
|
||
(ein:get-kernel)
|
||
(format "__import__('ein').export_nb('%s', '%s')"
|
||
(ein:$notebook-notebook-name nb)
|
||
format)
|
||
#'(lambda (export buffer)
|
||
(with-current-buffer buffer
|
||
(erase-buffer)
|
||
(insert export)))
|
||
(list buffer)))
|
||
(switch-to-buffer buffer)))
|
||
|
||
|
||
|
||
(provide 'ein-pytools)
|
||
|
||
;;; ein-pytools.el ends here
|