Files
emacs/lisp/org-preview-html/org-preview-html.el
2024-06-14 12:10:52 +02:00

239 lines
9.2 KiB
EmacsLisp

;;; org-preview-html.el --- Automatically preview org-exported HTML files within Emacs -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Jake B <jakebox0@protonmail.com>
;; Author: Jake B <jakebox0@protonmail.com>
;; Original author of org-preview-html (until 2021-09): DarkSun <lujun9972@gmail.com>
;; Url: https://github.com/jakebox/org-preview-html
;; Keywords: Org, convenience, outlines
;; Version: 0.3.1
;; Package-Requires: ((emacs "25.1") (org "8.0"))
;; This file is NOT part of GNU Emacs.
;; 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This minor mode provides a side-by-side preview of your org-exported HTML
;; files using the either the eww or xwidget browsers. The update frequency of
;; the preview can be configured to suit your preference.
;;
;; Quick start:
;; Put this file under your load path.
;; Enable the minor mode in an Org buffer:
;; M-x org-preview-html-mode
;; Configure options with M-x customize-group org-preview-html
;;
;; Source code
;; org-preview-html's code can be found here:
;; http://github.com/jakebox/org-preview-html
;;; Code:
;;;; Requirements
(require 'org)
(require 'xwidget)
(require 'eww)
(defgroup org-preview-html nil
"Automatically preview org-exported HTML files within Emacs."
:group 'org-mode
:link '(url-link :tag "Homepage" "https://github.com/jakebox/org-preview-html/"))
(defcustom org-preview-html-refresh-configuration 'save
"Specifies how often the HTML preview will be refreshed.
If `manual', update manually by running `org-preview-html-refresh'.
If `save', update on save (default).
If `export', update on manual export \(using `org-html-export-to-html').
If `timer', update preview on timer (`org-preview-html-timer-interval')."
:type '(choice
(symbol :tag "Update preview manually" manual)
(symbol :tag "Update preview on save" save)
(symbol :tag "Update preview on export" export)
(symbol :tag "Update preview on a timer" timer))
:group 'org-preview-html)
(defcustom org-preview-html-timer-interval 2
"Integer seconds to wait between exports when in 'timer mode."
:type 'integer
:group 'org-preview-html)
(defcustom org-preview-html-viewer 'eww
"Which Emacs browser `org-preview-html-mode' will use.
If `eww', use eww browser (default).
If `xwidget', use xwidget browser."
:type '(choice
(symbol :tag "Use eww" eww)
(symbol :tag "Use xwidget" xwidget))
:group 'org-preview-html)
(define-obsolete-variable-alias 'org-preview-html/body-only 'org-preview-html-subtree-only "Version 0.3")
(defcustom org-preview-html-subtree-only nil
"If non-nil, scope the preview to the current subtree."
:type 'boolean
:group 'org-preview-html)
(defcustom org-preview-html/body-only nil
"Scope the preview to the body or include the entire document.
Obselete as of version 0.3, instead use `org-preview-html-subtree-only'."
:type 'boolean
:group 'org-preview-html)
;; Internal variables
(defvar org-preview-html--browser-buffer nil)
(defvar org-preview-html--previewed-buffer-name nil)
(defvar org-preview-html--refresh-timer nil)
(defvar-local org-preview-html--html-file nil)
;; https://emacs.stackexchange.com/questions/7116/pop-a-window-into-a-frame
(defun org-preview-html-pop-window-to-frame ()
"Pop a window to a frame."
(interactive)
(let ((buffer (current-buffer)))
(unless (one-window-p)
(delete-window))
(display-buffer-pop-up-frame buffer nil)))
;; Taken from frame.el Emacs 27.1, copied here for better version compatibility.
;; Without this here 27.1 required. With, 25.1.
(defun org-preview-html--previous-window-any-frame ()
(select-window (previous-window (selected-window)
(> (minibuffer-depth) 0)
0))
(select-frame-set-input-focus (selected-frame)))
(defun org-preview-html-refresh ()
"Exports the org file to HTML and refreshes the preview."
(interactive)
(when (eq org-preview-html-refresh-configuration 'manual)
(pop-to-buffer org-preview-html--previewed-buffer-name nil t))
(org-preview-html--org-export-html)
(org-preview-html--reload-preview))
(defun org-preview-html--org-export-html ()
"Silently export org to HTML."
(let ((standard-output 'ignore))
(org-export-to-file 'html org-preview-html--html-file
nil org-preview-html-subtree-only nil nil nil nil)))
(defun org-preview-html--reload-preview ()
"Reload preview."
(save-selected-window
(pop-to-buffer org-preview-html--browser-buffer)
(cond ((eq org-preview-html-viewer 'xwidget) (xwidget-webkit-reload))
((eq org-preview-html-viewer 'eww)
(with-selected-window (selected-window)
;; This stuff is to keep eww window scrolled at same point
(let ((eww-point (point))
(eww-window-start (window-start)))
(eww-reload)
(goto-char eww-point)
(set-window-start nil eww-window-start)))))))
(defun org-preview-html--kill-preview-buffer ()
"Kill the preview buffer and pop back to the previewed org buffer."
;; Only do these things if the preview is around
(when (bound-and-true-p org-preview-html--browser-buffer)
;; If preview is visible we first delete the window, otherwise
;; just kill the preview buffer
(if (get-buffer-window org-preview-html--browser-buffer 'visible)
(delete-window (get-buffer-window org-preview-html--browser-buffer)))
(let ((kill-buffer-query-functions nil))
(kill-buffer org-preview-html--browser-buffer))
(pop-to-buffer org-preview-html--previewed-buffer-name)))
(defun org-preview-html--run-with-timer ()
"Configure timer to refresh preview for `timer' mode."
(setq org-preview-html--refresh-timer
(run-at-time 1 org-preview-html-timer-interval #'org-preview-html-refresh)))
(defun org-preview-html--config ()
"Configure buffer for preview: add exit hooks; configure refresh hooks."
(setq org-preview-html--previewed-buffer-name (buffer-name))
(dolist (hook '(kill-buffer-hook kill-emacs-hook)) ;; Configure exit hooks
(add-hook hook #'org-preview-html--stop-preview nil t))
(let ((conf org-preview-html-refresh-configuration))
(cond
((eq conf 'manual))
((eq conf 'save) ;; On save
(add-hook 'after-save-hook #'org-preview-html-refresh nil t))
((eq conf 'timer) ;; every X seconds
(org-preview-html--run-with-timer))
((eq conf 'export) ;; On export using org-html-export-html command manually
(advice-add 'org-html-export-to-html :after #'org-preview-html--reload-preview)))))
(defun org-preview-html--unconfig ()
"Unconfigure 'org-preview-html-mode' (remove hooks and advice)."
(let ((conf org-preview-html-refresh-configuration))
(cond ((eq conf 'save)
(remove-hook 'after-save-hook #'org-preview-html-refresh t))
((eq conf 'timer)
(cancel-timer org-preview-html--refresh-timer))
((eq conf 'export)
(advice-remove 'org-html-export-to-html #'org-preview-html--reload-preview))))
(dolist (hook '(kill-buffer-hook kill-emacs-hook)) ;; Remove hooks
(remove-hook hook #'org-preview-html--stop-preview t))
;; Reset variables
(dolist (var '(org-preview-html--browser-buffer org-preview-html--previewed-buffer-name))
(set var nil)))
(defun org-preview-html--open-browser ()
"Open a browser to preview the exported HTML file."
;; Store the exported HTML filename
(setq-local org-preview-html--html-file (concat (file-name-sans-extension buffer-file-name) ".html"))
(org-preview-html--org-export-html) ;; Export the org file to HTML
;; Procedure to open the side-by-side preview
(split-window-right)
(other-window 1)
(let ((file org-preview-html--html-file))
(cond ((eq org-preview-html-viewer 'xwidget) (xwidget-webkit-browse-url (concat "file://" file)))
((eq org-preview-html-viewer 'eww) (eww-open-file file))))
(setq org-preview-html--browser-buffer (get-buffer (buffer-name)))
(org-preview-html--previous-window-any-frame))
(defun org-preview-html--start-preview ()
"Begin the org-preview-html preview."
(when buffer-file-name
(cond ((derived-mode-p 'org-mode)
(message "org-preview-html has recieved a major update - xwidgets support, refresh configurations and more! \n M-x customize-group org-preview-html-mode")
(org-preview-html--open-browser)
(org-preview-html--config))
(t
(org-preview-html-mode -1)
(user-error "`%s' not supported by org-preview-html preview, only `org mode'!" major-mode)))))
(defun org-preview-html--stop-preview ()
"Stop the org-preview-html preview."
(org-preview-html--kill-preview-buffer)
(org-preview-html--unconfig))
;;;###autoload
(define-minor-mode org-preview-html-mode
"(Optionally) live preview for Org exports to HTML."
:lighter " org-preview-html"
(if org-preview-html-mode
(org-preview-html--start-preview)
(org-preview-html--stop-preview)))
(provide 'org-preview-html)
;;; org-preview-html.el ends here