pdf-occur.el (30447B)
1 ;;; pdf-occur.el --- Display matching lines of PDF documents. -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2013, 2014 Andreas Politz 4 5 ;; Author: Andreas Politz <politza@fh-trier.de> 6 ;; Keywords: files, multimedia 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 ;; 23 24 (require 'pdf-tools) 25 (require 'pdf-view) 26 (require 'pdf-util) 27 (require 'pdf-info) 28 (require 'pdf-isearch) 29 (require 'tablist) 30 (require 'ibuf-ext) 31 (require 'dired) 32 (require 'let-alist) 33 34 ;;; Code: 35 36 37 38 39 ;; * ================================================================== * 40 ;; * Custom & Variables 41 ;; * ================================================================== * 42 43 (defgroup pdf-occur nil 44 "Display matching lines of PDF documents." 45 :group 'pdf-tools) 46 47 (defface pdf-occur-document-face 48 '((default (:inherit font-lock-string-face))) 49 "Face used to highlight documents in the list buffer." 50 :group 'pdf-occur) 51 52 (defface pdf-occur-page-face 53 '((default (:inherit font-lock-type-face))) 54 "Face used to highlight page numbers in the list buffer." 55 :group 'pdf-occur) 56 57 (defcustom pdf-occur-search-batch-size 16 58 "Maximum number of pages searched in one query. 59 60 Lower numbers will make Emacs more responsive when searching at 61 the cost of slightly increased search time." 62 :group 'pdf-occur 63 :type 'integer) 64 65 (defcustom pdf-occur-prefer-string-search nil 66 "If non-nil, reverse the meaning of the regexp-p prefix-arg." 67 :group 'pdf-occur 68 :type 'boolean) 69 70 (defvar pdf-occur-history nil 71 "The history variable for search strings.") 72 73 (defvar pdf-occur-search-pages-left nil 74 "The total number of pages left to search.") 75 76 (defvar pdf-occur-search-documents nil 77 "The list of searched documents. 78 79 Each element should be either the filename of a PDF document or a 80 cons \(FILENAME . PAGES\), where PAGES is the list of pages to 81 search. See `pdf-info-normalize-page-range' for it's format.") 82 83 (defvar pdf-occur-number-of-matches 0 84 "The number of matches in all searched documents.") 85 86 (defvar pdf-occur-search-string nil 87 "The currently used search string, resp. regexp.") 88 89 (defvar pdf-occur-search-regexp-p nil 90 "Non-nil, if searching for a regexp.") 91 92 (defvar pdf-occur-buffer-mode-map 93 (let ((kmap (make-sparse-keymap))) 94 (set-keymap-parent kmap tablist-mode-map) 95 (define-key kmap (kbd "RET") 'pdf-occur-goto-occurrence) 96 (define-key kmap (kbd "C-o") 'pdf-occur-view-occurrence) 97 (define-key kmap (kbd "SPC") 'pdf-occur-view-occurrence) 98 (define-key kmap (kbd "C-c C-f") 'next-error-follow-minor-mode) 99 (define-key kmap (kbd "g") 'pdf-occur-revert-buffer-with-args) 100 (define-key kmap (kbd "K") 'pdf-occur-abort-search) 101 (define-key kmap (kbd "D") 'pdf-occur-tablist-do-delete) 102 (define-key kmap (kbd "x") 'pdf-occur-tablist-do-flagged-delete) 103 (define-key kmap (kbd "A") 'pdf-occur-tablist-gather-documents) 104 kmap) 105 "The keymap used for `pdf-occur-buffer-mode'.") 106 107 108 ;; * ================================================================== * 109 ;; * High level functions 110 ;; * ================================================================== * 111 112 (define-derived-mode pdf-occur-buffer-mode tablist-mode "PDFOccur" 113 "Major mode for output from `pdf-occur`. \\<pdf-occur-buffer-mode-map> 114 115 Some useful keys are: 116 117 \\[pdf-occur-abort-search] - Abort the search. 118 \\[pdf-occur-revert-buffer-with-args] - Restart the search. 119 \\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Restart search with different regexp. 120 \\[universal-argument] \\[universal-argument] \\[pdf-occur-revert-buffer-with-args] - Same, but do a plain string search. 121 122 \\[tablist-push-regexp-filter] - Filter matches by regexp on current or prefix-th column. 123 \\[tablist-pop-filter] - Remove last added filter. 124 125 \\[pdf-occur-tablist-do-delete] - Remove the current file from the search. 126 \\[pdf-occur-tablist-gather-documents] - Include marked files from displayed `dired'/`ibuffer' and 127 `pdf-view-mode' buffers in the search. 128 129 \\{pdf-occur-buffer-mode-map}" 130 (setq-local case-fold-search case-fold-search) 131 (setq-local next-error-function 'pdf-occur-next-error) 132 (setq-local revert-buffer-function 133 'pdf-occur-revert-buffer) 134 (setq next-error-last-buffer (current-buffer)) 135 (setq-local tabulated-list-sort-key nil) 136 (setq-local tabulated-list-use-header-line t) 137 (setq-local tablist-operations-function 138 (lambda (op &rest _) 139 (cl-case op 140 (supported-operations '(find-entry)) 141 (find-entry 142 (let ((display-buffer-overriding-action 143 '(display-buffer-same-window))) 144 (pdf-occur-goto-occurrence))))))) 145 146 ;;;###autoload 147 (defun pdf-occur (string &optional regexp-p) 148 "List lines matching STRING or PCRE. 149 150 Interactively search for a regexp. Unless a prefix arg was given, 151 in which case this functions performs a string search. 152 153 If `pdf-occur-prefer-string-search' is non-nil, the meaning of 154 the prefix-arg is inverted." 155 (interactive 156 (progn 157 (pdf-util-assert-pdf-buffer) 158 (list 159 (pdf-occur-read-string 160 (pdf-occur-want-regexp-search-p)) 161 (pdf-occur-want-regexp-search-p)))) 162 (pdf-util-assert-pdf-buffer) 163 (pdf-occur-search (list (current-buffer)) string regexp-p)) 164 165 (defvar ibuffer-filtering-qualifiers) 166 ;;;###autoload 167 (defun pdf-occur-multi-command () 168 "Perform `pdf-occur' on multiple buffer. 169 170 For a programmatic search of multiple documents see 171 `pdf-occur-search'." 172 (interactive) 173 (ibuffer) 174 (with-current-buffer "*Ibuffer*" 175 (pdf-occur-ibuffer-minor-mode) 176 (unless (member '(derived-mode . pdf-view-mode) 177 ibuffer-filtering-qualifiers) 178 (ibuffer-filter-by-derived-mode 'pdf-view-mode)) 179 (message 180 "%s" 181 (substitute-command-keys 182 "Mark a bunch of PDF buffers and type \\[pdf-occur-ibuffer-do-occur]")) 183 (sit-for 3))) 184 185 (defun pdf-occur-revert-buffer (&rest _) 186 "Restart the search." 187 (pdf-occur-assert-occur-buffer-p) 188 (unless pdf-occur-search-documents 189 (error "No documents to search")) 190 (unless pdf-occur-search-string 191 (error "Nothing to search for")) 192 (let* ((2-columns-p (= 1 (length pdf-occur-search-documents))) 193 (filename-width 194 (min 24 195 (apply 'max 196 (mapcar 'length 197 (mapcar 'pdf-occur-abbrev-document 198 (mapcar 'car pdf-occur-search-documents)))))) 199 (page-sorter (tablist-generate-sorter 200 (if 2-columns-p 0 1) 201 '< 202 'string-to-number))) 203 (setq tabulated-list-format 204 (if 2-columns-p 205 `[("Page" 4 ,page-sorter :right-align t) 206 ("Line" 0 t)] 207 `[("Document" ,filename-width t) 208 ("Page" 4 ,page-sorter :right-align t) 209 ("Line" 0 t)]) 210 tabulated-list-entries nil)) 211 (tabulated-list-revert) 212 (pdf-occur-start-search 213 pdf-occur-search-documents 214 pdf-occur-search-string 215 pdf-occur-search-regexp-p) 216 (pdf-occur-update-header-line) 217 (setq mode-line-process 218 '(:propertize ":run" face compilation-mode-line-run))) 219 220 (defun pdf-occur-revert-buffer-with-args (string &optional regexp-p documents) 221 "Restart the search with modified arguments. 222 223 Interactively just restart the search, unless a prefix was given. 224 In this case read a new search string. With `C-u C-u' as prefix 225 additionally invert the current state of 226 `pdf-occur-search-regexp-p'." 227 (interactive 228 (progn 229 (pdf-occur-assert-occur-buffer-p) 230 (cond 231 (current-prefix-arg 232 (let ((regexp-p 233 (if (equal current-prefix-arg '(16)) 234 (not pdf-occur-search-regexp-p) 235 pdf-occur-search-regexp-p))) 236 (list 237 (pdf-occur-read-string regexp-p) 238 regexp-p))) 239 (t 240 (list pdf-occur-search-string 241 pdf-occur-search-regexp-p))))) 242 (setq pdf-occur-search-string string 243 pdf-occur-search-regexp-p regexp-p) 244 (when documents 245 (setq pdf-occur-search-documents 246 (pdf-occur-normalize-documents documents))) 247 (pdf-occur-revert-buffer)) 248 249 (defun pdf-occur-abort-search () 250 "Abort the current search. 251 252 This immediately kills the search process." 253 (interactive) 254 (unless (pdf-occur-search-in-progress-p) 255 (user-error "No search in progress")) 256 (pdf-info-kill-local-server) 257 (pdf-occur-search-finished t)) 258 259 260 ;; * ================================================================== * 261 ;; * Finding occurrences 262 ;; * ================================================================== * 263 264 265 (defun pdf-occur-goto-occurrence (&optional no-select-window-p) 266 "Go to the occurrence at point. 267 268 If EVENT is nil, use occurrence at current line. Select the 269 PDF's window, unless NO-SELECT-WINDOW-P is non-nil. 270 271 FIXME: EVENT not used at the moment." 272 (interactive) 273 (let ((item (tabulated-list-get-id))) 274 (when item 275 (let* ((doc (plist-get item :document)) 276 (page (plist-get item :page)) 277 (match (plist-get item :match-edges)) 278 (buffer (if (bufferp doc) 279 doc 280 (or (find-buffer-visiting doc) 281 (find-file-noselect doc)))) 282 window) 283 (if no-select-window-p 284 (setq window (display-buffer buffer)) 285 (pop-to-buffer buffer) 286 (setq window (selected-window))) 287 (with-selected-window window 288 (when page 289 (pdf-view-goto-page page)) 290 ;; Abuse isearch. 291 (when match 292 (let ((pixel-match 293 (pdf-util-scale-relative-to-pixel match)) 294 (pdf-isearch-batch-mode t)) 295 (pdf-isearch-hl-matches pixel-match nil t) 296 (pdf-isearch-focus-match-batch pixel-match)))))))) 297 298 (defun pdf-occur-view-occurrence (&optional _event) 299 "View the occurrence at EVENT. 300 301 If EVENT is nil, use occurrence at current line." 302 (interactive (list last-nonmenu-event)) 303 (pdf-occur-goto-occurrence t)) 304 305 (defun pdf-occur-next-error (&optional arg reset) 306 "Move to the Nth (default 1) next match in an PDF Occur mode buffer. 307 Compatibility function for \\[next-error] invocations." 308 (interactive "p") 309 ;; we need to run pdf-occur-find-match from within the Occur buffer 310 (with-current-buffer 311 ;; Choose the buffer and make it current. 312 (if (next-error-buffer-p (current-buffer)) 313 (current-buffer) 314 (next-error-find-buffer 315 nil nil 316 (lambda () 317 (eq major-mode 'pdf-occur-buffer-mode)))) 318 (when (bobp) 319 (setq reset t)) 320 (if reset 321 (goto-char (point-min)) 322 (beginning-of-line)) 323 (when (/= arg 0) 324 (when (eobp) 325 (forward-line -1)) 326 (when reset 327 (cl-decf arg)) 328 (let ((line (line-number-at-pos)) 329 (limit (line-number-at-pos 330 (if (>= arg 0) 331 (1- (point-max)) 332 (point-min))))) 333 (when (= line limit) 334 (error "No more matches")) 335 (forward-line 336 (if (>= arg 0) 337 (min arg (- limit line)) 338 (max arg (- limit line)))))) 339 ;; In case the *Occur* buffer is visible in a nonselected window. 340 (tablist-move-to-major-column) 341 (let ((win (get-buffer-window (current-buffer) t))) 342 (if win (set-window-point win (point)))) 343 (pdf-occur-goto-occurrence))) 344 345 346 ;; * ================================================================== * 347 ;; * Integration with other modes 348 ;; * ================================================================== * 349 350 ;;;###autoload 351 (define-minor-mode pdf-occur-global-minor-mode 352 "Enable integration of Pdf Occur with other modes. 353 354 This global minor mode enables (or disables) 355 `pdf-occur-ibuffer-minor-mode' and `pdf-occur-dired-minor-mode' 356 in all current and future ibuffer/dired buffer." 357 :global t 358 :group 'pdf-occur 359 (let ((arg (if pdf-occur-global-minor-mode 1 -1))) 360 (dolist (buf (buffer-list)) 361 (with-current-buffer buf 362 (cond 363 ((derived-mode-p 'dired-mode) 364 (pdf-occur-dired-minor-mode arg)) 365 ((derived-mode-p 'ibuffer-mode) 366 (pdf-occur-ibuffer-minor-mode arg))))) 367 (cond 368 (pdf-occur-global-minor-mode 369 (add-hook 'dired-mode-hook 'pdf-occur-dired-minor-mode) 370 (add-hook 'ibuffer-mode-hook 'pdf-occur-ibuffer-minor-mode)) 371 (t 372 (remove-hook 'dired-mode-hook 'pdf-occur-dired-minor-mode) 373 (remove-hook 'ibuffer-mode-hook 'pdf-occur-ibuffer-minor-mode))))) 374 375 (defvar pdf-occur-ibuffer-minor-mode-map 376 (let ((map (make-sparse-keymap))) 377 (define-key map [remap ibuffer-do-occur] 'pdf-occur-ibuffer-do-occur) 378 map) 379 "Keymap used in `pdf-occur-ibuffer-minor-mode'.") 380 381 ;;;###autoload 382 (define-minor-mode pdf-occur-ibuffer-minor-mode 383 "Hack into ibuffer's do-occur binding. 384 385 This mode remaps `ibuffer-do-occur' to 386 `pdf-occur-ibuffer-do-occur', which will start the PDF Tools 387 version of `occur', if all marked buffer's are in `pdf-view-mode' 388 and otherwise fallback to `ibuffer-do-occur'." 389 :group 'pdf-occur) 390 391 (defun pdf-occur-ibuffer-do-occur (&optional regexp-p) 392 "Uses `pdf-occur-search', if appropriate. 393 394 I.e. all marked buffers are in PDFView mode." 395 (interactive 396 (list (pdf-occur-want-regexp-search-p))) 397 (let* ((buffer (or (ibuffer-get-marked-buffers) 398 (and (ibuffer-current-buffer) 399 (list (ibuffer-current-buffer))))) 400 (pdf-only-p (cl-every 401 (lambda (buf) 402 (with-current-buffer buf 403 (derived-mode-p 'pdf-view-mode))) 404 buffer))) 405 (if (not pdf-only-p) 406 (call-interactively 'ibuffer-do-occur) 407 (let ((regexp (pdf-occur-read-string regexp-p))) 408 (pdf-occur-search buffer regexp regexp-p))))) 409 410 (defvar pdf-occur-dired-minor-mode-map 411 (let ((map (make-sparse-keymap))) 412 (define-key map [remap dired-do-search] 'pdf-occur-dired-do-search) 413 map) 414 "Keymap used in `pdf-occur-dired-minor-mode'.") 415 416 ;;;###autoload 417 (define-minor-mode pdf-occur-dired-minor-mode 418 "Hack into dired's `dired-do-search' binding. 419 420 This mode remaps `dired-do-search' to 421 `pdf-occur-dired-do-search', which will start the PDF Tools 422 version of `occur', if all marked buffer's are in `pdf-view-mode' 423 and otherwise fallback to `dired-do-search'." 424 :group 'pdf-occur) 425 426 (defun pdf-occur-dired-do-search () 427 "Uses `pdf-occur-search', if appropriate. 428 429 I.e. all marked files look like PDF documents." 430 (interactive) 431 (let ((files (dired-get-marked-files))) 432 (if (not (cl-every (lambda (file) 433 (string-match-p 434 (car pdf-tools-auto-mode-alist-entry) 435 file)) 436 files)) 437 (call-interactively 'dired-do-search) 438 (let* ((regex-p (pdf-occur-want-regexp-search-p)) 439 (regexp (pdf-occur-read-string regex-p))) 440 (pdf-occur-search files regexp regex-p))))) 441 442 443 444 ;; * ================================================================== * 445 ;; * Search engine 446 ;; * ================================================================== * 447 448 449 (defun pdf-occur-search (documents string &optional regexp-p) 450 "Search DOCUMENTS for STRING. 451 452 DOCUMENTS should be a list of buffers (objects, not names), 453 filenames or conses \(BUFFER-OR-FILENAME . PAGES\), where PAGES 454 determines the scope of the search of the respective document. 455 See `pdf-info-normalize-page-range' for it's format. 456 457 STRING is either the string to search for or, if REGEXP-P is 458 non-nil, a Perl compatible regular expression (PCRE). 459 460 Display the occur buffer and start the search asynchronously. 461 462 Returns the window where the buffer is displayed." 463 464 (unless documents 465 (error "No documents to search")) 466 (when (or (null string) (= (length string) 0)) 467 (error "Not searching for the empty string")) 468 (with-current-buffer (get-buffer-create "*PDF-Occur*") 469 (pdf-occur-buffer-mode) 470 (setq-local pdf-occur-search-documents 471 (pdf-occur-normalize-documents documents)) 472 (setq-local pdf-occur-search-string string) 473 (setq-local pdf-occur-search-regexp-p regexp-p) 474 (setq-local pdf-occur-search-pages-left 0) 475 (setq-local pdf-occur-number-of-matches 0) 476 (pdf-occur-revert-buffer) 477 (display-buffer 478 (current-buffer)))) 479 480 (defadvice tabulated-list-init-header (after update-header activate) 481 "We want our own headers, thank you." 482 (when (derived-mode-p 'pdf-occur-buffer-mode) 483 (save-current-buffer 484 (with-no-warnings (pdf-occur-update-header-line))))) 485 486 (defun pdf-occur-create-entry (filename page &optional match) 487 "Create a `tabulated-list-entries' entry for a search result. 488 489 If match is nil, create a fake entry for documents w/o any 490 matches linked with PAGE." 491 (let* ((text (or (car match) "[No matches]")) 492 (edges (cdr match)) 493 (displayed-text 494 (if match 495 (replace-regexp-in-string "\n" "\\n" text t t) 496 (propertize text 'face 'font-lock-warning-face))) 497 (displayed-page 498 (if match 499 (propertize (format "%d" page) 500 'face 'pdf-occur-page-face) 501 "")) 502 (displayed-document 503 (propertize 504 (pdf-occur-abbrev-document filename) 505 'face 'pdf-occur-document-face)) 506 (id `(:document ,filename 507 :page ,page 508 :match-text ,(if match text) 509 :match-edges ,(if match edges)))) 510 (list id 511 (if (= (length pdf-occur-search-documents) 1) 512 (vector displayed-page displayed-text) 513 (vector displayed-document 514 displayed-page 515 displayed-text))))) 516 517 (defun pdf-occur-update-header-line () 518 (pdf-occur-assert-occur-buffer-p) 519 (save-current-buffer 520 ;;force-mode-line-update seems to sometimes spuriously change the 521 ;;current buffer. 522 (setq header-line-format 523 `(:eval (concat 524 (if (= (length pdf-occur-search-documents) 1) 525 (format "%d match%s in document `%s'" 526 pdf-occur-number-of-matches 527 (if (/= 1 pdf-occur-number-of-matches) "es" "") 528 (pdf-occur-abbrev-document 529 (caar pdf-occur-search-documents))) 530 (format "%d match%s in %d documents" 531 pdf-occur-number-of-matches 532 (if (/= 1 pdf-occur-number-of-matches) "es" "") 533 (length pdf-occur-search-documents))) 534 (if (pdf-occur-search-in-progress-p) 535 (propertize 536 (concat " [" 537 (if (numberp pdf-occur-search-pages-left) 538 (format "%d pages left" 539 pdf-occur-search-pages-left) 540 "Searching") 541 "]") 542 'face 'compilation-mode-line-run))))) 543 (force-mode-line-update))) 544 545 (defun pdf-occur-search-finished (&optional abort-p) 546 (setq pdf-occur-search-pages-left 0) 547 (setq mode-line-process 548 (if abort-p 549 '(:propertize 550 ":aborted" face compilation-mode-line-fail) 551 '(:propertize 552 ":exit" face compilation-mode-line-exit))) 553 (let ((unmatched 554 (mapcar (lambda (doc) 555 (pdf-occur-create-entry doc 1)) 556 (cl-set-difference 557 (mapcar 'car 558 pdf-occur-search-documents) 559 (mapcar (lambda (elt) 560 (plist-get (car elt) :document)) 561 tabulated-list-entries) 562 :test 'equal)))) 563 (when (and unmatched 564 (> (length pdf-occur-search-documents) 1)) 565 (pdf-occur-insert-entries unmatched))) 566 (tablist-apply-filter) 567 (pdf-occur-update-header-line) 568 (pdf-isearch-message 569 (if abort-p 570 "Search aborted." 571 (format "Occur search finished with %d matches" 572 pdf-occur-number-of-matches)))) 573 574 (defun pdf-occur-add-matches (filename matches) 575 (pdf-occur-assert-occur-buffer-p) 576 (when matches 577 (let (entries) 578 (dolist (match matches) 579 (let-alist match 580 (push (pdf-occur-create-entry filename .page (cons .text .edges)) 581 entries))) 582 (setq entries (nreverse entries)) 583 (pdf-occur-insert-entries entries)))) 584 585 (defun pdf-occur-insert-entries (entries) 586 "Insert tabulated-list ENTRIES at the end." 587 (pdf-occur-assert-occur-buffer-p) 588 (let ((inhibit-read-only t) 589 (end-of-buffer (and (eobp) (not (bobp))))) 590 (save-excursion 591 (goto-char (point-max)) 592 (dolist (elt entries) 593 (apply tabulated-list-printer elt)) 594 (set-buffer-modified-p nil)) 595 (when end-of-buffer 596 (dolist (win (get-buffer-window-list)) 597 (set-window-point win (point-max)))) 598 (setq tabulated-list-entries 599 (append tabulated-list-entries 600 entries)))) 601 602 (defun pdf-occur-search-in-progress-p () 603 (and (numberp pdf-occur-search-pages-left) 604 (> pdf-occur-search-pages-left 0))) 605 606 (defun pdf-occur-start-search (documents string 607 &optional regexp-p) 608 (pdf-occur-assert-occur-buffer-p) 609 (pdf-info-make-local-server nil t) 610 (let ((batches (pdf-occur-create-batches 611 documents (or pdf-occur-search-batch-size 1)))) 612 (pdf-info-local-batch-query 613 (lambda (document pages) 614 (if regexp-p 615 (pdf-info-search-regexp string pages nil document) 616 (pdf-info-search-string string pages document))) 617 (lambda (status response document pages) 618 (if status 619 (error "%s" response) 620 (when (numberp pdf-occur-search-pages-left) 621 (cl-decf pdf-occur-search-pages-left 622 (1+ (- (cdr pages) (car pages))))) 623 (when (cl-member document pdf-occur-search-documents 624 :key 'car 625 :test 'equal) 626 (cl-incf pdf-occur-number-of-matches 627 (length response)) 628 (pdf-occur-add-matches document response) 629 (pdf-occur-update-header-line)))) 630 (lambda (status buffer) 631 (when (buffer-live-p buffer) 632 (with-current-buffer buffer 633 (pdf-occur-search-finished (eq status 'killed))))) 634 batches) 635 (setq pdf-occur-number-of-matches 0) 636 (setq pdf-occur-search-pages-left 637 (apply '+ (mapcar (lambda (elt) 638 (1+ (- (cdr (nth 1 elt)) 639 (car (nth 1 elt))))) 640 batches))))) 641 642 643 644 ;; * ================================================================== * 645 ;; * Editing searched documents 646 ;; * ================================================================== * 647 648 (defun pdf-occur-tablist-do-delete (&optional arg) 649 "Delete ARG documents from the search list." 650 (interactive "P") 651 (when (pdf-occur-search-in-progress-p) 652 (user-error "Can't delete while a search is in progress.")) 653 (let* ((items (tablist-get-marked-items arg)) 654 (documents (cl-remove-duplicates 655 (mapcar (lambda (entry) 656 (plist-get (car entry) :document)) 657 items) 658 :test 'equal))) 659 (unless documents 660 (error "No documents selected")) 661 (when (tablist-yes-or-no-p 662 'Stop\ searching 663 nil (mapcar (lambda (d) (cons nil (vector d))) 664 documents)) 665 (setq pdf-occur-search-documents 666 (cl-remove-if (lambda (elt) 667 (member (car elt) documents)) 668 pdf-occur-search-documents) 669 tabulated-list-entries 670 (cl-remove-if (lambda (elt) 671 (when (member (plist-get (car elt) :document) 672 documents) 673 (when (plist-get (car elt) :match-edges) 674 (cl-decf pdf-occur-number-of-matches)) 675 t)) 676 tabulated-list-entries)) 677 (tablist-revert) 678 (pdf-occur-update-header-line) 679 (tablist-move-to-major-column)))) 680 681 (defun pdf-occur-tablist-do-flagged-delete (&optional interactive) 682 "Stop searching all documents marked with a D." 683 (interactive "p") 684 (let* ((tablist-marker-char ?D)) 685 (if (save-excursion 686 (goto-char (point-min)) 687 (re-search-forward (tablist-marker-regexp) nil t)) 688 (pdf-occur-tablist-do-delete) 689 (or (not interactive) 690 (message "(No deletions requested)"))))) 691 692 (defun pdf-occur-tablist-gather-documents () 693 "Gather marked documents in windows. 694 695 Examine all dired/ibuffer windows and offer to put marked files 696 in the search list." 697 (interactive) 698 (let ((searched (mapcar 'car pdf-occur-search-documents)) 699 files) 700 (dolist (win (window-list)) 701 (with-selected-window win 702 (cond 703 ((derived-mode-p 'dired-mode) 704 (let ((marked (dired-get-marked-files nil nil nil t))) 705 (when (> (length marked) 1) 706 (when (eq t (car marked)) 707 (setq marked (cdr marked))) 708 (setq files 709 (append files marked nil))))) 710 ((derived-mode-p 'ibuffer-mode) 711 (dolist (fname (mapcar 'buffer-file-name 712 (ibuffer-get-marked-buffers))) 713 (when fname 714 (push fname files)))) 715 ((and (derived-mode-p 'pdf-view-mode) 716 (buffer-file-name)) 717 (push (buffer-file-name) files))))) 718 719 (setq files 720 (cl-sort ;Looks funny. 721 (cl-set-difference 722 (cl-remove-duplicates 723 (cl-remove-if-not 724 (lambda (file) (string-match-p 725 (car pdf-tools-auto-mode-alist-entry) 726 file)) 727 files) 728 :test 'file-equal-p) 729 searched 730 :test 'file-equal-p) 731 'string-lessp)) 732 (if (null files) 733 (message "No marked, new PDF files found in windows") 734 (when (tablist-yes-or-no-p 735 'add nil (mapcar (lambda (file) 736 (cons nil (vector file))) 737 (cl-sort files 'string-lessp))) 738 (setq pdf-occur-search-documents 739 (append pdf-occur-search-documents 740 (pdf-occur-normalize-documents files))) 741 (message "Added %d file%s to the list of searched documents%s" 742 (length files) 743 (dired-plural-s (length files)) 744 (substitute-command-keys 745 " - Hit \\[pdf-occur-revert-buffer-with-args]")))))) 746 747 748 ;; * ================================================================== * 749 ;; * Utilities 750 ;; * ================================================================== * 751 752 (defun pdf-occur-read-string (&optional regexp-p) 753 (read-string 754 (concat 755 (format "List lines %s" 756 (if regexp-p "matching PCRE" "containing string")) 757 (if pdf-occur-search-string 758 (format " (default %s)" pdf-occur-search-string)) 759 ": ") 760 nil 'pdf-occur-history pdf-occur-search-string)) 761 762 (defun pdf-occur-assert-occur-buffer-p () 763 (unless (derived-mode-p 'pdf-occur-buffer-mode) 764 (error "Not in PDF occur buffer"))) 765 766 (defun pdf-occur-want-regexp-search-p () 767 (or (and current-prefix-arg 768 pdf-occur-prefer-string-search) 769 (and (null current-prefix-arg) 770 (not pdf-occur-prefer-string-search)))) 771 772 ;; FIXME: This will be confusing when searching documents with the 773 ;; same base file-name. 774 (defun pdf-occur-abbrev-document (file-or-buffer) 775 (if (bufferp file-or-buffer) 776 (buffer-name file-or-buffer) 777 (let ((abbrev (file-name-nondirectory file-or-buffer))) 778 (if (> (length abbrev) 0) 779 abbrev 780 file-or-buffer)))) 781 782 (defun pdf-occur-create-batches (documents batch-size) 783 (let (queries) 784 (dolist (d documents) 785 (let* ((file-or-buffer (car d)) 786 (pages (pdf-info-normalize-page-range (cdr d))) 787 (first (car pages)) 788 (last (if (eq (cdr pages) 0) 789 (pdf-info-number-of-pages file-or-buffer) 790 (cdr pages))) 791 (npages (1+ (- last first))) 792 (nbatches (ceiling 793 (/ (float npages) batch-size)))) 794 (dotimes (i nbatches) 795 (push 796 (list file-or-buffer 797 (cons (+ first (* i batch-size)) 798 (min last (+ first (1- (* (1+ i) batch-size)))))) 799 queries)))) 800 (nreverse queries))) 801 802 (defun pdf-occur-normalize-documents (documents) 803 "Normalize list of documents. 804 805 Replaces buffers with their associated filenames \(if 806 applicable\) and ensures that every element looks like 807 \(FILENAME-OR-BUFFER . PAGES\)." 808 (cl-sort (mapcar (lambda (doc) 809 (unless (consp doc) 810 (setq doc (cons doc nil))) 811 (when (and (bufferp (car doc)) 812 (buffer-file-name (car doc))) 813 (setq doc (cons (buffer-file-name (car doc)) 814 (cdr doc)))) 815 (if (stringp (car doc)) 816 (cons (expand-file-name (car doc)) (cdr doc)) 817 doc)) 818 documents) 819 (lambda (a b) (string-lessp 820 (if (bufferp a) (buffer-name a) a) 821 (if (bufferp b) (buffer-name b) b))) 822 :key 'car)) 823 824 (provide 'pdf-occur) 825 826 ;;; pdf-occur.el ends here