(sgml-mode-map): Bind 8-bit codes above 127 to sgml-maybe-name-self.
[bpt/emacs.git] / lisp / tooltip.el
CommitLineData
7840ced1
GM
1;;; tooltip.el --- Show tooltip windows
2
06f76f9f 3;; Copyright (C) 1997, 1999, 2000, 2001 Free Software Foundation, Inc.
7840ced1
GM
4
5;; Author: Gerd Moellmann <gerd@acm.org>
6;; Keywords: help c mouse tools
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;; Put into your `.emacs'
28
29;; (require 'tooltip)
30;; (tooltip-mode 1)
31
32
33\f
34;;; Code:
35
36(eval-when-compile
37 (require 'cl)
38 (require 'comint)
39 (require 'gud))
40
41(provide 'tooltip)
42
43\f
44;;; Customizable settings
45
46(defgroup tooltip nil
47 "Customization group for the `tooltip' package."
48 :group 'help
3e6ed970 49 :group 'gud
7840ced1
GM
50 :group 'mouse
51 :group 'tools
01b23b99 52 :version "21.1"
7840ced1
GM
53 :tag "Tool Tips")
54
01b23b99 55(defvar tooltip-mode)
7840ced1
GM
56
57(defcustom tooltip-delay 1.0
58 "Seconds to wait before displaying a tooltip the first time."
59 :tag "Delay"
60 :type 'number
61 :group 'tooltip)
62
63
64(defcustom tooltip-short-delay 0.1
65 "Seconds to wait between subsequent tooltips on different items."
66 :tag "Short delay"
67 :type 'number
68 :group 'tooltip)
69
70
71(defcustom tooltip-recent-seconds 1
01b23b99
DL
72 "Display tooltips if changing tip items within this many seconds.
73Do so after `tooltip-short-delay'."
7840ced1
GM
74 :tag "Recent seconds"
75 :type 'number
76 :group 'tooltip)
77
78
a93c8ba8
GM
79(defcustom tooltip-hide-delay 5
80 "Hide tooltips automatically after this many seconds."
81 :tag "Hide delay"
82 :type 'number
83 :group 'tooltip)
84
85
e4df9b40 86(defcustom tooltip-x-offset nil
7142670a 87 "Specify an X offset, in pixels, for the display of tooltips.
e4df9b40
GM
88The offset is relative to the position of the mouse. It must
89be chosen so that the tooltip window doesn't contain the mouse
7142670a
EZ
90when it pops up. If the value is nil, the default offset is 5
91pixels.
92
93If `tooltip-frame-parameters' includes the `left' parameter,
94the value of `tooltip-x-offset' is ignored."
e4df9b40
GM
95 :tag "X offset"
96 :type '(choice (const :tag "Default" nil)
97 (integer :tag "Offset" :value 1))
98 :group 'tooltip)
99
100
101(defcustom tooltip-y-offset nil
7142670a 102 "Specify a Y offset, in pixels, for the display of tooltips.
e4df9b40
GM
103The offset is relative to the position of the mouse. It must
104be chosen so that the tooltip window doesn't contain the mouse
7142670a
EZ
105when it pops up. If the value is nil, the default offset is -10
106pixels.
107
108If `tooltip-frame-parameters' includes the `top' parameter,
109the value of `tooltip-y-offset' is ignored."
e4df9b40
GM
110 :tag "Y offset"
111 :type '(choice (const :tag "Default" nil)
112 (integer :tag "Offset" :value 1))
113 :group 'tooltip)
114
115
7840ced1
GM
116(defcustom tooltip-frame-parameters
117 '((name . "tooltip")
7840ced1 118 (internal-border-width . 5)
7840ced1 119 (border-width . 1))
7142670a
EZ
120 "Frame parameters used for tooltips.
121
122If `left' or `top' parameters are included, they specify the absolute
123position to pop up the tooltip."
7840ced1
GM
124 :type 'sexp
125 :tag "Frame Parameters"
126 :group 'tooltip)
127
128
2621f5a9
GM
129(defface tooltip
130 '((((class color))
131 (:background "lightyellow" :foreground "black"))
132 (t ()))
133 "Face for tooltips."
134 :group 'tooltip)
135
136
7840ced1 137(defcustom tooltip-gud-tips-p nil
024e5da6 138 "*Non-nil means show tooltips in GUD sessions."
7840ced1
GM
139 :type 'boolean
140 :tag "GUD"
c39e8983
MB
141 :set #'(lambda (symbol on)
142 (setq tooltip-gud-tips-p on)
143 (if on (tooltip-gud-tips-setup)))
7840ced1
GM
144 :group 'tooltip)
145
146
147(defcustom tooltip-gud-modes '(gud-mode c-mode c++-mode)
148 "List of modes for which to enable GUD tips."
149 :type 'sexp
150 :tag "GUD modes"
151 :group 'tooltip)
152
a1f84f6d 153
7840ced1
GM
154(defcustom tooltip-gud-display
155 '((eq (tooltip-event-buffer tooltip-gud-event)
156 (marker-buffer overlay-arrow-position)))
157 "List of forms determining where GUD tooltips are displayed.
158
159Forms in the list are combined with AND. The default is to display
160only tooltips in the buffer containing the overlay arrow."
161 :type 'sexp
162 :tag "GUD buffers predicate"
163 :group 'tooltip)
164
165
72200f89 166(defcustom tooltip-use-echo-area nil
8be44f9d
DL
167 "Use the echo area instead of tooltip frames.
168This is only relevant GUD display, since otherwise it is equivalent to
169turning off Tooltip mode."
72200f89 170 :type 'boolean
e775fcc4 171 :tag "Use echo area"
72200f89
SS
172 :group 'tooltip)
173
7840ced1
GM
174\f
175;;; Variables that are not customizable.
176
177(defvar tooltip-hook nil
178 "Functions to call to display tooltips.
179Each function is called with one argument EVENT which is a copy of
180the last mouse movement event that occurred.")
181
182
183(defvar tooltip-timeout-id nil
184 "The id of the timeout started when Emacs becomes idle.")
185
186
187(defvar tooltip-last-mouse-motion-event nil
188 "A copy of the last mouse motion event seen.")
189
190
191(defvar tooltip-hide-time nil
192 "Time when the last tooltip was hidden.")
193
194
7840ced1
GM
195(defvar tooltip-gud-debugger nil
196 "The debugger for which we show tooltips.")
197
198
199\f
200;;; Event accessors
201
202(defun tooltip-event-buffer (event)
203 "Return the buffer over which event EVENT occurred.
204This might return nil if the event did not occur over a buffer."
205 (let ((window (posn-window (event-end event))))
206 (and window (window-buffer window))))
207
208
209\f
210;;; Switching tooltips on/off
211
212;; We don't set track-mouse globally because this is a big redisplay
213;; problem in buffers having a pre-command-hook or such installed,
214;; which does a set-buffer, like the summary buffer of Gnus. Calling
215;; set-buffer prevents redisplay optimizations, so every mouse motion
216;; would be accompanied by a full redisplay.
217
218;;;###autoload
219(defun tooltip-mode (&optional arg)
220 "Mode for tooltip display.
221With ARG, turn tooltip mode on if and only if ARG is positive."
222 (interactive "P")
c6da4eb4
GM
223 (unless (fboundp 'x-show-tip)
224 (error "Sorry, tooltips are not yet available on this system"))
7840ced1
GM
225 (let* ((on (if arg
226 (> (prefix-numeric-value arg) 0)
227 (not tooltip-mode)))
228 (hook-fn (if on 'add-hook 'remove-hook)))
229 (setq tooltip-mode on)
230 (funcall hook-fn 'change-major-mode-hook 'tooltip-change-major-mode)
231 (tooltip-activate-mouse-motions-if-enabled)
232 (funcall hook-fn 'pre-command-hook 'tooltip-hide)
233 (funcall hook-fn 'tooltip-hook 'tooltip-gud-tips)
234 (funcall hook-fn 'tooltip-hook 'tooltip-help-tips)
235 (setq show-help-function (if on 'tooltip-show-help-function nil))
236 ;; `ignore' is the default binding for mouse movements.
237 (define-key global-map [mouse-movement]
238 (if on 'tooltip-mouse-motion 'ignore))
c39e8983
MB
239 (tooltip-gud-tips-setup)))
240
241(defun tooltip-gud-tips-setup ()
242 "Setup debugger mode-hooks for tooltips."
243 (when (and tooltip-mode tooltip-gud-tips-p)
244 (global-set-key [S-mouse-3] 'tooltip-gud-toggle-dereference)
245 (add-hook 'gdb-mode-hook
246 #'(lambda () (setq tooltip-gud-debugger 'gdb)))
247 (add-hook 'sdb-mode-hook
248 #'(lambda () (setq tooltip-gud-debugger 'sdb)))
249 (add-hook 'dbx-mode-hook
250 #'(lambda () (setq tooltip-gud-debugger 'dbx)))
251 (add-hook 'xdb-mode-hook
252 #'(lambda () (setq tooltip-gud-debugger 'xdb)))
253 (add-hook 'perldb-mode-hook
254 #'(lambda () (setq tooltip-gud-debugger 'perldb)))))
7840ced1
GM
255\f
256;;; Timeout for tooltip display
257
7840ced1
GM
258(defun tooltip-delay ()
259 "Return the delay in seconds for the next tooltip."
260 (let ((delay tooltip-delay)
a1f84f6d 261 (now (float-time)))
7840ced1
GM
262 (when (and tooltip-hide-time
263 (< (- now tooltip-hide-time) tooltip-recent-seconds))
264 (setq delay tooltip-short-delay))
265 delay))
266
267
06f76f9f 268(defun tooltip-cancel-delayed-tip ()
7840ced1
GM
269 "Disable the tooltip timeout."
270 (when tooltip-timeout-id
271 (disable-timeout tooltip-timeout-id)
272 (setq tooltip-timeout-id nil)))
273
274
06f76f9f 275(defun tooltip-start-delayed-tip ()
7840ced1
GM
276 "Add a one-shot timeout to call function tooltip-timeout."
277 (setq tooltip-timeout-id
278 (add-timeout (tooltip-delay) 'tooltip-timeout nil)))
279
280
281(defun tooltip-timeout (object)
282 "Function called when timer with id tooltip-timeout-id fires."
283 (run-hook-with-args-until-success 'tooltip-hook
284 tooltip-last-mouse-motion-event))
285
286
287\f
288;;; Reacting on mouse movements
289
290(defun tooltip-change-major-mode ()
291 "Function added to `change-major-mode-hook' when tooltip mode is on."
292 (add-hook 'post-command-hook 'tooltip-activate-mouse-motions-if-enabled))
293
294
295(defun tooltip-activate-mouse-motions-if-enabled ()
296 "Reconsider for all buffers whether mouse motion events are desired."
297 (remove-hook 'post-command-hook 'tooltip-activate-mouse-motions-if-enabled)
298 (let ((buffers (buffer-list)))
299 (save-excursion
300 (while buffers
301 (set-buffer (car buffers))
302 (if (and tooltip-mode
303 tooltip-gud-tips-p
304 (memq major-mode tooltip-gud-modes))
305 (tooltip-activate-mouse-motions t)
306 (tooltip-activate-mouse-motions nil))
307 (setq buffers (cdr buffers))))))
308
309
310(defun tooltip-activate-mouse-motions (activatep)
311 "Activate/deactivate mouse motion events for the current buffer.
312ACTIVATEP non-nil means activate mouse motion events."
313 (if activatep
314 (progn
315 (make-local-variable 'track-mouse)
316 (setq track-mouse t))
317 (kill-local-variable 'track-mouse)))
318
319
320(defun tooltip-mouse-motion (event)
321 "Command handler for mouse movement events in `global-map'."
322 (interactive "e")
323 (tooltip-hide)
324 (when (car (mouse-pixel-position))
325 (setq tooltip-last-mouse-motion-event (copy-sequence event))
06f76f9f 326 (tooltip-start-delayed-tip)))
7840ced1
GM
327
328
329\f
330;;; Displaying tips
331
2621f5a9
GM
332(defun tooltip-set-param (alist key value)
333 "Change the value of KEY in alist ALIAS to VALUE.
334If there's no association for KEY in ALIST, add one, otherwise
335change the existing association. Value is the resulting alist."
336 (let ((param (assq key alist)))
337 (if (consp param)
338 (setcdr param value)
339 (push (cons key value) alist))
340 alist))
341
342
7840ced1 343(defun tooltip-show (text)
7142670a
EZ
344 "Show a tooltip window displaying TEXT.
345
346Text larger than `x-max-tooltip-size' (which see) is clipped.
347
348If the alist in `tooltip-frame-parameters' includes `left' and `top'
349parameters, they determine the x and y position where the tooltip
350is displayed. Otherwise, the tooltip pops at offsets specified by
351`tooltip-x-offset' and `tooltip-y-offset' from the current mouse
352position."
72200f89
SS
353 (if tooltip-use-echo-area
354 (message "%s" text)
e5603149 355 (condition-case error
2621f5a9
GM
356 (let ((params (copy-sequence tooltip-frame-parameters))
357 (fg (face-attribute 'tooltip :foreground))
358 (bg (face-attribute 'tooltip :background)))
06f76f9f
GM
359 (when (stringp fg)
360 (setq params (tooltip-set-param params 'foreground-color fg))
361 (setq params (tooltip-set-param params 'border-color fg)))
362 (when (stringp bg)
363 (setq params (tooltip-set-param params 'background-color bg)))
2621f5a9
GM
364 (x-show-tip (propertize text 'face 'tooltip)
365 (selected-frame)
f3b05e99 366 params
a93c8ba8 367 tooltip-hide-delay
2621f5a9
GM
368 tooltip-x-offset
369 tooltip-y-offset))
e5603149
GM
370 (error
371 (message "Error while displaying tooltip: %s" error)
372 (sit-for 1)
373 (message "%s" text)))))
374
7840ced1
GM
375
376(defun tooltip-hide (&optional ignored-arg)
377 "Hide a tooltip, if one is displayed.
378Value is non-nil if tooltip was open."
06f76f9f 379 (tooltip-cancel-delayed-tip)
7840ced1 380 (when (x-hide-tip)
a1f84f6d 381 (setq tooltip-hide-time (float-time))))
7840ced1
GM
382
383
384\f
385;;; Debugger-related functions
386
387(defun tooltip-identifier-from-point (point)
388 "Extract the identifier at POINT, if any.
389Value is nil if no identifier exists at point. Identifier extraction
390is based on the current syntax table."
391 (save-excursion
392 (goto-char point)
393 (let ((start (progn (skip-syntax-backward "w_") (point))))
394 (unless (looking-at "[0-9]")
395 (skip-syntax-forward "w_")
396 (when (> (point) start)
397 (buffer-substring start (point)))))))
398
399
400(defmacro tooltip-region-active-p ()
401 "Value is non-nil if the region is currently active."
402 (if (string-match "^GNU" (emacs-version))
403 `(and transient-mark-mode mark-active)
404 `(region-active-p)))
405
406
407(defun tooltip-expr-to-print (event)
408 "Return an expression that should be printed for EVENT.
409If a region is active and the mouse is inside the region, print
410the region. Otherwise, figure out the identifier around the point
411where the mouse is."
412 (save-excursion
413 (set-buffer (tooltip-event-buffer event))
414 (let ((point (posn-point (event-end event))))
415 (if (tooltip-region-active-p)
416 (when (and (<= (region-beginning) point) (<= point (region-end)))
417 (buffer-substring (region-beginning) (region-end)))
418 (tooltip-identifier-from-point point)))))
419
420
421(defun tooltip-process-prompt-regexp (process)
422 "Return regexp matching the prompt of PROCESS at the end of a string.
423The prompt is taken from the value of COMINT-PROMPT-REGEXP in the buffer
424of PROCESS."
425 (let ((prompt-regexp (save-excursion
426 (set-buffer (process-buffer process))
427 comint-prompt-regexp)))
428 ;; Most start with `^' but the one for `sdb' cannot be easily
429 ;; stripped. Code the prompt for `sdb' fixed here.
430 (if (= (aref prompt-regexp 0) ?^)
431 (setq prompt-regexp (substring prompt-regexp 1))
432 (setq prompt-regexp "\\*"))
433 (concat "\n*" prompt-regexp "$")))
434
435
436(defun tooltip-strip-prompt (process output)
437 "Return OUTPUT with any prompt of PROCESS stripped from its end."
438 (let ((prompt-regexp (tooltip-process-prompt-regexp process)))
439 (save-match-data
440 (when (string-match prompt-regexp output)
441 (setq output (substring output 0 (match-beginning 0)))))
442 output))
443
444
445\f
446;;; Tips for `gud'
447
448(defvar tooltip-gud-original-filter nil
449 "Process filter to restore after GUD output has been received.")
450
451
452(defvar tooltip-gud-dereference nil
453 "Non-nil means print expressions with a `*' in front of them.
454For C this would dereference a pointer expression.")
455
456
457(defvar tooltip-gud-event nil
458 "The mouse movement event that led to a tooltip display.
459This event can be examined by forms in TOOLTIP-GUD-DISPLAY.")
460
461
462(defvar tooltip-gud-debugger nil
463 "A symbol describing the debugger running under GUD.")
464
465
466(defun tooltip-gud-toggle-dereference ()
024e5da6 467 "Toggle whether tooltips should show `* expr' or `expr'."
7840ced1
GM
468 (interactive)
469 (setq tooltip-gud-dereference (not tooltip-gud-dereference))
470 (when (interactive-p)
471 (message "Dereferencing is now %s."
472 (if tooltip-gud-dereference "on" "off"))))
473
474
475(defun tooltip-gud-process-output (process output)
476 "Process debugger output and show it in a tooltip window."
477 (set-process-filter process tooltip-gud-original-filter)
478 (tooltip-show (tooltip-strip-prompt process output)))
479
480
481(defun tooltip-gud-print-command (expr)
482 "Return a suitable command to print the expression EXPR.
483If TOOLTIP-GUD-DEREFERENCE is t, also prepend a `*' to EXPR."
484 (when tooltip-gud-dereference
485 (setq expr (concat "*" expr)))
486 (case tooltip-gud-debugger
487 ((gdb dbx) (concat "print " expr))
488 (xdb (concat "p " expr))
489 (sdb (concat expr "/"))
490 (perldb expr)))
a1f84f6d 491
7840ced1
GM
492
493(defun tooltip-gud-tips (event)
01b23b99
DL
494 "Show tip for identifier or selection under the mouse.
495The mouse must either point at an identifier or inside a selected
496region for the tip window to be shown. If tooltip-gud-dereference is t,
497add a `*' in front of the printed expression.
7840ced1
GM
498
499This function must return nil if it doesn't handle EVENT."
500 (let (gud-buffer process)
501 (when (and (eventp event)
502 tooltip-gud-tips-p
503 (boundp 'gud-comint-buffer)
504 (setq gud-buffer gud-comint-buffer)
505 (setq process (get-buffer-process gud-buffer))
506 (posn-point (event-end event))
507 (progn (setq tooltip-gud-event event)
508 (eval (cons 'and tooltip-gud-display))))
509 (let ((expr (tooltip-expr-to-print event)))
510 (when expr
c39e8983
MB
511 (let ((cmd (tooltip-gud-print-command expr)))
512 (unless (null cmd) ; CMD can be nil if unknown debugger
513 (setq tooltip-gud-original-filter (process-filter process))
514 (set-process-filter process 'tooltip-gud-process-output)
515 (gud-basic-call cmd)
516 expr)))))))
7840ced1
GM
517
518\f
519;;; Tooltip help.
520
521(defvar tooltip-help-message nil
522 "The last help message received via `tooltip-show-help-function'.")
523
524
525(defun tooltip-show-help-function (msg)
526 "Function installed as `show-help-function'.
527MSG is either a help string to display, or nil to cancel the display."
528 (let ((previous-help tooltip-help-message))
529 (setq tooltip-help-message msg)
530 (cond ((null msg)
06f76f9f
GM
531 ;; Cancel display. This also cancels a delayed tip, if
532 ;; there is one.
7840ced1 533 (tooltip-hide))
06f76f9f
GM
534 ((equal previous-help msg)
535 ;; Same help as before (but possibly the mouse has moved).
536 ;; Keep what we have.
537 )
7840ced1 538 (t
06f76f9f
GM
539 ;; A different help. Remove a previous tooltip, and
540 ;; display a new one, with some delay.
541 (tooltip-hide)
542 (tooltip-start-delayed-tip)))))
7840ced1
GM
543
544
545(defun tooltip-help-tips (event)
546 "Hook function to display a help tooltip.
06f76f9f
GM
547This is installed on the hook `tooltip-hook', which is run when
548the the timer with ID `tooltip-timeout-id' fires.
7840ced1
GM
549Value is non-nil if this function handled the tip."
550 (when (stringp tooltip-help-message)
551 (tooltip-show tooltip-help-message)
7840ced1
GM
552 t))
553
554
555\f
90e69ae5
GM
556;;; Do this after all functions have been defined that are called from
557;;; `tooltip-mode'. The actual default value of `tooltip-mode' is set
558;;; in startup.el.
01b23b99
DL
559
560;;;###autoload
561(defcustom tooltip-mode nil
562 "Toggle tooltip-mode.
563Setting this variable directly does not take effect;
564use either \\[customize] or the function `tooltip-mode'."
565 :set (lambda (symbol value)
566 (tooltip-mode (or value 0)))
567 :initialize 'custom-initialize-default
7840ced1 568 :type 'boolean
7840ced1
GM
569 :require 'tooltip
570 :group 'tooltip)
571
572
573;;; tooltip.el ends here