emacs/org/elpa/helm-20220423.1712/helm-info.el

301 lines
11 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; helm-info.el --- Browse info index with helm -*- lexical-binding: t -*-
;; Copyright (C) 2012 ~ 2021 Thierry Volpiatto <thierry.volpiatto@gmail.com>
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'helm)
(require 'helm-lib)
(require 'helm-utils)
(require 'info)
(declare-function Info-index-nodes "info" (&optional file))
(declare-function Info-goto-node "info" (&optional fork))
(declare-function Info-find-node "info" (filename nodename &optional no-going-back))
(declare-function ring-insert "ring")
(declare-function ring-empty-p "ring")
(declare-function ring-ref "ring")
(defvar Info-history)
(defvar Info-directory-list)
;;; Customize
(defgroup helm-info nil
"Info-related applications and libraries for Helm."
:group 'helm)
(defcustom helm-info-default-sources
'(helm-source-info-elisp
helm-source-info-cl
helm-source-info-eieio
helm-source-info-pages)
"Default sources to use for looking up symbols at point in Info
files with `helm-info-at-point'."
:group 'helm-info
:type '(repeat (choice symbol)))
;;; Build info-index sources with `helm-info-source' class.
(cl-defun helm-info-init (&optional (file (helm-get-attr 'info-file)))
"Initialize candidates for info FILE.
If FILE have nodes, loop through all nodes and accumulate candidates
found in each node, otherwise scan only the current info buffer."
;; Allow reinit candidate buffer when using edebug.
(helm-aif (and debug-on-error
(helm-candidate-buffer))
(kill-buffer it))
(unless (helm-candidate-buffer)
(save-selected-window
(info file " *helm info temp buffer*")
(let ((tobuf (helm-candidate-buffer 'global))
Info-history)
(helm-aif (Info-index-nodes)
(cl-dolist (node it)
(Info-goto-node node)
(helm-info-scan-current-buffer tobuf))
(helm-info-scan-current-buffer tobuf))
(bury-buffer)))))
(defun helm-info-scan-current-buffer (tobuf)
"Scan current info buffer and print lines to TOBUF.
Argument TOBUF is the `helm-candidate-buffer'."
(let (start end line)
(goto-char (point-min))
(while (search-forward "\n* " nil t)
(unless (search-forward "Menu:\n" (1+ (point-at-eol)) t)
(setq start (point-at-bol)
;; Fix Bug#1503 by getting the invisible
;; info displayed on next line in long strings.
;; e.g "* Foo.\n (line 12)" instead of
;; "* Foo.(line 12)"
end (or (save-excursion
(goto-char (point-at-bol))
(re-search-forward "(line +[0-9]+)" nil t))
(point-at-eol))
;; Long string have a new line inserted before the
;; invisible spec, remove it.
line (replace-regexp-in-string
"\n" "" (buffer-substring start end)))
(with-current-buffer tobuf
(insert line)
(insert "\n"))))))
(defun helm-info-goto (node-line)
"The helm-info action to jump to NODE-LINE."
(Info-goto-node (car node-line))
(helm-goto-line (cdr node-line)))
(defvar helm-info--node-regexp
"^\\* +\\(.+\\):[ \\t]+\\(.*\\)\\(?:[ \\t]*\\)(line +\\([0-9]+\\))"
"A regexp that should match file name, node name and line number in
a line like this:
\* bind: Bash Builtins. (line 21).")
(defun helm-info-display-to-real (line)
"Transform LINE to an acceptable argument for `info'.
If line have a node use the node, otherwise use directly first name found."
(let (nodename linum)
(when (string-match helm-info--node-regexp line)
(setq nodename (match-string 2 line)
linum (match-string 3 line)))
(if nodename
(cons (format "(%s)%s"
(helm-get-attr 'info-file)
(replace-regexp-in-string ":\\'" "" nodename))
(string-to-number (or linum "1")))
(cons (format "(%s)%s"
(helm-get-attr 'info-file)
(helm-aand (replace-regexp-in-string "^* " "" line)
(replace-regexp-in-string "::?.*\\'" "" it)))
1))))
(defclass helm-info-source (helm-source-in-buffer)
((info-file :initarg :info-file
:initform nil
:custom 'string)
(init :initform #'helm-info-init)
(display-to-real :initform #'helm-info-display-to-real)
(get-line :initform #'buffer-substring)
(action :initform '(("Goto node" . helm-info-goto)))))
(defmacro helm-build-info-source (fname &rest args)
`(helm-make-source (concat "Info Index: " ,fname) 'helm-info-source
:info-file ,fname ,@args))
(defun helm-build-info-index-command (name doc source buffer)
"Define a Helm command NAME with documentation DOC.
Arg SOURCE will be an existing helm source named
`helm-source-info-<NAME>' and BUFFER a string buffer name."
(defalias (intern (concat "helm-info-" name))
(lambda ()
(interactive)
(helm :sources source
:buffer buffer
:candidate-number-limit 1000))
doc))
(defun helm-define-info-index-sources (var-value &optional commands)
"Define Helm sources named helm-source-info-<NAME>.
Sources are generated for all entries of
`helm-default-info-index-list'.
If COMMANDS arg is non-nil, also build commands named
`helm-info-<NAME>'.
Where NAME is an element of `helm-default-info-index-list'."
(cl-loop for str in var-value
for sym = (intern (concat "helm-source-info-" str))
do (set sym (helm-build-info-source str))
when commands
do (helm-build-info-index-command
str (format "Predefined helm for %s info." str)
sym (format "*helm info %s*" str))))
(defun helm-info-index-set (var value)
(set var value)
(helm-define-info-index-sources value t))
;;; Search Info files
;; `helm-info' is the main entry point here. It prompts the user for an Info
;; file, then a term in the file's index to jump to.
(defvar helm-info-searched (make-ring 32)
"Ring of previously searched Info files.")
(defun helm-get-info-files ()
"Return list of Info files to use for `helm-info'.
Elements of the list are strings of Info file names without
extensions (e.g., \"emacs\" for file \"emacs.info.gz\"). Info
files are found by searching directories in
`Info-directory-list'."
(info-initialize) ; Build Info-directory-list from INFOPATH (Bug#2118)
(let ((files (cl-loop for d in (or Info-directory-list
Info-default-directory-list)
when (file-directory-p d)
append (directory-files d nil "\\.info"))))
(helm-fast-remove-dups
(cl-loop for f in files collect
(helm-file-name-sans-extension f))
:test 'equal)))
(defcustom helm-default-info-index-list
(helm-get-info-files)
"Info files to search in with `helm-info'."
:group 'helm-info
:type '(repeat (choice string))
:set 'helm-info-index-set)
(defun helm-info-search-index (candidate)
"Search the index of CANDIDATE's Info file using the function
helm-info-<CANDIDATE>."
(let ((helm-info-function
(intern-soft (concat "helm-info-" candidate))))
(when (fboundp helm-info-function)
(funcall helm-info-function)
(ring-insert helm-info-searched candidate))))
(defun helm-def-source--info-files ()
"Return a Helm source for Info files."
(helm-build-sync-source "Helm Info"
:candidates
(lambda () (copy-sequence helm-default-info-index-list))
:candidate-number-limit 999
:candidate-transformer
(lambda (candidates)
(sort candidates #'string-lessp))
:nomark t
:action '(("Search index" . helm-info-search-index))))
;;;###autoload
(defun helm-info (&optional refresh)
"Preconfigured `helm' for searching Info files' indices.
With a prefix argument \\[universal-argument], set REFRESH to
non-nil.
Optional parameter REFRESH, when non-nil, re-evaluates
`helm-default-info-index-list'. If the variable has been
customized, set it to its saved value. If not, set it to its
standard value. See `custom-reevaluate-setting' for more.
REFRESH is useful when new Info files are installed. If
`helm-default-info-index-list' has not been customized, the new
Info files are made available."
(interactive "P")
(let ((default (unless (ring-empty-p helm-info-searched)
(ring-ref helm-info-searched 0))))
(when refresh
(custom-reevaluate-setting 'helm-default-info-index-list))
(helm :sources (helm-def-source--info-files)
:buffer "*helm Info*"
:preselect (and default
(concat "\\_<" (regexp-quote default) "\\_>")))))
;;;; Info at point
;; `helm-info-at-point' is the main entry point here. It searches for the
;; symbol at point through the Info sources defined in
;; `helm-info-default-sources' and jumps to it.
(defvar helm-info--pages-cache nil
"Cache for all Info pages on the system.")
(defvar helm-source-info-pages
(helm-build-sync-source "Info Pages"
:init #'helm-info-pages-init
:candidates (lambda () helm-info--pages-cache)
:action '(("Show with Info" .
(lambda (node-str)
(info (replace-regexp-in-string
"^[^:]+: " "" node-str)))))
:requires-pattern 2)
"Helm source for Info pages.")
(defun helm-info-pages-init ()
"Collect candidates for initial Info node Top."
(or helm-info--pages-cache
(let ((info-topic-regexp "\\* +\\([^:]+: ([^)]+)[^.]*\\)\\."))
(save-selected-window
(info "dir" " *helm info temp buffer*")
(Info-find-node "dir" "top")
(goto-char (point-min))
(while (re-search-forward info-topic-regexp nil t)
(push (match-string-no-properties 1)
helm-info--pages-cache))
(kill-buffer)))))
;;;###autoload
(defun helm-info-at-point ()
"Preconfigured `helm' for searching info at point."
(interactive)
;; Symbol at point is used as default as long as one of the sources
;; in `helm-info-default-sources' is member of
;; `helm-sources-using-default-as-input'.
(cl-loop for src in helm-info-default-sources
for name = (if (symbolp src)
(assoc 'name (symbol-value src))
(assoc 'name src))
unless name
do (warn "Couldn't build source `%S' without its info file" src))
(helm :sources helm-info-default-sources
:buffer "*helm info*"))
(provide 'helm-info)
;;; helm-info.el ends here