diff --git a/doc/racket-mode.texi b/doc/racket-mode.texi index edd87c62..6c463fad 100644 --- a/doc/racket-mode.texi +++ b/doc/racket-mode.texi @@ -2141,20 +2141,8 @@ the Racket ``Search Manuals'' page. Search installed documentation; view using @code{racket-describe-mode}. -Always prompts you to enter a symbol, defaulting to the symbol at -point if any. - -@itemize -@item -If just one module exports the name, you go directly to a -Racket Describe buffer with its documentation. - -@item -If multiple modules export the name, you go first to a -``disambiguation'' buffer similar to the Racket ``Search -Manuals'' web page. You may press RET on any item to get a -Racket Describe buffer for that module's version of the thing. -@end itemize +Tip: Use M-p -- the Emacs ``future history'' feature -- to yank +the symbol at point into the minibuffer. @node Run @section Run diff --git a/racket-describe.el b/racket-describe.el index 85ae2bba..135eefee 100644 --- a/racket-describe.el +++ b/racket-describe.el @@ -528,132 +528,143 @@ browser program -- are given `racket-describe-ext-link-face'. (setq-local imenu-max-items 999)) (imenu-add-to-menubar "On this page")) -;;; Search and disambiguation using local docs +;;; Search local docs ;; For people who don't want to use a web browser at all: Search local -;; documentation, disambiguate in a buffer, and view in a -;; racket-describe-mode buffer. +;; documentation, and view in a `racket-describe-mode' buffer. -(defvar racket--describe-terms (make-hash-table :test 'equal) - "Used for completion candidates.") +(defvar racket--describe-index (make-hash-table :test 'equal) + "Hash-table from back end name to association list of doc index info.") -(defun racket--remove-describe-terms () - "A `racket-stop-back-end-hook' to clean up `racket--describe-terms'." - (let ((key (racket-back-end-name))) - (when key - (remhash key racket--describe-terms)))) +(defun racket--remove-describe-index () + "A `racket-stop-back-end-hook' to clean up `racket--describe-index'." + (when-let (key (racket-back-end-name)) + (remhash key racket--describe-index))) -(add-hook 'racket-stop-back-end-hook #'racket--remove-describe-terms) +(add-hook 'racket-stop-back-end-hook #'racket--remove-describe-index) -(defun racket--describe-terms () +(defun racket--describe-index () (let ((key (racket-back-end-name))) - (pcase (gethash key racket--describe-terms) - (`nil - (puthash key 'fetching - racket--describe-terms) - (racket--cmd/async nil - '(doc-index-names) - (lambda (names) - (puthash key - (sort names #'string-lessp) - racket--describe-terms))) + (pcase (gethash key racket--describe-index) + (`() + (puthash key 'fetching racket--describe-index) + (racket--cmd/async + nil + '(doc-index) + (lambda (items) + (puthash key + (racket--describe-index-make-alist items) + racket--describe-index))) ;; Wait for response but if waiting too long just return nil, ;; and use the response next time. - (with-temp-message "Getting completion candidates from back end..." - (with-timeout (5 nil) - (while (equal 'fetching (gethash key racket--describe-terms)) + (with-temp-message "Getting doc index from back end..." + (with-timeout (15 nil) + (while (equal 'fetching (gethash key racket--describe-index)) (accept-process-output nil 0.01)) - (gethash key racket--describe-terms)))) - ('fetching nil) - ((and (pred listp) names) names)))) + (gethash key racket--describe-index)))) + ('fetching (make-hash-table :test 'equal)) + ((and (pred listp) alist) alist)))) + +(defun racket--describe-index-make-alist (items) + "Given back end ITEMS make an association list. + +A list is a valid collection for completion, where the `car' is +the completion string. For this we use a string with invisible +text to influence sorting, appended to the visible search term. +This is vastly faster than using a :display-sort-function. + +Our affixation function gets the propertized string, so we can +tuck the values it needs into a text property. + +However the final result from `completing-read' is stripped of +text properties -- which is the only reason we need an +association list, to look up the path and anchor." + (cl-flet ((wstr (width s) + (let ((len (length s))) + (if (<= len width) + (concat s + (make-string (- width len) 32)) + (substring s 0 width))))) + (mapcar + (pcase-lambda (`(,uid ,term ,sort ,what ,from ,fams ,path ,anchor)) + (let* ((term (propertize term + 'racket-affix (list term what from fams))) + (sort (let ((fams (wstr 32 + (pcase fams + ("Racket" " Racket") + (v v)))) + (from (wstr 64 + (cond + ((string-match-p "^racket/" from) + (concat " 0_" from)) + ((string-match-p "^typed/racket/" from) + (concat " 1_" from)) + ((string-match-p "^rhombus" from) + (concat " 2_" from)) + ((string-equal "" from) + (make-string 16 ?z)) + (t from)))) + (what (wstr 32 what)) + (sort (format "%09d" sort)) + (uid (format "%05d" uid))) + ;; We need uid only to guarantee uniqueness, not + ;; to influence sorting, so it goes last. + (concat fams from what sort uid))) + (str (concat term (propertize sort 'display "")))) + (list str path anchor))) + items))) + +(defun racket--describe-index-affixator (strs) + "Value for :affixation-function." + (let ((max-term 16) + (max-what 16) + (max-from 32)) + (dolist (str strs) + (pcase-let ((`(,term ,what ,from ,_fams) + (get-text-property 0 'racket-affix str))) + (setq max-term (max max-term (1+ (length term)))) + (setq max-what (max max-what (1+ (length what)))) + (setq max-from (max max-from (1+ (length from)))))) + (seq-map (lambda (str) + (pcase-let* + ((`(,term ,what ,from ,fams) + (get-text-property 0 'racket-affix str)) + (suffix (concat + (make-string (- max-term (length term)) 32) + what + (make-string (- max-what (length what)) 32) + from + (make-string (- max-from (length from)) 32) + fams)) + (suffix (propertize suffix + 'face 'completions-annotations))) + (list str "" suffix))) + strs))) (defun racket-describe-search () "Search installed documentation; view using `racket-describe-mode'. -Always prompts you to enter a symbol, defaulting to the symbol at -point if any. - -- If just one module exports the name, you go directly to a - Racket Describe buffer with its documentation. - -- If multiple modules export the name, you go first to a - \"disambiguation\" buffer similar to the Racket \"Search - Manuals\" web page. You may press RET on any item to get a - Racket Describe buffer for that module's version of the thing. -" +Tip: Use M-p -- the Emacs \"future history\" feature -- to yank +the symbol at point into the minibuffer." (interactive) - (let* ((name (racket--symbol-at-point-or-prompt t "Describe: " - (racket--describe-terms))) - (buf-name (format "*Racket Describe Search `%s` <%s>*" - name - (racket-back-end-name)))) - (racket--cmd/async - nil - `(doc-index-lookup ,name) - (lambda (result) - (pcase result - (`() - (message "No documentation found for %s" name)) - (`((,_term ,_what ,_from ,_fams ,path ,anchor)) - (racket-describe-search-visit name - (racket-file-name-back-to-front path) - anchor)) - (vs - (with-current-buffer - (get-buffer-create buf-name) - (racket-describe-search-mode) - (let ((max-term 0) - (max-what 0) - (max-from 0)) - (setq tabulated-list-entries - (mapcar - (pcase-lambda (`(,term ,what ,from ,fams ,path ,anchor)) - (setq max-term (max max-term (length term))) - (setq max-what (max max-what (length what))) - (setq max-from (max max-from (length from))) - (list nil - (vector - (list term - 'name term - 'path (racket-file-name-back-to-front path) - 'anchor anchor - 'action #'racket-describe-search-button) - what - from - fams))) - vs)) - (setq tabulated-list-sort-key nil) - (setq tabulated-list-format - (vector (list "Name" (max max-term (length "Name ")) nil) - (list "Kind" (max max-what (length "Kind ")) t) - (list "From" (max max-from (length "From")) t) - (list "Language families" 99 t))) - (setq tabulated-list-padding 0) - (tabulated-list-init-header) - (tabulated-list-print) - (pop-to-buffer (current-buffer)))))))))) - -(defun racket-describe-search-button (button) - (racket-describe-search-visit - (button-get button 'name) - (button-get button 'path) - (button-get button 'anchor))) - -(defun racket-describe-search-visit (term path anchor) - (racket--do-describe - (cons path anchor) - nil - term)) - -(defvar racket-describe-search-mode-map - (let ((map (racket--easy-keymap-define - '(("C-c C-s" racket-describe-search))))) - map)) - -(define-derived-mode racket-describe-search-mode tabulated-list-mode - "RacketSearchDescribe" - "Major mode for disambiguating documentation search results. -\\{racket-describe-search-mode-map}") + (when-let (str + (completing-read + "Describe: " + (racket--completion-table + (racket--describe-index) + `((category . ,racket--identifier-category) + (affixation-function . ,#'racket--describe-index-affixator))) + nil ;predicate + t ;require-match + nil ;initial-input + nil ;hist + (racket--thing-at-point 'symbol t))) + (pcase (assoc str (racket--describe-index)) + (`(,_str ,path ,anchor) + (racket--do-describe (cons (racket-file-name-back-to-front path) + anchor) + nil + (substring-no-properties str)))))) (provide 'racket-describe) diff --git a/racket/command-server.rkt b/racket/command-server.rkt index fe88ae1c..6685747e 100644 --- a/racket/command-server.rkt +++ b/racket/command-server.rkt @@ -18,8 +18,7 @@ "repl-session.rkt" (only-in "scribble.rkt" bluebox-command - doc-index-names - doc-index-lookup + doc-index libs-exporting-documented) "util.rkt") @@ -146,8 +145,7 @@ [`(requires/trim ,path-str ,reqs) (requires/trim path-str reqs)] [`(requires/base ,path-str ,reqs) (requires/base path-str reqs)] [`(requires/find ,str) (libs-exporting-documented str)] - [`(doc-index-names) (doc-index-names)] - [`(doc-index-lookup ,str) (doc-index-lookup str)] + [`(doc-index) (doc-index)] [`(hash-lang . ,more) (apply hash-lang more)] [`(pkg-list) (package-list)] [`(pkg-details ,str) (package-details str)] diff --git a/racket/scribble.rkt b/racket/scribble.rkt index 355dd8ee..79899ec0 100644 --- a/racket/scribble.rkt +++ b/racket/scribble.rkt @@ -21,8 +21,8 @@ setup/xref syntax/parse/define version/utils - "elisp.rkt" - "util.rkt") + "define-fallbacks.rkt" + "elisp.rkt") ;; Fallbacks when new index structs aren't available (before ;; scribble-lib 1.54, which ~= Racket 8.14.0.6). @@ -35,8 +35,7 @@ (provide binding->path+anchor identifier->bluebox bluebox-command - doc-index-names - doc-index-lookup + doc-index libs-exporting-documented module-doc-path refresh-module-doc-path-index!) @@ -129,166 +128,95 @@ (or (hash-ref ht 'hidden? #f) (hash-ref ht 'constructor? #f)))))) -(define ((doc-index-names)) +(define ((doc-index)) (with-less-memory-pressure - (with-parens - (define xref (load-collections-xref)) - (for* ([entry (in-list (xref-index xref))] - [desc (in-value (entry-desc entry))] - #:unless (hide-desc? desc) - [term (in-value (car (entry-words entry)))]) - (elisp-write term)) - (newline)))) - -(define (doc-index-lookup str) - (with-less-memory-pressure - (define xref (load-collections-xref)) - (define results - (for*/set ([entry (in-list (xref-index xref))] - [term (in-value (car (entry-words entry)))] - #:when (string=? str term) - [desc (in-value (entry-desc entry))] - #:when desc - ;;[_ (in-value (println desc))] ;;; DEBUG - #:unless (hide-desc? desc) - [tag (in-value (entry-tag entry))]) - (define-values (path anchor) (xref-tag->path+anchor xref tag)) - (define (method-what) - (cond - [(method-tag? tag) - (define-values (c/i _m) (get-class/interface-and-method tag)) - (format "method of ~a" c/i)] - [else "method"])) - (define (doc-from) - (string-append - "◊ " - (match (path->main-doc-relative path) - [(cons 'doc byte-strings) - (define path-parts (map bytes->path byte-strings)) - (define rel-html (apply build-path path-parts)) - (path->string - (path-replace-extension rel-html #""))] - [_ (~a tag)]))) - (define-values (what from fams sort-order) - (cond - ;; New structs - [(exported-index-desc*? desc) - (define ht (exported-index-desc*-extras desc)) - (define kind (hash-ref ht 'kind)) - (define what (if (string=? kind "method") - (method-what) - kind)) - (define from - (string-join (match (hash-ref ht 'display-from-libs #f) - [(? list? contents) - (map content->string contents)] - [#f - (map ~s (exported-index-desc-from-libs desc))]) - ", ")) - (define fams (match (hash-ref ht 'language-family #f) - [(? list? fams) (string-join (map ~a fams) ", ")] - [#f "Racket"])) - (define sort-order (hash-ref ht 'sort-order 0)) - (values what from fams sort-order)] - [(index-desc? desc) - (define ht (index-desc-extras desc)) - (define what (match (hash-ref ht 'module-kind #f) - ['lib "module"] - ['lang "language"] - ['reader "reader"] - [#f "documentation"] - [v (~a v)])) - (define from - (match (hash-ref ht 'display-from-libs #f) - [(? list? contents) - (string-join (map content->string contents) ", ")] - [#f (doc-from)])) - (define fams (match (hash-ref ht 'language-family #f) - [(? list? fams) (string-join (map ~a fams) ", ")] - [#f "Racket"])) - (define sort-order (hash-ref ht 'sort-order 0)) - (values what from fams sort-order)] - ;; Older structs - [(exported-index-desc? desc) - (define what - (match desc - [(? language-index-desc?) "language"] - [(? reader-index-desc?) "reader"] - [(? form-index-desc?) "syntax"] - [(? procedure-index-desc?) "procedure"] - [(? thing-index-desc?) "value"] - [(? struct-index-desc?) "structure"] - [(? class-index-desc?) "class"] - [(? interface-index-desc?) "interface"] - [(? mixin-index-desc?) "mixin"] - [(? method-index-desc?) (method-what)] - [_ ""])) - (define from (string-join (map ~s (exported-index-desc-from-libs desc)) ", ")) - (values what from "" 0)] - [(module-path-index-desc? desc) - (values "module" "" "" 0)] - [else - (values "documentation" (doc-from) "" 0)])) - (list sort-order term what from fams path anchor))) - (define sort-key - (match-lambda - [(list sort-order _term what from fams _path _anchor) - (string-append (match fams - ["Racket" " Racket"] - [v v]) - (~r sort-order - #:min-width 9 - #:pad-string "0") - (match from - [(and (pregexp "^racket/") v) - (string-append " 0_" v)] - [(and (pregexp "^typed/racket/") v) - (string-append " 1_" v)] - [(and (pregexp "^rhombus") v) - (string-append " 2_" v)] - ["" (make-string 64 #\z)] - [v v]) - what)])) - (define sorted - (sort (set->list results) - stringpath+anchor xref tag)) + (define (method-what) + (cond + [(method-tag? tag) + (define-values (c/i _m) (get-class/interface-and-method tag)) + (format "method of ~a" c/i)] + [else "method"])) + (define (doc-from) + (string-append + "◊ " + (match (path->main-doc-relative path) + [(cons 'doc byte-strings) + (define path-parts (map bytes->path byte-strings)) + (define rel-html (apply build-path path-parts)) + (path->string + (path-replace-extension rel-html #""))] + [_ (~a tag)]))) + (define-values (what from fams sort-order) + (cond + ;; New structs + [(exported-index-desc*? desc) + (define ht (exported-index-desc*-extras desc)) + (define kind (hash-ref ht 'kind)) + (define what (if (string=? kind "method") + (method-what) + kind)) + (define from + (string-join (match (hash-ref ht 'display-from-libs #f) + [(? list? contents) + (map content->string contents)] + [#f + (map ~s (exported-index-desc-from-libs desc))]) + ", ")) + (define fams (match (hash-ref ht 'language-family #f) + [(? list? fams) (string-join (map ~a fams) ", ")] + [#f "Racket"])) + (define sort-order (hash-ref ht 'sort-order 0)) + (values what from fams sort-order)] + [(index-desc? desc) + (define ht (index-desc-extras desc)) + (define what (match (hash-ref ht 'module-kind #f) + ['lib "module"] + ['lang "language"] + ['reader "reader"] + [#f "documentation"] + [v (~a v)])) + (define from + (match (hash-ref ht 'display-from-libs #f) + [(? list? contents) + (string-join (map content->string contents) ", ")] + [#f (doc-from)])) + (define fams (match (hash-ref ht 'language-family #f) + [(? list? fams) (string-join (map ~a fams) ", ")] + [#f "Racket"])) + (define sort-order (hash-ref ht 'sort-order 0)) + (values what from fams sort-order)] + ;; Older structs + [(exported-index-desc? desc) + (define what + (match desc + [(? language-index-desc?) "language"] + [(? reader-index-desc?) "reader"] + [(? form-index-desc?) "syntax"] + [(? procedure-index-desc?) "procedure"] + [(? thing-index-desc?) "value"] + [(? struct-index-desc?) "structure"] + [(? class-index-desc?) "class"] + [(? interface-index-desc?) "interface"] + [(? mixin-index-desc?) "mixin"] + [(? method-index-desc?) (method-what)] + [_ ""])) + (define from (string-join (map ~s (exported-index-desc-from-libs desc)) ", ")) + (values what from "" 0)] + [(module-path-index-desc? desc) + (values "module" "" "" 0)] + [else + (values "documentation" (doc-from) "" 0)])) + (elisp-writeln (list uid term sort-order what from fams path anchor))) + (newline)))) ;;; This is for the requires/find command