branch: externals/doc-view-follow
commit 592eb01b8ba102fba33f9ddee46152857126170d
Author: Paul Nelson <ultr...@gmail.com>
Commit: Paul Nelson <ultr...@gmail.com>

    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

Reply via email to