Skip to content

Commit e8e5196

Browse files
committed
Add devdocs-grep command
1 parent 4f64975 commit e8e5196

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

README.org

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ devdocs-install=. This will query https://devdocs.io for the
2222
available documents and save the selected one to disk. To read the
2323
installed documentation, there are two 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+
This feature is experimental, and somewhat slow. You are usually
29+
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: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ name and a count."
9090
Fontification is done using the `org-src' library, which see."
9191
:type 'boolean)
9292

93+
(defvar devdocs-buffer-name "*devdocs*")
94+
9395
(defvar devdocs-history nil
9496
"History of documentation entries.")
9597

@@ -432,7 +434,7 @@ Interactively, read a page name with completion."
432434
ENTRY is an alist like those in the entry index of the document,
433435
possibly with an additional ENTRY.fragment which overrides the
434436
fragment part of ENTRY.path."
435-
(with-current-buffer (get-buffer-create "*devdocs*")
437+
(with-current-buffer (get-buffer-create devdocs-buffer-name)
436438
(unless (eq major-mode 'devdocs-mode)
437439
(devdocs-mode))
438440
(let-alist entry
@@ -567,6 +569,74 @@ If INITIAL-INPUT is not nil, insert it into the minibuffer."
567569
(interactive (list (devdocs--read-document "Peruse documentation: ")))
568570
(pop-to-buffer (devdocs-goto-page doc 0)))
569571

572+
(defun devdocs--next-error-function (n &optional reset)
573+
"A `next-error-function' suitable for *devdocs-grep* buffers."
574+
(cl-letf (((symbol-function 'compilation-find-file)
575+
(lambda (_marker filename &rest _)
576+
(string-match "\\([^/]*\\)/\\(.*\\)" filename)
577+
;; TODO: use goto-page
578+
(devdocs--render
579+
`((doc . ,(devdocs--doc-metadata (match-string 1 filename)))
580+
(path . ,(match-string 2 filename)))))))
581+
(compilation-next-error-function n reset)))
582+
583+
;;;###autoload
584+
(defun devdocs-grep (docs regexp)
585+
"Perform full-text search in a collection of documents."
586+
(interactive (list (devdocs--relevant-docs current-prefix-arg)
587+
(read-regexp "Search for regexp: ")))
588+
(let* ((slugs (mapcar (lambda (doc) (alist-get 'slug doc)) docs))
589+
(outbuf (get-buffer-create "*devdocs-grep*"))
590+
(pages (mapcan (lambda (doc)
591+
(mapcar (lambda (path) `((doc . ,doc) (path . ,path)))
592+
(devdocs--index doc 'pages)))
593+
docs))
594+
(npages (length pages))
595+
(nmatches 0)
596+
(progress (make-progress-reporter "Searching" 0 npages)))
597+
(pop-to-buffer outbuf)
598+
(let ((inhibit-read-only t))
599+
(erase-buffer)
600+
(grep-mode)
601+
(buffer-disable-undo)
602+
(setq-local next-error-function #'devdocs--next-error-function)
603+
(insert (format "Search results for ‘%s’ in the following documents: %s.\n\n"
604+
regexp (string-join slugs ", "))))
605+
(letrec ((worker (pcase-lambda (`(,page . ,rest))
606+
(unless (buffer-live-p outbuf)
607+
(user-error "Grep buffer killed"))
608+
(progress-reporter-update progress (- npages (length rest) 1))
609+
(with-temp-buffer
610+
(let ((devdocs-buffer-name (current-buffer))
611+
(devdocs-fontify-code-blocks nil))
612+
(devdocs--render page))
613+
(while (re-search-forward regexp nil t)
614+
(setq nmatches (1+ nmatches))
615+
(goto-char (match-beginning 0))
616+
(end-of-line)
617+
(let* ((text (buffer-substring (line-beginning-position)
618+
(point)))
619+
(result (let-alist page
620+
(format "%s/%s:%s:%s\n"
621+
.doc.slug .path
622+
(line-number-at-pos)
623+
text))))
624+
(with-current-buffer outbuf
625+
(save-excursion
626+
(goto-char (point-max))
627+
(let ((inhibit-read-only t))
628+
(insert result)))))))
629+
(if rest
630+
(run-with-idle-timer 0.2 nil worker rest)
631+
(progress-reporter-done progress)
632+
(with-current-buffer outbuf
633+
(save-excursion
634+
(goto-char (point-max))
635+
(let ((inhibit-read-only t))
636+
(insert (format "\nSearch finished with %s results.\n"
637+
nmatches)))))))))
638+
(funcall worker pages))))
639+
570640
;;; Compatibility with the old devdocs package
571641

572642
;;;###autoload

0 commit comments

Comments
 (0)