branch: externals/doc-view-follow
commit 592eb01b8ba102fba33f9ddee46152857126170d
Author: Paul Nelson <[email protected]>
Commit: Paul Nelson <[email protected]>
Refactor using generics
* README.org (Overview): Update.
(Installation): Add section for configuring pdf-tools support.
(Extensibility): Revise to describe the new generic-based mechanism.
* doc-view-follow.el: Replace mode config alist with generic methods.
(doc-view-follow-supported-p, doc-view-follow-setup)
(doc-view-follow-teardown, doc-view-follow-set-page)
(doc-view-follow-get-page): New generic methods to handle
mode-specific operations. New implementations for doc-view-mode.
(doc-view-follow-sync-pages, doc-view-follow-mode)
(global-doc-view-follow-mode): Use them.
---
README.org | 13 ++++-
doc-view-follow.el | 151 ++++++++++++++++++++++++++++-------------------------
2 files changed, 90 insertions(+), 74 deletions(-)
diff --git a/README.org b/README.org
index b89c6512f0..690173456c 100644
--- a/README.org
+++ b/README.org
@@ -3,7 +3,7 @@
* Overview
-doc-view-follow.el provides a minor mode, =doc-view-follow-mode=, that
automatically synchronizes page navigation between multiple windows displaying
the same PS/PDF/DVI/DjVu document. This is essentially an analogue of Emacs'
built-in =follow-mode=, but for document buffers instead rather than text
buffers.
+doc-view-follow.el provides a minor mode, =doc-view-follow-mode=, that
automatically synchronizes page navigation between multiple windows displaying
the same document (PS/PDF/DVI/DjVu, etc.). This is essentially an analogue of
Emacs' built-in =follow-mode=, but for document buffers instead rather than
text buffers.
In particular, this allows for a convenient "book view," with two windows
showing consecutive pages side-by-side.
@@ -16,6 +16,15 @@ Alternatively, download the source file and run:
M-x package-install-file RET /path/to/doc-view-follow.el RET
#+end_src
+** PDF-Tools Support (Optional)
+
+This package supports the built-in =doc-view-mode= as well as =pdf-view-mode=
from [[https://github.com/vedang/pdf-tools][pdf-tools]]. If you use pdf-tools,
add the following to your init file:
+
+#+begin_src emacs-lisp
+(with-eval-after-load 'pdf-view
+ (require 'doc-view-follow-pdf-tools))
+#+end_src
+
* Usage
After installation, you can activate the mode:
@@ -41,4 +50,4 @@ Both windows will automatically stay synchronized.
* Extensibility
-One could add support for additional document viewing modes (if any) via the
=doc-view-follow-modes= variable.
+The package uses generic functions, making it straightforward to add support
for additional document viewing modes. See =doc-view-follow-pdf-tools.el= for
an example of how to do this.
diff --git a/doc-view-follow.el b/doc-view-follow.el
index c2a401a730..8bf43aba4f 100644
--- a/doc-view-follow.el
+++ b/doc-view-follow.el
@@ -34,9 +34,15 @@
;; particular, this allows a "book view" where the document is shown
;; in two side-by-side windows on consecutive pages.
;;
-;; `doc-view-mode' (built-in to Emacs) and `pdf-view-mode' (from
-;; pdf-tools) are supported by default. Additional viewing modes (if
-;; any) could be added via the `doc-view-follow-modes' variable.
+;; `doc-view-mode' (built-in to Emacs) is supported by default. To
+;; enable support for `pdf-view-mode' (from pdf-tools), add the
+;; following to your init file:
+;;
+;; (with-eval-after-load 'pdf-view
+;; (require 'doc-view-follow-pdf-tools))
+;;
+;; The file `doc-view-follow-pdf-tools.el' shows by example how the
+;; package might be extended to other document viewing modes.
;;
;; Usage:
;; - Enable globally: M-x global-doc-view-follow-mode
@@ -56,95 +62,96 @@
"Synchronize pages between two windows displaying the same document."
:group 'convenience)
-(defvar doc-view-follow-modes
- '((doc-view-mode
- :goto doc-view-goto-page
- :next doc-view-next-page
- :prev doc-view-previous-page
- :current (lambda () (doc-view-current-page))
- :max (lambda () (doc-view-last-page-number)))
- (pdf-view-mode
- :goto pdf-view-goto-page
- :next pdf-view-next-page-command
- :prev pdf-view-previous-page-command
- :current (lambda () (pdf-view-current-page))
- :max (lambda () (pdf-cache-number-of-pages))))
- "Alist of supported major modes and relevant functions.
-Each entry has the format: (MAJOR-MODE . CONFIG), where CONFIG is a list
-with entries:
-
-:goto GOTO-PAGE-FUNCTION
-:next NEXT-PAGE-FUNCTION
-:prev PREV-PAGE-FUNCTION
-:current FUNCTION-RETURNING-CURRENT-PAGE
-:max FUNCTION-RETURNING-MAX-PAGE
-
-Other packages can add support for additional document viewing modes
-by adding entries to this list.")
-
-(defun doc-view-follow--call-func (mode-config action &rest args)
- "Call function for ACTION from MODE-CONFIG with ARGS."
- (apply (plist-get mode-config action) args))
+(cl-defgeneric doc-view-follow-supported-p ()
+ "Check if the current buffer is supported by `doc-view-follow-mode'."
+ nil)
+
+(cl-defgeneric doc-view-follow-setup ()
+ "Setup function for `doc-view-follow-mode'."
+ (error "`doc-view-follow-mode' is not supported in this buffer"))
+
+(cl-defgeneric doc-view-follow-teardown ()
+ "Teardown function for `doc-view-follow-mode'."
+ (error "`doc-view-follow-mode' is not supported in this buffer"))
+
+(cl-defgeneric doc-view-follow-set-page (_page)
+ "Go to PAGE in the current document buffer.
+Clamps PAGE to the valid range of pages."
+ (error "`doc-view-follow-mode' is not supported in this buffer"))
+
+(cl-defgeneric doc-view-follow-get-page ()
+ "Return the current page number in the document buffer."
+ (error "`doc-view-follow-mode' is not supported in this buffer"))
(defvar doc-view-follow--sync-in-progress nil
"Flag to prevent recursive sync operations.")
;;;###autoload
(define-minor-mode doc-view-follow-mode
- "Minor mode to sync pages between two windows showing the same document."
+ "Minor mode to sync pages between windows showing the same document."
:global nil
+ :lighter nil
+ (unless (doc-view-follow-supported-p)
+ (error "`doc-view-follow-mode' is not supported in this buffer"))
(if doc-view-follow-mode
- (doc-view-follow--manage-advice 'add)
- (unless (doc-view-follow--some-buffer-active-p)
- (doc-view-follow--manage-advice 'remove))))
+ (progn
+ (doc-view-follow-setup)
+ (doc-view-follow-sync-pages))
+ (doc-view-follow-teardown)))
-(defun doc-view-follow--sync-pages (&rest _args)
+(defun doc-view-follow-sync-pages (&rest _args)
"Sync pages between windows showing the same document."
(when (and doc-view-follow-mode
(not doc-view-follow--sync-in-progress))
- (let ((doc-view-follow--sync-in-progress t))
- (when-let*
- ((cfg (cdr (assoc major-mode doc-view-follow-modes)))
- (windows (follow-all-followers))
- ((> (length windows) 1)))
- (let* ((current-page (doc-view-follow--call-func cfg :current))
- (max-page (doc-view-follow--call-func cfg :max))
- (current-window (selected-window))
- (window-index (seq-position windows current-window))
- (i 0))
+ (let ((doc-view-follow--sync-in-progress t)
+ (windows (follow-all-followers)))
+ (when (> (length windows) 1)
+ (let* ((current-i (seq-position windows (selected-window)))
+ (current-page (doc-view-follow-get-page))
+ (page (- current-page current-i)))
(dolist (win windows)
- (let ((target-page
- (min max-page
- (max 1 (+ current-page (- i window-index))))))
+ (unless (eq (selected-window) win)
(with-selected-window win
- (doc-view-follow--call-func cfg :goto target-page)))
- (setq i (1+ i))))))))
-
-(defun doc-view-follow--manage-advice (add-or-remove)
- "Add or remove advice for all functions in `doc-view-follow-modes'.
-ADD-OR-REMOVE should be either \\='add or \\='remove."
- (dolist (mode-entry doc-view-follow-modes)
- (dolist (action '(:goto :next :prev))
- (when-let* ((func (plist-get (cdr mode-entry) action)))
- (if (eq add-or-remove 'add)
- (advice-add func :after #'doc-view-follow--sync-pages)
- (advice-remove func #'doc-view-follow--sync-pages))))))
-
-(defun doc-view-follow--some-buffer-active-p ()
- "Return non-nil if some buffer has `doc-view-follow-mode' active."
- (seq-some (lambda (buf)
- (buffer-local-value 'doc-view-follow-mode buf))
- (buffer-list)))
+ (doc-view-follow-set-page page)))
+ (incf page)))))))
(defun doc-view-follow--maybe-enable ()
"Enable `doc-view-follow-mode' if appropriate for this buffer."
- (when (assq major-mode doc-view-follow-modes)
- (doc-view-follow-mode 1)))
+ (when (doc-view-follow-supported-p)
+ (unless doc-view-follow-mode
+ (doc-view-follow-mode 1))))
;;;###autoload
(define-globalized-minor-mode global-doc-view-follow-mode
- doc-view-follow-mode
- doc-view-follow--maybe-enable)
+ doc-view-follow-mode doc-view-follow--maybe-enable)
+
+(require 'doc-view)
+
+(cl-defmethod doc-view-follow-supported-p (&context (major-mode doc-view-mode))
+ "When MAJOR-MODE is `doc-view-mode', return t."
+ t)
+
+(cl-defmethod doc-view-follow-setup (&context (major-mode doc-view-mode))
+ "When MAJOR-MODE is `doc-view-mode', setup `doc-view-follow-mode'."
+ (advice-add 'doc-view-goto-page :after #'doc-view-follow-sync-pages))
+
+(cl-defmethod doc-view-follow-teardown (&context (major-mode doc-view-mode))
+ "When MAJOR-MODE is `doc-view-mode', teardown `doc-view-follow-mode'."
+ (unless
+ (seq-some (lambda (buf)
+ (and (eq (buffer-local-value 'major-mode buf) 'doc-view-mode)
+ (buffer-local-value 'doc-view-follow-mode buf)))
+ (buffer-list))
+ (advice-remove 'doc-view-goto-page #'doc-view-follow-sync-pages)))
+
+(cl-defmethod doc-view-follow-set-page (page &context (major-mode
doc-view-mode))
+ "When MAJOR-MODE is `doc-view-mode', go to PAGE in the document buffer."
+ (let ((page (max 1 (min page (doc-view-last-page-number)))))
+ (doc-view-goto-page page)))
+
+(cl-defmethod doc-view-follow-get-page (&context (major-mode doc-view-mode))
+ "When MAJOR-MODE is `doc-view-mode', return the current page number."
+ (doc-view-current-page))
(provide 'doc-view-follow)
;;; doc-view-follow.el ends here