Skip to content

Commit 82d75e7

Browse files
racket-describe: Surface "On this page" links via imenu
1 parent b26b3a3 commit 82d75e7

File tree

3 files changed

+134
-69
lines changed

3 files changed

+134
-69
lines changed

racket-company-doc.el

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
;; SPDX-License-Identifier: GPL-3.0-or-later
1010

11+
(require 'cl-macs)
1112
(require 'seq)
1213
(require 'shr)
1314
(require 'racket-back-end)
@@ -34,11 +35,8 @@
3435
(with-temp-message (format "Getting and formatting documentation %s %s ..."
3536
path anchor)
3637
(let* ((tramp-verbose 2) ;avoid excessive messages
37-
(dom (racket--html-file->dom path))
38-
(body (racket--scribble-body dom))
39-
(elems (racket--company-elements-for-anchor body anchor))
40-
(dom `(div () ,@elems))
41-
(dom (racket--walk-dom dom)))
38+
(dom (racket--scribble-path->shr-dom path))
39+
(dom (racket--company-elements-for-anchor dom anchor)))
4240
(ignore tramp-verbose)
4341
(save-excursion
4442
(let ((shr-use-fonts nil)
@@ -48,29 +46,32 @@
4846
(while (re-search-forward (string racket--scribble-temp-nbsp) nil t)
4947
(replace-match " " t t)))))
5048

51-
(defun racket--company-elements-for-anchor (xs anchor)
52-
"Return the subset of XS dom elements pertaining to ANCHOR."
53-
(while (and xs (not (racket--anchored-element (car xs) anchor)))
54-
(setq xs (cdr xs)))
55-
(and xs
56-
(let ((result nil))
57-
(push (car xs) result)
58-
(setq xs (cdr xs))
59-
(while (and xs (not (or (racket--heading-element (car xs))
60-
(racket--anchored-element (car xs)))))
61-
(push (car xs) result)
62-
(setq xs (cdr xs)))
63-
(reverse result))))
64-
65-
(defun racket--heading-element (x)
66-
(and (listp x)
67-
(memq (car x) '(h1 h2 h3 h4 h5 h6))))
68-
69-
(defun racket--anchored-element (x &optional name)
70-
(pcase x
71-
(`(a ((name . ,a)) . ,_) (or (not name) (equal name a)))
72-
(`(,_tag ,_as . ,es) (seq-some (lambda (v) (racket--anchored-element v name))
73-
es))))
49+
(defun racket--company-elements-for-anchor (dom anchor)
50+
"Return the subset of DOM elements pertaining to ANCHOR."
51+
(cl-labels
52+
((heading-p (x)
53+
(memq (dom-tag x) '(h1 h2 h3 h4 h5 h6)))
54+
(anchor-p (x name)
55+
(if (and (eq 'racket-anchor (dom-tag x))
56+
(or (not name) (equal name (dom-attr x 'name))))
57+
t
58+
(seq-some (lambda (v) (anchor-p v name))
59+
(dom-non-text-children x)))))
60+
;; Consider immediate children of the "main" div.
61+
(let ((result nil)
62+
(xs (dom-children (car (dom-by-class dom "main\\'")))))
63+
;; Discard elements before the one containing a matching anchor.
64+
(while (and xs (not (anchor-p (car xs) anchor)))
65+
(setq xs (cdr xs)))
66+
;; Accumulate result up to another anchor or a heading.
67+
(when xs
68+
(push (car xs) result)
69+
(setq xs (cdr xs))
70+
(while (and xs (not (or (heading-p (car xs))
71+
(anchor-p (car xs) nil))))
72+
(push (car xs) result)
73+
(setq xs (cdr xs))))
74+
(racket--walk-dom `(div () ,@(reverse result))))))
7475

7576
(provide 'racket-company-doc)
7677

racket-describe.el

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ anchor. If numberp, move to that position."
151151
(racket-ext-link . ,#'racket-render-tag-racket-ext-link)
152152
(racket-anchor . ,#'racket-render-tag-racket-anchor)
153153
(racket-nav . ,#'racket-render-tag-racket-nav))))
154-
(shr-insert-document dom))
154+
(shr-insert-document
155+
(racket--describe-handle-toc-nodes dom)))
155156
;; See doc string for `racket--scribble-temp-nbsp'.
156157
(goto-char (point-min))
157158
(while (re-search-forward (string racket--scribble-temp-nbsp) nil t)
@@ -177,19 +178,62 @@ text. We want point left where `racket-search-describe' can use
177178
((numberp goto)
178179
goto)
179180
((stringp goto)
180-
(or (let ((i nil)) ;silence byte-compiler warning...
181-
i ;...on all versions of emacs
182-
(cl-loop for i being the intervals
183-
if (equal (get-text-property (car i) 'racket-anchor)
184-
goto)
185-
return (cl-loop for j from (car i) to (point-max)
186-
if (not (get-text-property j 'racket-anchor))
187-
return j)))
181+
(or (racket--describe-anchor->position goto)
188182
(point-min)))
189183
(t (point-min))))
190184
(setq racket--describe-here
191185
(cons (car racket--describe-here) (point))))
192186

187+
(defun racket--describe-anchor->position (anchor)
188+
(let ((i nil)) ;silence byte-compiler warning...
189+
i ;...on all versions of emacs
190+
(cl-loop for i being the intervals
191+
if (equal (get-text-property (car i) 'racket-anchor)
192+
anchor)
193+
return (cl-loop for j from (car i) to (point-max)
194+
if (not (get-text-property j 'racket-anchor))
195+
return j))))
196+
197+
(defvar-local racket--describe-on-this-page nil)
198+
199+
(defun racket--describe-handle-toc-nodes (dom)
200+
"Handle nodes that render as a \"left nav panel\" in a web browser.
201+
202+
These aren't effective in a shr buffer, due to window width and
203+
lack of independent scrolling columns. Instead:
204+
205+
- \"tocview\": Just delete it. User can nav up to see.
206+
207+
- \"tocsub\" a.k.a. \"On this page:\": Useful, but present via
208+
`imenu'.
209+
210+
Both are children of a \"tocscet\" div."
211+
(setq-local
212+
racket--describe-on-this-page
213+
(let* ((tocsublist-table (car (dom-by-class dom "tocsublist")))
214+
(trs (dom-children tocsublist-table)))
215+
(seq-map (lambda (tr)
216+
(let* ((td (car (dom-children tr)))
217+
(num (car (dom-by-class td "tocsublinknumber")))
218+
(link (dom-child-by-tag td 'racket-doc-link))
219+
(label (concat (dom-texts num "")
220+
(dom-texts link "")))
221+
(label (subst-char-in-string racket--scribble-temp-nbsp
222+
32
223+
label))
224+
(anchor (dom-attr link 'anchor)))
225+
(cons label anchor)))
226+
trs)))
227+
(pcase (dom-by-class dom "tocset")
228+
(`(,node . ,_) (dom-remove-node dom node)))
229+
dom)
230+
231+
(defun racket--describe-imenu-create-index ()
232+
(seq-map (lambda (v)
233+
(cons (car v)
234+
(racket--describe-anchor->position (cdr v))))
235+
racket--describe-on-this-page))
236+
193237
(defconst racket--shr-faces
194238
'(("RktSym" . font-lock-keyword-face)
195239
("RktVal" . font-lock-constant-face)
@@ -474,7 +518,15 @@ browser program -- are given `racket-describe-ext-link-face'.
474518
\\{racket-describe-mode-map}"
475519
(setq show-trailing-whitespace nil)
476520
(setq-local revert-buffer-function #'racket-describe-mode-revert-buffer)
477-
(buffer-disable-undo))
521+
(buffer-disable-undo)
522+
;; imenu
523+
(setq-local imenu-create-index-function
524+
#'racket--describe-imenu-create-index)
525+
(when (boundp 'imenu-auto-rescan)
526+
(setq-local imenu-auto-rescan t))
527+
(when (boundp 'imenu-max-items)
528+
(setq-local imenu-max-items 999))
529+
(imenu-add-to-menubar "On this page"))
478530

479531
;;; Search and disambiguation using local docs
480532

racket-scribble.el

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,38 @@
88

99
;; SPDX-License-Identifier: GPL-3.0-or-later
1010

11+
(require 'dom)
1112
(require 'seq)
1213
(require 'shr)
1314
(require 'subr-x)
1415
(require 'url-util)
1516
(require 'tramp)
1617

18+
(eval-when-compile
19+
(unless (fboundp 'dom-remove-node) ;added circa Emacs 27
20+
(defun dom-remove-node (dom node)
21+
"Remove NODE from DOM."
22+
;; If we're removing the top level node, just return nil.
23+
(dolist (child (dom-children dom))
24+
(cond
25+
((eq node child)
26+
(delq node dom))
27+
((not (stringp child))
28+
(dom-remove-node child node))))))
29+
30+
(unless (fboundp 'dom-search) ;added circa Emacs 27
31+
(defun dom-search (dom predicate)
32+
"Return elements in DOM where PREDICATE is non-nil.
33+
PREDICATE is called with the node as its only parameter."
34+
(let ((matches (cl-loop for child in (dom-children dom)
35+
for matches = (and (not (stringp child))
36+
(dom-search child predicate))
37+
when matches
38+
append matches)))
39+
(if (funcall predicate dom)
40+
(cons dom matches)
41+
matches)))))
42+
1743
(defconst racket--scribble-temp-nbsp #x2020
1844
"Character we substitute for #xA0 non-breaking-space.
1945
@@ -33,42 +59,23 @@ This will ensure that the non-breaking-space chars actually have
3359
the effect of being non-breaking.")
3460

3561
(defun racket--scribble-path->shr-dom (path)
36-
(with-temp-message (format "Getting and formatting documentation %s..."
37-
path)
38-
(let* ((tramp-verbose 2) ;avoid excessive messages
39-
(base (file-name-directory path))
40-
(dom (racket--html-file->dom path))
41-
(body (racket--scribble-body dom))
42-
(body (racket--massage-scribble-dom path base body)))
43-
`(html ()
44-
(head () (base ((href . ,base))))
45-
,body))))
62+
(let* ((tramp-verbose 2) ;avoid excessive messages
63+
(base (file-name-directory path))
64+
(dom (with-temp-message (format "Getting %s..." path)
65+
(racket--html-file->dom path)))
66+
(body (with-temp-message (format "Adjusting %s..." path)
67+
(racket--massage-scribble-dom path
68+
base
69+
(dom-child-by-tag dom 'body)))))
70+
`(html ()
71+
(head () (base ((href . ,base))))
72+
,body)))
4673

4774
(defun racket--html-file->dom (path)
4875
(with-temp-buffer
4976
(insert-file-contents-literally path)
5077
(libxml-parse-html-region (point-min) (point-max))))
5178

52-
(defun racket--scribble-body (dom)
53-
"Return a body with the interesting elements in DOM.
54-
55-
With a normal Racket documentation page produced by Scribble,
56-
these are only elements from the maincolumn/main div -- not the
57-
tocset sibling.
58-
59-
With other doc pages, e.g. from r5rs, these are simply all the
60-
body elements."
61-
(pcase (seq-some (lambda (v)
62-
(pcase v (`(body . ,_) v)))
63-
dom)
64-
(`(body ,_
65-
(div ((class . "tocset")) . ,_)
66-
(div ((class . "maincolumn"))
67-
(div ((class . "main")) . ,xs))
68-
. ,_)
69-
`(body () ,@xs))
70-
(body body)))
71-
7279
;; Dynamically bound (like Racket parameters).
7380
(defvar racket--scribble-file nil)
7481
(defvar racket--scribble-base nil)
@@ -251,6 +258,11 @@ In some cases we resort to returning custom elements for
251258
(`(span ((style . "font-style: italic")) . ,xs)
252259
`(i () ,@(mapcar #'racket--walk-dom xs)))
253260

261+
;; <span class="mywbr"> </span> added in e.g. "tocsub" for
262+
;; "case/equal". As render in shr, undesired space.
263+
(`(span ((class . "mywbr")) ,_)
264+
"")
265+
254266
;; Delete some things that produce unwanted blank lines and/or
255267
;; indents.
256268
(`(blockquote ((class . ,(or "SVInsetFlow" "SubFlow"))) . ,xs)
@@ -547,7 +559,7 @@ In some cases we resort to returning custom elements for
547559
"HTML entity symbols to strings.
548560
From <https://github.com/GNOME/libxml2/blob/master/HTMLparser.c>."
549561
(string (or (cdr (assq sym racket--html-char-entities))
550-
??)))
562+
(format "%s" sym))))
551563

552564
(provide 'racket-scribble)
553565

0 commit comments

Comments
 (0)