Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

831 line
30 KiB

5 年之前
  1. ;;; pdf-isearch.el --- Isearch in pdf buffers. -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2013, 2014 Andreas Politz
  3. ;; Author: Andreas Politz <politza@fh-trier.de>
  4. ;; Keywords: files, multimedia
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;;; Todo:
  18. ;;
  19. ;; * Add the possibility to limit the search to a range of pages.
  20. (require 'cl-lib)
  21. (require 'pdf-util)
  22. (require 'pdf-info)
  23. (require 'pdf-misc)
  24. (require 'pdf-view)
  25. (require 'pdf-cache)
  26. (require 'let-alist)
  27. ;;; Code:
  28. ;; * ================================================================== *
  29. ;; * Customizations
  30. ;; * ================================================================== *
  31. (defgroup pdf-isearch nil
  32. "Isearch in pdf buffers."
  33. :group 'pdf-tools)
  34. (defface pdf-isearch-match
  35. '((((background dark)) (:inherit isearch))
  36. (((background light)) (:inherit isearch)))
  37. "Face used to determine the colors of the current match."
  38. :group 'pdf-isearch
  39. :group 'pdf-tools-faces)
  40. (defface pdf-isearch-lazy
  41. '((((background dark)) (:inherit lazy-highlight))
  42. (((background light)) (:inherit lazy-highlight)))
  43. "Face used to determine the colors of non-current matches."
  44. :group 'pdf-isearch
  45. :group 'pdf-tools-faces)
  46. (defface pdf-isearch-batch
  47. '((((background dark)) (:inherit match))
  48. (((background light)) (:inherit match)))
  49. "Face used to determine the colors in `pdf-isearch-batch-mode'."
  50. :group 'pdf-isearch
  51. :group 'pdf-tools-faces)
  52. (defcustom pdf-isearch-hyphenation-character ""
  53. "Characters used as hyphens when word searching."
  54. :group 'pdf-isearch
  55. :type 'string)
  56. (defvar pdf-isearch-search-fun-function nil
  57. "Search function used when searching.
  58. Like `isearch-search-fun-function', though it should return a
  59. function \(FN STRING &optional PAGES\), which in turn should
  60. return a result like `pdf-info-search-regexp'.")
  61. ;; * ================================================================== *
  62. ;; * Internal Variables
  63. ;; * ================================================================== *
  64. (defvar-local pdf-isearch-current-page nil
  65. "The page that is currently searched.")
  66. (defvar-local pdf-isearch-current-match nil
  67. "A list ((LEFT TOP RIGHT BOT) ...) of the current match or nil.
  68. A match may contain more than one edges-element, e.g. when regexp
  69. searching across multiple lines.")
  70. (defvar-local pdf-isearch-current-matches nil
  71. "A list of matches of the last search.")
  72. (defvar-local pdf-isearch-current-parameter nil
  73. "A list of search parameter \(search-string regex-p case-fold word-search\).")
  74. ;; * ================================================================== *
  75. ;; * Modes
  76. ;; * ================================================================== *
  77. (declare-function pdf-occur "pdf-occur.el")
  78. (declare-function pdf-sync-backward-search "pdf-sync.el")
  79. (defvar pdf-isearch-minor-mode-map
  80. (let ((kmap (make-sparse-keymap)))
  81. (define-key kmap [remap occur] 'pdf-occur)
  82. kmap)
  83. "Keymap used in `pdf-isearch-minor-mode'.")
  84. (defvar pdf-isearch-active-mode-map
  85. (let ((kmap (make-sparse-keymap)))
  86. (set-keymap-parent kmap isearch-mode-map)
  87. (define-key kmap (kbd "C-d") 'pdf-view-dark-minor-mode)
  88. (define-key kmap (kbd "C-b") 'pdf-isearch-batch-mode)
  89. (define-key kmap (kbd "M-s o") 'pdf-isearch-occur)
  90. (define-key kmap (kbd "M-s s") 'pdf-isearch-sync-backward)
  91. kmap)
  92. "Keymap used in `pdf-isearch-active-mode'.
  93. This keymap is used, when isearching in PDF buffers. Its parent
  94. keymap is `isearch-mode-map'.")
  95. (put 'image-scroll-up 'isearch-scroll t)
  96. (put 'image-scroll-down 'isearch-scroll t)
  97. (define-minor-mode pdf-isearch-active-mode "" nil nil nil
  98. (cond
  99. (pdf-isearch-active-mode
  100. (set (make-local-variable 'isearch-mode-map)
  101. pdf-isearch-active-mode-map)
  102. (setq overriding-terminal-local-map
  103. isearch-mode-map))
  104. (t
  105. ;;(setq overriding-terminal-local-map nil) ?
  106. (kill-local-variable 'isearch-mode-map))))
  107. ;;;###autoload
  108. (define-minor-mode pdf-isearch-minor-mode
  109. "Isearch mode for PDF buffer.
  110. When this mode is enabled \\[isearch-forward], among other keys,
  111. starts an incremental search in this PDF document. Since this mode
  112. uses external programs to highlight found matches via
  113. image-processing, proceeding to the next match may be slow.
  114. Therefore two isearch behaviours have been defined: Normal isearch and
  115. batch mode. The later one is a minor mode
  116. \(`pdf-isearch-batch-mode'\), which when activated inhibits isearch
  117. from stopping at and highlighting every single match, but rather
  118. display them batch-wise. Here a batch means a number of matches
  119. currently visible in the selected window.
  120. The kind of highlighting is determined by three faces
  121. `pdf-isearch-match' \(for the current match\), `pdf-isearch-lazy'
  122. \(for all other matches\) and `pdf-isearch-batch' \(when in batch
  123. mode\), which see.
  124. Colors may also be influenced by the minor-mode
  125. `pdf-view-dark-minor-mode'. If this is minor mode enabled, each face's
  126. dark colors, are used (see e.g. `frame-background-mode'), instead
  127. of the light ones.
  128. \\{pdf-isearch-minor-mode-map}
  129. While in `isearch-mode' the following keys are available. Note
  130. that not every isearch command work as expected.
  131. \\{pdf-isearch-active-mode-map}"
  132. :group 'pdf-isearch
  133. (pdf-util-assert-pdf-buffer)
  134. (cond
  135. (pdf-isearch-minor-mode
  136. (when (boundp 'character-fold-search)
  137. (setq-local character-fold-search nil))
  138. (set (make-local-variable 'isearch-search-fun-function)
  139. (lambda nil 'pdf-isearch-search-function))
  140. (set (make-local-variable 'isearch-push-state-function)
  141. 'pdf-isearch-push-state-function)
  142. (set (make-local-variable 'isearch-wrap-function)
  143. 'pdf-isearch-wrap-function)
  144. (set (make-local-variable 'isearch-lazy-highlight) nil)
  145. ;; Make our commands work in isearch-mode.
  146. (set (make-local-variable 'isearch-allow-scroll) t)
  147. (set (make-local-variable 'search-exit-option)
  148. ;; This maybe edit or t, but edit would suppress our cmds
  149. ;; in isearch-other-meta-char.
  150. (not (not search-exit-option)))
  151. ;; FIXME: Die Variable imagemagick-render-type entweder an anderer
  152. ;; Stelle global setzen oder nur irgendwo auf den
  153. ;; Performancegewinn hinweisen.
  154. (when (and (boundp 'imagemagick-render-type)
  155. (= 0 imagemagick-render-type))
  156. ;; This enormously speeds up rendering.
  157. (setq imagemagick-render-type 1))
  158. (add-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize nil t)
  159. (add-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup nil t)
  160. (add-hook 'isearch-update-post-hook 'pdf-isearch-update nil t))
  161. (t
  162. (when (boundp 'character-fold-search)
  163. (kill-local-variable 'character-fold-search))
  164. (kill-local-variable 'search-exit-option)
  165. (kill-local-variable 'isearch-allow-scroll)
  166. (kill-local-variable 'isearch-search-fun-function)
  167. (kill-local-variable 'isearch-push-state-function)
  168. (kill-local-variable 'isearch-wrap-function)
  169. (kill-local-variable 'isearch-lazy-highlight)
  170. (remove-hook 'isearch-update-post-hook 'pdf-isearch-update t)
  171. (remove-hook 'isearch-mode-hook 'pdf-isearch-mode-initialize t)
  172. (remove-hook 'isearch-mode-end-hook 'pdf-isearch-mode-cleanup t))))
  173. (define-minor-mode pdf-isearch-batch-mode
  174. "Isearch PDF documents batch-wise.
  175. If this mode is enabled, isearching does not stop at every match,
  176. but rather moves to the next one not currently visible. This
  177. behaviour is much faster than ordinary isearch, since far less
  178. different images have to be displayed."
  179. nil nil nil
  180. :group 'pdf-isearch
  181. (when isearch-mode
  182. (pdf-isearch-redisplay)
  183. (pdf-isearch-message
  184. (if pdf-isearch-batch-mode "batch mode" "isearch mode"))))
  185. ;; * ================================================================== *
  186. ;; * Isearch interface
  187. ;; * ================================================================== *
  188. (defvar pdf-isearch-filter-matches-function nil
  189. "A function for filtering isearch matches.
  190. The function receives one argument: a list of matches, each
  191. being a list of edges. It should return a subset of this list.
  192. Edge coordinates are in image-space.")
  193. (defvar pdf-isearch-narrow-to-page nil
  194. "Non-nil, if the search should be limited to the current page.")
  195. (defun pdf-isearch-search-function (string &rest _)
  196. "Search for STRING in the current PDF buffer.
  197. This is a Isearch interface function."
  198. (when (> (length string) 0)
  199. (let ((same-search-p (pdf-isearch-same-search-p))
  200. (oldpage pdf-isearch-current-page)
  201. (matches (pdf-isearch-search-page string))
  202. next-match)
  203. ;; matches is a list of list of edges ((x0 y1 x1 y2) ...),
  204. ;; sorted top to bottom ,left to right. Coordinates are in image
  205. ;; space.
  206. (unless isearch-forward
  207. (setq matches (reverse matches)))
  208. (when pdf-isearch-filter-matches-function
  209. (setq matches (funcall pdf-isearch-filter-matches-function matches)))
  210. ;; Where to go next ?
  211. (setq pdf-isearch-current-page (pdf-view-current-page)
  212. pdf-isearch-current-matches matches
  213. next-match
  214. (pdf-isearch-next-match
  215. oldpage pdf-isearch-current-page
  216. pdf-isearch-current-match matches
  217. same-search-p
  218. isearch-forward)
  219. pdf-isearch-current-parameter
  220. (list string isearch-regexp
  221. isearch-case-fold-search isearch-word))
  222. (cond
  223. (next-match
  224. (setq pdf-isearch-current-match next-match)
  225. (pdf-isearch-hl-matches next-match matches)
  226. (pdf-isearch-focus-match next-match)
  227. ;; Don't get off track.
  228. (when (or (and (bobp) (not isearch-forward))
  229. (and (eobp) isearch-forward))
  230. (goto-char (1+ (/ (buffer-size) 2))))
  231. ;; Signal success to isearch.
  232. (if isearch-forward
  233. (re-search-forward ".")
  234. (re-search-backward ".")))
  235. ((and (not pdf-isearch-narrow-to-page)
  236. (not (pdf-isearch-empty-match-p matches)))
  237. (let ((next-page (pdf-isearch-find-next-matching-page
  238. string pdf-isearch-current-page t)))
  239. (when next-page
  240. (pdf-view-goto-page next-page)
  241. (pdf-isearch-search-function string))))))))
  242. (defun pdf-isearch-push-state-function ()
  243. "Push the current search state.
  244. This is a Isearch interface function."
  245. (let ((hscroll (window-hscroll))
  246. (vscroll (window-vscroll))
  247. (parms pdf-isearch-current-parameter)
  248. (matches pdf-isearch-current-matches)
  249. (match pdf-isearch-current-match)
  250. (page pdf-isearch-current-page))
  251. (lambda (_state)
  252. (setq pdf-isearch-current-parameter parms
  253. pdf-isearch-current-matches matches
  254. pdf-isearch-current-match match
  255. pdf-isearch-current-page page)
  256. (pdf-view-goto-page pdf-isearch-current-page)
  257. (when pdf-isearch-current-match
  258. (pdf-isearch-hl-matches
  259. pdf-isearch-current-match
  260. pdf-isearch-current-matches))
  261. (image-set-window-hscroll hscroll)
  262. (image-set-window-vscroll vscroll))))
  263. (defun pdf-isearch-wrap-function ()
  264. "Go to first or last page.
  265. This is a Isearch interface function."
  266. (let ((page (if isearch-forward
  267. 1
  268. (pdf-cache-number-of-pages))))
  269. (unless (or pdf-isearch-narrow-to-page
  270. (= page (pdf-view-current-page)))
  271. (pdf-view-goto-page page)
  272. (let ((next-screen-context-lines 0))
  273. (if (= page 1)
  274. (image-scroll-down)
  275. (image-scroll-up)))))
  276. (setq pdf-isearch-current-match nil))
  277. (defun pdf-isearch-mode-cleanup ()
  278. "Cleanup after exiting Isearch.
  279. This is a Isearch interface function."
  280. (pdf-isearch-active-mode -1)
  281. (pdf-view-redisplay))
  282. (defun pdf-isearch-mode-initialize ()
  283. "Initialize isearching.
  284. This is a Isearch interface function."
  285. (pdf-isearch-active-mode 1)
  286. (setq pdf-isearch-current-page (pdf-view-current-page)
  287. pdf-isearch-current-match nil
  288. pdf-isearch-current-matches nil
  289. pdf-isearch-current-parameter nil)
  290. (goto-char (1+ (/ (buffer-size) 2))))
  291. (defun pdf-isearch-same-search-p (&optional ignore-search-string-p)
  292. "Return non-nil, if search parameter have not changed.
  293. Parameter inspected are `isearch-string' (unless
  294. IGNORE-SEARCH-STRING-P is t) and `isearch-case-fold-search'. If
  295. there was no previous search, this function returns t."
  296. (or (null pdf-isearch-current-parameter)
  297. (let ((parameter (list isearch-string
  298. isearch-regexp
  299. isearch-case-fold-search
  300. isearch-word)))
  301. (if ignore-search-string-p
  302. (equal (cdr pdf-isearch-current-parameter)
  303. (cdr parameter))
  304. (equal pdf-isearch-current-parameter
  305. parameter)))))
  306. (defun pdf-isearch-next-match (last-page this-page last-match
  307. all-matches continued-p
  308. forward-p)
  309. "Determine the next match."
  310. (funcall (if pdf-isearch-batch-mode
  311. 'pdf-isearch-next-match-batch
  312. 'pdf-isearch-next-match-isearch)
  313. last-page this-page last-match
  314. all-matches continued-p forward-p))
  315. (defun pdf-isearch-focus-match (current-match)
  316. "Make the CURRENT-MATCH visible in the window."
  317. (funcall (if pdf-isearch-batch-mode
  318. 'pdf-isearch-focus-match-batch
  319. 'pdf-isearch-focus-match-isearch)
  320. current-match))
  321. (defun pdf-isearch-redisplay ()
  322. "Redisplay the current highlighting."
  323. (pdf-isearch-hl-matches pdf-isearch-current-match
  324. pdf-isearch-current-matches))
  325. (defun pdf-isearch-update ()
  326. "Update search and redisplay, if necessary."
  327. (unless (pdf-isearch-same-search-p t)
  328. (setq pdf-isearch-current-parameter
  329. (list isearch-string isearch-regexp
  330. isearch-case-fold-search isearch-word)
  331. pdf-isearch-current-matches
  332. (pdf-isearch-search-page isearch-string))
  333. (pdf-isearch-redisplay)))
  334. (defun pdf-isearch-message (fmt &rest args)
  335. "Like `message', but Isearch friendly."
  336. (unless args (setq args (list fmt) fmt "%s"))
  337. (let ((msg (apply 'format fmt args)))
  338. (if (cl-some (lambda (buf)
  339. (buffer-local-value 'isearch-mode buf))
  340. (mapcar 'window-buffer (window-list)))
  341. (let ((isearch-message-suffix-add
  342. (format " [%s]" msg)))
  343. (isearch-message)
  344. (sit-for 1))
  345. (message "%s" msg))))
  346. (defun pdf-isearch-empty-match-p (matches)
  347. (and matches
  348. (cl-every
  349. (lambda (match)
  350. (cl-every (lambda (edges)
  351. (cl-every 'zerop edges))
  352. match))
  353. matches)))
  354. (defun pdf-isearch-occur ()
  355. "Run `occur' using the last search string or regexp."
  356. (interactive)
  357. (let ((case-fold-search isearch-case-fold-search)
  358. (regexp
  359. (cond
  360. ((functionp isearch-word)
  361. (funcall isearch-word isearch-string))
  362. (isearch-word (pdf-isearch-word-search-regexp
  363. isearch-string nil
  364. pdf-isearch-hyphenation-character))
  365. (isearch-regexp isearch-string))))
  366. (save-selected-window
  367. (pdf-occur (or regexp isearch-string) regexp))
  368. (isearch-message)))
  369. (defun pdf-isearch-sync-backward ()
  370. "Visit the source of the beginning of the current match."
  371. (interactive)
  372. (pdf-util-assert-pdf-window)
  373. (unless pdf-isearch-current-match
  374. (user-error "No current or recent match"))
  375. (when isearch-mode
  376. (isearch-exit))
  377. (cl-destructuring-bind (left top _right _bot)
  378. (car pdf-isearch-current-match)
  379. (pdf-sync-backward-search left top)))
  380. ;; * ================================================================== *
  381. ;; * Interface to epdfinfo
  382. ;; * ================================================================== *
  383. (defun pdf-isearch-search-page (string &optional page)
  384. "Search STRING on PAGE in the current window.
  385. Returns a list of edges (LEFT TOP RIGHT BOTTOM) in PDF
  386. coordinates, sorted top to bottom, then left to right."
  387. (unless page (setq page (pdf-view-current-page)))
  388. (mapcar (lambda (match)
  389. (let-alist match
  390. (pdf-util-scale-relative-to-pixel .edges 'round)))
  391. (let ((case-fold-search isearch-case-fold-search))
  392. (funcall (pdf-isearch-search-fun)
  393. string page))))
  394. (defun pdf-isearch-search-fun ()
  395. (funcall (or pdf-isearch-search-fun-function
  396. 'pdf-isearch-search-fun-default)))
  397. (defun pdf-isearch-search-fun-default ()
  398. "Return default functions to use for the search."
  399. (cond
  400. ((eq isearch-word t)
  401. (lambda (string &optional pages)
  402. ;; Use lax versions to not fail at the end of the word while
  403. ;; the user adds and removes characters in the search string
  404. ;; (or when using nonincremental word isearch)
  405. (let ((lax (not (or isearch-nonincremental
  406. (null (car isearch-cmds))
  407. (eq (length isearch-string)
  408. (length (isearch--state-string
  409. (car isearch-cmds))))))))
  410. (pdf-info-search-regexp
  411. (pdf-isearch-word-search-regexp
  412. string lax pdf-isearch-hyphenation-character)
  413. pages 'invalid-regexp))))
  414. (isearch-regexp
  415. (lambda (string &optional pages)
  416. (pdf-info-search-regexp string pages 'invalid-regexp)))
  417. (t
  418. 'pdf-info-search-string)))
  419. (defun pdf-isearch-word-search-regexp (string &optional lax hyphenization-chars)
  420. "Return a PCRE which matches words, ignoring punctuation."
  421. (let ((hyphenization-regexp
  422. (and hyphenization-chars
  423. (format "(?:[%s]\\n)?"
  424. (replace-regexp-in-string
  425. "[]^\\\\-]" "\\\\\\&"
  426. hyphenization-chars t)))))
  427. (cond
  428. ((equal string "") "")
  429. ((string-match-p "\\`\\W+\\'" string) "\\W+")
  430. (t (concat
  431. (if (string-match-p "\\`\\W" string) "\\W+"
  432. (unless lax "\\b"))
  433. (mapconcat (lambda (word)
  434. (if hyphenization-regexp
  435. (mapconcat
  436. (lambda (ch)
  437. (pdf-util-pcre-quote (string ch)))
  438. (append word nil)
  439. hyphenization-regexp)
  440. (pdf-util-pcre-quote word)))
  441. (split-string string "\\W+" t) "\\W+")
  442. (if (string-match-p "\\W\\'" string) "\\W+"
  443. (unless lax "\\b")))))))
  444. (defun pdf-isearch-find-next-matching-page (string page &optional interactive-p)
  445. "Find STRING after or before page PAGE, according to FORWARD-P.
  446. If INTERACTIVE-P is non-nil, give some progress feedback.
  447. Returns the page number where STRING was found, or nil if there
  448. is no such page."
  449. ;; Do a exponentially expanding search.
  450. (let* ((incr 1)
  451. (pages (if isearch-forward
  452. (cons (1+ page)
  453. (1+ page))
  454. (cons (1- page)
  455. (1- page))))
  456. (fn (pdf-isearch-search-fun))
  457. matched-page
  458. reporter)
  459. (while (and (null matched-page)
  460. (or (and isearch-forward
  461. (<= (car pages)
  462. (pdf-cache-number-of-pages)))
  463. (and (not isearch-forward)
  464. (>= (cdr pages) 1))))
  465. (let* ((case-fold-search isearch-case-fold-search)
  466. (matches (funcall fn string pages)))
  467. (setq matched-page
  468. (alist-get 'page (if isearch-forward
  469. (car matches)
  470. (car (last matches))))))
  471. (setq incr (* incr 2))
  472. (cond (isearch-forward
  473. (setcar pages (1+ (cdr pages)))
  474. (setcdr pages (min (pdf-cache-number-of-pages)
  475. (+ (cdr pages) incr))))
  476. (t
  477. (setcdr pages (1- (car pages)))
  478. (setcar pages (max 1 (- (car pages)
  479. incr)))))
  480. (when interactive-p
  481. (when (and (not reporter)
  482. (= incr 8)) ;;Don't bother right away.
  483. (setq reporter
  484. (apply
  485. 'make-progress-reporter "Searching"
  486. (if isearch-forward
  487. (list (car pages) (pdf-cache-number-of-pages) nil 0)
  488. (list 1 (cdr pages) nil 0)))))
  489. (when reporter
  490. (progress-reporter-update
  491. reporter (if isearch-forward
  492. (- (cdr pages) page)
  493. (- page (car pages)))))))
  494. matched-page))
  495. ;; * ================================================================== *
  496. ;; * Isearch Behavior
  497. ;; * ================================================================== *
  498. (defun pdf-isearch-next-match-isearch (last-page this-page last-match
  499. matches same-search-p
  500. forward)
  501. "Default function for choosing the next match.
  502. Implements default isearch behaviour, i.e. it stops at every
  503. match."
  504. (cond
  505. ((null last-match)
  506. ;; Goto first match from top or bottom of the window.
  507. (let* ((iedges (pdf-util-image-displayed-edges))
  508. (pos (pdf-util-with-edges (iedges)
  509. (if forward
  510. (list iedges-left iedges-top
  511. iedges-left iedges-top)
  512. (list iedges-right iedges-bot
  513. iedges-right iedges-bot)))))
  514. (pdf-isearch-closest-match (list pos) matches forward)))
  515. ((not (eq last-page this-page))
  516. ;; First match from top-left or bottom-right of the new
  517. ;; page.
  518. (car matches))
  519. (same-search-p
  520. ;; Next match after the last one.
  521. (if last-match
  522. (cadr (member last-match matches))))
  523. (matches
  524. ;; Next match of new search closest to the last one.
  525. (pdf-isearch-closest-match
  526. last-match matches forward))))
  527. (defun pdf-isearch-focus-match-isearch (match)
  528. "Make the image area in MATCH visible in the selected window."
  529. (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match)))
  530. (defun pdf-isearch-next-match-batch (last-page this-page last-match
  531. matches same-search-p
  532. forward-p)
  533. "Select the next match, unseen in the current search direction."
  534. (if (or (null last-match)
  535. (not same-search-p)
  536. (not (eq last-page this-page)))
  537. (pdf-isearch-next-match-isearch
  538. last-page this-page last-match matches same-search-p forward-p)
  539. (pdf-util-with-edges (match iedges)
  540. (let ((iedges (pdf-util-image-displayed-edges)))
  541. (car (cl-remove-if
  542. ;; Filter matches visible on screen.
  543. (lambda (edges)
  544. (let ((match (apply 'pdf-util-edges-union edges)))
  545. (and (<= match-right iedges-right)
  546. (<= match-bot iedges-bot)
  547. (>= match-left iedges-left)
  548. (>= match-top iedges-top))))
  549. (cdr (member last-match matches))))))))
  550. (defun pdf-isearch-focus-match-batch (match)
  551. "Make the image area in MATCH eagerly visible in the selected window."
  552. (pdf-util-scroll-to-edges (apply 'pdf-util-edges-union match) t))
  553. (cl-deftype pdf-isearch-match ()
  554. `(satisfies
  555. (lambda (match)
  556. (cl-every (lambda (edges)
  557. (and (consp edges)
  558. (= (length edges) 4)
  559. (cl-every 'numberp edges)))
  560. match))))
  561. (cl-deftype list-of (type)
  562. `(satisfies
  563. (lambda (l)
  564. (and (listp l)
  565. (cl-every (lambda (x)
  566. (cl-typep x ',type))
  567. l)))))
  568. (defun pdf-isearch-closest-match (match matches
  569. &optional forward-p)
  570. "Find the nearest element to MATCH in MATCHES.
  571. The direction in which to look is determined by FORWARD-P.
  572. MATCH should be a list of edges, MATCHES a list of such element;
  573. it is assumed to be ordered with respect to FORWARD-P."
  574. (cl-check-type match pdf-isearch-match)
  575. (cl-check-type matches (list-of pdf-isearch-match))
  576. (let ((matched (apply 'pdf-util-edges-union match)))
  577. (pdf-util-with-edges (matched)
  578. (cl-loop for next in matches do
  579. (let ((edges (apply 'pdf-util-edges-union next)))
  580. (pdf-util-with-edges (edges)
  581. (when (if forward-p
  582. (or (>= edges-top matched-bot)
  583. (and (or (>= edges-top matched-top)
  584. (>= edges-bot matched-bot))
  585. (>= edges-right matched-right)))
  586. (or (<= edges-bot matched-top)
  587. (and (or (<= edges-bot matched-bot)
  588. (<= edges-top matched-top))
  589. (<= edges-left matched-left))))
  590. (cl-return next))))))))
  591. ;; * ================================================================== *
  592. ;; * Display
  593. ;; * ================================================================== *
  594. (defun pdf-isearch-current-colors ()
  595. "Return the current color set.
  596. The return value depends on `pdf-view-dark-minor-mode' and
  597. `pdf-isearch-batch-mode'. It is a list of four colors \(MATCH-FG
  598. MATCH-BG LAZY-FG LAZY-BG\)."
  599. (let ((dark-p pdf-view-dark-minor-mode))
  600. (cond
  601. (pdf-isearch-batch-mode
  602. (let ((colors (pdf-util-face-colors 'pdf-isearch-batch dark-p)))
  603. (list (car colors)
  604. (cdr colors)
  605. (car colors)
  606. (cdr colors))))
  607. (t
  608. (let ((match (pdf-util-face-colors 'pdf-isearch-match dark-p))
  609. (lazy (pdf-util-face-colors 'pdf-isearch-lazy dark-p)))
  610. (list (car match)
  611. (cdr match)
  612. (car lazy)
  613. (cdr lazy)))))))
  614. (defvar pdf-isearch--hl-matches-tick 0)
  615. (defun pdf-isearch-hl-matches (current matches &optional occur-hack-p)
  616. "Highlighting edges CURRENT and MATCHES."
  617. (cl-check-type current pdf-isearch-match)
  618. (cl-check-type matches (list-of pdf-isearch-match))
  619. (cl-destructuring-bind (fg1 bg1 fg2 bg2)
  620. (pdf-isearch-current-colors)
  621. (let* ((width (car (pdf-view-image-size)))
  622. (page (pdf-view-current-page))
  623. (window (selected-window))
  624. (buffer (current-buffer))
  625. (tick (cl-incf pdf-isearch--hl-matches-tick))
  626. (pdf-info-asynchronous
  627. (lambda (status data)
  628. (when (and (null status)
  629. (eq tick pdf-isearch--hl-matches-tick)
  630. (buffer-live-p buffer)
  631. (window-live-p window)
  632. (eq (window-buffer window)
  633. buffer))
  634. (with-selected-window window
  635. (when (and (derived-mode-p 'pdf-view-mode)
  636. (or isearch-mode
  637. occur-hack-p)
  638. (eq page (pdf-view-current-page)))
  639. (pdf-view-display-image
  640. (pdf-view-create-image data))))))))
  641. (pdf-info-renderpage-text-regions
  642. page width t nil
  643. `(,fg1 ,bg1 ,@(pdf-util-scale-pixel-to-relative
  644. current))
  645. `(,fg2 ,bg2 ,@(pdf-util-scale-pixel-to-relative
  646. (apply 'append
  647. (remove current matches))))))))
  648. ;; * ================================================================== *
  649. ;; * Debug
  650. ;; * ================================================================== *
  651. ;; The following isearch-search function is debuggable.
  652. ;;
  653. (when nil
  654. (defun isearch-search ()
  655. ;; Do the search with the current search string.
  656. (if isearch-message-function
  657. (funcall isearch-message-function nil t)
  658. (isearch-message nil t))
  659. (if (and (eq isearch-case-fold-search t) search-upper-case)
  660. (setq isearch-case-fold-search
  661. (isearch-no-upper-case-p isearch-string isearch-regexp)))
  662. (condition-case lossage
  663. (let ((inhibit-point-motion-hooks
  664. ;; FIXME: equality comparisons on functions is asking for trouble.
  665. (and (eq isearch-filter-predicate 'isearch-filter-visible)
  666. search-invisible))
  667. (inhibit-quit nil)
  668. (case-fold-search isearch-case-fold-search)
  669. (retry t))
  670. (setq isearch-error nil)
  671. (while retry
  672. (setq isearch-success
  673. (isearch-search-string isearch-string nil t))
  674. ;; Clear RETRY unless the search predicate says
  675. ;; to skip this search hit.
  676. (if (or (not isearch-success)
  677. (bobp) (eobp)
  678. (= (match-beginning 0) (match-end 0))
  679. (funcall isearch-filter-predicate
  680. (match-beginning 0) (match-end 0)))
  681. (setq retry nil)))
  682. (setq isearch-just-started nil)
  683. (if isearch-success
  684. (setq isearch-other-end
  685. (if isearch-forward (match-beginning 0) (match-end 0)))))
  686. (quit (isearch-unread ?\C-g)
  687. (setq isearch-success nil))
  688. (invalid-regexp
  689. (setq isearch-error (car (cdr lossage)))
  690. (if (string-match
  691. "\\`Premature \\|\\`Unmatched \\|\\`Invalid "
  692. isearch-error)
  693. (setq isearch-error "incomplete input")))
  694. (search-failed
  695. (setq isearch-success nil)
  696. (setq isearch-error (nth 2 lossage)))
  697. ;; (error
  698. ;; ;; stack overflow in regexp search.
  699. ;; (setq isearch-error (format "%s" lossage)))
  700. )
  701. (if isearch-success
  702. nil
  703. ;; Ding if failed this time after succeeding last time.
  704. (and (isearch--state-success (car isearch-cmds))
  705. (ding))
  706. (if (functionp (isearch--state-pop-fun (car isearch-cmds)))
  707. (funcall (isearch--state-pop-fun (car isearch-cmds))
  708. (car isearch-cmds)))
  709. (goto-char (isearch--state-point (car isearch-cmds))))))
  710. (provide 'pdf-isearch)
  711. ;;; pdf-isearch.el ends here
  712. ;; Local Variables:
  713. ;; byte-compile-warnings: (not obsolete)
  714. ;; End: