My emacs init file
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.
 
 
 

717 lines
28 KiB

;;; sage-view.el --- Typeset Sage output on the fly
;; Copyright (C) 2008 Matthias Meulien, Nick Alexander
;; Authors: Matthias Meulien <matthias.meulien@xlim.fr>, Nick Alexander
;; <ncalexander@gmail.com>
;; Keywords: sage math image
;; This program 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.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; `sage-view' typesets output in an `inferior-sage-mode' buffer and displays
;; plots inline in an `inferior-sage-mode'. Inline displays are context
;; sensitive; by default, right-clicking brings up a context-menu.
;; Use `sage-view' to enable the minor mode, and then
;; `sage-view-enable-inline-output', `sage-view-disable-inline-output' and
;; `sage-view-enable-inline-plots', `sage-view-disable-inline-plots' enable
;; and disable the relevant features. You might add some of those functions
;; to `sage-view-hook' to configure `sage-view' to your liking.
;; You can customize `sage-view' using the Emacs customize interface by M-x
;; customize-group RET sage-view RET. In particular you can customize
;; magnification and margins
;; This mode was inspired by doc-view.el by Tassilo Horn, preview.el
;; by David Kastrup, and imath.el by Yasuaki Honda.
;; The LaTeX style used by preview.el is mandatory to use
;; sage-view.el. It is shipped with AUCTeX.
;;; Todo:
;; - Add a auto-reveal stuff to overlays
;; - Check that display is image capable
;; - Disabling sage-view mode should remove overlays
;; - Set color, center image, enlarge overlay to window full size
;; - Add zoom features to overlays
;; - Add horizontal scrolling
;; Bugs:
;; - Numpy output can be a text array... should not be inserted into
;; $$ signs (hum... example?)
;;; Code:
(require 'sage)
(require 'sage-mode)
(require 'mouse) ;; for popup-menu
(defcustom sage-view-latex-head
"\\documentclass{article}
\\usepackage[active, tightpage, pdftex, displaymath]{preview}
\\usepackage{amstext}
\\begin{document}
\\begin{preview}
\\begin{math}\n"
"String to be inserted at the top of a LaTeX document."
:type 'string
:group 'sage-view)
(defcustom sage-view-latex-tail
"\n\\end{math}
\\end{preview}
\\end{document}\n"
"String to be inserted at the end of a LaTeX document."
:type 'string
:group 'sage-view)
(defcustom sage-view-default-commands t
"Determine what to enable when `sage-view' is started.
If equal to the symbol `plots' then will start inline plotting.
If equal to the symbol `output' then will start typesetting output.
Otherwise, if non-nil will start both.
Each of these can be enabled or disabled later by calling
`sage-view-enable-inline-plots', `sage-view-disable-inline-plots',
`sage-view-enable-inline-output', or `sage-view-disable-inline-output'."
:type '(choice (const :tag "Inline Plots" plots)
(const :tag "Typeset Output" output)
(const :tag "Both" t))
:group 'sage-view)
(defvar sage-view-inline-plots-enabled nil)
(defvar sage-view-inline-output-enabled nil)
(defcustom sage-view-inline-plots-method 'embedded-mode
"Determine how plots are included inline.
This has an effect when enabling and disabling inline plots and
changing it's value before disabling will disable the new method,
leaving the old method in place.
If equal to the symbol `emacsclient' then it will use
emacsclient (as specified by `sage-view-emacsclient') to open the
file. This should be robust across Sage versions, but requires
`sage-view-emacsclient' to be set correctly in order to work.
If equal to the symbol `emacsclient-window' then it will use
emacsclient as above, but open in a different window rather than
inline with the rest of the output.
If equal to the symbol `doctest-mode' then it will use
DOCTEST_MODE. This is known to work on older versions of Sage,
but has problems in recent versions.
If equal to the symbol `embedded-mode' then it will use
EMBEDDED_MODE. This is known to cause problems with some Sage
functionality like trace, since Sage thinks it's running in a
browser."
:type '(choice (const :tag "Emacs client inline" emacsclient)
(const :tag "Emacs client in separate window" emacsclient-window)
(const :tag "Doctest mode" doctest-mode)
(const :tag "Embedded mode" embedded-mode))
:set (lambda (symbol value)
(if (not (buffer-live-p sage-buffer))
(set symbol value)
(with-current-buffer sage-buffer
(let ((enabled sage-view-inline-plots-enabled))
(when enabled
(sage-view-disable-inline-plots))
(set symbol value)
(when enabled
(sage-view-enable-inline-plots))))))
:group 'sage-view)
(defcustom sage-view-emacsclient
;; Magit goes to quite some length to find the right executable, so
;; use that if we have it.
(or (and (fboundp 'magit-locate-emacsclient)
(magit-locate-emacsclient))
(and invocation-directory
(file-executable-p (concat invocation-directory "emacsclient"))
(concat invocation-directory "emacsclient"))
(and invocation-directory
(file-executable-p (concat invocation-directory "bin/emacsclient"))
(concat invocation-directory "bin/emacsclient"))
(executable-find "emacsclient"))
"The emacslient executable to use for viewing plots inline.
If `sage-view-inline-plots-method' is set to `emacsclient' or
`emacsclient-window', then this is used. If this is set
incorrectly, then it's known to cause issues, at least on OS X."
:type 'string
:group 'sage-view)
(defcustom sage-view-emacsclient-wrapper
(concat (file-name-directory
(or load-file-name
(buffer-file-name))) "sage-view.sh")
"A wrapper script around emacsclient for when `sage-view-inline-plots-method' is emacsclient.
The script calls the function `sage-view-handle-emacslient'
instead of simply opening the image which is what happens when
`sage-view-inline-plots-method' is emacsclient.
In most cases you should not have to change this."
:type 'string
:group 'sage-view)
(defvar sage-view-start-string "<html><\\(?:span class=\"math\"\\|script type=\"math/tex\"\\)>"
"HTML tags that identify the begining of a math formula in Sage output.")
(defvar sage-view-final-string "</\\(?:span\\|script\\)></html>"
"HTML tags that identify the end of a math formula in Sage output.")
(defvar sage-view-plot-regex
"<html>\\(?:<font color='[^']*'>\\)?<img src='cell://\\(sage[0-9]+.png\\)'>\\(?:</font>\\)?</html>"
"HTML tags that identify a plot in Sage output.")
(defvar sage-view-output-regexp
"BEGIN_TEXT:\\(\\(?:.\\|\n\\)*?\\):END_TEXT\nBEGIN_LATEX:\\(\\(?:.\\|\n\\)*?\\):END_LATEX"
"Regular expression matching typeset output from BackendEmacs.")
(defvar sage-view-dir-name nil)
(defun sage-view-latex->pdf (ov)
"Start conversion of the LATEX document associated to OV to PDF.
See also `sage-view-process-overlay'."
(let* ((latex (concat (overlay-get ov 'file-sans-extension) ".tex"))
(options (append
(list (concat "--output-directory=" sage-view-dir-name)
(concat "-interaction=" "nonstopmode")
(concat "-output-format=" "pdf")
latex)))
(proc (apply 'start-process
(append (list "latex->pdf" nil "latex") options))))
(process-put proc 'overlay ov)
(set-process-sentinel proc 'sage-view-latex->pdf-sentinel)))
(defun sage-view-pdf->png (ov)
"Start conversion of the PDF file associated to OV to PNG.
See also `sage-view-process-overlay'."
(let* ((base (overlay-get ov 'file-sans-extension))
(png (concat base ".png"))
(pdf (concat base ".pdf"))
(level (int-to-string sage-view-anti-aliasing-level))
(scale (or (overlay-get ov 'scale) sage-view-scale))
(options (append
sage-view-gs-options
(list (concat "-dTextAlphaBits=" level)
(concat "-dGraphicsAlphaBits=" level)
(concat "-sOutputFile=" png)
(concat "-r" (sage-view-compute-resolution scale))
pdf)))
(proc (apply 'start-process
(append
(list "pdf->png" "*sage-view*" sage-view-gs-command)
options))))
(process-put proc 'overlay ov)
(set-process-sentinel proc 'sage-view-pdf->png-sentinel)))
(defvar sage-view-conversion-failed-map
(let ((map (make-sparse-keymap))
(l (lambda () (interactive)
(dolist (ov (overlays-at (point)))
(if (overlay-get ov 'file-sans-extension)
(find-file (concat (overlay-get ov 'file-sans-extension) ".log")))))))
(define-key map "RET" l)
(define-key map [return] l)
(define-key map [mouse-1]
(lambda (event) (interactive "e")
(dolist (ov (overlays-at (posn-point (event-end event))))
(if (overlay-get ov 'file-sans-extension)
(find-file (concat (overlay-get ov 'file-sans-extension) ".log"))))))
(define-key map [mouse-3]
(lambda (event) (interactive "e")
(dolist (ov (overlays-at (posn-point (event-end event))))
(if (overlay-get ov 'file-sans-extension)
(sage-view-context-menu ov event)))))
map)
"Keymap for overlays in which the conversion has failed.")
(defun sage-view-latex->pdf-sentinel (proc event)
"If PROC (supposed to be a conversion process from LATEX to
PDF) was successful, convert the PDF to PNG.
See also `sage-view-process-overlay'."
(let* ((ov (process-get proc 'overlay))
(base (overlay-get ov 'file-sans-extension)))
(if (string-match "finished" event)
(sage-view-pdf->png ov)
(overlay-put ov 'keymap sage-view-conversion-failed-map)
(overlay-put ov 'display
(concat "Conversion failed (see " base ".log" ")")))))
(defun sage-view-pdf->png-sentinel (proc event)
"If PROC (supposed to be a conversion process from PDF to PNG)
was successful, update the overlay associated to PROC.
See also `sage-view-process-overlay'."
(let* ((ov (process-get proc 'overlay))
(base (overlay-get ov 'file-sans-extension))
(png (concat base ".png"))
(image (when (and (string-match "finished" event)
png (file-readable-p png))
(append (list 'image :type 'png :file png :margin sage-view-margin)))))
(if image
(overlay-put ov 'display image)
(overlay-put ov 'keymap sage-view-conversion-failed-map)
(overlay-put ov 'display
(concat "Conversion failed (see " base ".log" ")")))
(sit-for 0)))
(defun sage-view-compute-resolution (scale)
(if (display-graphic-p)
;; In a terminal, display-mm-width returns nil and
;; display-pixel-width returns the number of characters.
(let ((w (* scale (/ (* 25.4 (display-pixel-width))
(display-mm-width))))
(h (* scale (/ (* 25.4 (display-pixel-height))
(display-mm-height)))))
(concat (int-to-string w) "x" (int-to-string h)))
"72x72"))
(defun sage-view-process-overlay (ov)
"Associate a LATEX document to OV and start conversion process
from LATEX to PDF.
The conversion process is done by `sage-view-latex->pdf'. When it
ends, `sage-view-latex->pdf-sentinel' is called: If the
conversion is successful, a conversion process from PDF to PNG
starts. When it ends, `sage-view-pdf->png-sentinel' is called: If
the last conversion is successful, OV displays the resulting
image."
(let* ((base (expand-file-name
(make-temp-name "sage-view_") sage-view-dir-name))
(file (concat base ".tex")))
(with-temp-file file
(insert sage-view-latex-head)
(insert (overlay-get ov 'math))
(insert sage-view-latex-tail)
;; The LaTeX created by Sage for MathJax (in some cases) isn't valid.
;; This is our attempt to work around it.
(goto-char (point-min))
(while (search-forward-regexp "\\verb!\\([^!]*\\)!" nil t)
(replace-match "\mathtt{\\1}")))
(overlay-put ov 'file-sans-extension base)
(sage-view-latex->pdf ov)))
(defun sage-view-output-filter-process-inline-output (string)
"Substitute overlays to inline output.
Each region delimited by `sage-view-start-string' and
`sage-view-final-string' is replaced by an overlay.
This function expects the buffer to be narrowed to the current
output. And should be wrapped in a `save-excursion' and
`save-restriction' call.
See also `sage-view-output-filter'."
(goto-char (point-min))
(while (re-search-forward sage-view-start-string (point-max) t)
(let* ((header-beg (match-beginning 0))
(header-end (match-end 0))
(footer-beg (when (re-search-forward sage-view-final-string (point-max) t)
(match-beginning 0)))
(footer-end (match-end 0))
(text (buffer-substring-no-properties header-end footer-beg))
(ov (make-overlay header-beg
footer-end
nil nil nil))
(map (make-sparse-keymap)))
(overlay-put ov 'help-echo "mouse-3: Open contextual menu")
(overlay-put ov 'math text)
(overlay-put ov 'display (format "Typesetting %s..." text))
(define-key map [mouse-3]
`(lambda (event) (interactive "e")
(sage-view-context-menu ,ov event)))
(overlay-put ov 'keymap map)
(sage-view-process-overlay ov)))
;; Sage 6.6 and on
(goto-char (point-min))
(while (re-search-forward sage-view-output-regexp (point-max) t)
(let* ((full-beg (match-beginning 0))
(full-end (match-end 0))
(text-beg (match-beginning 1))
(text-end (match-end 1))
(latex-beg (match-beginning 2))
(latex-end (match-end 2))
(text (buffer-substring-no-properties text-beg text-end))
(latex (buffer-substring-no-properties latex-beg latex-end))
(ov (make-overlay full-beg full-end
nil nil nil))
(map (make-sparse-keymap)))
;; Delete everything except the text
(delete-region text-end full-end)
(delete-region full-beg text-beg)
;; Populate the overlay
(overlay-put ov 'help-echo "mouse-3: Open contextual menu")
(overlay-put ov 'text text)
(overlay-put ov 'math latex)
(define-key map [mouse-3]
`(lambda (event) (interactive "e")
(sage-view-context-menu ,ov event)))
(overlay-put ov 'keymap map)
(sage-view-process-overlay ov))))
(defun sage-view-output-filter-process-inline-plots (string)
"Generate and place one overlay image for one inline plot,
found by looking for a particular png file in directory
`sage-view-dir-name'.
This function expects the buffer to be narrowed to just the
current output; see `sage-view-output-filter' for how to do
that."
(goto-char (point-min))
(while (re-search-forward sage-view-plot-regex (point-max) t)
(let* ((html-beg (match-beginning 0))
(html-end (match-end 0))
(pngname (match-string-no-properties 1))
(base (expand-file-name (make-temp-name "sage-view-plot_") sage-view-dir-name))
(pngname2 (concat base ".png")))
(when (and pngname
(file-exists-p pngname)
(file-readable-p pngname))
;; the found branch
(rename-file pngname pngname2 t)
(goto-char comint-last-input-end)
(let ((im (create-image pngname2 'png))
(ov (make-overlay html-beg
html-end
nil nil nil))
(map (make-sparse-keymap)))
(overlay-put ov 'display im)
;; help alignment as much as possible
(overlay-put ov 'before-string "\n")
(define-key map [mouse-3]
`(lambda (event) (interactive "e")
(sage-view-plot-context-menu ,ov event)))
(overlay-put ov 'keymap map))))))
(defun sage-view-output-filter (string)
"Generate and place overlay images for inline output and inline plots.
Function to be inserted in `comint-output-filter-functions'."
(save-excursion
(save-restriction
(narrow-to-region comint-last-input-end
(process-mark (get-buffer-process (current-buffer))))
(when sage-view-inline-plots-enabled
(sage-view-output-filter-process-inline-plots string))
(when (and sage-view-inline-output-enabled
(or (string-match sage-view-final-string string)
(string-match ":END_LATEX" string)))
(sage-view-output-filter-process-inline-output string)))))
;;;###autoload
(defun sage-view-enable-inline-output ()
"Enable inline output pretty-printing, i.e. typeset output from sage in the `inferior-sage-mode' buffer.
WARNING: this communicates with the sage process. Only use this
when `sage-view' mode is enabled and sage is running."
(interactive)
;; older sage
(sage-send-command "if hasattr(sys.displayhook, 'set_display'): pretty_print_default(True)" nil t)
;; sage 5.12
(sage-send-command "import IPython.core.ipapi; IPython.core.ipapi.get().magic('display typeset')" nil t)
;; Sage 6.6
(sage-send-command "from sage.repl.rich_output.backend_emacs import BackendEmacs" nil t)
(sage-send-command "from sage.repl.rich_output import get_display_manager" nil t)
(sage-send-command "get_display_manager().switch_backend(BackendEmacs(),shell=get_ipython())" nil t)
;; fallthrough
;; Sage 6.2
(sage-send-command "get_ipython().magic('display typeset')" nil t)
(setq sage-view-inline-output-enabled t)
(sage-view-update-modeline))
;;;###autoload
(defun sage-view-disable-inline-output ()
"Disable inline output pretty-printing, i.e. do not typeset output from sage in the `inferior-sage-mode' buffer.
WARNING: this communicates with the sage process. Only use this
when `sage-view' mode is enabled and sage is running."
(interactive)
;; older sage
(sage-send-command "if hasattr(sys.displayhook, 'set_display'): pretty_print_default(False)" nil t)
;; sage 5.12
(sage-send-command "import IPython.core.ipapi; IPython.core.ipapi.get().magic('display')" nil t)
;; Sage 6.2
(sage-send-command "get_ipython().magic('display simple')" nil t)
(setq sage-view-inline-output-enabled nil)
(sage-view-update-modeline))
;;;###autoload
(defun sage-view-enable-inline-plots ()
"Enable inline plotting, i.e. display plots in the `inferior-sage-mode' buffer and do not spawn an external viewer.
WARNING: this communicates with the sage process. Only use this
when `sage-view' mode is enabled and sage is running."
(interactive)
(cond
((or (eq sage-view-inline-plots-method 'emacsclient)
(eq sage-view-inline-plots-method 'emacsclient-window))
(when (not (server-running-p))
(error "Server appears to not be running. Try adding (server-start) to your .emacs"))
;; Store the old viewer
(sage-send-command "_emacs_sage_viewer_ = _emacs_sage_viewer_ if '_emacs_sage_viewer_' in globals() else sage.misc.viewer.viewer.png_viewer()" nil t)
(sage-send-command (concat "sage.misc.viewer.viewer.png_viewer('''"
(when (eq sage-view-inline-plots-method 'emacsclient)
sage-view-emacsclient-wrapper)
" "
sage-view-emacsclient
"''')")
nil t))
((eq sage-view-inline-plots-method 'embedded-mode)
(sage-send-command "sage.plot.plot.EMBEDDED_MODE = True;" nil t))
((eq sage-view-inline-plots-method 'doctest-mode)
(sage-send-command "sage.plot.plot.DOCTEST_MODE = True;" nil t)
;; sage 4.7
(sage-send-command
(format "sage.plot.plot.DOCTEST_MODE_FILE = '%s/sage-view.png';"
sage-view-dir-name)
nil t)
;; sage 5.0
(sage-send-command
(format "if hasattr(sage.plot,'graphics'): sage.plot.graphics.DOCTEST_MODE_FILE = '%s/sage-view.png';\n"
sage-view-dir-name)
nil t)
;; sage 5.something
(sage-send-command "if hasattr(sage,'doctest'): sage.doctest.DOCTEST_MODE = True;\n" nil t))
(t
(error "Unknown value for `sage-view-inline-plots-method'")))
(setq sage-view-inline-plots-enabled t)
(sage-view-update-modeline))
;;;###autoload
(defun sage-view-disable-inline-plots ()
"Disable inline plotting, i.e. do not display plots in the `inferior-sage-mode' buffer and instead spawn an external viewer.
WARNING: this communicates with the sage process. Only use this
when `sage-view' mode is enabled and sage is running."
(interactive)
(cond
((or (eq sage-view-inline-plots-method 'emacsclient)
(eq sage-view-inline-plots-method 'emacsclient-window))
(sage-send-command (concat "sage.misc.viewer.viewer.png_viewer(_emacs_sage_viewer_)")
nil t))
((eq sage-view-inline-plots-method 'embedded-mode)
(sage-send-command "sage.plot.plot.EMBEDDED_MODE = False;" nil t))
((eq sage-view-inline-plots-method 'doctest-mode)
(sage-send-command "sage.plot.plot.DOCTEST_MODE = False;" nil t)
;; sage 4.7
(sage-send-command "sage.plot.plot.DOCTEST_MODE_FILE = None;" nil t)
;; sage 5.0
(sage-send-command "if hasattr(sage.plot,'graphics'): sage.plot.graphics.DOCTEST_MODE_FILE = None;\n" nil t)
;; sage 5.something
(sage-send-command "if hasattr(sage,'doctest'): sage.doctest.DOCTEST_MODE = False;\n" nil t))
(t
(error "Unknown value for `sage-view-inline-plots-method'")))
(setq sage-view-inline-plots-enabled nil)
(sage-view-update-modeline))
(defun sage-view-handle-emacslient (image-file)
(when (buffer-live-p sage-buffer)
(with-current-buffer sage-buffer
(save-excursion
(goto-char comint-last-output-start)
;; Go to a blank line (where it should be output, but if not don't worry)
(re-search-backward "^$" comint-last-input-end t)
(let* ((inhibit-read-only t)
(im (create-image image-file 'png))
(p (point))
(junk (insert (concat "image:" image-file)))
(ov (make-overlay p (point) nil nil nil))
(map (make-sparse-keymap)))
(overlay-put ov 'display im)
;; help alignment as much as possible
(overlay-put ov 'before-string "\n")
(overlay-put ov 'after-string "\n")
(define-key map [mouse-3]
`(lambda (event) (interactive "e")
(sage-view-plot-context-menu ,ov event)))
(overlay-put ov 'keymap map))))))
(defun sage-view-create-temp ()
"Create a temporary directory and set `sage-view-dir-name'
to its name.
Nothing is done if `sage-view-dir-name' equals the name of a
writable directory."
(unless (and sage-view-dir-name
(file-directory-p sage-view-dir-name)
(file-writable-p sage-view-dir-name))
;; I'm not sure SAGE_TESTDIR is the right thing, but it's
;; .sage/tmp which seems like what I should use.
(let ((tmp-dir (shell-command-to-string (concat sage-command " -sh -c 'echo -n $SAGE_TESTDIR' 2>/dev/null"))))
(when (= 0 (length tmp-dir))
(setq tmp-dir "~/.sage/tmp"))
(setq sage-view-dir-name
(make-temp-name (expand-file-name "emacs" tmp-dir))))
(condition-case err
(make-directory sage-view-dir-name t)
(error (message "Creation of `%s' failed: %s"
sage-view-dir-name (error-message-string err))))))
(defun sage-view-delete-temp ()
"Delete the directory named after `sage-view-dir-name'."
(when sage-view-dir-name
(condition-case err
(progn
(mapc #'delete-file
(directory-files sage-view-dir-name t "^sage-view" t))
(delete-directory sage-view-dir-name))
(error (message "Deletion of `%s' failed: %s"
sage-view-dir-name (error-message-string err))))))
(defun sage-view-overlay-activep (ov)
"Check whether there is a valid image associated with OV."
(eq (car (overlay-get ov 'display)) 'image))
(defun sage-view-cleanup-copied-text (str)
"Remove some boilerplate text added by Sage to all LaTeX output."
(replace-regexp-in-string
(regexp-quote "\\newcommand{\\Bold}[1]{\\mathbf{#1}}")
"" str))
(defun sage-view-copy-text (ov)
"Copy text source of OV into the kill buffer."
(let ((text (overlay-get ov 'text)))
(if text
(kill-new text)
(message "No text available"))))
(defun sage-view-copy-latex (ov)
"Copy LaTeX source of OV into the kill buffer."
(let ((text (overlay-get ov 'math)))
(if text
(kill-new (sage-view-cleanup-copied-text text))
(message "No LaTeX code available"))))
(defun sage-view-save-image (ov)
"Copy image file associated to OV.
Make sure that there is a valid image associated with OV with
`sage-view-overlay-activep'."
(let* ((spec (cdr (overlay-get ov 'display)))
(file (plist-get spec :file))
(name (when (and file (file-readable-p file))
(expand-file-name
(read-file-name "Write image to file: "
default-directory
"sage-view.png")))))
(if name
(copy-file file name))))
(defun sage-view-regenerate (ov)
"Return zoom to normal and regenerate the overlay."
(overlay-put ov 'scale sage-view-scale)
(sage-view-process-overlay ov))
(defun sage-view-zoom-in (ov &optional multiplier)
"Internal function to zoom in on an overlay."
(unless (numberp multiplier)
(setq multiplier 1))
(let ((scale (or (overlay-get ov 'scale) sage-view-scale)))
(overlay-put ov 'scale (+ scale (* multiplier sage-view-scale-factor)))
(message "Overlay's scale set to %s" scale)
(sage-view-process-overlay ov)))
(defun sage-view-zoom-out (ov &optional multiplier)
"Internal function to zoom out on an overlay."
(unless (numberp multiplier)
(setq multiplier 1))
(let* ((scale (or (overlay-get ov 'scale) sage-view-scale))
(new-scale (- scale (* multiplier sage-view-scale-factor))))
;; Ensure it's not too small (or negative)
(when (< new-scale sage-view-scale-factor)
(setq new-scale sage-view-scale-factor))
(overlay-put ov 'scale new-scale)
(message "Overlay's scale set to %s" scale)
(sage-view-process-overlay ov)))
(defun sage-view-context-menu (ov ev)
"Pop up a menu for OV at position EV."
(popup-menu
`("Sage View Mode"
["Regenerate" (lambda () (interactive) (sage-view-regenerate ,ov))]
["Copy Text" (lambda () (interactive) (sage-view-copy-text ,ov))]
["Copy LaTeX" (lambda () (interactive) (sage-view-copy-latex ,ov))]
["Save As..." (lambda () (interactive) (sage-view-save-image ,ov))
`(sage-view-overlay-activep ,ov)]
["Zoom in" (lambda (multiplier) (interactive "p")
(sage-view-zoom-in ,ov multiplier))
`(sage-view-overlay-activep ,ov)]
["Zoom out" (lambda (multiplier) (interactive "p")
(sage-view-zoom-out ,ov multiplier))
`(sage-view-overlay-activep ,ov)]
"--"
["Customize Conversion Options" (lambda () (interactive)
(customize-group 'sage-view t))
`(sage-view-overlay-activep ,ov)])
ev))
(defun sage-view-plot-context-menu (ov ev)
"Pop up a menu for OV at position EV."
(popup-menu
`("Sage View Mode"
["Save As..." (lambda () (interactive) (sage-view-save-image ,ov))
`(sage-view-overlay-activep ,ov)])
ev))
;; Inspired by c-update-modeline
(defun sage-view-update-modeline ()
"Update modeline to include information about whether sage-view is enabled."
(when (eq major-mode 'inferior-sage-mode)
(let ((fmt (format "/%s%s"
(if sage-view-inline-plots-enabled "p" "")
(if sage-view-inline-output-enabled "t" "")))
(bare-mode-name (if (string-match "\\(^[^/]*\\)/" mode-name)
(match-string 1 mode-name)
mode-name)))
(setq mode-name
(if (> (length fmt) 1)
(concat bare-mode-name fmt)
bare-mode-name))
(force-mode-line-update))))
;; TODO: require a graphics console before turning it on, or make it possile to turn it on
;;;###autoload
(define-minor-mode sage-view
"Toggle automatic typesetting of Sage output.
Typesetting of math formulas is done by LATEX subprocesses and
PDF to PNG conversions." nil
:group 'sage-view
:lighter " Sage-View"
(if sage-view
(progn
(make-local-variable 'sage-view-dir-name)
(sage-view-create-temp)
(cond
((eq sage-view-default-commands 'plots)
(sage-view-enable-inline-plots))
((eq sage-view-default-commands 'output)
(sage-view-enable-inline-output))
(sage-view-default-commands
(sage-view-enable-inline-plots)
(sage-view-enable-inline-output)))
(make-local-variable 'comint-output-filter-functions)
(add-hook 'comint-output-filter-functions 'sage-view-output-filter)
(add-hook 'kill-buffer-hook 'sage-view-delete-temp))
(progn
(sage-view-disable-inline-output)
(sage-view-disable-inline-plots)
(remove-hook 'comint-output-filter-functions 'sage-view-output-filter))))
(provide 'sage-view)
;;; sage-view.el ends here