53203
|
1 ;;; help-at-pt.el --- local help through the keyboard
|
|
2
|
|
3 ;; Copyright (C) 2003 Free Software Foundation, Inc.
|
|
4
|
|
5 ;; Author: Luc Teirlinck <teirllm@auburn.edu>
|
|
6 ;; Keywords: help
|
|
7
|
|
8 ;; This file is part of GNU Emacs.
|
|
9
|
|
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
11 ;; it under the terms of the GNU General Public License as published by
|
|
12 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
13 ;; any later version.
|
|
14
|
|
15 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18 ;; GNU General Public License for more details.
|
|
19
|
|
20 ;; You should have received a copy of the GNU General Public License
|
|
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
23 ;; Boston, MA 02111-1307, USA.
|
|
24
|
|
25 ;;; Commentary:
|
|
26
|
|
27 ;; This file contains functionality to make the help provided by the
|
|
28 ;; help-echo text or overlay property available to the keyboard user.
|
|
29 ;; It also supports a more keyboard oriented alternative to
|
|
30 ;; `help-echo', namely a new text or overlay property `kbd-help'.
|
|
31 ;;
|
|
32 ;; It provides facilities to access the local help available at point
|
|
33 ;; either on demand, using the command `display-local-help', or
|
|
34 ;; automatically after a suitable idle time, through the customizable
|
|
35 ;; variable `help-at-pt-display-when-idle'.
|
|
36 ;;
|
|
37 ;; You can get a more global overview of the local help available in
|
|
38 ;; the buffer, using the commands `scan-buf-next-region' and
|
|
39 ;; `scan-buf-previous-region', which move to the start of the next or
|
|
40 ;; previous region with available local help and print the help found
|
|
41 ;; there.
|
|
42 ;;
|
|
43 ;; You do not have to do anything special to use the functionality
|
|
44 ;; provided by this file, because all important functions autoload.
|
|
45
|
|
46 ;;; Code:
|
|
47
|
|
48 (defgroup help-at-pt nil
|
|
49 "Features for displaying local help."
|
|
50 :group 'help
|
|
51 :version "21.4")
|
|
52
|
|
53 ;;;###autoload
|
|
54 (defun help-at-pt-string (&optional kbd)
|
|
55 "Return the help-echo string at point.
|
|
56 Normally, the string produced by the `help-echo' text or overlay
|
|
57 property, or nil, is returned.
|
|
58 If KBD is non-nil, `kbd-help' is used instead, and any
|
|
59 `help-echo' property is ignored. In this case, the return value
|
|
60 can also be t, if that is the value of the `kbd-help' property."
|
|
61 (let* ((prop (if kbd 'kbd-help 'help-echo))
|
|
62 (pair (get-char-property-and-overlay (point) prop))
|
|
63 (val (car pair))
|
|
64 (ov (cdr pair)))
|
|
65 (if (functionp val)
|
|
66 (funcall val (selected-window) (if ov ov (current-buffer)) (point))
|
|
67 (eval val))))
|
|
68
|
|
69 ;;;###autoload
|
|
70 (defun help-at-pt-kbd-string ()
|
|
71 "Return the keyboard help string at point.
|
|
72 If the `kbd-help' text or overlay property at point produces a
|
|
73 string, return it. Otherwise, use the `help-echo' property. If
|
|
74 this produces no string either, return nil."
|
|
75 (let ((kbd (help-at-pt-string t))
|
|
76 (echo (help-at-pt-string)))
|
|
77 (if (and kbd (not (eq kbd t))) kbd echo)))
|
|
78
|
|
79 ;;;###autoload
|
|
80 (defun display-local-help (&optional arg)
|
|
81 "Display local help in the echo area.
|
|
82 This displays a short help message, namely the string produced by
|
|
83 the `kbd-help' property at point. If `kbd-help' does not produce
|
|
84 a string, but the `help-echo' property does, then that string is
|
|
85 printed instead.
|
|
86
|
|
87 A numeric argument ARG prevents display of a message in case
|
|
88 there is no help. While ARG can be used interactively, it is
|
|
89 mainly meant for use from Lisp."
|
|
90 (interactive "P")
|
|
91 (let ((help (help-at-pt-kbd-string)))
|
|
92 (if help
|
|
93 (message "%s" help)
|
|
94 (if (not arg) (message "No local help at point")))))
|
|
95
|
|
96 (defcustom help-at-pt-timer-delay 1
|
|
97 "*Delay before displaying local help.
|
|
98 This is used if `help-at-pt-display-when-idle' is enabled.
|
|
99 The value may be an integer or floating point number.
|
|
100
|
|
101 If a timer is already active, there are two ways to make the new
|
|
102 value take effect immediately. After setting the value, you can
|
|
103 first call `help-at-pt-cancel-timer' and then set a new timer
|
|
104 with `help-at-pt-set-timer' . Alternatively, you can set this
|
|
105 variable through Custom. This will not set a timer if none is
|
|
106 active, but if one is already active, Custom will make it use the
|
|
107 new value."
|
|
108 :group 'help-at-pt
|
|
109 :type 'number
|
|
110 :set (lambda (variable value)
|
|
111 (set-default variable value)
|
|
112 (when (and (boundp 'help-at-pt-timer) help-at-pt-timer)
|
|
113 (timer-set-idle-time help-at-pt-timer value t))))
|
|
114
|
|
115 (defvar help-at-pt-timer nil
|
|
116 "Non-nil means that a timer is set that checks for local help.
|
|
117 If non-nil, this is the value returned by the call of
|
|
118 `run-with-idle-timer' that set that timer. This variable is used
|
|
119 internally to enable `help-at-pt-display-when-idle'. Do not set it
|
|
120 yourself.")
|
|
121
|
|
122 ;;;###autoload
|
|
123 (defun help-at-pt-cancel-timer ()
|
|
124 "Cancel any timer set by `help-at-pt-set-timer'.
|
|
125 This disables `help-at-pt-display-when-idle'."
|
|
126 (interactive)
|
|
127 (let ((inhibit-quit t))
|
|
128 (when help-at-pt-timer
|
|
129 (cancel-timer help-at-pt-timer)
|
|
130 (setq help-at-pt-timer nil))))
|
|
131
|
|
132 ;;;###autoload
|
|
133 (defun help-at-pt-set-timer ()
|
|
134 "Enable `help-at-pt-display-when-idle'.
|
|
135 This is done by setting a timer, if none is currently active."
|
|
136 (interactive)
|
|
137 (unless help-at-pt-timer
|
|
138 (setq help-at-pt-timer
|
|
139 (run-with-idle-timer
|
|
140 help-at-pt-timer-delay t #'help-at-pt-maybe-display))))
|
|
141
|
|
142 ;;;###autoload
|
|
143 (defcustom help-at-pt-display-when-idle 'never
|
|
144 "*Automatically show local help on point-over.
|
|
145 If the value is t, the string obtained from any `kbd-help' or
|
|
146 `help-echo' property at point is automatically printed in the
|
|
147 echo area, if nothing else is already displayed there, or after a
|
|
148 quit. If both `kbd-help' and `help-echo' produce help strings,
|
|
149 `kbd-help' is used. If the value is a list, the help only gets
|
|
150 printed if there is a text or overlay property at point that is
|
|
151 included in this list. Suggested properties are `keymap',
|
|
152 `local-map', `button' and `kbd-help'. Any value other than t or
|
|
153 a non-empty list disables the feature.
|
|
154
|
|
155 This variable only takes effect after a call to
|
|
156 `help-at-pt-set-timer'. The help gets printed after Emacs has
|
|
157 been idle for `help-at-pt-timer-delay' seconds. You can call
|
|
158 `help-at-pt-cancel-timer' to cancel the timer set by, and the
|
|
159 effect of, `help-at-pt-set-timer'.
|
|
160
|
|
161 When this variable is set through Custom, `help-at-pt-set-timer'
|
|
162 is called automatically, unless the value is `never', in which
|
|
163 case `help-at-pt-cancel-timer' is called. Specifying an empty
|
|
164 list of properties through Custom will set the timer, thus
|
|
165 enabling buffer local values. It sets the actual value to nil.
|
|
166 Thus, Custom distinguishes between a nil value and other values
|
|
167 that disable the feature, which Custom identifies with `never'.
|
|
168 The default is `never'."
|
|
169 :group 'help-at-pt
|
|
170 :type '(choice (const :tag "Always"
|
|
171 :format "%t\n%h"
|
|
172 :doc
|
|
173 "This choice can get noisy.
|
|
174 The text printed from the `help-echo' property is often only
|
|
175 relevant when using the mouse. If you mind about too many
|
|
176 messages getting printed in the echo area, use \"In certain
|
|
177 situations\". See the documentation there for more information."
|
|
178 t)
|
|
179 (repeat :tag "In certain situations"
|
|
180 ;; unless we specify 0 offset the doc string
|
|
181 ;; for this choice gets indented very
|
|
182 ;; differently than for the other two
|
|
183 ;; choices, when "More" is selected.
|
|
184 :offset 0
|
|
185 :format "%{%t%}:\n%v%i\n%h"
|
|
186 :doc
|
|
187 "This choice lets you specify a list of \
|
|
188 text properties.
|
|
189 Presence of any of these properties will trigger display of
|
|
190 available local help on point-over.
|
|
191 If you use this alternative through Custom without listing any
|
|
192 properties, a timer will be set anyway. This will enable buffer
|
|
193 local values. Use \"Never\" if you do not want a timer to be set.
|
|
194
|
|
195 Suggested properties:
|
|
196 The `keymap' and `local-map' properties change keybindings in
|
|
197 parts of the buffer. Some of these keymaps are mode independent
|
|
198 and are not mentioned in the mode documentation. Hence, the help
|
|
199 text is likely to be useful.
|
|
200 Specifying `button' is relevant in Custom and similar buffers.
|
|
201 In these buffers, most, but not all, of the text shown this way is
|
|
202 available by default when using tab, but not on regular point-over.
|
|
203 The presence of a `kbd-help' property guarantees that non mouse
|
|
204 specific help is available."
|
|
205 :value (keymap local-map button kbd-help)
|
|
206 symbol)
|
|
207 (other :tag "Never"
|
|
208 :format "%t\n%h"
|
|
209 :doc
|
|
210 "This choice normally disables buffer local values.
|
|
211 If you choose this value through Custom and a timer checking for
|
|
212 local help is currently active, it will be canceled. No new
|
|
213 timer will be set. Call `help-at-pt-set-timer' after choosing
|
|
214 this option, or use \"In certain situations\" and specify no text
|
|
215 properties, to enable buffer local values."
|
|
216 never))
|
|
217 :initialize 'custom-initialize-default
|
|
218 :set #'(lambda (variable value)
|
|
219 (set-default variable value)
|
|
220 (if (eq value 'never)
|
|
221 (help-at-pt-cancel-timer)
|
|
222 (help-at-pt-set-timer)))
|
|
223 :set-after '(help-at-pt-timer-delay)
|
|
224 :require 'help-at-pt)
|
|
225
|
|
226 ;; Function for use in `help-at-pt-set-timer'.
|
|
227 (defun help-at-pt-maybe-display ()
|
|
228 (and (or (eq help-at-pt-display-when-idle t)
|
|
229 (and (consp help-at-pt-display-when-idle)
|
|
230 (catch 'found
|
|
231 (dolist (prop help-at-pt-display-when-idle)
|
|
232 (if (get-char-property (point) prop)
|
|
233 (throw 'found t))))))
|
|
234 (or (not (current-message))
|
|
235 (string= (current-message) "Quit"))
|
|
236 (display-local-help t)))
|
|
237
|
|
238 ;;;###autoload
|
|
239 (defun scan-buf-move-to-region (prop &optional arg hook)
|
|
240 "Go to the start of the next region with non-nil PROP property.
|
|
241 Then run HOOK, which should be a quoted symbol that is a normal
|
|
242 hook.variable, or an expression evaluating to such a symbol.
|
|
243 Adjacent areas with different non-nil PROP properties are
|
|
244 considered different regions.
|
|
245
|
|
246 With numeric argument ARG, move to the start of the ARGth next
|
|
247 such region, then run HOOK. If ARG is negative, move backward.
|
|
248 If point is already in a region, then that region does not count
|
|
249 toward ARG. If ARG is 0 and point is inside a region, move to
|
|
250 the start of that region. If ARG is 0 and point is not in a
|
|
251 region, print a message to that effect, but do not move point and
|
|
252 do not run HOOK. If there are not enough regions to move over,
|
|
253 an error results and the number of available regions is mentioned
|
|
254 in the error message. Point is not moved and HOOK is not run."
|
|
255 (cond ((> arg 0)
|
|
256 (if (= (point) (point-max))
|
|
257 (error "No further `%s' regions" prop))
|
|
258 (let ((pos (point)))
|
|
259 (dotimes (x arg)
|
|
260 (setq pos (next-single-char-property-change pos prop))
|
|
261 (unless (get-char-property pos prop)
|
|
262 (setq pos (next-single-char-property-change pos prop))
|
|
263 (unless (get-char-property pos prop)
|
|
264 (cond ((= x 0)
|
|
265 (error "No further `%s' regions" prop))
|
|
266 ((= x 1)
|
|
267 (error "There is only one further `%s' region" prop))
|
|
268 (t
|
|
269 (error
|
|
270 "There are only %d further `%s' regions"
|
|
271 x prop))))))
|
|
272 (goto-char pos)
|
|
273 (run-hooks hook)))
|
|
274 ((= arg 0)
|
|
275 (let ((val (get-char-property (point) prop)))
|
|
276 (cond ((not val)
|
|
277 (message "Point is not in a `%s' region" prop))
|
|
278 ((eq val (get-char-property (1- (point)) prop))
|
|
279 (goto-char
|
|
280 (previous-single-char-property-change (point) prop))
|
|
281 (run-hooks hook))
|
|
282 (t (run-hooks hook)))))
|
|
283 ((< arg 0)
|
|
284 (let ((pos (point)) (val (get-char-property (point) prop)))
|
|
285 (and val
|
|
286 (eq val (get-char-property (1- pos) prop))
|
|
287 (setq pos
|
|
288 (previous-single-char-property-change pos prop)))
|
|
289 (if (= pos (point-min))
|
|
290 (error "No prior `%s' regions" prop))
|
|
291 (dotimes (x (- arg))
|
|
292 (setq pos (previous-single-char-property-change pos prop))
|
|
293 (unless (get-char-property pos prop)
|
|
294 (setq pos (previous-single-char-property-change pos prop))
|
|
295 (unless (get-char-property pos prop)
|
|
296 (cond ((= x 0)
|
|
297 (error "No prior `%s' regions" prop))
|
|
298 ((= x 1)
|
|
299 (error "There is only one prior `%s' region" prop))
|
|
300 (t
|
|
301 (error "There are only %d prior `%s' regions"
|
|
302 x prop))))))
|
|
303 (goto-char pos)
|
|
304 (run-hooks hook)))))
|
|
305
|
|
306 ;; To be moved to a different file and replaced by a defcustom in a
|
|
307 ;; future version.
|
|
308 (defvar scan-buf-move-hook '(display-local-help)
|
|
309 "Normal hook run by `scan-buf-next-region'.
|
|
310 Also used by `scan-buf-previous-region'. The hook is run after
|
|
311 positioning point.")
|
|
312
|
|
313 ;;;###autoload
|
|
314 (defun scan-buf-next-region (&optional arg)
|
|
315 "Go to the start of the next region with non-nil help-echo.
|
|
316 Print the help found there using `display-local-help'. Adjacent
|
|
317 areas with different non-nil help-echo properties are considered
|
|
318 different regions.
|
|
319
|
|
320 With numeric argument ARG, move to the start of the ARGth next
|
|
321 help-echo region. If ARG is negative, move backward. If point
|
|
322 is already in a help-echo region, then that region does not count
|
|
323 toward ARG. If ARG is 0 and point is inside a help-echo region,
|
|
324 move to the start of that region. If ARG is 0 and point is not
|
|
325 in such a region, just print a message to that effect. If there
|
|
326 are not enough regions to move over, an error results and the
|
|
327 number of available regions is mentioned in the error message.
|
|
328
|
|
329 A potentially confusing subtlety is that point can be in a
|
|
330 help-echo region without any local help being available. This is
|
|
331 because `help-echo' can be a function evaluating to nil. This
|
|
332 rarely happens in practice."
|
|
333 (interactive "p")
|
|
334 (scan-buf-move-to-region 'help-echo arg 'scan-buf-move-hook))
|
|
335
|
|
336 ;;;###autoload
|
|
337 (defun scan-buf-previous-region (&optional arg)
|
|
338 "Go to the start of the previous region with non-nil help-echo.
|
|
339 Print the help found there using `display-local-help'. Adjacent
|
|
340 areas with different non-nil help-echo properties are considered
|
|
341 different regions. With numeric argument ARG, behaves like
|
|
342 `scan-buf-next-region' with argument -ARG.."
|
|
343 (interactive "p")
|
|
344 (scan-buf-move-to-region 'help-echo (- arg) 'scan-buf-move-hook))
|
|
345
|
|
346 (defvar help-at-pt-unload-hook '(help-at-pt-cancel-timer)
|
|
347 "Normal hook run when `help-at-pt' is unloaded.")
|
|
348
|
|
349 ;; Suggested key bindings:
|
|
350 ;;
|
|
351 ;; (global-set-key [C-tab] 'scan-buf-next-region)
|
|
352 ;; (global-set-key [C-M-tab] 'scan-buf-previous-region)
|
|
353
|
|
354 (provide 'help-at-pt)
|
|
355
|
53208
|
356 ;;; arch-tag: d0b8b86d-d23f-45d0-a82d-208d6205a583
|
53203
|
357 ;;; help-at-pt.el ends here
|