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

376 lines
14 KiB
EmacsLisp
Raw Normal View History

2012-05-23 04:23:17 +02:00
;;; ein-connect.el --- Connect external buffers to IPython
;; Copyright (C) 2012- Takafumi Arakaki
2012-07-01 20:18:05 +02:00
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
2012-05-23 04:23:17 +02:00
;; This file is NOT part of GNU Emacs.
;; ein-connect.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-connect.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-connect.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
2012-05-23 11:04:22 +02:00
;; FIXME: There is a problem when connected notebook is closed.
;; This can be fixed in some ways:
;; * Turn off ein:connect when the command that uses kernel is invoked
;; but corresponding notebook was closed already.
;; * Connect directly to ein:kernel and make its destructor to care
;; about connecting buffers.
2012-05-23 04:23:17 +02:00
;;; Code:
(require 'eieio)
(eval-when-compile (require 'auto-complete nil t))
2012-05-23 04:23:17 +02:00
(require 'ein)
2012-05-23 04:23:17 +02:00
(require 'ein-notebook)
(declare-function ein:notebooklist-list-notebooks "ein-notebooklist")
(declare-function ein:notebooklist-open-notebook-global "ein-notebooklist")
;;; Utils
(defun ein:maybe-save-buffer (option)
"Conditionally save current buffer.
Return `t' if the buffer is unmodified or `nil' otherwise.
If the buffer is modified, buffer is saved depending on the value
of OPTION:
ask : Ask whether the buffer should be saved.
yes : Save buffer always.
no : Do not save buffer."
(if (not (buffer-modified-p))
t
(case option
(ask (when (y-or-n-p "Save buffer? ")
(save-buffer)
t))
(yes (save-buffer)
t)
(t nil))))
2012-06-08 10:42:33 +02:00
2012-07-29 17:33:08 +02:00
;;; Configuration
2012-05-23 04:23:17 +02:00
2012-08-16 03:43:14 +02:00
(defcustom ein:connect-run-command "%run"
"``%run`` magic command used for `ein:connect-run-buffer'.
Types same as `ein:notebook-console-security-dir' are valid."
:type '(choice
(string :tag "command" "%run")
(alist :tag "command mapping"
:key-type (choice :tag "URL or PORT"
(string :tag "URL" "http://127.0.0.1:8888")
(integer :tag "PORT" 8888)
(const default))
:value-type (string :tag "command" "%run"))
(function :tag "command getter"
(lambda (url-or-port) (format "%%run -n -i -t -d"))))
:group 'ein)
2012-08-16 03:43:14 +02:00
(defcustom ein:connect-reload-command "%run -n"
"Setting for `ein:connect-reload-buffer'.
Same as `ein:connect-run-command'."
:type '(choice
(string :tag "command" "%run")
(alist :tag "command mapping"
:key-type (choice :tag "URL or PORT"
(string :tag "URL" "http://127.0.0.1:8888")
(integer :tag "PORT" 8888)
(const default))
:value-type (string :tag "command" "%run"))
(function :tag "command getter"
(lambda (url-or-port) (format "%%run -n -i -t -d"))))
:group 'ein)
(defun ein:connect-run-command-get ()
(ein:choose-setting 'ein:connect-run-command
(ein:$notebook-url-or-port (ein:connect-get-notebook))))
(defcustom ein:connect-save-before-run 'yes
"Whether the buffer should be saved before `ein:connect-run-buffer'."
:type '(choice (const :tag "Always save buffer" yes)
(const :tag "Always do not save buffer" no)
(const :tag "Ask" ask))
:group 'ein)
2012-07-29 17:33:08 +02:00
(defcustom ein:connect-aotoexec-lighter nil
"String appended to the lighter of `ein:connect-mode' (`ein:c')
when auto-execution mode is on. When `nil', use the same string
as `ein:cell-autoexec-prompt'."
:type '(choice (string :tag "String appended to ein:c" "@")
(const :tag "Use `ein:cell-autoexec-prompt'." nil))
:group 'ein)
;;; Class
2012-05-23 04:23:17 +02:00
(ein:deflocal ein:@connect nil
"Buffer local variable to store an instance of `ein:$connect'")
(defclass ein:$connect ()
((notebook :initarg :notebook :type ein:$notebook)
(buffer :initarg :buffer :type buffer)
2012-07-29 16:52:54 +02:00
(autoexec :initarg :autoexec :initform nil :type boolean
:document "Auto-execution mode flag.
See also the document of the `autoexec' slot of `ein:codecell'
class.")))
2012-05-23 04:23:17 +02:00
(defun ein:connect-setup (notebook buffer)
(with-current-buffer buffer
(setq ein:@connect
(ein:$connect "Connect" :notebook notebook :buffer buffer))
ein:@connect))
2012-06-08 10:42:33 +02:00
;;; Methods
2012-08-14 20:42:18 +02:00
;; FIXME: Clarify names of these `connect-to-*' functions:
2012-08-01 21:04:50 +02:00
(defun ein:connect-to-notebook-command (&optional not-yet-opened)
"Connect to notebook. When the prefix argument is given,
you can choose any notebook on your server including the ones
not yet opened. Otherwise, already chose from already opened
notebooks."
(interactive "P")
(call-interactively (if not-yet-opened
#'ein:connect-to-notebook
#'ein:connect-to-notebook-buffer)))
(defun ein:connect-to-notebook (nbpath)
2012-05-23 11:09:54 +02:00
"Connect any buffer to notebook and its kernel."
2012-05-23 04:23:17 +02:00
(interactive
(list
(completing-read
"Notebook to connect [URL-OR-PORT/NAME]: "
(ein:notebooklist-list-notebooks))))
(ein:notebooklist-open-notebook-global
nbpath
(lambda (notebook -ignore- buffer)
(ein:connect-buffer-to-notebook notebook buffer))
(list (current-buffer))))
(defun ein:connect-to-notebook-buffer (buffer-or-name)
"Connect any buffer to opened notebook and its kernel."
(interactive (list (completing-read "Notebook buffer to connect: "
(ein:notebook-opened-buffer-names))))
(let ((notebook
(buffer-local-value 'ein:notebook (get-buffer buffer-or-name))))
(ein:connect-buffer-to-notebook notebook)))
2012-08-14 19:39:44 +02:00
(defun ein:connect-buffer-to-notebook (notebook &optional buffer
no-reconnection)
"Connect BUFFER to NOTEBOOK."
(unless buffer
(setq buffer (current-buffer)))
2012-08-14 19:39:44 +02:00
(if (or (not no-reconnection)
(not ein:@connect))
(let ((connection (ein:connect-setup notebook buffer)))
(when (ein:eval-if-bound 'ac-sources)
(push 'ac-source-ein-cached ac-sources))
(with-current-buffer buffer
(ein:connect-mode))
(ein:log 'info "Connected to %s"
(ein:$notebook-notebook-name notebook))
connection)
(ein:log 'info "Buffer is already connected to notebook.")))
2012-05-23 04:23:17 +02:00
(defun ein:connect-get-notebook ()
(oref ein:@connect :notebook))
(defun ein:connect-get-kernel ()
(ein:$notebook-kernel (ein:connect-get-notebook)))
(defun ein:connect-eval-buffer ()
"Evaluate the whole buffer. Note that this will run the code
inside the ``if __name__ == \"__main__\":`` block."
2012-05-23 04:23:17 +02:00
(interactive)
(ein:shared-output-eval-string (buffer-string))
(ein:log 'info "Whole buffer is sent to the kernel."))
2012-05-23 04:23:17 +02:00
(defun ein:connect-run-buffer (&optional ask-command)
"Run buffer using ``%run``. Ask for command if the prefix ``C-u`` is given.
Variable `ein:connect-run-command' sets the default command."
(interactive "P")
;; FIXME: this should be more intelligent than just `buffer-file-name'
;; to support connecting IPython over ssh.
(ein:aif (buffer-file-name)
(let* ((default-command (ein:connect-run-command-get))
(command (if ask-command
(read-from-minibuffer "Command: " default-command)
default-command))
(cmd (format "%s %s" command it)))
(if (ein:maybe-save-buffer ein:connect-save-before-run)
(progn
(ein:shared-output-eval-string cmd)
(ein:log 'info "Command sent to the kernel: %s" cmd))
(ein:log 'info "Buffer must be saved before %%run.")))
(error (concat "This buffer has no associated file. "
"Use `ein:connect-eval-buffer' instead."))))
(defun ein:connect-run-or-eval-buffer (&optional eval)
"Run buffer using the ``%run`` magic command or eval whole
buffer if the prefix ``C-u`` is given.
Variable `ein:connect-run-command' sets the command to run.
You can change the command and/or set the options.
See also: `ein:connect-run-buffer', `ein:connect-eval-buffer'."
(interactive "P")
(if eval
(ein:connect-eval-buffer)
(ein:connect-run-buffer)))
2012-08-16 03:43:14 +02:00
(defun ein:connect-reload-buffer ()
"Reload buffer using the command set by `ein:connect-reload-command'."
(interactive)
(let ((ein:connect-run-command ein:connect-reload-command))
(call-interactively #'ein:connect-run-buffer)))
2012-05-23 04:23:17 +02:00
(defun ein:connect-eval-region (start end)
(interactive "r")
(ein:shared-output-eval-string (buffer-substring start end))
(ein:log 'info "Selected region is sent to the kernel."))
(define-obsolete-function-alias
'ein:connect-eval-string-internal
'ein:shared-output-eval-string "0.1.2")
2012-05-23 04:23:17 +02:00
(defun ein:connect-request-tool-tip-command ()
(interactive)
(let ((notebook (ein:connect-get-notebook)))
(ein:kernel-if-ready (ein:$notebook-kernel notebook)
(let ((func (ein:object-at-point)))
;; Set cell=nil. In fact, the argument cell is not used.
;; FIXME: refactor `ein:notebook-request-tool-tip'
(ein:notebook-request-tool-tip notebook nil func)))))
(defun ein:connect-request-help-command ()
2012-05-23 04:23:17 +02:00
(interactive)
(ein:notebook-request-help (ein:connect-get-notebook)))
(defun ein:connect-request-tool-tip-or-help-command (&optional pager)
(interactive "P")
(if pager
(ein:connect-request-help-command)
(ein:connect-request-tool-tip-command)))
2012-05-23 11:26:51 +02:00
(defun ein:connect-complete-command ()
(interactive)
(ein:notebook-complete-at-point (ein:connect-get-notebook)))
(defun ein:connect-complete-dot ()
"Insert dot and request completion."
(interactive)
(insert ".")
(let ((notebook (ein:connect-get-notebook)))
(when (and notebook
(not (ac-cursor-on-diable-face-p))
(ein:kernel-live-p (ein:$notebook-kernel notebook)))
(ein:notebook-complete-at-point notebook))))
2012-05-23 04:23:17 +02:00
(defun ein:connect-pop-to-notebook ()
(interactive)
(pop-to-buffer (ein:notebook-buffer (ein:connect-get-notebook))))
;;; Generic getter
(defun ein:get-url-or-port--connect ()
(ein:aand (ein:get-notebook--connect) (ein:$notebook-url-or-port it)))
(defun ein:get-notebook--connect ()
(when (ein:$connect-p ein:@connect)
(oref ein:@connect :notebook)))
(defun ein:get-kernel--connect ()
(ein:aand (ein:get-notebook--connect) (ein:$notebook-kernel it)))
(defun ein:get-traceback-data--connect ()
;; FIXME: Check if the TB in shared-output buffer is originated from
;; the current buffer.
(ein:aand (ein:shared-output-get-cell) (ein:cell-get-tb-data it)))
(autoload 'ein:shared-output-get-cell "ein-shared-output") ; FIXME: Remove!
;;; Auto-execution
(defun ein:connect-assert-connected ()
(assert (ein:$connect-p ein:@connect) nil
2012-07-29 16:52:54 +02:00
"Current buffer (%s) is not connected to IPython notebook."
(buffer-name)))
(defun ein:connect-execute-autoexec-cells ()
"Call `ein:notebook-execute-autoexec-cells' via `after-save-hook'."
(ein:connect-assert-connected)
(let ((notebook (ein:connect-get-notebook)))
(ein:notebook-with-buffer notebook
(ein:notebook-execute-autoexec-cells notebook))))
(defun ein:connect-toggle-autoexec ()
2012-07-29 16:52:54 +02:00
"Toggle auto-execution mode of the current connected buffer.
Note that you need to set cells to run in the connecting buffer
or no cell will be executed.
Use the `ein:notebook-turn-on-autoexec' command in notebook to
change the cells to run."
(interactive)
(ein:connect-assert-connected)
(oset ein:@connect :autoexec (not (oref ein:@connect :autoexec)))
(let ((autoexec-p (oref ein:@connect :autoexec)))
(if autoexec-p
(add-hook 'after-save-hook 'ein:connect-execute-autoexec-cells nil t)
(remove-hook 'after-save-hook 'ein:connect-execute-autoexec-cells t))
(ein:log 'info "Auto-execution mode is %s."
(if autoexec-p "enabled" "disabled"))))
2012-06-08 10:42:33 +02:00
;;; ein:connect-mode
(defvar ein:connect-mode-map (make-sparse-keymap))
(let ((map ein:connect-mode-map))
(define-key map "\C-c\C-c" 'ein:connect-run-or-eval-buffer)
2012-08-16 03:43:14 +02:00
(define-key map "\C-c\C-l" 'ein:connect-reload-buffer)
(define-key map "\C-c\C-r" 'ein:connect-eval-region)
(define-key map (kbd "C-:") 'ein:shared-output-eval-string)
(define-key map "\C-c\C-f" 'ein:connect-request-tool-tip-or-help-command)
(define-key map "\C-c\C-i" 'ein:connect-complete-command)
(define-key map "\C-c\C-z" 'ein:connect-pop-to-notebook)
(define-key map "\C-c\C-a" 'ein:connect-toggle-autoexec)
(define-key map "\C-c\C-x" 'ein:tb-show)
(define-key map "\M-." 'ein:pytools-jump-to-source-command)
(define-key map (kbd "C-c C-.") 'ein:pytools-jump-to-source-command)
(define-key map "\M-," 'ein:pytools-jump-back-command)
(define-key map (kbd "C-c C-,") 'ein:pytools-jump-back-command)
map)
2012-05-23 04:23:17 +02:00
(defun ein:connect-mode-get-lighter ()
(if (oref ein:@connect :autoexec)
(format " ein:c%s" (or ein:connect-aotoexec-lighter
ein:cell-autoexec-prompt))
" ein:c"))
2012-05-23 04:23:17 +02:00
(define-minor-mode ein:connect-mode
2012-06-02 20:41:41 +02:00
"Minor mode for communicating with IPython notebook.
\\{ein:connect-mode-map}"
:lighter (:eval (ein:connect-mode-get-lighter))
2012-05-23 04:23:17 +02:00
:keymap ein:connect-mode-map
:group 'ein
(ein:complete-on-dot-install ein:connect-mode-map 'ein:connect-complete-dot))
2012-05-23 04:23:17 +02:00
(provide 'ein-connect)
;;; ein-connect.el ends here