123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- ;;; ein-notification.el --- Notification widget for Notebook
- ;; Copyright (C) 2012- Takafumi Arakaki
- ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
- ;; This file is NOT part of GNU Emacs.
- ;; ein-notification.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-notification.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-notification.el. If not, see <http://www.gnu.org/licenses/>.
- ;;; Commentary:
- ;;
- ;;; Code:
- (eval-when-compile (require 'cl))
- (require 'eieio)
- (require 'ein-core)
- (require 'ein-events)
- ;; Class and variable
- (ein:deflocal ein:%notification% nil
- "Buffer local variable to hold an instance of `ein:notification'.")
- (define-obsolete-variable-alias 'ein:@notification 'ein:%notification% "0.1.2")
- (defvar ein:header-line-format '(:eval (ein:header-line)))
- (defvar ein:header-line-tab-map (make-sparse-keymap))
- (defvar ein:header-line-insert-tab-map (make-sparse-keymap))
- (defvar ein:header-line-tab-help
- "\
- mouse-1 (left click) : switch to this tab
- mouse-3 (right click) : pop to this tab
- mouse-2 (middle click) : delete this tab
- M-mouse-1/3 (Alt + left/right click): insert new tab to left/right
- S-mouse-1/3 (Shift + left/right click): move this tab to left/right"
- "Help message.")
- ;; Note: can't put this below of `ein:notification-setup'...
- (defclass ein:notification-status ()
- ((status :initarg :status :initform nil)
- (message :initarg :message :initform nil)
- (s2m :initarg :s2m))
- "Hold status and it's string representation (message).")
- (defclass ein:notification-tab ()
- ((get-list :initarg :get-list :type function)
- (get-current :initarg :get-current :type function)
- (get-name :initarg :get-name :type function)
- (get-buffer :initarg :get-buffer :type function)
- (delete :initarg :delete :type function)
- (insert-prev :initarg :insert-prev :type function)
- (insert-next :initarg :insert-next :type function)
- (move-prev :initarg :move-prev :type function)
- (move-next :initarg :move-next :type function)
- )
- ;; These "methods" are for not depending on what the TABs for.
- ;; Probably I'd want change this to be a separated Emacs lisp
- ;; library at some point.
- "See `ein:notification-setup' for explanation.")
- (defclass ein:notification ()
- ((buffer :initarg :buffer :type buffer :document "Notebook buffer")
- (tab :initarg :tab :type ein:notification-tab)
- (execution-count
- :initform "y" :initarg :execution-count
- :documentation "Last `execution_count' sent by `execute_reply'.")
- (notebook
- :initarg :notebook
- :initform
- (ein:notification-status
- "NotebookStatus"
- :s2m
- '((notebook_saving.Notebook . "Saving Notebook...")
- (notebook_saved.Notebook . "Notebook is saved")
- (notebook_save_failed.Notebook . "Failed to save Notebook!")))
- :type ein:notification-status)
- (kernel
- :initarg :kernel
- :initform
- (ein:notification-status
- "KernelStatus"
- :s2m
- '((status_idle.Kernel . nil)
- (status_busy.Kernel . "Kernel is busy...")
- (status_dead.Kernel . "Kernel is dead. Need restart.")))
- :type ein:notification-status))
- "Notification widget for Notebook.")
- (defmethod ein:notification-status-set ((ns ein:notification-status) status)
- (let* ((message (cdr (assoc status (oref ns :s2m)))))
- (oset ns :status status)
- (oset ns :message message)))
- (defmethod ein:notification-bind-events ((notification ein:notification)
- events)
- "Bind a callback to events of the event handler EVENTS which
- just set the status \(= event-type):
- \(ein:notification-status-set NS EVENT-TYPE)
- where NS is `:kernel' or `:notebook' slot of NOTIFICATION."
- (loop for ns in (list (oref notification :kernel)
- (oref notification :notebook))
- for statuses = (mapcar #'car (oref ns :s2m))
- do (loop for st in statuses
- do (ein:events-on events
- st ; = event-type
- #'ein:notification--callback
- (cons ns st))))
- (ein:events-on events
- 'notebook_saved.Notebook
- #'ein:notification--fadeout-callback
- (list (oref notification :notebook)
- "Notebook is saved"
- 'notebook_saved.Notebook
- nil))
- (ein:events-on events
- 'execution_count.Kernel
- #'ein:notification--set-execution-count
- notification)
- (ein:events-on events
- 'status_restarting.Kernel
- #'ein:notification--fadeout-callback
- (list (oref notification :kernel)
- "Restarting kernel..."
- 'status_restarting.Kernel
- 'status_idle.Kernel)))
- (defun ein:notification--callback (packed data)
- (let ((ns (car packed))
- (status (cdr packed)))
- (ein:notification-status-set ns status)))
- (defun ein:notification--set-execution-count (notification count)
- (oset notification :execution-count count))
- (defun ein:notification--fadeout-callback (packed data)
- ;; FIXME: I can simplify this.
- ;; Do not pass around message, for exmaple.
- (let ((ns (nth 0 packed))
- (message (nth 1 packed))
- (status (nth 2 packed))
- (next (nth 3 packed)))
- (oset ns :status status)
- (oset ns :message message)
- (apply #'run-at-time
- 1 nil
- (lambda (ns message status next)
- (when (equal (oref ns :status) status)
- (ein:notification-status-set ns next)
- (ein:with-live-buffer (oref ns :buffer)
- (force-mode-line-update))))
- packed)))
- (defun ein:notification-setup (buffer events &rest tab-slots)
- "Setup a new notification widget in the BUFFER.
- This function saves the new notification widget instance in the
- local variable of the BUFFER.
- Rest of the arguments are for TABs in `header-line'.
- GET-LIST : function
- Return a list of worksheets.
- GET-CURRENT : function
- Return the current worksheet.
- GET-NAME : function
- Return a name of the worksheet given as its argument.
- GET-BUFFER : function
- Get a buffer of given worksheet. Render it if needed.
- DELETE : function
- Remove a given worksheet.
- INSERT-PREV / INSERT-NEXT : function
- Insert new worksheet before/after the specified worksheet.
- MOVE-PREV / MOVE-NEXT : function
- Switch this worksheet to the previous/next one.
- \(fn buffer events &key get-list get-current get-name get-buffer delete \
- insert-prev insert-next move-prev move-next)"
- (with-current-buffer buffer
- (setq ein:%notification%
- (ein:notification "NotificationWidget" :buffer buffer))
- (setq header-line-format ein:header-line-format)
- (ein:notification-bind-events ein:%notification% events)
- (oset ein:%notification% :tab
- (apply #'make-instance 'ein:notification-tab tab-slots))
- ein:%notification%))
- ;;; Tabs
- (defface ein:notification-tab-selected
- '((t :inherit (header-line match) :underline t))
- "Face for headline selected tab."
- :group 'ein)
- (defface ein:notification-tab-normal
- '((t :inherit (header-line) :underline t :height 0.8))
- "Face for headline selected tab."
- :group 'ein)
- (defmethod ein:notification-tab-create-line ((tab ein:notification-tab))
- (let ((list (funcall (oref tab :get-list)))
- (current (funcall (oref tab :get-current)))
- (get-name (oref tab :get-name)))
- (ein:join-str
- " "
- (append
- (loop for i from 1
- for elem in list
- if (eq elem current)
- collect (propertize
- (or (ein:and-let* ((name (funcall get-name elem)))
- (format "/%d: %s\\" i name))
- (format "/%d\\" i))
- 'ein:worksheet elem
- 'keymap ein:header-line-tab-map
- 'help-echo ein:header-line-tab-help
- 'mouse-face 'highlight
- 'face 'ein:notification-tab-selected)
- else
- collect (propertize
- (format "/%d\\" i)
- 'ein:worksheet elem
- 'keymap ein:header-line-tab-map
- 'help-echo ein:header-line-tab-help
- 'mouse-face 'highlight
- 'face 'ein:notification-tab-normal))
- (list
- (propertize "[+]"
- 'keymap ein:header-line-insert-tab-map
- 'help-echo "Click (mouse-1) to insert a new tab."
- 'mouse-face 'highlight
- 'face 'ein:notification-tab-normal))))))
- ;;; Header line
- (let ((map ein:header-line-tab-map))
- (define-key map [header-line M-mouse-1] 'ein:header-line-insert-prev-tab)
- (define-key map [header-line M-mouse-3] 'ein:header-line-insert-next-tab)
- (define-key map [header-line S-mouse-1] 'ein:header-line-move-prev-tab)
- (define-key map [header-line S-mouse-3] 'ein:header-line-move-next-tab)
- (define-key map [header-line mouse-1] 'ein:header-line-switch-to-this-tab)
- (define-key map [header-line mouse-2] 'ein:header-line-delete-this-tab)
- (define-key map [header-line mouse-3] 'ein:header-line-pop-to-this-tab))
- (define-key ein:header-line-insert-tab-map
- [header-line mouse-1] 'ein:header-line-insert-new-tab)
- (defmacro ein:with-destructuring-bind-key-event (key-event &rest body)
- (declare (debug (form &rest form))
- (indent 1))
- ;; See: (info "(elisp) Click Events")
- `(destructuring-bind
- (event-type
- (window pos-or-area (x . y) timestamp
- object text-pos (col . row)
- image (dx . dy) (width . height)))
- ,key-event
- ,@body))
- (defun ein:header-line-select-window (key-event)
- (ein:with-destructuring-bind-key-event key-event (select-window window)))
- (defun ein:header-line-key-event-get-worksheet (key-event)
- (ein:with-destructuring-bind-key-event key-event
- (get-char-property (cdr object) 'ein:worksheet (car object))))
- (defun ein:header-line-key-event-get-buffer (key-event)
- (funcall (oref (oref ein:%notification% :tab) :get-buffer)
- (ein:header-line-key-event-get-worksheet key-event)))
- (defun ein:header-line-switch-to-this-tab (key-event)
- (interactive "e")
- (ein:header-line-select-window key-event)
- (switch-to-buffer (ein:header-line-key-event-get-buffer key-event)))
- (defun ein:header-line-pop-to-this-tab (key-event)
- (interactive "e")
- (ein:header-line-select-window key-event)
- (pop-to-buffer (ein:header-line-key-event-get-buffer key-event)))
- (defun ein:header-line-do-slot-function (key-event slot)
- "Call SLOT function on worksheet instance fetched from KEY-EVENT."
- (ein:header-line-select-window key-event)
- (funcall (slot-value (oref ein:%notification% :tab) slot)
- (ein:header-line-key-event-get-worksheet key-event)))
- (defmacro ein:header-line-define-mouse-commands (&rest name-slot-list)
- `(progn
- ,@(loop for (name slot) on name-slot-list by 'cddr
- collect
- `(defun ,name (key-event)
- ,(format "Run slot %s
- Generated by `ein:header-line-define-mouse-commands'" slot)
- (interactive "e")
- (ein:header-line-do-slot-function key-event ,slot)))))
- (ein:header-line-define-mouse-commands
- ein:header-line-delete-this-tab :delete
- ein:header-line-insert-prev-tab :insert-prev
- ein:header-line-insert-next-tab :insert-next
- ein:header-line-move-prev-tab :move-prev
- ein:header-line-move-next-tab :move-next
- )
- (defun ein:header-line-insert-new-tab (key-event)
- "Insert new tab."
- (interactive "e")
- (ein:header-line-select-window key-event)
- (let ((notification (oref ein:%notification% :tab)))
- (funcall (oref notification :insert-next)
- (car (last (funcall (oref notification :get-list)))))))
- (defun ein:header-line ()
- (format
- "IP[%s]: %s"
- (oref ein:%notification% :execution-count)
- (ein:join-str
- " | "
- (ein:filter
- 'identity
- (list (oref (oref ein:%notification% :notebook) :message)
- (oref (oref ein:%notification% :kernel) :message)
- (ein:notification-tab-create-line
- (oref ein:%notification% :tab)))))))
- (defun ein:header-line-setup-maybe ()
- "Setup `header-line-format' for mumamo.
- As `header-line-format' is buffer local variable, it must be set
- for each chunk when in
- See also `ein:ac-setup-maybe'."
- (and (ein:eval-if-bound 'ein:%notebook%)
- (ein:eval-if-bound 'mumamo-multi-major-mode)
- (setq header-line-format ein:header-line-format)))
- (add-hook 'after-change-major-mode-hook 'ein:header-line-setup-maybe)
- (provide 'ein-notification)
- ;;; ein-notification.el ends here
|