|
39 | 39 | (require 'shr)
|
40 | 40 | (require 'url-expand)
|
41 | 41 | (eval-when-compile
|
| 42 | + (require 'grep) |
42 | 43 | (require 'let-alist))
|
43 | 44 |
|
44 | 45 | (unless (libxml-available-p)
|
@@ -90,6 +91,9 @@ name and a count."
|
90 | 91 | Fontification is done using the `org-src' library, which see."
|
91 | 92 | :type 'boolean)
|
92 | 93 |
|
| 94 | +(defvar devdocs--buffer-name "*devdocs*" |
| 95 | + "Name of the buffer to display DevDocs documents.") |
| 96 | + |
93 | 97 | (defvar devdocs-history nil
|
94 | 98 | "History of documentation entries.")
|
95 | 99 |
|
@@ -386,6 +390,7 @@ Interactively, read a page name with completion."
|
386 | 390 | (define-key map [backtab] #'backward-button)
|
387 | 391 | (define-key map "d" #'devdocs-peruse)
|
388 | 392 | (define-key map "i" #'devdocs-lookup)
|
| 393 | + (define-key map "s" #'devdocs-grep) |
389 | 394 | (define-key map "p" #'devdocs-previous-entry)
|
390 | 395 | (define-key map "n" #'devdocs-next-entry)
|
391 | 396 | (define-key map "g" #'devdocs-goto-page)
|
@@ -432,7 +437,7 @@ Interactively, read a page name with completion."
|
432 | 437 | ENTRY is an alist like those in the entry index of the document,
|
433 | 438 | possibly with an additional ENTRY.fragment which overrides the
|
434 | 439 | fragment part of ENTRY.path."
|
435 |
| - (with-current-buffer (get-buffer-create "*devdocs*") |
| 440 | + (with-current-buffer (get-buffer-create devdocs--buffer-name) |
436 | 441 | (unless (eq major-mode 'devdocs-mode)
|
437 | 442 | (devdocs-mode))
|
438 | 443 | (let-alist entry
|
@@ -567,6 +572,80 @@ If INITIAL-INPUT is not nil, insert it into the minibuffer."
|
567 | 572 | (interactive (list (devdocs--read-document "Peruse documentation: ")))
|
568 | 573 | (pop-to-buffer (devdocs-goto-page doc 0)))
|
569 | 574 |
|
| 575 | +(defun devdocs--next-error-function (n &optional reset) |
| 576 | + "A `next-error-function' suitable for *devdocs-grep* buffers." |
| 577 | + (cl-letf (((symbol-function 'compilation-find-file) |
| 578 | + (lambda (_marker filename &rest _) |
| 579 | + ;; Certain markers associated to hits in each file are |
| 580 | + ;; stored by grep-mode. Since we erase and reuse the |
| 581 | + ;; *devdocs* buffer, we need to get rid of them. |
| 582 | + (maphash (lambda (_ file-struct) |
| 583 | + (dolist (tree (compilation--file-struct->loc-tree file-struct)) |
| 584 | + (when-let ((marker (compilation--loc->marker (assq nil tree)))) |
| 585 | + (set-marker marker nil)))) |
| 586 | + compilation-locs) |
| 587 | + (string-match "\\([^/]*\\)/\\(.*\\)" filename) |
| 588 | + (devdocs-goto-page (devdocs--doc-metadata (match-string 1 filename)) |
| 589 | + (match-string 2 filename))))) |
| 590 | + (compilation-next-error-function n reset))) |
| 591 | + |
| 592 | +;;;###autoload |
| 593 | +(defun devdocs-grep (docs regexp) |
| 594 | + "Perform full-text search on a collection of documents." |
| 595 | + (interactive (list (devdocs--relevant-docs current-prefix-arg) |
| 596 | + (read-regexp "Search for regexp: " |
| 597 | + (thing-at-point 'symbol) |
| 598 | + 'grep-regexp-history))) |
| 599 | + (let* ((slugs (mapcar (lambda (doc) (alist-get 'slug doc)) docs)) |
| 600 | + (outbuf (get-buffer-create "*devdocs-grep*")) |
| 601 | + (pages (mapcan (lambda (doc) |
| 602 | + (mapcar (lambda (path) `((doc . ,doc) (path . ,path))) |
| 603 | + (devdocs--index doc 'pages))) |
| 604 | + docs)) |
| 605 | + (npages (length pages)) |
| 606 | + (progress (make-progress-reporter "Searching" 0 npages))) |
| 607 | + (pop-to-buffer outbuf) |
| 608 | + (let ((inhibit-read-only t)) |
| 609 | + (erase-buffer) |
| 610 | + (grep-mode) |
| 611 | + (buffer-disable-undo) |
| 612 | + (setq-local next-error-function #'devdocs--next-error-function) |
| 613 | + (insert (format "Search results for ‘%s’ in the following documents: %s.\n\n" |
| 614 | + regexp (string-join slugs ", ")))) |
| 615 | + (letrec ((worker (pcase-lambda (`(,page . ,rest)) |
| 616 | + (unless (buffer-live-p outbuf) |
| 617 | + (user-error "Grep buffer killed")) |
| 618 | + (progress-reporter-update progress (- npages (length rest) 1)) |
| 619 | + (with-temp-buffer |
| 620 | + (let ((devdocs--buffer-name (current-buffer)) |
| 621 | + (devdocs-fontify-code-blocks nil)) |
| 622 | + (devdocs--render page)) |
| 623 | + (while (re-search-forward regexp nil t) |
| 624 | + (goto-char (match-beginning 0)) |
| 625 | + (end-of-line) |
| 626 | + (let* ((text (buffer-substring (line-beginning-position) |
| 627 | + (point))) |
| 628 | + (result (let-alist page |
| 629 | + (format "%s/%s:%s:%s\n" |
| 630 | + .doc.slug .path |
| 631 | + (line-number-at-pos) |
| 632 | + text)))) |
| 633 | + (with-current-buffer outbuf |
| 634 | + (save-excursion |
| 635 | + (goto-char (point-max)) |
| 636 | + (let ((inhibit-read-only t)) |
| 637 | + (insert result))))))) |
| 638 | + (if rest |
| 639 | + (run-with-idle-timer 0.2 nil worker rest) |
| 640 | + (progress-reporter-done progress) |
| 641 | + (with-current-buffer outbuf |
| 642 | + (save-excursion |
| 643 | + (goto-char (point-max)) |
| 644 | + (let ((inhibit-read-only t)) |
| 645 | + (insert (format "\nSearch finished with %s results.\n" |
| 646 | + compilation-num-errors-found))))))))) |
| 647 | + (funcall worker pages)))) |
| 648 | + |
570 | 649 | ;;; Compatibility with the old devdocs package
|
571 | 650 |
|
572 | 651 | ;;;###autoload
|
|
0 commit comments