(defun info-list-all (&optional topics) "Create list of all manual file names on system. With TOPICS is non-nil, create list of all topics in Info dir" (let ((case-fold-search t) manuals (pattern (if topics "^[A-Z].*" "\\*.*: *(\\([^)]+\\))"))) (Info-directory) (goto-char (point-min)) (re-search-forward "\\* Menu: *\n" nil t) (while (re-search-forward pattern nil t) ;; Make sure we don't have duplicates in `manuals', ;; so that the following dolist loop runs faster. (cl-pushnew (match-string-no-properties (if topics 0 1)) manuals :test #'equal)) (nreverse manuals))) (defun info-topic-list-all (topic) "List all manual file names under a TOPIC in Info dir." (let ((case-fold-search t)) (Info-directory) (goto-char (point-min)) (re-search-forward "\\* Menu: *\n" nil t) (re-search-forward topic nil t) (let (manuals (switch t)) (while switch (re-search-forward "^[A-Z].*\\|\\*.*: *(\\([^)]+\\))" nil t) ;; Make sure we don't have duplicates in `manuals', ;; so that the following dolist loop runs faster. (if (string-match "[A-Z]" (substring (match-string 0) 0 1)) (setq switch nil) (cl-pushnew (match-string 1) manuals :test #'equal))) (nreverse manuals)))) (defcustom Info-apropos-default-manuals (info-topic-list-all "Emacs") "Default list of manuals searched by `info-apropos'. Its value should be a list with manual file names (strings) as found between the parenthesis before the node name at the the top an Info pages.") (defun Info-apropos-matches (string &optional manuals-list) "Collect STRING matches from all Info files in the MANUALS-LIST. Return a list of matches where each element is in the format \((FILENAME INDEXTEXT NODENAME LINENUMBER))." (unless (string= string "") (let ((pattern (format "\n\\* +\\([^\n]*\\(%s\\)[^\n]*\\):[ \t]+\\([^\n]+\\)\\.\\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" (regexp-quote string))) (ohist Info-history) (ohist-list Info-history-list) (current-node Info-current-node) (current-file Info-current-file) manuals matches node nodes) (let ((Info-fontify-maximum-menu-size nil)) (Info-directory) ;; current-node and current-file are nil when they invoke info-apropos ;; as the first Info command, i.e. info-apropos loads info.el. In that ;; case, we use (DIR)Top instead, to avoid signaling an error after ;; the search is complete. (when (null current-node) (setq current-file Info-current-file) (setq current-node Info-current-node)) (message "Searching indices...") (goto-char (point-min)) (re-search-forward "\\* Menu: *\n" nil t) (while (re-search-forward "\\*.*: *(\\([^)]+\\))" nil t) ;; Make sure we don't have duplicates in `manuals', ;; so that the following dolist loop runs faster. (when (member (match-string 1) manuals-list) (cl-pushnew (match-string 1) manuals :test #'equal))) (dolist (manual (nreverse manuals)) (message "Searching %s" manual) (condition-case err (if (setq nodes (Info-index-nodes (Info-find-file manual))) (save-excursion (Info-find-node manual (car nodes)) (while (progn (goto-char (point-min)) (while (re-search-forward pattern nil t) (let ((entry (match-string-no-properties 1)) (nodename (match-string-no-properties 3)) (line (match-string-no-properties 4))) (add-text-properties (- (match-beginning 2) (match-beginning 1)) (- (match-end 2) (match-beginning 1)) '(face info-index-match) entry) (setq matches (cons (list manual entry nodename line) matches)))) (setq nodes (cdr nodes) node (car nodes))) (Info-goto-node node)))) (error (message "%s" (if (eq (car-safe err) 'error) (nth 1 err) err)) (sit-for 1 t))))) (Info-find-node current-file current-node) (setq Info-history ohist Info-history-list ohist-list) (message "Searching indices...done") (or (nreverse matches) t)))) ;;;###autoload (defun info-apropos (string &optional arg manuals-list) "Grovel indices of all Info files in the MANUALS-LIST for STRING. Build a menu of the possible matches. The default value of the customizable variable `Info-apropos-default-manuals' is used as default value for MANUALS-LIST. When prefixed with the `universal-argument' \\[universal-argument] the command searches through all known Info files on your system." (interactive "sIndex apropos: \nP") (let ((manuals (unless arg (or manuals-list Info-apropos-default-manuals)))) (if (equal string "") (Info-find-node Info-apropos-file "Top") (let* ((nodes Info-apropos-nodes) nodename) (while (and nodes (not (equal string (nth 1 (car nodes))))) (setq nodes (cdr nodes))) ;; (if nodes ;; (Info-find-node Info-apropos-file (car (car nodes))) (setq nodename (format "Index for ā€˜%sā€™" string)) (push (list nodename string (Info-apropos-matches string manuals)) Info-apropos-nodes) (Info-find-node Info-apropos-file nodename))))) ;; ) (defun info-apropos-select (string &optional arg) "Select manual and Grovel for STRING. Build a menu of the possible matches. When prefixed with universal argument \\[universal-argument], select topic and grovel all manuals under topic." (interactive "sIndex apropos: \nP") (let ((object (completing-read (format "Select %s to grovel: " (if arg "topic" "manual")) (info-list-all (when arg t))))) (info-apropos string nil (if arg (info-topic-list-all object) object))))