summaryrefslogtreecommitdiff
path: root/elpa/pdf-tools-20200512.1524/pdf-isearch.el
diff options
context:
space:
mode:
authorBlendoit <blendoit@gmail.com>2020-08-01 15:18:40 -0700
committerBlendoit <blendoit@gmail.com>2020-08-01 15:18:40 -0700
commit374ae3de24187512adddf01a56e5eb52c79db65f (patch)
tree847adf6824b56394f5a040ba45863e2dbdceac70 /elpa/pdf-tools-20200512.1524/pdf-isearch.el
parent54fbf6576cf2dd94ef5af332a6075723a9dfa8b3 (diff)
Include contents of elpa/ sources + theme update.
Diffstat (limited to 'elpa/pdf-tools-20200512.1524/pdf-isearch.el')
-rw-r--r--elpa/pdf-tools-20200512.1524/pdf-isearch.el831
1 files changed, 831 insertions, 0 deletions
diff --git a/elpa/pdf-tools-20200512.1524/pdf-isearch.el b/elpa/pdf-tools-20200512.1524/pdf-isearch.el
new file mode 100644
index 0000000..563e721
--- /dev/null
+++ b/elpa/pdf-tools-20200512.1524/pdf-isearch.el
@@ -0,0 +1,831 @@
+;;; pdf-isearch.el --- Isearch in pdf buffers. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2013, 2014 Andreas Politz
+
+;; Author: Andreas Politz <politza@fh-trier.de>
+;; Keywords: files, multimedia
+
+;; 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:
+;;
+;;; Todo:
+;;
+;; * Add the possibility to limit the search to a range of pages.
+
+(require 'cl-lib)
+(require 'pdf-util)
+(require 'pdf-info)
+(require 'pdf-misc)
+(require 'pdf-view)
+(require 'pdf-cache)
+(require 'let-alist)
+
+;;; Code:
+
+
+
+;; * ================================================================== *
+;; * Customizations
+;; * ================================================================== *
+
+(defgroup pdf-isearch nil
+ "Isearch in pdf buffers."
+ :group 'pdf-tools)
+
+(defface pdf-isearch-match
+ '((((background dark)) (:inherit isearch))
+ (((background light)) (:inherit isearch)))
+ "Face used to determine the colors of the current match."
+ :group 'pdf-isearch
+ :group 'pdf-tools-faces)
+
+(defface pdf-isearch-lazy
+ '((((background dark)) (:inherit lazy-highlight))
+ (((background light)) (:inherit lazy-highlight)))
+ "Face used to determine the colors of non-current matches."
+ :group 'pdf-isearch
+ :group 'pdf-tools-faces)
+
+(defface pdf-isearch-batch
+ '((((background dark)) (:inherit match))
+ (((background light)) (:inherit match)))
+ "Face used to determine the colors in `pdf-isearch-batch-mode'."
+ :group 'pdf-isearch
+ :group 'pdf-tools-faces)
+
+(defcustom pdf-isearch-hyphenation-character "-­"
+ "Characters used as hyphens when word searching."
+ :group 'pdf-isearch
+ :type 'string)
+
+(defvar pdf-isearch-search-fun-function nil
+ "Search function used when searching.
+
+Like `isearch-search-fun-function', though it should return a
+function \(FN STRING &optional PAGES\), which in turn should
+return a result like `pdf-info-search-regexp'.")
+
+
+;; * ================================================================== *
+;; * Internal Variables
+;; * ================================================================== *
+
+(defvar-local pdf-isearch-current-page nil
+ "The page that is currently searched.")
+
+(defvar-local pdf-isearch-current-match nil
+ "A list ((LEFT TOP RIGHT BOT) ...) of the current match or nil.
+
+A match may contain more than one edges-element, e.g. when regexp
+searching across multiple lines.")
+
+(defvar-local pdf-isearch-current-matches nil
+ "A list of matches of the last search.")
+
+(defvar-local pdf-isearch-current-parameter nil
+ "A list of search parameter \(search-string regex-p case-fold word-search\).")
+
+
+;; * ================================================================== *
+;; * Modes
+;; * ================================================================== *
+
+(declare-function pdf-occur "pdf-occur.el")
+(declare-function pdf-sync-backward-search "pdf-sync.el")
+
+(defvar pdf-isearch-minor-mode-map
+ (let ((kmap (make-sparse-keymap)))
+ (define-key kmap [remap occur] 'pdf-occur)
+ kmap)
+ "Keymap used in `pdf-isearch-minor-mode'.")
+
+(defvar pdf-isearch-active-mode-map
+ (let ((kmap (make-sparse-keymap)))
+ (set-keymap-parent kmap isearch-mode-map)
+ (define-key kmap (kbd "C-d") 'pdf-view-dark-minor-mode)
+ (define-key kmap (kbd "C-b") 'pdf-isearch-batch-mode)
+ (define-key kmap (kbd "M-s o") 'pdf-isearch-occur)
+ (define-key kmap (kbd "M-s s") 'pdf-isearch-sync-backward)
+ kmap)
+ "Keymap used in `pdf-isearch-active-mode'.
+
+This keymap is used, when isearching in PDF buffers. Its parent
+keymap is `isearch-mode-map'.")
+
+(put 'image-scroll-up 'isearch-scroll t)
+(put 'image-scroll-down 'isearch-scroll t)
+
+(define-minor-mode pdf-isearch-active-mode "" nil nil nil
+ (cond
+ (pdf-isearch-active-mode
+ (set (make-local-variable 'isearch-mode-map)
+ pdf-isearch-active-mode-map)
+ (setq overriding-terminal-local-map
+ isearch-mode-map))
+ (t
+ ;;(setq overriding-terminal-local-map nil) ?
+ (kill-local-variable 'isearch-mode-map))))
+
+;;;###autoload
+(define-minor-mode pdf-isearch-minor-mode
+ "Isearch mode for PDF buffer.
+
+When this mode is enabled \\[isearch-forward], among other keys,
+starts an incremental search in this PDF document. Since this mode
+uses external programs to highlight found matches via
+image-processing, proceeding to the next match may be slow.
+
+Therefore two isearch behaviours have been defined: Normal isearch and
+batch mode. The later one is a minor mode
+\(`pdf-isearch-batch-mode'\), which when activated inhibits isearch
+from stopping at and highlighting every single match, but rather
+display them batch-wise. Here a batch means a number of matches
+currently visible in the selected window.
+
+The kind of highlighting is determined by three faces
+`pdf-isearch-match' \(for the current match\), `pdf-isearch-lazy'
+\(for all other matches\) and `pdf-isearch-batch' \(when in batch
+mode\), which see.
+
+Colors may also be influenced by the minor-mode
+`pdf-view-dark-minor-mode'. If this is minor mode enabled, each face's
+dark colors, are used (see e.g. `frame-background-mode'), instead
+of the light ones.
+
+\\{pdf-isearch-minor-mode-map}
+While in `isearch-mode' the following keys are available. Note
+that not every isearch command work as expected.
+
+\\{pdf-isearch-active-mode-map}"
+ :group 'pdf-isearch
+ (pdf-util-assert-pdf-buffer)
+ (cond
+ (pdf-isearch-minor-mode
+ (when (boundp 'character-fold-search)
+ (setq-local character-fold-search nil))
+ (set (make-local-variable 'isearch-search-fun-function)
+ (lambda nil 'pdf-isearch-search-function))
+ (set (make-local-variable 'isearch-push-state-function)
+ 'pdf-isearch-push-state-function)
+ (set (make-local-variable 'isearch-wrap-function)
+ 'pdf-isearch-wrap-function)
+ (set (make-local-variable 'isearch-lazy-highlight) nil)
+ ;; Make our commands work in isearch-mode.
+ (set (make-local-variable 'isearch-allow-scroll) t)
+ (set (make-local-variable 'search-exit-option)
+ ;; This maybe edit or t, but edit would suppress our cmds
+ ;; in isearch-other-meta-char.
+ (not (not search-exit-option)))
+ ;; FIXME: Die Variable imagemagick-render-type entweder an anderer
+ ;; Stelle global setzen oder nur irgendwo auf den
+ ;; Performancegewinn hinweisen.
+ (when (and (boundp 'imagemagick-render-type)
+ (= 0 imagemagick-render-type))
+ ;; This enormously speeds up rendering.
+ (setq imagemagick-render-type 1))
+ (add-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize nil t)
+ (add-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup nil t)
+ (add-hook 'isearch-update-post-hook 'pdf-isearch-update nil t))
+ (t
+ (when (boundp 'character-fold-search)
+ (kill-local-variable 'character-fold-search))
+ (kill-local-variable 'search-exit-option)
+ (kill-local-variable 'isearch-allow-scroll)
+ (kill-local-variable 'isearch-search-fun-function)
+ (kill-local-variable 'isearch-push-state-function)
+ (kill-local-variable 'isearch-wrap-function)
+ (kill-local-variable 'isearch-lazy-highlight)
+ (remove-hook 'isearch-update-post-hook 'pdf-isearch-update t)
+ (remove-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize t)
+ (remove-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup t))))
+
+(define-minor-mode pdf-isearch-batch-mode
+ "Isearch PDF documents batch-wise.
+
+If this mode is enabled, isearching does not stop at every match,
+but rather moves to the next one not currently visible. This
+behaviour is much faster than ordinary isearch, since far less
+different images have to be displayed."
+ nil nil nil
+ :group 'pdf-isearch
+ (when isearch-mode
+ (pdf-isearch-redisplay)
+ (pdf-isearch-message
+ (if pdf-isearch-batch-mode "batch mode" "isearch mode"))))
+
+
+
+;; * ================================================================== *
+;; * Isearch interface
+;; * ================================================================== *
+
+(defvar pdf-isearch-filter-matches-function nil
+ "A function for filtering isearch matches.
+
+The function receives one argument: a list of matches, each
+being a list of edges. It should return a subset of this list.
+Edge coordinates are in image-space.")
+
+(defvar pdf-isearch-narrow-to-page nil
+ "Non-nil, if the search should be limited to the current page.")
+
+(defun pdf-isearch-search-function (string &rest _)
+ "Search for STRING in the current PDF buffer.
+
+This is a Isearch interface function."
+ (when (> (length string) 0)
+ (let ((same-search-p (pdf-isearch-same-search-p))
+ (oldpage pdf-isearch-current-page)
+ (matches (pdf-isearch-search-page string))
+ next-match)
+ ;; matches is a list of list of edges ((x0 y1 x1 y2) ...),
+ ;; sorted top to bottom ,left to right. Coordinates are in image
+ ;; space.
+ (unless isearch-forward
+ (setq matches (reverse matches)))
+ (when pdf-isearch-filter-matches-function
+ (setq matches (funcall pdf-isearch-filter-matches-function matches)))
+ ;; Where to go next ?
+ (setq pdf-isearch-current-page (pdf-view-current-page)
+ pdf-isearch-current-matches matches
+ next-match
+ (pdf-isearch-next-match
+ oldpage pdf-isearch-current-page
+ pdf-isearch-current-match matches
+ same-search-p
+ isearch-forward)
+ pdf-isearch-current-parameter
+ (list string isearch-regexp
+ isearch-case-fold-search isearch-word))
+ (cond
+ (next-match
+ (setq pdf-isearch-current-match next-match)
+ (pdf-isearch-hl-matches next-match matches)
+ (pdf-isearch-focus-match next-match)
+ ;; Don't get off track.
+ (when (or (and (bobp) (not isearch-forward))
+ (and (eobp) isearch-forward))
+ (goto-char (1+ (/ (buffer-size) 2))))
+ ;; Signal success to isearch.
+ (if isearch-forward
+ (re-search-forward ".")
+ (re-search-backward ".")))
+ ((and (not pdf-isearch-narrow-to-page)
+ (not (pdf-isearch-empty-match-p matches)))
+ (let ((next-page (pdf-isearch-find-next-matching-page
+ string pdf-isearch-current-page t)))
+ (when next-page
+ (pdf-view-goto-page next-page)
+ (pdf-isearch-search-function string))))))))
+
+(defun pdf-isearch-push-state-function ()
+ "Push the current search state.
+
+This is a Isearch interface function."
+ (let ((hscroll (window-hscroll))
+ (vscroll (window-vscroll))
+ (parms pdf-isearch-current-parameter)
+ (matches pdf-isearch-current-matches)
+ (match pdf-isearch-current-match)
+ (page pdf-isearch-current-page))
+ (lambda (_state)
+ (setq pdf-isearch-current-parameter parms
+ pdf-isearch-current-matches matches
+ pdf-isearch-current-match match
+ pdf-isearch-current-page page)
+
+ (pdf-view-goto-page pdf-isearch-current-page)
+ (when pdf-isearch-current-match
+ (pdf-isearch-hl-matches
+ pdf-isearch-current-match
+ pdf-isearch-current-matches))
+ (image-set-window-hscroll hscroll)
+ (image-set-window-vscroll vscroll))))
+
+(defun pdf-isearch-wrap-function ()
+ "Go to first or last page.
+
+This is a Isearch interface function."
+ (let ((page (if isearch-forward
+ 1
+ (pdf-cache-number-of-pages))))
+ (unless (or pdf-isearch-narrow-to-page
+ (= page (pdf-view-current-page)))
+ (pdf-view-goto-page page)
+ (let ((next-screen-context-lines 0))
+ (if (= page 1)
+ (image-scroll-down)
+ (image-scroll-up)))))
+ (setq pdf-isearch-current-match nil))
+
+(defun pdf-isearch-mode-cleanup ()
+ "Cleanup after exiting Isearch.
+
+This is a Isearch interface function."
+ (pdf-isearch-active-mode -1)
+ (pdf-view-redisplay))
+
+(defun pdf-isearch-mode-initialize ()
+ "Initialize isearching.
+
+This is a Isearch interface function."
+ (pdf-isearch-active-mode 1)
+ (setq pdf-isearch-current-page (pdf-view-current-page)
+ pdf-isearch-current-match nil
+ pdf-isearch-current-matches nil
+ pdf-isearch-current-parameter nil)
+ (goto-char (1+ (/ (buffer-size) 2))))
+
+(defun pdf-isearch-same-search-p (&optional ignore-search-string-p)
+ "Return non-nil, if search parameter have not changed.
+
+Parameter inspected are `isearch-string' (unless
+IGNORE-SEARCH-STRING-P is t) and `isearch-case-fold-search'. If
+there was no previous search, this function returns t."
+ (or (null pdf-isearch-current-parameter)
+ (let ((parameter (list isearch-string
+ isearch-regexp
+ isearch-case-fold-search
+ isearch-word)))
+ (if ignore-search-string-p
+ (equal (cdr pdf-isearch-current-parameter)
+ (cdr parameter))
+ (equal pdf-isearch-current-parameter
+ parameter)))))
+
+(defun pdf-isearch-next-match (last-page this-page last-match
+ all-matches continued-p
+ forward-p)
+ "Determine the next match."
+ (funcall (if pdf-isearch-batch-mode
+ 'pdf-isearch-next-match-batch
+ 'pdf-isearch-next-match-isearch)
+ last-page this-page last-match
+ all-matches continued-p forward-p))
+
+(defun pdf-isearch-focus-match (current-match)
+ "Make the CURRENT-MATCH visible in the window."
+ (funcall (if pdf-isearch-batch-mode
+ 'pdf-isearch-focus-match-batch
+ 'pdf-isearch-focus-match-isearch)
+ current-match))
+
+(defun pdf-isearch-redisplay ()
+ "Redisplay the current highlighting."
+ (pdf-isearch-hl-matches pdf-isearch-current-match
+ pdf-isearch-current-matches))
+
+(defun pdf-isearch-update ()
+ "Update search and redisplay, if necessary."
+ (unless (pdf-isearch-same-search-p t)
+ (setq pdf-isearch-current-parameter
+ (list isearch-string isearch-regexp
+ isearch-case-fold-search isearch-word)
+ pdf-isearch-current-matches
+ (pdf-isearch-search-page isearch-string))
+ (pdf-isearch-redisplay)))
+
+(defun pdf-isearch-message (fmt &rest args)
+ "Like `message', but Isearch friendly."
+ (unless args (setq args (list fmt) fmt "%s"))
+ (let ((msg (apply 'format fmt args)))
+ (if (cl-some (lambda (buf)
+ (buffer-local-value 'isearch-mode buf))
+ (mapcar 'window-buffer (window-list)))
+ (let ((isearch-message-suffix-add
+ (format " [%s]" msg)))
+ (isearch-message)
+ (sit-for 1))
+ (message "%s" msg))))
+
+(defun pdf-isearch-empty-match-p (matches)
+ (and matches
+ (cl-every
+ (lambda (match)
+ (cl-every (lambda (edges)
+ (cl-every 'zerop edges))
+ match))
+ matches)))
+
+(defun pdf-isearch-occur ()
+ "Run `occur' using the last search string or regexp."
+ (interactive)
+ (let ((case-fold-search isearch-case-fold-search)
+ (regexp
+ (cond
+ ((functionp isearch-word)
+ (funcall isearch-word isearch-string))
+ (isearch-word (pdf-isearch-word-search-regexp
+ isearch-string nil
+ pdf-isearch-hyphenation-character))
+ (isearch-regexp isearch-string))))
+ (save-selected-window
+ (pdf-occur (or regexp isearch-string) regexp))
+ (isearch-message)))
+
+(defun pdf-isearch-sync-backward ()
+ "Visit the source of the beginning of the current match."
+ (interactive)
+ (pdf-util-assert-pdf-window)
+ (unless pdf-isearch-current-match
+ (user-error "No current or recent match"))
+ (when isearch-mode
+ (isearch-exit))
+ (cl-destructuring-bind (left top _right _bot)
+ (car pdf-isearch-current-match)
+ (pdf-sync-backward-search left top)))
+
+
+;; * ================================================================== *
+;; * Interface to epdfinfo
+;; * ================================================================== *
+
+(defun pdf-isearch-search-page (string &optional page)
+ "Search STRING on PAGE in the current window.
+
+Returns a list of edges (LEFT TOP RIGHT BOTTOM) in PDF
+coordinates, sorted top to bottom, then left to right."
+
+ (unless page (setq page (pdf-view-current-page)))
+ (mapcar (lambda (match)
+ (let-alist match
+ (pdf-util-scale-relative-to-pixel .edges 'round)))
+ (let ((case-fold-search isearch-case-fold-search))
+ (funcall (pdf-isearch-search-fun)
+ string page))))
+
+(defun pdf-isearch-search-fun ()
+ (funcall (or pdf-isearch-search-fun-function
+ 'pdf-isearch-search-fun-default)))
+
+(defun pdf-isearch-search-fun-default ()
+ "Return default functions to use for the search."
+ (cond
+ ((eq isearch-word t)
+ (lambda (string &optional pages)
+ ;; Use lax versions to not fail at the end of the word while
+ ;; the user adds and removes characters in the search string
+ ;; (or when using nonincremental word isearch)
+ (let ((lax (not (or isearch-nonincremental
+ (null (car isearch-cmds))
+ (eq (length isearch-string)
+ (length (isearch--state-string
+ (car isearch-cmds))))))))
+ (pdf-info-search-regexp
+ (pdf-isearch-word-search-regexp
+ string lax pdf-isearch-hyphenation-character)
+ pages 'invalid-regexp))))
+ (isearch-regexp
+ (lambda (string &optional pages)
+ (pdf-info-search-regexp string pages 'invalid-regexp)))
+ (t
+ 'pdf-info-search-string)))
+
+
+(defun pdf-isearch-word-search-regexp (string &optional lax hyphenization-chars)
+ "Return a PCRE which matches words, ignoring punctuation."
+ (let ((hyphenization-regexp
+ (and hyphenization-chars
+ (format "(?:[%s]\\n)?"
+ (replace-regexp-in-string
+ "[]^\\\\-]" "\\\\\\&"
+ hyphenization-chars t)))))
+ (cond
+ ((equal string "") "")
+ ((string-match-p "\\`\\W+\\'" string) "\\W+")
+ (t (concat
+ (if (string-match-p "\\`\\W" string) "\\W+"
+ (unless lax "\\b"))
+ (mapconcat (lambda (word)
+ (if hyphenization-regexp
+ (mapconcat
+ (lambda (ch)
+ (pdf-util-pcre-quote (string ch)))
+ (append word nil)
+ hyphenization-regexp)
+ (pdf-util-pcre-quote word)))
+ (split-string string "\\W+" t) "\\W+")
+ (if (string-match-p "\\W\\'" string) "\\W+"
+ (unless lax "\\b")))))))
+
+(defun pdf-isearch-find-next-matching-page (string page &optional interactive-p)
+ "Find STRING after or before page PAGE, according to FORWARD-P.
+
+If INTERACTIVE-P is non-nil, give some progress feedback.
+Returns the page number where STRING was found, or nil if there
+is no such page."
+ ;; Do a exponentially expanding search.
+ (let* ((incr 1)
+ (pages (if isearch-forward
+ (cons (1+ page)
+ (1+ page))
+ (cons (1- page)
+ (1- page))))
+ (fn (pdf-isearch-search-fun))
+ matched-page
+ reporter)
+
+ (while (and (null matched-page)
+ (or (and isearch-forward
+ (<= (car pages)
+ (pdf-cache-number-of-pages)))
+ (and (not isearch-forward)
+ (>= (cdr pages) 1))))
+ (let* ((case-fold-search isearch-case-fold-search)
+ (matches (funcall fn string pages)))
+ (setq matched-page
+ (alist-get 'page (if isearch-forward
+ (car matches)
+ (car (last matches))))))
+ (setq incr (* incr 2))
+ (cond (isearch-forward
+ (setcar pages (1+ (cdr pages)))
+ (setcdr pages (min (pdf-cache-number-of-pages)
+ (+ (cdr pages) incr))))
+ (t
+ (setcdr pages (1- (car pages)))
+ (setcar pages (max 1 (- (car pages)
+ incr)))))
+ (when interactive-p
+ (when (and (not reporter)
+ (= incr 8)) ;;Don't bother right away.
+ (setq reporter
+ (apply
+ 'make-progress-reporter "Searching"
+ (if isearch-forward
+ (list (car pages) (pdf-cache-number-of-pages) nil 0)
+ (list 1 (cdr pages) nil 0)))))
+ (when reporter
+ (progress-reporter-update
+ reporter (if isearch-forward
+ (- (cdr pages) page)
+ (- page (car pages)))))))
+ matched-page))
+
+
+
+;; * ================================================================== *
+;; * Isearch Behavior
+;; * ================================================================== *
+
+(defun pdf-isearch-next-match-isearch (last-page this-page last-match
+ matches same-search-p
+ forward)
+ "Default function for choosing the next match.
+
+Implements default isearch behaviour, i.e. it stops at every
+match."
+ (cond
+ ((null last-match)
+ ;; Goto first match from top or bottom of the window.
+ (let* ((iedges (pdf-util-image-displayed-edges))
+ (pos (pdf-util-with-edges (iedges)
+ (if forward
+ (list iedges-left iedges-top
+ iedges-left iedges-top)
+ (list iedges-right iedges-bot
+ iedges-right iedges-bot)))))
+ (pdf-isearch-closest-match (list pos) matches forward)))
+ ((not (eq last-page this-page))
+ ;; First match from top-left or bottom-right of the new
+ ;; page.
+ (car matches))
+ (same-search-p
+ ;; Next match after the last one.
+ (if last-match
+ (cadr (member last-match matches))))
+ (matches
+ ;; Next match of new search closest to the last one.
+ (pdf-isearch-closest-match
+ last-match matches forward))))
+
+(defun pdf-isearch-focus-match-isearch (match)
+ "Make the image area in MATCH visible in the selected window."
+ (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match)))
+
+(defun pdf-isearch-next-match-batch (last-page this-page last-match
+ matches same-search-p
+ forward-p)
+ "Select the next match, unseen in the current search direction."
+
+ (if (or (null last-match)
+ (not same-search-p)
+ (not (eq last-page this-page)))
+ (pdf-isearch-next-match-isearch
+ last-page this-page last-match matches same-search-p forward-p)
+ (pdf-util-with-edges (match iedges)
+ (let ((iedges (pdf-util-image-displayed-edges)))
+ (car (cl-remove-if
+ ;; Filter matches visible on screen.
+ (lambda (edges)
+ (let ((match (apply 'pdf-util-edges-union edges)))
+ (and (<= match-right iedges-right)
+ (<= match-bot iedges-bot)
+ (>= match-left iedges-left)
+ (>= match-top iedges-top))))
+ (cdr (member last-match matches))))))))
+
+(defun pdf-isearch-focus-match-batch (match)
+ "Make the image area in MATCH eagerly visible in the selected window."
+ (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match) t))
+
+(cl-deftype pdf-isearch-match ()
+ `(satisfies
+ (lambda (match)
+ (cl-every (lambda (edges)
+ (and (consp edges)
+ (= (length edges) 4)
+ (cl-every 'numberp edges)))
+ match))))
+
+(cl-deftype list-of (type)
+ `(satisfies
+ (lambda (l)
+ (and (listp l)
+ (cl-every (lambda (x)
+ (cl-typep x ',type))
+ l)))))
+
+(defun pdf-isearch-closest-match (match matches
+ &optional forward-p)
+ "Find the nearest element to MATCH in MATCHES.
+
+The direction in which to look is determined by FORWARD-P.
+
+MATCH should be a list of edges, MATCHES a list of such element;
+it is assumed to be ordered with respect to FORWARD-P."
+
+
+ (cl-check-type match pdf-isearch-match)
+ (cl-check-type matches (list-of pdf-isearch-match))
+ (let ((matched (apply 'pdf-util-edges-union match)))
+ (pdf-util-with-edges (matched)
+ (cl-loop for next in matches do
+ (let ((edges (apply 'pdf-util-edges-union next)))
+ (pdf-util-with-edges (edges)
+ (when (if forward-p
+ (or (>= edges-top matched-bot)
+ (and (or (>= edges-top matched-top)
+ (>= edges-bot matched-bot))
+ (>= edges-right matched-right)))
+ (or (<= edges-bot matched-top)
+ (and (or (<= edges-bot matched-bot)
+ (<= edges-top matched-top))
+ (<= edges-left matched-left))))
+ (cl-return next))))))))
+
+
+
+;; * ================================================================== *
+;; * Display
+;; * ================================================================== *
+
+
+(defun pdf-isearch-current-colors ()
+ "Return the current color set.
+
+The return value depends on `pdf-view-dark-minor-mode' and
+`pdf-isearch-batch-mode'. It is a list of four colors \(MATCH-FG
+MATCH-BG LAZY-FG LAZY-BG\)."
+ (let ((dark-p pdf-view-dark-minor-mode))
+ (cond
+ (pdf-isearch-batch-mode
+ (let ((colors (pdf-util-face-colors 'pdf-isearch-batch dark-p)))
+ (list (car colors)
+ (cdr colors)
+ (car colors)
+ (cdr colors))))
+ (t
+ (let ((match (pdf-util-face-colors 'pdf-isearch-match dark-p))
+ (lazy (pdf-util-face-colors 'pdf-isearch-lazy dark-p)))
+ (list (car match)
+ (cdr match)
+ (car lazy)
+ (cdr lazy)))))))
+
+(defvar pdf-isearch--hl-matches-tick 0)
+
+(defun pdf-isearch-hl-matches (current matches &optional occur-hack-p)
+ "Highlighting edges CURRENT and MATCHES."
+ (cl-check-type current pdf-isearch-match)
+ (cl-check-type matches (list-of pdf-isearch-match))
+ (cl-destructuring-bind (fg1 bg1 fg2 bg2)
+ (pdf-isearch-current-colors)
+ (let* ((width (car (pdf-view-image-size)))
+ (page (pdf-view-current-page))
+ (window (selected-window))
+ (buffer (current-buffer))
+ (tick (cl-incf pdf-isearch--hl-matches-tick))
+ (pdf-info-asynchronous
+ (lambda (status data)
+ (when (and (null status)
+ (eq tick pdf-isearch--hl-matches-tick)
+ (buffer-live-p buffer)
+ (window-live-p window)
+ (eq (window-buffer window)
+ buffer))
+ (with-selected-window window
+ (when (and (derived-mode-p 'pdf-view-mode)
+ (or isearch-mode
+ occur-hack-p)
+ (eq page (pdf-view-current-page)))
+ (pdf-view-display-image
+ (pdf-view-create-image data))))))))
+ (pdf-info-renderpage-text-regions
+ page width t nil
+ `(,fg1 ,bg1 ,@(pdf-util-scale-pixel-to-relative
+ current))
+ `(,fg2 ,bg2 ,@(pdf-util-scale-pixel-to-relative
+ (apply 'append
+ (remove current matches))))))))
+
+
+;; * ================================================================== *
+;; * Debug
+;; * ================================================================== *
+
+;; The following isearch-search function is debuggable.
+;;
+(when nil
+ (defun isearch-search ()
+ ;; Do the search with the current search string.
+ (if isearch-message-function
+ (funcall isearch-message-function nil t)
+ (isearch-message nil t))
+ (if (and (eq isearch-case-fold-search t) search-upper-case)
+ (setq isearch-case-fold-search
+ (isearch-no-upper-case-p isearch-string isearch-regexp)))
+ (condition-case lossage
+ (let ((inhibit-point-motion-hooks
+ ;; FIXME: equality comparisons on functions is asking for trouble.
+ (and (eq isearch-filter-predicate 'isearch-filter-visible)
+ search-invisible))
+ (inhibit-quit nil)
+ (case-fold-search isearch-case-fold-search)
+ (retry t))
+ (setq isearch-error nil)
+ (while retry
+ (setq isearch-success
+ (isearch-search-string isearch-string nil t))
+ ;; Clear RETRY unless the search predicate says
+ ;; to skip this search hit.
+ (if (or (not isearch-success)
+ (bobp) (eobp)
+ (= (match-beginning 0) (match-end 0))
+ (funcall isearch-filter-predicate
+ (match-beginning 0) (match-end 0)))
+ (setq retry nil)))
+ (setq isearch-just-started nil)
+ (if isearch-success
+ (setq isearch-other-end
+ (if isearch-forward (match-beginning 0) (match-end 0)))))
+
+ (quit (isearch-unread ?\C-g)
+ (setq isearch-success nil))
+
+ (invalid-regexp
+ (setq isearch-error (car (cdr lossage)))
+ (if (string-match
+ "\\`Premature \\|\\`Unmatched \\|\\`Invalid "
+ isearch-error)
+ (setq isearch-error "incomplete input")))
+
+ (search-failed
+ (setq isearch-success nil)
+ (setq isearch-error (nth 2 lossage)))
+
+ ;; (error
+ ;; ;; stack overflow in regexp search.
+ ;; (setq isearch-error (format "%s" lossage)))
+ )
+
+ (if isearch-success
+ nil
+ ;; Ding if failed this time after succeeding last time.
+ (and (isearch--state-success (car isearch-cmds))
+ (ding))
+ (if (functionp (isearch--state-pop-fun (car isearch-cmds)))
+ (funcall (isearch--state-pop-fun (car isearch-cmds))
+ (car isearch-cmds)))
+ (goto-char (isearch--state-point (car isearch-cmds))))))
+
+
+(provide 'pdf-isearch)
+
+;;; pdf-isearch.el ends here
+
+;; Local Variables:
+;; byte-compile-warnings: (not obsolete)
+;; End:
Copyright 2019--2024 Marius PETER