Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
15 KiB

  1. ;;; ein-connect.el --- Connect external buffers to IPython -*- lexical-binding: t -*-
  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-connect.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-connect.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-connect.el. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; FIXME: There is a problem when connected notebook is closed.
  17. ;; This can be fixed in some ways:
  18. ;; * Turn off ein:connect when the command that uses kernel is invoked
  19. ;; but corresponding notebook was closed already.
  20. ;; * Connect directly to ein:kernel and make its destructor to care
  21. ;; about connecting buffers.
  22. ;;; Code:
  23. (require 'eieio)
  24. (require 'company nil t)
  25. (require 'ein-notebook)
  26. (eval-when-compile (require 'auto-complete))
  27. (autoload 'company-mode "company")
  28. ;;; Utils
  29. (defun ein:maybe-save-buffer (option)
  30. "Conditionally save current buffer.
  31. Return `t' if the buffer is unmodified or `nil' otherwise.
  32. If the buffer is modified, buffer is saved depending on the value
  33. of OPTION:
  34. ask : Ask whether the buffer should be saved.
  35. yes : Save buffer always.
  36. no : Do not save buffer."
  37. (if (not (buffer-modified-p))
  38. t
  39. (cl-case option
  40. (ask (when (y-or-n-p "Save buffer? ")
  41. (save-buffer)
  42. t))
  43. (yes (save-buffer)
  44. t)
  45. (t nil))))
  46. ;;; Configuration
  47. (defcustom ein:connect-run-command "%run"
  48. "``%run`` magic command used for `ein:connect-run-buffer'.
  49. Types same as `ein:console-security-dir' are valid."
  50. :type '(choice
  51. (string :tag "command" "%run")
  52. (alist :tag "command mapping"
  53. :key-type (choice :tag "URL or PORT"
  54. (string :tag "URL" "http://127.0.0.1:8888")
  55. (integer :tag "PORT" 8888)
  56. (const default))
  57. :value-type (string :tag "command" "%run"))
  58. (function :tag "command getter"
  59. (lambda (url-or-port) (format "%%run -n -i -t -d"))))
  60. :group 'ein)
  61. (defcustom ein:connect-reload-command "%run -n"
  62. "Setting for `ein:connect-reload-buffer'.
  63. Same as `ein:connect-run-command'."
  64. :type '(choice
  65. (string :tag "command" "%run")
  66. (alist :tag "command mapping"
  67. :key-type (choice :tag "URL or PORT"
  68. (string :tag "URL" "http://127.0.0.1:8888")
  69. (integer :tag "PORT" 8888)
  70. (const default))
  71. :value-type (string :tag "command" "%run"))
  72. (function :tag "command getter"
  73. (lambda (url-or-port) (format "%%run -n -i -t -d"))))
  74. :group 'ein)
  75. (defun ein:connect-run-command-get ()
  76. (ein:choose-setting 'ein:connect-run-command
  77. (ein:$notebook-url-or-port (ein:connect-get-notebook))))
  78. (defcustom ein:connect-save-before-run 'yes
  79. "Whether the buffer should be saved before `ein:connect-run-buffer'."
  80. :type '(choice (const :tag "Always save buffer" yes)
  81. (const :tag "Always do not save buffer" no)
  82. (const :tag "Ask" ask))
  83. :group 'ein)
  84. (defcustom ein:connect-aotoexec-lighter nil
  85. "String appended to the lighter of `ein:connect-mode' (`ein:c')
  86. when auto-execution mode is on. When `nil', use the same string
  87. as `ein:cell-autoexec-prompt'."
  88. :type '(choice (string :tag "String appended to ein:c" "@")
  89. (const :tag "Use `ein:cell-autoexec-prompt'." nil))
  90. :group 'ein)
  91. (defcustom ein:connect-default-notebook nil
  92. "Notebook to be connect when `ein:connect-to-default-notebook' is called.
  93. Example setting to connect to \"My_Notebook\" in the server at
  94. port 8888 when opening any buffer in `python-mode'::
  95. (setq ein:connect-default-notebook \"8888/My_Notebook\")
  96. (add-hook 'python-mode-hook 'ein:connect-to-default-notebook)
  97. `ein:connect-default-notebook' can also be a function without any
  98. argument. This function must return a string (notebook path of
  99. the form \"URL-OR-PORT/NOTEBOOK-NAME\").
  100. As `ein:connect-to-default-notebook' requires notebook list to be
  101. loaded, consider using `ein:notebooklist-load' to load notebook
  102. list if you want to connect to notebook without manually opening
  103. notebook list."
  104. :type '(choice (string :tag "URL-OR-PORT/NOTEBOOK-NAME")
  105. (function :tag "Notebook path getter"))
  106. :group 'ein)
  107. ;;; Class
  108. (ein:deflocal ein:%connect% nil
  109. "Buffer local variable to store an instance of `ein:connect'")
  110. (define-obsolete-variable-alias 'ein:@connect 'ein:%connect% "0.1.2")
  111. (defclass ein:connect ()
  112. ((notebook :initarg :notebook :type ein:$notebook)
  113. (buffer :initarg :buffer :type buffer)
  114. (autoexec :initarg :autoexec :initform nil :type boolean
  115. :document "Auto-execution mode flag.
  116. See also the document of the `autoexec' slot of `ein:codecell'
  117. class.")))
  118. (defun ein:connect-setup (notebook buffer)
  119. (with-current-buffer buffer
  120. (setq ein:%connect%
  121. (ein:connect :notebook notebook :buffer buffer))
  122. ein:%connect%))
  123. ;;; Methods
  124. ;; FIXME: Clarify names of these `connect-to-*' functions:
  125. ;;;###autoload
  126. (defun ein:connect-to-notebook-command (&optional not-yet-opened)
  127. "Connect to notebook. When the prefix argument is given,
  128. you can choose any notebook on your server including the ones
  129. not yet opened. Otherwise, already chose from already opened
  130. notebooks."
  131. (interactive "P")
  132. (call-interactively (if not-yet-opened
  133. #'ein:connect-to-notebook
  134. #'ein:connect-to-notebook-buffer)))
  135. ;;;###autoload
  136. (defun ein:connect-to-notebook (nbpath &optional buffer no-reconnection)
  137. "Connect any buffer to notebook and its kernel."
  138. (interactive (list (ein:notebooklist-ask-path "notebook")))
  139. (cl-multiple-value-bind (url-or-port path) (ein:notebooklist-parse-nbpath nbpath)
  140. (ein:notebook-open url-or-port path nil
  141. (apply-partially
  142. (lambda (buffer* no-reconnection* notebook _created)
  143. (ein:connect-buffer-to-notebook notebook buffer* no-reconnection*))
  144. (or buffer (current-buffer)) no-reconnection))))
  145. ;;;###autoload
  146. (defun ein:connect-to-notebook-buffer (buffer-or-name)
  147. "Connect any buffer to opened notebook and its kernel."
  148. (interactive (list (ein:completing-read "Notebook buffer to connect: "
  149. (ein:notebook-opened-buffer-names))))
  150. (ein:aif (get-buffer buffer-or-name)
  151. (let ((notebook (buffer-local-value 'ein:%notebook% it)))
  152. (ein:connect-buffer-to-notebook notebook))
  153. (error "No buffer %s" buffer-or-name)))
  154. ;;;###autoload
  155. (defun ein:connect-buffer-to-notebook (notebook &optional buffer
  156. no-reconnection)
  157. "Connect BUFFER to NOTEBOOK."
  158. (unless buffer
  159. (setq buffer (current-buffer)))
  160. (with-current-buffer buffer
  161. (if (or (not no-reconnection)
  162. (not ein:%connect%))
  163. (let ((connection (ein:connect-setup notebook buffer)))
  164. (when (ein:eval-if-bound 'ac-sources)
  165. (push 'ac-source-ein-async ac-sources))
  166. (ein:connect-mode)
  167. (ein:log 'info "Connected to %s"
  168. (ein:$notebook-notebook-name notebook))
  169. connection)
  170. (ein:log 'info "Buffer is already connected to notebook."))))
  171. (defun ein:connect-get-notebook ()
  172. (slot-value ein:%connect% 'notebook))
  173. (defun ein:connect-get-kernel ()
  174. (ein:$notebook-kernel (ein:connect-get-notebook)))
  175. (defun ein:connect-eval-buffer ()
  176. "Evaluate the whole buffer. Note that this will run the code
  177. inside the ``if __name__ == \"__main__\":`` block."
  178. (interactive)
  179. (let ((b (current-buffer)))
  180. (deferred:$
  181. (deferred:next
  182. (lambda ()
  183. (with-current-buffer b
  184. (ein:shared-output-eval-string (ein:connect-get-kernel) (buffer-string) nil :silent t))))
  185. (deferred:nextc it
  186. (lambda ()
  187. (with-current-buffer b
  188. (ein:connect-execute-autoexec-cells))))))
  189. (ein:log 'info "Whole buffer is sent to the kernel."))
  190. (defun ein:connect-run-buffer (&optional ask-command)
  191. "Run buffer using ``%run``. Ask for command if the prefix ``C-u`` is given.
  192. Variable `ein:connect-run-command' sets the default command."
  193. (interactive "P")
  194. (ein:aif (ein:aand (ein:get-url-or-port)
  195. (ein:filename-to-python it (buffer-file-name)))
  196. (let* ((default-command (ein:connect-run-command-get))
  197. (command (if ask-command
  198. (read-from-minibuffer "Command: " default-command)
  199. default-command))
  200. (cmd (format "%s \"%s\"" command it)))
  201. (if (ein:maybe-save-buffer ein:connect-save-before-run)
  202. (progn
  203. (ein:shared-output-eval-string (ein:connect-get-kernel) cmd nil :silent t)
  204. (ein:connect-execute-autoexec-cells)
  205. (ein:log 'info "Command sent to the kernel: %s" cmd))
  206. (ein:log 'info "Buffer must be saved before %%run.")))
  207. (error (concat "This buffer has no associated file. "
  208. "Use `ein:connect-eval-buffer' instead."))))
  209. (defun ein:connect-run-or-eval-buffer (&optional eval)
  210. "Run buffer using the ``%run`` magic command or eval whole
  211. buffer if the prefix ``C-u`` is given.
  212. Variable `ein:connect-run-command' sets the command to run.
  213. You can change the command and/or set the options.
  214. See also: `ein:connect-run-buffer', `ein:connect-eval-buffer'."
  215. (interactive "P")
  216. (if eval
  217. (ein:connect-eval-buffer)
  218. (ein:connect-run-buffer)))
  219. (defun ein:connect-reload-buffer ()
  220. "Reload buffer using the command set by `ein:connect-reload-command'."
  221. (interactive)
  222. (let ((ein:connect-run-command ein:connect-reload-command))
  223. (call-interactively #'ein:connect-run-buffer)))
  224. (defun ein:connect-eval-region (start end)
  225. (interactive "r")
  226. (ein:shared-output-eval-string (ein:connect-get-kernel) (buffer-substring start end) nil)
  227. (ein:log 'info "Selected region is sent to the kernel."))
  228. (define-obsolete-function-alias
  229. 'ein:connect-eval-string-internal
  230. 'ein:shared-output-eval-string "0.1.2")
  231. (define-obsolete-function-alias
  232. 'ein:connect-request-tool-tip-or-help-command
  233. 'ein:pytools-request-tooltip-or-help "0.1.2")
  234. (defun ein:connect-pop-to-notebook ()
  235. (interactive)
  236. (ein:connect-assert-connected)
  237. (pop-to-buffer (ein:notebook-buffer (ein:connect-get-notebook))))
  238. ;;; Generic getter
  239. (defun ein:get-url-or-port--connect ()
  240. (ein:aand (ein:get-notebook--connect) (ein:$notebook-url-or-port it)))
  241. (defun ein:get-notebook--connect ()
  242. (when (ein:connect-p ein:%connect%)
  243. (slot-value ein:%connect% 'notebook)))
  244. (defun ein:get-kernel--connect ()
  245. (ein:aand (ein:get-notebook--connect) (ein:$notebook-kernel it)))
  246. (defun ein:get-traceback-data--connect ()
  247. ;; FIXME: Check if the TB in shared-output buffer is originated from
  248. ;; the current buffer.
  249. (ein:aand (ein:shared-output-get-cell) (ein:cell-get-tb-data it)))
  250. (autoload 'ein:shared-output-get-cell "ein-shared-output") ; FIXME: Remove!
  251. ;;; Auto-execution
  252. (defun ein:connect-assert-connected ()
  253. (cl-assert (ein:connect-p ein:%connect%) nil
  254. "Current buffer (%s) is not connected to IPython notebook."
  255. (buffer-name))
  256. (cl-assert (ein:notebook-live-p (slot-value ein:%connect% 'notebook)) nil
  257. "Connected notebook is not live (probably already closed)."))
  258. (defun ein:connect-execute-autoexec-cells ()
  259. "Call `ein:notebook-execute-autoexec-cells' via `after-save-hook'."
  260. (ein:connect-assert-connected)
  261. (when (slot-value ein:%connect% 'autoexec)
  262. (ein:notebook-execute-autoexec-cells (ein:connect-get-notebook))))
  263. (defun ein:connect-toggle-autoexec ()
  264. "Toggle auto-execution mode of the current connected buffer.
  265. When auto-execution mode is on, cells in connected notebook will
  266. be automatically executed whenever run, eval or reload command [#]_
  267. is called in this buffer.
  268. .. [#] Namely, one of
  269. * `ein:connect-run-buffer'
  270. * `ein:connect-eval-buffer'
  271. * `ein:connect-run-or-eval-buffer'
  272. * `ein:connect-reload-buffer'
  273. Note that you need to set cells to run in the connecting buffer
  274. or no cell will be executed.
  275. Use the `ein:worksheet-turn-on-autoexec' command in notebook to
  276. change the cells to run."
  277. (interactive)
  278. (ein:connect-assert-connected)
  279. (let ((autoexec-p (not (slot-value ein:%connect% 'autoexec))))
  280. (setf (slot-value ein:%connect% 'autoexec) autoexec-p)
  281. (ein:log 'info "Auto-execution mode is %s."
  282. (if autoexec-p "enabled" "disabled"))))
  283. ;;; Auto-connect
  284. ;;;###autoload
  285. (defun ein:connect-to-default-notebook ()
  286. "Connect to the default notebook specified by
  287. `ein:connect-default-notebook'. Set this to `python-mode-hook'
  288. to automatically connect any python-mode buffer to the
  289. notebook."
  290. (ein:log 'verbose "CONNECT-TO-DEFAULT-NOTEBOOK")
  291. (ein:and-let* ((nbpath ein:connect-default-notebook)
  292. ((not (ein:worksheet-buffer-p))))
  293. (when (functionp nbpath)
  294. (setq nbpath (funcall nbpath)))
  295. (ein:connect-to-notebook nbpath nil t)))
  296. ;;; ein:connect-mode
  297. (defvar ein:connect-mode-map (make-sparse-keymap))
  298. (let ((map ein:connect-mode-map))
  299. (define-key map "\C-c\C-c" 'ein:connect-run-or-eval-buffer)
  300. (define-key map "\C-c\C-l" 'ein:connect-reload-buffer)
  301. (define-key map "\C-c\C-r" 'ein:connect-eval-region)
  302. (define-key map (kbd "C-:") 'ein:shared-output-eval-string)
  303. (define-key map "\C-c\C-h" 'ein:pytools-request-tooltip-or-help)
  304. (define-key map "\C-c\C-i" 'ein:completer-complete)
  305. (define-key map "\C-c\C-z" 'ein:connect-pop-to-notebook)
  306. (define-key map "\C-c\C-a" 'ein:connect-toggle-autoexec)
  307. (define-key map "\C-c\C-o" 'ein:console-open)
  308. (define-key map "\C-c\C-x" 'ein:tb-show)
  309. (define-key map "\M-." 'ein:pytools-jump-to-source-command)
  310. (define-key map (kbd "C-c C-.") 'ein:pytools-jump-to-source-command)
  311. (define-key map "\M-," 'ein:pytools-jump-back-command)
  312. (define-key map (kbd "C-c C-,") 'ein:pytools-jump-back-command)
  313. (define-key map (kbd "C-c C-/") 'ein:notebook-scratchsheet-open)
  314. map)
  315. (defun ein:connect-mode-get-lighter ()
  316. (if (slot-value ein:%connect% 'autoexec)
  317. (format " ein:c%s" (or ein:connect-aotoexec-lighter
  318. ein:cell-autoexec-prompt))
  319. " ein:c"))
  320. (define-minor-mode ein:connect-mode
  321. "Minor mode for communicating with IPython notebook.
  322. \\{ein:connect-mode-map}"
  323. :lighter (:eval (ein:connect-mode-get-lighter))
  324. :keymap ein:connect-mode-map
  325. :group 'ein
  326. (cl-case ein:completion-backend
  327. (ein:use-ac-backend
  328. (define-key ein:connect-mode-map "." 'ein:ac-dot-complete)
  329. (auto-complete-mode))
  330. (ein:use-company-backend
  331. (add-to-list 'company-backends #'ein:company-backend)
  332. (company-mode))))
  333. (put 'ein:connect-mode 'permanent-local t)
  334. (provide 'ein-connect)
  335. ;;; ein-connect.el ends here