;;; ein-cell-edit.el --- Notebook cell editing ;; Copyright (C) 2016 John M. Miller ;; Author: John Miller ;; This file is NOT part of GNU Emacs. ;; ein-cell-edit.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-cell-edit.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-worksheet.el. If not, see . ;;; Commentary: ;; This code inspired by borrowing from org-src.el. ;;; Code: (require 'ein-cell) (autoload 'julia-mode "julia-mode") (autoload 'markdown-mode "markdown-mode") (autoload 'R-mode "ess-r-mode") (autoload 'org-src--remove-overlay "org-src") (autoload 'org-src-switch-to-buffer "org-src") (defvar ein:src--cell nil) (defvar ein:src--ws nil) (defvar ein:src--allow-write-back t) (defvar ein:src--overlay nil) (defvar ein:src--saved-window-config nil) (declare-function ein:notebook--get-nb-or-error "ein-notebook" ()) (defvar ein:edit-cell-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c'" 'ein:edit-cell-exit) (define-key map "\C-c\C-k" 'ein:edit-cell-abort) (define-key map "\C-c\C-c" 'ein:edit-cell-save-and-execute) (define-key map "\C-x\C-s" 'ein:edit-cell-save) (define-key map "\C-c\C-x" 'ein:edit-cell-view-traceback) map)) (define-minor-mode ein:edit-cell-mode "Minor mode for language major mode buffers generated by EIN. This minor mode is turned on when editing a source code snippet with \\[ein:edit-cell-contents] \\{ein:edit-cell-mode-map} ." nil " EinCell" nil (set (make-local-variable 'header-line-format) (substitute-command-keys "Edit, execute with \\[ein:edit-cell-execute] then exit with \\[ein:edit-cell-exit] \ or abort with \\[ein:edit-cell-abort]")) ;; Possibly activate various auto-save features (for the edit buffer ;; or the source buffer). ;; (when org-edit-src-turn-on-auto-save ;; (setq buffer-auto-save-file-name ;; (concat (make-temp-name "org-src-") ;; (format-time-string "-%Y-%d-%m") ;; ".txt"))) ;; (unless (or org-src--auto-save-timer (zerop org-edit-src-auto-save-idle-delay)) ;; (setq org-src--auto-save-timer ;; (run-with-idle-timer ;; org-edit-src-auto-save-idle-delay t ;; (lambda () ;; (save-excursion ;; (let (edit-flag) ;; (dolist (b (buffer-list)) ;; (with-current-buffer b ;; (when (org-src-edit-buffer-p) ;; (unless edit-flag (setq edit-flag t)) ;; (when (buffer-modified-p) (org-edit-src-save))))) ;; (unless edit-flag ;; (cancel-timer org-src--auto-save-timer) ;; (setq org-src--auto-save-timer nil)))))))) ) (defun ein:cell-configure-edit-buffer () (when (and (bound-and-true-p org-src--from-org-mode) (boundp 'org-src--beg-marker)) (add-hook 'kill-buffer-hook #'org-src--remove-overlay nil 'local) (if (bound-and-true-p org-src--allow-write-back) (progn (setq buffer-offer-save t) (setq buffer-file-name (concat (buffer-file-name (marker-buffer org-src--beg-marker)) "[" (buffer-name) "]")) (setq write-contents-functions '(ein:edit-cell-save))) (setq buffer-read-only t)))) (defun ein:edit-cell-view-traceback () "Jump to traceback, if there is one, for current edit." (interactive) (let ((buf (current-buffer)) (cell ein:src--cell)) (with-current-buffer (ein:worksheet--get-buffer ein:src--ws) (ein:cell-goto cell) (ein:tb-show)))) (defun ein:edit-cell-save-and-execute () "Save, then execute the countents of the EIN source edit buffer and place results (if any) in output of original notebook cell." (interactive) (ein:edit-cell-save) (when (and (slot-exists-p ein:src--cell 'kernel) (slot-boundp ein:src--cell 'kernel)) (ein:cell-execute-internal ein:src--cell (slot-value ein:src--cell 'kernel) (buffer-string) :silent nil))) (defun ein:edit-cell-save () "Save contents of EIN source edit buffer back to original notebook cell." (interactive) (set-buffer-modified-p nil) (let* ((edited-code (buffer-string)) (cell ein:src--cell) (overlay ein:src--overlay) (read-only (overlay-get overlay 'modification-hooks))) (overlay-put overlay 'modification-hooks nil) (overlay-put overlay 'insert-in-front-hooks nil) (overlay-put overlay 'insert-behind-hooks nil) (with-current-buffer (ein:worksheet--get-buffer ein:src--ws) (ein:cell-set-text cell edited-code)) ;;(setf (slot-value ein:src--cell 'input) edited-code) (overlay-put overlay 'modification-hooks read-only) (overlay-put overlay 'insert-in-front-hooks read-only) (overlay-put overlay 'insert-behind-hooks read-only))) (defun ein:edit-cell-exit () "Close the EIN source edit buffer, saving contents back to the original notebook cell, unless being called via `ein:edit-cell-abort'." (interactive) (let ((edit-buffer (current-buffer)) (ws ein:src--ws) (cell ein:src--cell)) (ein:remove-overlay) (when ein:src--allow-write-back (ein:edit-cell-save)) (kill-buffer edit-buffer) (switch-to-buffer-other-window (ein:worksheet--get-buffer ws)) (ein:cell-goto cell) (when ein:src--saved-window-config (set-window-configuration ein:src--saved-window-config) (setq ein:src--saved-window-config nil)))) (defun ein:edit-cell-abort () "Abort editing the current cell, contents will revert to previous value." (interactive) (let (ein:src--allow-write-back) (ein:edit-cell-exit))) (defun ein:construct-cell-edit-buffer-name (bufname cid cell-type) (concat "*EIN Src " bufname "[ " cid "/" cell-type " ]*" )) (defun ein:get-mode-for-kernel (kernelspec) (if (null kernelspec) 'python ;; FIXME (ein:case-equal (ein:$kernelspec-language kernelspec) (("julia" "python" "R") (intern (ein:$kernelspec-language kernelspec))) (t 'python)))) (defun ein:edit-src-continue (e) (interactive "e") (mouse-set-point e) (let ((buf (get-char-property (point) 'edit-buffer))) (if buf (org-src-switch-to-buffer buf 'continue) (user-error "No sub-editing buffer for area at point")))) (defun ein:make-source-overlay (beg end edit-buffer) "Create overlay between BEG and END positions and return it. EDIT-BUFFER is the buffer currently editing area between BEG and END." (let ((overlay (make-overlay beg end))) (overlay-put overlay 'face 'secondary-selection) (overlay-put overlay 'edit-buffer edit-buffer) (overlay-put overlay 'help-echo "Click with mouse-1 to switch to buffer editing this segment") (overlay-put overlay 'face 'secondary-selection) (overlay-put overlay 'keymap (let ((map (make-sparse-keymap))) (define-key map [mouse-1] 'ein:edit-src-continue) map)) (let ((read-only (list (lambda (&rest _) (user-error "Cannot modify an area being edited in a dedicated buffer"))))) (overlay-put overlay 'modification-hooks read-only) (overlay-put overlay 'insert-in-front-hooks read-only) (overlay-put overlay 'insert-behind-hooks read-only)) overlay)) (defun ein:remove-overlay () "Remove overlay from current source buffer." (when (overlayp ein:src--overlay) (delete-overlay ein:src--overlay))) (defcustom ein:raw-cell-default-edit-mode 'LaTeX-mode "The major mode to use when editing a cell of type 'Raw' in the dedicated edit buffer. By default we use LaTeX-mode." :type 'symbol :group 'ein) (defun ein:edit-cell-contents () "Edit the contents of the current cell in a buffer using an appropriate language major mode. Functionality is very similar to `org-edit-special'." (interactive) (setq ein:src--saved-window-config (current-window-configuration)) (let* ((cell (or (ein:worksheet-get-current-cell) (error "Must be called from inside an EIN worksheet cell."))) (nb (ein:notebook--get-nb-or-error)) (ws (ein:worksheet--get-ws-or-error)) (type (slot-value cell 'cell-type)) (name (ein:construct-cell-edit-buffer-name (buffer-name) (ein:cell-id cell) type))) (ein:aif (get-buffer name) (switch-to-buffer-other-window it) (ein:create-edit-cell-buffer name cell nb ws)))) (defun ein:edit-cell-detect-type (contents notebook &optional raw-cell-p) (if (string-match "^%%\\(.*\\)" contents) (ein:case-equal (match-string 1 contents) (("html" "HTML") (html-mode)) (("latex" "LATEX") (LaTeX-mode)) (("ruby") (ruby-mode)) (("sh" "bash") (sh-mode)) (("javascript" "js") (javascript-mode)) (t (funcall ein:raw-cell-default-edit-mode))) (if raw-cell-p (funcall ein:raw-cell-default-edit-mode) (cl-case (ein:get-mode-for-kernel (ein:$notebook-kernelspec notebook)) (julia (julia-mode)) (python (python-mode)) (R (R-mode)))))) (defun ein:create-edit-cell-buffer (name cell notebook worksheet) (let* ((contents (ein:cell-get-text cell)) (type (slot-value cell 'cell-type)) (buffer (generate-new-buffer-name name)) (overlay (ein:make-source-overlay (ein:cell-input-pos-min cell) (ein:cell-input-pos-max cell) buffer))) (switch-to-buffer-other-window buffer) (insert contents) (remove-text-properties (point-min) (point-max) '(display nil invisible nil intangible nil)) (set-buffer-modified-p nil) (setq buffer-file-name buffer) ;; Breaks anaconda-mode without this special fix. (condition-case e (ein:case-equal type (("markdown") (markdown-mode)) (("raw") (ein:edit-cell-detect-type contents notebook t)) (("code") (ein:edit-cell-detect-type contents notebook))) (error (message "Language mode `%s' fails with: %S" type (nth 1 e)))) (set (make-local-variable 'ein:src--overlay) overlay) (set (make-local-variable 'ein:src--cell) cell) (set (make-local-variable 'ein:src--ws) worksheet) (set (make-local-variable 'ein:src--allow-write-back) t) (ein:edit-cell-mode))) (provide 'ein-cell-edit)