1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
;;; ol-file-paged.el --- Org file links with associated page number -*- lexical-binding: t; -*-
;; Copyright (C) 2025 Simon Parri
;; Author: Simon Parri <simonparri@ganzeria.com>
;; Keywords: multimedia, hypermedia, docs
;; Package-Requires: ((emacs "24.4") (compat "28.1") (seq "1") (org "9.3"))
;; Version: 0.50
;; 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 package provides a link type, "file-paged", that includes both the
;; name of the file and the page number.
;; Example: [[file-paged:doc.pdf::10]] is a link to doc.pdf at page 10.
;; ol-file-paged currently supports DocView and `pdf-tools'. To support more
;; paged-media viewers, see `ol-file-paged-get-page-alist',
;; `ol-file-paged-get-title-alist' and `ol-file-paged-goto-page-alist'.
;;; Code:
(require 'ol)
(defun ol-file-paged--substrings (regexp string &optional start)
"Return the list of all grouops of REGEXP in STRING.
Optional argument START causes search to start at that index in STRING."
(declare (side-effect-free t))
(save-match-data
(when (string-match regexp string start)
(mapcar
(lambda (x)
(if (and (car x) (cadr x))
(substring string (car x) (cadr x))
""))
(seq-partition
(match-data) 2)))))
(defun ol-file-paged--assoc (alist &optional buffer)
"Find the `cdr' of the element in ALIST whose key is BUFFER's major mode.
If BUFFER is nil, use the current buffer."
(with-current-buffer (or buffer (current-buffer))
(letrec
((f (lambda (mode)
(if-let ((fn (assoc mode alist)))
(cdr fn)
(when-let ((parent (get mode 'derived-mode-parent)))
(funcall f parent))))))
(funcall f major-mode))))
(defvar ol-file-paged-get-page-alist
;; FIXME: When doc-view and pdf-tools don't use these macros
;; then change this code
'((doc-view-mode . (lambda () (doc-view-current-page)))
(pdf-view-mode . (lambda () (pdf-view-current-page))))
"Alist of (MAJOR-MODE . FUNCTION) pairs.
Used by `ol-file-paged-get-page' to find the current page in a buffer in
MAJOR-MODE. FUNCTION is called with no arguments in the buffer in
question.")
(defun ol-file-paged-get-page (&optional buffer)
"Get the current page of BUFFER.
If BUFFER is nil, use the current buffer."
(with-current-buffer (or buffer (current-buffer))
(when-let ((f (ol-file-paged--assoc ol-file-paged-get-page-alist)))
(funcall f))))
(defvar ol-file-paged-get-title-alist
'()
"Alist of (MAJOR-MODE . FUNCTION) pairs.
Used by `ol-file-paged-get-title' to find the title of a buffer in
MAJOR-MODE. FUNCTION is called with no arguments in the buffer in
question.")
(defun ol-file-paged-get-title (&optional buffer)
"Get the title of BUFFER.
If BUFFER is nil, use the current buffer."
(with-current-buffer (or buffer (current-buffer))
(when-let ((f (ol-file-paged--assoc
ol-file-paged-get-title-alist)))
(funcall f))))
;;;###autoload
(defun ol-file-paged-store ()
"Store a link to the current file+page.
Does nothing if `ol-file-paged-get-page' cannot determine the current
page."
(when (apply #'derived-mode-p
(mapcar #'car ol-file-paged-get-page-alist))
(let ((page (ol-file-paged-get-page)))
(org-link-store-props
:type "file-paged"
:link (format "file-paged:%s::%d" buffer-file-name page)
:title (ol-file-paged-get-title)))))
(defvar ol-file-paged-goto-page-alist
'((doc-view-mode . doc-view-goto-page)
(pdf-view-mode . pdf-view-goto-page))
"Alist of (MAJOR-MODE . FUNCTION) pairs.
Used by `ol-file-paged-open' to jump to a given page in a buffer in
MAJOR-MODE. FUNCTION is called with the page number in the buffer in
question.")
;;;###autoload
(defun ol-file-paged-open (link &optional arg)
"Open LINK when LINK is an file-paged link.
ARG is ignored."
(ignore arg)
(let* ((x (cdr
(ol-file-paged--substrings
"\\`\\(.+\\)::\\(.+\\)\\'"
link)))
(file (car x))
(page (cadr x)))
(org-open-file file t)
(with-current-buffer (find-buffer-visiting file)
(when-let ((f (ol-file-paged--assoc
ol-file-paged-goto-page-alist)))
(funcall f (string-to-number page))))))
;;;###autoload
(with-eval-after-load 'ol
(org-link-set-parameters
"file-paged"
:store #'ol-file-paged-store
:follow #'ol-file-paged-open))
(provide 'ol-file-paged)
;;; ol-file-paged.el ends here
|