ein-notification.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. ;;; ein-notification.el --- Notification widget for Notebook
  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-notification.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-notification.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-notification.el. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;;; Code:
  18. (eval-when-compile (require 'cl))
  19. (require 'eieio)
  20. (require 'ein-core)
  21. (require 'ein-events)
  22. ;; Class and variable
  23. (ein:deflocal ein:%notification% nil
  24. "Buffer local variable to hold an instance of `ein:notification'.")
  25. (define-obsolete-variable-alias 'ein:@notification 'ein:%notification% "0.1.2")
  26. (defvar ein:header-line-format '(:eval (ein:header-line)))
  27. (defvar ein:header-line-tab-map (make-sparse-keymap))
  28. (defvar ein:header-line-insert-tab-map (make-sparse-keymap))
  29. (defvar ein:header-line-tab-help
  30. "\
  31. mouse-1 (left click) : switch to this tab
  32. mouse-3 (right click) : pop to this tab
  33. mouse-2 (middle click) : delete this tab
  34. M-mouse-1/3 (Alt + left/right click): insert new tab to left/right
  35. S-mouse-1/3 (Shift + left/right click): move this tab to left/right"
  36. "Help message.")
  37. ;; Note: can't put this below of `ein:notification-setup'...
  38. (defclass ein:notification-status ()
  39. ((status :initarg :status :initform nil)
  40. (message :initarg :message :initform nil)
  41. (s2m :initarg :s2m))
  42. "Hold status and it's string representation (message).")
  43. (defclass ein:notification-tab ()
  44. ((get-list :initarg :get-list :type function)
  45. (get-current :initarg :get-current :type function)
  46. (get-name :initarg :get-name :type function)
  47. (get-buffer :initarg :get-buffer :type function)
  48. (delete :initarg :delete :type function)
  49. (insert-prev :initarg :insert-prev :type function)
  50. (insert-next :initarg :insert-next :type function)
  51. (move-prev :initarg :move-prev :type function)
  52. (move-next :initarg :move-next :type function)
  53. )
  54. ;; These "methods" are for not depending on what the TABs for.
  55. ;; Probably I'd want change this to be a separated Emacs lisp
  56. ;; library at some point.
  57. "See `ein:notification-setup' for explanation.")
  58. (defclass ein:notification ()
  59. ((buffer :initarg :buffer :type buffer :document "Notebook buffer")
  60. (tab :initarg :tab :type ein:notification-tab)
  61. (execution-count
  62. :initform "y" :initarg :execution-count
  63. :documentation "Last `execution_count' sent by `execute_reply'.")
  64. (notebook
  65. :initarg :notebook
  66. :initform
  67. (ein:notification-status
  68. "NotebookStatus"
  69. :s2m
  70. '((notebook_saving.Notebook . "Saving Notebook...")
  71. (notebook_saved.Notebook . "Notebook is saved")
  72. (notebook_save_failed.Notebook . "Failed to save Notebook!")))
  73. :type ein:notification-status)
  74. (kernel
  75. :initarg :kernel
  76. :initform
  77. (ein:notification-status
  78. "KernelStatus"
  79. :s2m
  80. '((status_idle.Kernel . nil)
  81. (status_busy.Kernel . "Kernel is busy...")
  82. (status_dead.Kernel . "Kernel is dead. Need restart.")))
  83. :type ein:notification-status))
  84. "Notification widget for Notebook.")
  85. (defmethod ein:notification-status-set ((ns ein:notification-status) status)
  86. (let* ((message (cdr (assoc status (oref ns :s2m)))))
  87. (oset ns :status status)
  88. (oset ns :message message)))
  89. (defmethod ein:notification-bind-events ((notification ein:notification)
  90. events)
  91. "Bind a callback to events of the event handler EVENTS which
  92. just set the status \(= event-type):
  93. \(ein:notification-status-set NS EVENT-TYPE)
  94. where NS is `:kernel' or `:notebook' slot of NOTIFICATION."
  95. (loop for ns in (list (oref notification :kernel)
  96. (oref notification :notebook))
  97. for statuses = (mapcar #'car (oref ns :s2m))
  98. do (loop for st in statuses
  99. do (ein:events-on events
  100. st ; = event-type
  101. #'ein:notification--callback
  102. (cons ns st))))
  103. (ein:events-on events
  104. 'notebook_saved.Notebook
  105. #'ein:notification--fadeout-callback
  106. (list (oref notification :notebook)
  107. "Notebook is saved"
  108. 'notebook_saved.Notebook
  109. nil))
  110. (ein:events-on events
  111. 'execution_count.Kernel
  112. #'ein:notification--set-execution-count
  113. notification)
  114. (ein:events-on events
  115. 'status_restarting.Kernel
  116. #'ein:notification--fadeout-callback
  117. (list (oref notification :kernel)
  118. "Restarting kernel..."
  119. 'status_restarting.Kernel
  120. 'status_idle.Kernel)))
  121. (defun ein:notification--callback (packed data)
  122. (let ((ns (car packed))
  123. (status (cdr packed)))
  124. (ein:notification-status-set ns status)))
  125. (defun ein:notification--set-execution-count (notification count)
  126. (oset notification :execution-count count))
  127. (defun ein:notification--fadeout-callback (packed data)
  128. ;; FIXME: I can simplify this.
  129. ;; Do not pass around message, for exmaple.
  130. (let ((ns (nth 0 packed))
  131. (message (nth 1 packed))
  132. (status (nth 2 packed))
  133. (next (nth 3 packed)))
  134. (oset ns :status status)
  135. (oset ns :message message)
  136. (apply #'run-at-time
  137. 1 nil
  138. (lambda (ns message status next)
  139. (when (equal (oref ns :status) status)
  140. (ein:notification-status-set ns next)
  141. (ein:with-live-buffer (oref ns :buffer)
  142. (force-mode-line-update))))
  143. packed)))
  144. (defun ein:notification-setup (buffer events &rest tab-slots)
  145. "Setup a new notification widget in the BUFFER.
  146. This function saves the new notification widget instance in the
  147. local variable of the BUFFER.
  148. Rest of the arguments are for TABs in `header-line'.
  149. GET-LIST : function
  150. Return a list of worksheets.
  151. GET-CURRENT : function
  152. Return the current worksheet.
  153. GET-NAME : function
  154. Return a name of the worksheet given as its argument.
  155. GET-BUFFER : function
  156. Get a buffer of given worksheet. Render it if needed.
  157. DELETE : function
  158. Remove a given worksheet.
  159. INSERT-PREV / INSERT-NEXT : function
  160. Insert new worksheet before/after the specified worksheet.
  161. MOVE-PREV / MOVE-NEXT : function
  162. Switch this worksheet to the previous/next one.
  163. \(fn buffer events &key get-list get-current get-name get-buffer delete \
  164. insert-prev insert-next move-prev move-next)"
  165. (with-current-buffer buffer
  166. (setq ein:%notification%
  167. (ein:notification "NotificationWidget" :buffer buffer))
  168. (setq header-line-format ein:header-line-format)
  169. (ein:notification-bind-events ein:%notification% events)
  170. (oset ein:%notification% :tab
  171. (apply #'make-instance 'ein:notification-tab tab-slots))
  172. ein:%notification%))
  173. ;;; Tabs
  174. (defface ein:notification-tab-selected
  175. '((t :inherit (header-line match) :underline t))
  176. "Face for headline selected tab."
  177. :group 'ein)
  178. (defface ein:notification-tab-normal
  179. '((t :inherit (header-line) :underline t :height 0.8))
  180. "Face for headline selected tab."
  181. :group 'ein)
  182. (defmethod ein:notification-tab-create-line ((tab ein:notification-tab))
  183. (let ((list (funcall (oref tab :get-list)))
  184. (current (funcall (oref tab :get-current)))
  185. (get-name (oref tab :get-name)))
  186. (ein:join-str
  187. " "
  188. (append
  189. (loop for i from 1
  190. for elem in list
  191. if (eq elem current)
  192. collect (propertize
  193. (or (ein:and-let* ((name (funcall get-name elem)))
  194. (format "/%d: %s\\" i name))
  195. (format "/%d\\" i))
  196. 'ein:worksheet elem
  197. 'keymap ein:header-line-tab-map
  198. 'help-echo ein:header-line-tab-help
  199. 'mouse-face 'highlight
  200. 'face 'ein:notification-tab-selected)
  201. else
  202. collect (propertize
  203. (format "/%d\\" i)
  204. 'ein:worksheet elem
  205. 'keymap ein:header-line-tab-map
  206. 'help-echo ein:header-line-tab-help
  207. 'mouse-face 'highlight
  208. 'face 'ein:notification-tab-normal))
  209. (list
  210. (propertize "[+]"
  211. 'keymap ein:header-line-insert-tab-map
  212. 'help-echo "Click (mouse-1) to insert a new tab."
  213. 'mouse-face 'highlight
  214. 'face 'ein:notification-tab-normal))))))
  215. ;;; Header line
  216. (let ((map ein:header-line-tab-map))
  217. (define-key map [header-line M-mouse-1] 'ein:header-line-insert-prev-tab)
  218. (define-key map [header-line M-mouse-3] 'ein:header-line-insert-next-tab)
  219. (define-key map [header-line S-mouse-1] 'ein:header-line-move-prev-tab)
  220. (define-key map [header-line S-mouse-3] 'ein:header-line-move-next-tab)
  221. (define-key map [header-line mouse-1] 'ein:header-line-switch-to-this-tab)
  222. (define-key map [header-line mouse-2] 'ein:header-line-delete-this-tab)
  223. (define-key map [header-line mouse-3] 'ein:header-line-pop-to-this-tab))
  224. (define-key ein:header-line-insert-tab-map
  225. [header-line mouse-1] 'ein:header-line-insert-new-tab)
  226. (defmacro ein:with-destructuring-bind-key-event (key-event &rest body)
  227. (declare (debug (form &rest form))
  228. (indent 1))
  229. ;; See: (info "(elisp) Click Events")
  230. `(destructuring-bind
  231. (event-type
  232. (window pos-or-area (x . y) timestamp
  233. object text-pos (col . row)
  234. image (dx . dy) (width . height)))
  235. ,key-event
  236. ,@body))
  237. (defun ein:header-line-select-window (key-event)
  238. (ein:with-destructuring-bind-key-event key-event (select-window window)))
  239. (defun ein:header-line-key-event-get-worksheet (key-event)
  240. (ein:with-destructuring-bind-key-event key-event
  241. (get-char-property (cdr object) 'ein:worksheet (car object))))
  242. (defun ein:header-line-key-event-get-buffer (key-event)
  243. (funcall (oref (oref ein:%notification% :tab) :get-buffer)
  244. (ein:header-line-key-event-get-worksheet key-event)))
  245. (defun ein:header-line-switch-to-this-tab (key-event)
  246. (interactive "e")
  247. (ein:header-line-select-window key-event)
  248. (switch-to-buffer (ein:header-line-key-event-get-buffer key-event)))
  249. (defun ein:header-line-pop-to-this-tab (key-event)
  250. (interactive "e")
  251. (ein:header-line-select-window key-event)
  252. (pop-to-buffer (ein:header-line-key-event-get-buffer key-event)))
  253. (defun ein:header-line-do-slot-function (key-event slot)
  254. "Call SLOT function on worksheet instance fetched from KEY-EVENT."
  255. (ein:header-line-select-window key-event)
  256. (funcall (slot-value (oref ein:%notification% :tab) slot)
  257. (ein:header-line-key-event-get-worksheet key-event)))
  258. (defmacro ein:header-line-define-mouse-commands (&rest name-slot-list)
  259. `(progn
  260. ,@(loop for (name slot) on name-slot-list by 'cddr
  261. collect
  262. `(defun ,name (key-event)
  263. ,(format "Run slot %s
  264. Generated by `ein:header-line-define-mouse-commands'" slot)
  265. (interactive "e")
  266. (ein:header-line-do-slot-function key-event ,slot)))))
  267. (ein:header-line-define-mouse-commands
  268. ein:header-line-delete-this-tab :delete
  269. ein:header-line-insert-prev-tab :insert-prev
  270. ein:header-line-insert-next-tab :insert-next
  271. ein:header-line-move-prev-tab :move-prev
  272. ein:header-line-move-next-tab :move-next
  273. )
  274. (defun ein:header-line-insert-new-tab (key-event)
  275. "Insert new tab."
  276. (interactive "e")
  277. (ein:header-line-select-window key-event)
  278. (let ((notification (oref ein:%notification% :tab)))
  279. (funcall (oref notification :insert-next)
  280. (car (last (funcall (oref notification :get-list)))))))
  281. (defun ein:header-line ()
  282. (format
  283. "IP[%s]: %s"
  284. (oref ein:%notification% :execution-count)
  285. (ein:join-str
  286. " | "
  287. (ein:filter
  288. 'identity
  289. (list (oref (oref ein:%notification% :notebook) :message)
  290. (oref (oref ein:%notification% :kernel) :message)
  291. (ein:notification-tab-create-line
  292. (oref ein:%notification% :tab)))))))
  293. (defun ein:header-line-setup-maybe ()
  294. "Setup `header-line-format' for mumamo.
  295. As `header-line-format' is buffer local variable, it must be set
  296. for each chunk when in
  297. See also `ein:ac-setup-maybe'."
  298. (and (ein:eval-if-bound 'ein:%notebook%)
  299. (ein:eval-if-bound 'mumamo-multi-major-mode)
  300. (setq header-line-format ein:header-line-format)))
  301. (add-hook 'after-change-major-mode-hook 'ein:header-line-setup-maybe)
  302. (provide 'ein-notification)
  303. ;;; ein-notification.el ends here