mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 434b46beff1c5ec01cbefd5273d89971a82d6bab
commit | author | age
5cb5f7 1 ;;; helm-info.el --- Browse info index with helm -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2012 ~ 2018 Thierry Volpiatto <thierry.volpiatto@gmail.com>
4
5 ;; This program is free software; you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
9
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;; GNU General Public License for more details.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;;; Code:
19
20 (require 'cl-lib)
21 (require 'helm)
22 (require 'helm-lib)
23 (require 'helm-utils)
24 (require 'info)
25
26 (declare-function Info-index-nodes "info" (&optional file))
27 (declare-function Info-goto-node "info" (&optional fork))
28 (declare-function Info-find-node "info.el" (filename nodename &optional no-going-back))
29 (defvar Info-history)
30 (defvar Info-directory-list)
31
32 ;;; Customize
33
34 (defgroup helm-info nil
35   "Info-related applications and libraries for Helm."
36   :group 'helm)
37
38 (defcustom helm-info-default-sources
39   '(helm-source-info-elisp
40     helm-source-info-cl
41     helm-source-info-eieio
42     helm-source-info-pages)
43   "Default sources to use for looking up symbols at point in Info
44 files with `helm-info-at-point'."
45   :group 'helm-info
46   :type '(repeat (choice symbol)))
47
48 ;;; Build info-index sources with `helm-info-source' class.
49
50 (cl-defun helm-info-init (&optional (file (helm-attr 'info-file)))
51   ;; Allow reinit candidate buffer when using edebug.
52   (helm-aif (and debug-on-error
53                  (helm-candidate-buffer))
54       (kill-buffer it))
55   (unless (helm-candidate-buffer)
56     (save-selected-window
57       (info file " *helm info temp buffer*")
58       (let ((tobuf (helm-candidate-buffer 'global))
59             Info-history
60             start end line)
61         (cl-dolist (node (Info-index-nodes))
62           (Info-goto-node node)
63           (goto-char (point-min))
64           (while (search-forward "\n* " nil t)
65             (unless (search-forward "Menu:\n" (1+ (point-at-eol)) t)
66               (setq start (point-at-bol)
67                     ;; Fix issue #1503 by getting the invisible
68                     ;; info displayed on next line in long strings.
69                     ;; e.g "* Foo.\n   (line 12)" instead of
70                     ;;     "* Foo.(line 12)"
71                     end (or (save-excursion
72                               (goto-char (point-at-bol))
73                               (re-search-forward "(line +[0-9]+)" nil t))
74                             (point-at-eol))
75                     ;; Long string have a new line inserted before the
76                     ;; invisible spec, remove it.
77                     line (replace-regexp-in-string
78                           "\n" "" (buffer-substring start end)))
79               (with-current-buffer tobuf
80                 (insert line)
81                 (insert "\n")))))
82         (bury-buffer)))))
83
84 (defun helm-info-goto (node-line)
85   (Info-goto-node (car node-line))
86   (helm-goto-line (cdr node-line)))
87
88 (defun helm-info-display-to-real (line)
89   (and (string-match
90         ;; This regexp is stolen from Info-apropos-matches
91         "\\* +\\([^\n]*.+[^\n]*\\):[ \t]+\\([^\n]*\\)\\.\\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" line)
92        (cons (format "(%s)%s" (helm-attr 'info-file) (match-string 2 line))
93              (string-to-number (or (match-string 3 line) "1")))))
94
95 (defclass helm-info-source (helm-source-in-buffer)
96   ((info-file :initarg :info-file
97               :initform nil
98               :custom 'string)
99    (init :initform #'helm-info-init)
100    (display-to-real :initform #'helm-info-display-to-real)
101    (get-line :initform #'buffer-substring)
102    (action :initform '(("Goto node" . helm-info-goto)))))
103
104 (defmacro helm-build-info-source (fname &rest args)
105   `(helm-make-source (concat "Info Index: " ,fname) 'helm-info-source
106      :info-file ,fname ,@args))
107
108 (defun helm-build-info-index-command (name doc source buffer)
109   "Define a helm command NAME with documentation DOC.
110 Arg SOURCE will be an existing helm source named
111 `helm-source-info-<NAME>' and BUFFER a string buffer name."
112   (defalias (intern (concat "helm-info-" name))
113       (lambda ()
114         (interactive)
115         (helm :sources source
116               :buffer buffer
117               :candidate-number-limit 1000))
118     doc))
119
120 (defun helm-define-info-index-sources (var-value &optional commands)
121   "Define helm sources named helm-source-info-<NAME>.
122 Sources are generated for all entries of `helm-default-info-index-list'.
123 If COMMANDS arg is non-nil, also build commands named `helm-info-<NAME>'.
124 Where NAME is an element of `helm-default-info-index-list'."
125   (cl-loop for str in var-value
126            for sym = (intern (concat "helm-source-info-" str))
127            do (set sym (helm-build-info-source str))
128            when commands
129            do (helm-build-info-index-command
130                str (format "Predefined helm for %s info." str)
131                sym (format "*helm info %s*" str))))
132
133 (defun helm-info-index-set (var value)
134   (set var value)
135   (helm-define-info-index-sources value t))
136
137 ;;; Search Info files
138
139 ;; `helm-info' is the main entry point here. It prompts the user for an Info
140 ;; file, then a term in the file's index to jump to.
141
142 (defvar helm-info-searched (make-ring 32)
143   "Ring of previously searched Info files.")
144
145 (defun helm-get-info-files ()
146   "Return list of Info files to use for `helm-info'.
147
148 Elements of the list are strings of Info file names without
149 extensions (e.g. \"emacs\" for file \"emacs.info.gz\"). Info
150 files are found by searching directories in
151 `Info-directory-list'."
152   (let ((files (cl-loop for d in (or Info-directory-list
153                                      Info-default-directory-list)
154                         when (file-directory-p d)
155                         append (directory-files d nil "\\.info"))))
156     (helm-fast-remove-dups
157      (cl-loop for f in files collect
158               (helm-file-name-sans-extension f))
159      :test 'equal)))
160
161 (defcustom helm-default-info-index-list
162   (helm-get-info-files)
163   "Info files to search in with `helm-info'."
164   :group 'helm-info
165   :type  '(repeat (choice string))
166   :set   'helm-info-index-set)
167
168 (defun helm-info-search-index (candidate)
169   "Search the index of CANDIDATE's Info file using the function
170 helm-info-<CANDIDATE>."
171   (let ((helm-info-function
172          (intern-soft (concat "helm-info-" candidate))))
173     (when (fboundp helm-info-function)
174       (funcall helm-info-function)
175       (ring-insert helm-info-searched candidate))))
176
177 (defun helm-def-source--info-files ()
178   "Return a `helm' source for Info files."
179   (helm-build-sync-source "Helm Info"
180     :candidates
181     (lambda () (copy-sequence helm-default-info-index-list))
182     :candidate-number-limit 999
183     :candidate-transformer
184     (lambda (candidates)
185       (sort candidates #'string-lessp))
186     :nomark t
187     :action '(("Search index" . helm-info-search-index))))
188
189 ;;;###autoload
190 (defun helm-info (&optional refresh)
191   "Preconfigured `helm' for searching Info files' indices.
192
193 With a prefix argument \\[universal-argument], set REFRESH to non-nil.
194
195 Optional parameter REFRESH, when non-nil, reevaluates
196 `helm-default-info-index-list'.  If the variable has been
197 customized, set it to its saved value.  If not, set it to its
198 standard value.  See `custom-reevaluate-setting' for more.
199
200 REFRESH is useful when new Info files are installed.  If
201 `helm-default-info-index-list' has not been customized, the new
202 Info files are made available."
203   (interactive "P")
204   (let ((default (unless (ring-empty-p helm-info-searched)
205                    (ring-ref helm-info-searched 0))))
206     (when refresh
207       (custom-reevaluate-setting 'helm-default-info-index-list))
208     (helm :sources (helm-def-source--info-files)
209           :buffer "*helm Info*"
210           :preselect (and default
211                           (concat "\\_<" (regexp-quote default) "\\_>")))))
212
213 ;;;; Info at point
214
215 ;; `helm-info-at-point' is the main entry point here. It searches for the
216 ;; symbol at point through the Info sources defined in
217 ;; `helm-info-default-sources' and jumps to it.
218
219 (defvar helm-info--pages-cache nil
220   "Cache for all Info pages on the system.")
221
222 (defvar helm-source-info-pages
223   (helm-build-sync-source "Info Pages"
224     :init #'helm-info-pages-init
225     :candidates (lambda () helm-info--pages-cache)
226     :action '(("Show with Info" .
227                (lambda (node-str)
228                  (info (replace-regexp-in-string
229                         "^[^:]+: " "" node-str)))))
230     :requires-pattern 2)
231   "Helm source for Info pages.")
232
233 (defun helm-info-pages-init ()
234   "Collect candidates for initial Info node Top."
235   (or helm-info--pages-cache
236       (let ((info-topic-regexp "\\* +\\([^:]+: ([^)]+)[^.]*\\)\\."))
237         (save-selected-window
238           (info "dir" " *helm info temp buffer*")
239           (Info-find-node "dir" "top")
240           (goto-char (point-min))
241           (while (re-search-forward info-topic-regexp nil t)
242             (push (match-string-no-properties 1)
243                   helm-info--pages-cache))
244           (kill-buffer)))))
245
246 ;;;###autoload
247 (defun helm-info-at-point ()
248   "Preconfigured `helm' for searching info at point."
249   (interactive)
250   (cl-loop for src in helm-info-default-sources
251            for name = (if (symbolp src)
252                           (assoc 'name (symbol-value src))
253                         (assoc 'name src))
254            unless name
255            do (warn "Couldn't build source `%S' without its info file" src))
256   (helm :sources helm-info-default-sources
257         :buffer "*helm info*"))
258
259 (provide 'helm-info)
260
261 ;; Local Variables:
262 ;; byte-compile-warnings: (not obsolete)
263 ;; coding: utf-8
264 ;; indent-tabs-mode: nil
265 ;; End:
266
267 ;;; helm-info.el ends here