Skip to content

Commit df54891

Browse files
committed
Add devdocs-grep command
1 parent cdc1a7c commit df54891

File tree

2 files changed

+87
-5
lines changed

2 files changed

+87
-5
lines changed

README.org

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ package-install RET devdocs=.
2020
To get started, download some documentation with =M-x
2121
devdocs-install=. This will query https://devdocs.io for the
2222
available documents and save the selected one to disk. To read the
23-
installed documentation, there are two options:
23+
installed documentation, there are a few different options:
2424

25-
- =devdocs-peruse=: Select a document and display its first page.
2625
- =devdocs-lookup=: Select an index entry and display it.
26+
- =devdocs-peruse=: Select a document and display its first page.
27+
- =devdocs-grep=: Do full-text search on a collection of documents,
28+
placing the results in a grep buffer. This feature is experimental,
29+
and somewhat slow; you are usually better served by an index lookup.
2730

28-
It's handy to have a keybinding for the latter command. One
31+
It's handy to have a keybinding for the lookup command. One
2932
possibility, in analogy to =C-h S= (=info-lookup-symbol=), is
3033

3134
#+begin_src elisp
@@ -39,7 +42,7 @@ in subsequent calls to =devdocs-lookup=, unless a prefix argument is
3942
given; in this case you can select a new list of documents.
4043

4144
In the =*devdocs*= buffer, navigation keys similar to Info and
42-
=*Help*= buffers are available; press =C-h m= for details. Internal
45+
=*Help*= buffers are available; press =?= for details. Internal
4346
hyperlinks are opened in the same viewing buffer, and external links
4447
are opened as =browse-url= normally would.
4548

devdocs.el

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
(require 'shr)
4040
(require 'url-expand)
4141
(eval-when-compile
42+
(require 'grep)
4243
(require 'let-alist))
4344

4445
(unless (libxml-available-p)
@@ -90,6 +91,9 @@ name and a count."
9091
Fontification is done using the `org-src' library, which see."
9192
:type 'boolean)
9293

94+
(defvar devdocs--buffer-name "*devdocs*"
95+
"Name of the buffer to display DevDocs documents.")
96+
9397
(defvar devdocs-history nil
9498
"History of documentation entries.")
9599

@@ -386,6 +390,7 @@ Interactively, read a page name with completion."
386390
(define-key map [backtab] #'backward-button)
387391
(define-key map "d" #'devdocs-peruse)
388392
(define-key map "i" #'devdocs-lookup)
393+
(define-key map "s" #'devdocs-grep)
389394
(define-key map "p" #'devdocs-previous-entry)
390395
(define-key map "n" #'devdocs-next-entry)
391396
(define-key map "g" #'devdocs-goto-page)
@@ -432,7 +437,7 @@ Interactively, read a page name with completion."
432437
ENTRY is an alist like those in the entry index of the document,
433438
possibly with an additional ENTRY.fragment which overrides the
434439
fragment part of ENTRY.path."
435-
(with-current-buffer (get-buffer-create "*devdocs*")
440+
(with-current-buffer (get-buffer-create devdocs--buffer-name)
436441
(unless (eq major-mode 'devdocs-mode)
437442
(devdocs-mode))
438443
(let-alist entry
@@ -567,6 +572,80 @@ If INITIAL-INPUT is not nil, insert it into the minibuffer."
567572
(interactive (list (devdocs--read-document "Peruse documentation: ")))
568573
(pop-to-buffer (devdocs-goto-page doc 0)))
569574

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+
570649
;;; Compatibility with the old devdocs package
571650

572651
;;;###autoload

0 commit comments

Comments
 (0)