833 lines
30 KiB
EmacsLisp
833 lines
30 KiB
EmacsLisp
;;; 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
|
||
"This mode is enabled when isearch is active in a PDF file."
|
||
:group 'pdf-isearch
|
||
(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."
|
||
: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 :width width))))))))
|
||
(pdf-info-renderpage-text-regions
|
||
page width t nil 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:
|