Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,77 @@
#+html: <a href="https://melpa.org/#/devdocs"><img alt="MELPA" src="https://melpa.org/packages/devdocs-badge.svg"/></a>

devdocs.el is a documentation viewer for Emacs similar to the built-in
Info browser, but geared towards documentation obtained from the
[[https://devdocs.io][DevDocs]] website. The stable version is available from [[https://elpa.gnu.org/packages/devdocs.html][GNU ELPA]] and a
Info browser, but geared towards documentation distributed by the
[[https://devdocs.io][DevDocs]] website. Currently, this covers over 500 versions of 188
different software components.

The stable version of the package is available from [[https://elpa.gnu.org/packages/devdocs.html][GNU ELPA]] and a
development version is available from [[https://melpa.org/#/devdocs][MELPA]]; to install, type =M-x
package-install RET devdocs=.

#+caption: image
[[https://user-images.githubusercontent.com/6500902/135726213-683b1f7d-5502-4afa-a549-c1aedaad8519.png]]

** Basic usage

To get started, download some documentation with =M-x
devdocs-install=. This will first query https://devdocs.io for the
available documents and save the selected one to disk. Once you have
the desired documents at hand, call =M-x devdocs-lookup= to search for
entries.
devdocs-install=. This will query https://devdocs.io for the
available documents and save the selected one to disk. To read the
installed documentation, there are two options:

- =devdocs-peruse=: Select a document and display its first page.
- =devdocs-lookup=: Select an index entry and display it.

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

#+begin_src elisp
(global-set-key (kbd ("C-h D")) 'devdocs-lookup)
#+end_src

In any given buffer, the first call to =devdocs-lookup= will query for
a list of documents to search (you can select more than one option by
entering a comma-separated list). This selection will be remembered
in subsequent calls to =devdocs-lookup=, unless a prefix argument is
given; in this case you can select a new list of documents.
Alternatively, you can set the =devdocs-current-docs= variable
directly, say via [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][dir-local variables]] or a mode hook:

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

** Managing documents

To manage the collection of installed documents, use the following
commands:

- =devdocs-install=: Download and install (or reinstall) a document
distributed by [[https://devdocs.io]].
- =devdocs-delete=: Remove an installed document.
- =devdocs-update-all=: Download and reinstall all installed documents
for which a newer version is available.

In some cases, variants of a document are available for each (major)
version. It is possible to install several versions in parallel.

Documents are installed under =devdocs-data-dir=, which defaults to
=~/.emacs.d/devdocs=. To completely uninstall the package, remove
this directory.

** Setting the default documents for a collection of buffers

You may wish to select a predefined list of documents in all buffers
of a certain major mode or project. To achieve this, set the
=devdocs-current-docs= variable directly, say via [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][dir-local variables]]
or a mode hook:

#+begin_src elisp
(add-hook 'python-mode-hook
(lambda () (setq-local devdocs-current-docs '("python~3.9"))))
#+end_src

In the =*devdocs*= buffer, navigation keys similar to Info and
=*Help*= buffers are available; press =C-h m= for details. Internal
hyperlinks are opened in the same viewing buffer, and external links
are opened as =browse-url= normally would.
As usual, calling =devdocs-lookup= with a prefix argument redefines
the selected documents for that specific buffer.

** Contributing

Expand Down
68 changes: 53 additions & 15 deletions devdocs.el
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ name and a count."
Fontification is done using the `org-src' library, which see."
:type 'boolean)

(defcustom devdocs-isearch-wrap #'devdocs-isearch-wrap
"How to wrap isearch when reaching the beginning or end of a page.
This is used as `isearch-wrap-function' in DevDocs buffers. If
nil, keep the default wrapping behavior."
:type '(choice (const :tag "Jump to next or previous page" #'devdocs-isearch-wrap)
(const :tag "Wrap as usual, staying in the same page" nil)
function))

(defvar devdocs-buffer-name "*devdocs*")

(defvar devdocs-history nil
"History of documentation entries.")

Expand Down Expand Up @@ -117,7 +127,7 @@ its return value; take the necessary precautions."
(timer-set-time (car data) (time-add nil devdocs-cache-timeout)))
(let ((val (funcall fun))
(timer (run-at-time devdocs-cache-timeout nil
(lambda () (remhash funrep devdocs--cache)))))
#'remhash funrep devdocs--cache)))
(prog1 val
(puthash funrep (cons timer val) devdocs--cache)))))

Expand Down Expand Up @@ -169,11 +179,11 @@ otherwise, offer only installed documents.

Return a document metadata alist if MULTIPLE is nil; otherwise, a
list of metadata alists."
(let ((cands (seq-map (lambda (it) (cons (alist-get 'slug it) it))
(if available
(devdocs--available-docs)
(or (devdocs--installed-docs)
(user-error "No documents in `%s'" devdocs-data-dir))))))
(let ((cands (mapcar (lambda (it) (cons (alist-get 'slug it) it))
(if available
(devdocs--available-docs)
(or (devdocs--installed-docs)
(user-error "No documents in `%s'" devdocs-data-dir))))))
(if multiple
(delq nil (mapcar (lambda (s) (cdr (assoc s cands)))
(completing-read-multiple prompt cands)))
Expand Down Expand Up @@ -202,17 +212,17 @@ DOC is a document metadata alist."
pages)
(with-temp-buffer
(url-insert-file-contents (format "%s/%s/db.json?%s" devdocs-cdn-url slug mtime))
(seq-doseq (entry (let ((json-key-type 'string))
(json-read)))
(dolist (entry (let ((json-key-type 'string))
(json-read)))
(with-temp-file (expand-file-name
(url-hexify-string (format "%s.html" (car entry))) temp)
(push (car entry) pages)
(insert (cdr entry)))))
(with-temp-buffer
(url-insert-file-contents (format "%s/%s/index.json?%s" devdocs-cdn-url slug mtime))
(let ((index (json-read)))
(push `(pages . ,(vconcat (nreverse pages))) index)
(with-temp-file (expand-file-name "index" temp)
(push `(pages . ,(apply #'vector (nreverse pages))) index)
(prin1 index (current-buffer)))))
(with-temp-file (expand-file-name "metadata" temp)
(prin1 (cons devdocs--data-format-version doc) (current-buffer)))
Expand Down Expand Up @@ -284,7 +294,9 @@ This is an alist containing `entries', `pages' and `types'."
buffer-undo-list t
header-line-format devdocs-header-line
revert-buffer-function 'devdocs--revert-buffer
truncate-lines t))
truncate-lines t)
(when devdocs-isearch-wrap
(setq-local isearch-wrap-function devdocs-isearch-wrap)))

(defun devdocs-goto-target ()
"Go to the original position in a DevDocs buffer."
Expand Down Expand Up @@ -316,10 +328,12 @@ Note that this refers to the index order, which may not coincide
with the order of appearance in the text."
(interactive "p")
(let-alist (car devdocs--stack)
(unless .index
(user-error "No current entry"))
(devdocs--render
(or (ignore-error 'args-out-of-range
(seq-elt (alist-get 'entries (devdocs--index .doc))
(+ count .index)))
(elt (alist-get 'entries (devdocs--index .doc))
(+ count .index)))
(user-error (if (< count 0) "No previous entry" "No next entry"))))))

(defun devdocs-previous-entry (count)
Expand All @@ -331,9 +345,10 @@ with the order of appearance in the text."
"Go forward COUNT pages in this document."
(interactive "p")
(let-alist (car devdocs--stack)
(let* ((pages (alist-get 'pages (devdocs--index .doc)))
(let* ((pages (devdocs--with-cache
(alist-get 'pages (devdocs--index .doc))))
(page (+ count (seq-position pages (devdocs--path-file .path))))
(path (or (ignore-error 'args-out-of-range (seq-elt pages page))
(path (or (ignore-error 'args-out-of-range (elt pages page))
(user-error (if (< count 0) "No previous page" "No next page")))))
(devdocs--render `((doc . ,.doc)
(path . ,path)
Expand All @@ -359,6 +374,29 @@ with the order of appearance in the text."
(kill-new url)
(message "Copied %s" url))))

(defun devdocs-isearch-wrap ()
"Continue isearch in the next or previous document page."
(let ((reporter (make-progress-reporter "Searching"))
(entry (car devdocs--stack))
(direction (if isearch-forward +1 -1)))
(with-temp-buffer
(let ((devdocs-buffer-name (buffer-name (current-buffer)))
(case-fold-search isearch-case-fold-search)
(isearch-forward t))
(setq-local devdocs--stack (list entry))
(while (progn
(progress-reporter-update reporter)
(when (bound-and-true-p isearch-mb-mode)
(let ((inhibit-redisplay nil)) (redisplay))
(when quit-flag (signal 'quit nil)))
(devdocs-next-page direction)
(not (isearch-search-string isearch-string nil t))))
(setq entry (car devdocs--stack))))
(progress-reporter-done reporter)
(devdocs--render entry)
(setq isearch-wrapped nil)
(goto-char (if isearch-forward (point-min) (point-max)))))

(let ((map devdocs-mode-map))
(define-key map [tab] 'forward-button)
(define-key map [backtab] 'backward-button)
Expand Down Expand Up @@ -407,7 +445,7 @@ with the order of appearance in the text."
ENTRY is an alist like those in the variable `devdocs--index',
possibly with an additional ENTRY.fragment which overrides the
fragment part of ENTRY.path."
(with-current-buffer (get-buffer-create "*devdocs*")
(with-current-buffer (get-buffer-create devdocs-buffer-name)
(unless (eq major-mode 'devdocs-mode)
(devdocs-mode))
(let-alist entry
Expand Down