tooltip.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. ;;; tooltip.el --- show tooltip windows
  2. ;; Copyright (C) 1997, 1999-2017 Free Software Foundation, Inc.
  3. ;; Author: Gerd Moellmann <gerd@acm.org>
  4. ;; Keywords: help c mouse tools
  5. ;; Package: emacs
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;; Code:
  19. (require 'syntax)
  20. (defvar comint-prompt-regexp)
  21. (defgroup tooltip nil
  22. "Customization group for the `tooltip' package."
  23. :group 'help
  24. :group 'gud
  25. :group 'mouse
  26. :group 'tools
  27. :version "21.1"
  28. :tag "Tool Tips")
  29. ;;; Switching tooltips on/off
  30. (define-minor-mode tooltip-mode
  31. "Toggle Tooltip mode.
  32. With a prefix argument ARG, enable Tooltip mode if ARG is positive,
  33. and disable it otherwise. If called from Lisp, enable the mode
  34. if ARG is omitted or nil.
  35. When this global minor mode is enabled, Emacs displays help
  36. text (e.g. for buttons and menu items that you put the mouse on)
  37. in a pop-up window.
  38. When Tooltip mode is disabled, Emacs displays help text in the
  39. echo area, instead of making a pop-up window."
  40. :global t
  41. ;; Even if we start on a text-only terminal, make this non-nil by
  42. ;; default because we can open a graphical frame later (multi-tty).
  43. :init-value t
  44. :initialize 'custom-initialize-delay
  45. :group 'tooltip
  46. (if (and tooltip-mode (fboundp 'x-show-tip))
  47. (progn
  48. (add-hook 'pre-command-hook 'tooltip-hide)
  49. (add-hook 'tooltip-functions 'tooltip-help-tips))
  50. (unless (and (boundp 'gud-tooltip-mode) gud-tooltip-mode)
  51. (remove-hook 'pre-command-hook 'tooltip-hide))
  52. (remove-hook 'tooltip-functions 'tooltip-help-tips))
  53. (setq show-help-function
  54. (if tooltip-mode 'tooltip-show-help 'tooltip-show-help-non-mode)))
  55. ;;; Customizable settings
  56. (defcustom tooltip-delay 0.7
  57. "Seconds to wait before displaying a tooltip the first time."
  58. :type 'number
  59. :group 'tooltip)
  60. (defcustom tooltip-short-delay 0.1
  61. "Seconds to wait between subsequent tooltips on different items."
  62. :type 'number
  63. :group 'tooltip)
  64. (defcustom tooltip-recent-seconds 1
  65. "Display tooltips if changing tip items within this many seconds.
  66. Do so after `tooltip-short-delay'."
  67. :type 'number
  68. :group 'tooltip)
  69. (defcustom tooltip-hide-delay 10
  70. "Hide tooltips automatically after this many seconds."
  71. :type 'number
  72. :group 'tooltip)
  73. (defcustom tooltip-x-offset 5
  74. "X offset, in pixels, for the display of tooltips.
  75. The offset is the distance between the X position of the mouse and
  76. the left border of the tooltip window. It must be chosen so that the
  77. tooltip window doesn't contain the mouse when it pops up, or it may
  78. interfere with clicking where you wish.
  79. If `tooltip-frame-parameters' includes the `left' parameter,
  80. the value of `tooltip-x-offset' is ignored."
  81. :type 'integer
  82. :group 'tooltip)
  83. (defcustom tooltip-y-offset +20
  84. "Y offset, in pixels, for the display of tooltips.
  85. The offset is the distance between the Y position of the mouse and
  86. the top border of the tooltip window. It must be chosen so that the
  87. tooltip window doesn't contain the mouse when it pops up, or it may
  88. interfere with clicking where you wish.
  89. If `tooltip-frame-parameters' includes the `top' parameter,
  90. the value of `tooltip-y-offset' is ignored."
  91. :type 'integer
  92. :group 'tooltip)
  93. (defcustom tooltip-frame-parameters
  94. '((name . "tooltip")
  95. (internal-border-width . 2)
  96. (border-width . 1))
  97. "Frame parameters used for tooltips.
  98. If `left' or `top' parameters are included, they specify the absolute
  99. position to pop up the tooltip.
  100. Note that font and color parameters are ignored, and the attributes
  101. of the `tooltip' face are used instead."
  102. :type '(repeat (cons :format "%v"
  103. (symbol :tag "Parameter")
  104. (sexp :tag "Value")))
  105. :group 'tooltip)
  106. (defface tooltip
  107. '((((class color))
  108. :background "lightyellow"
  109. :foreground "black"
  110. :inherit variable-pitch)
  111. (t
  112. :inherit variable-pitch))
  113. "Face for tooltips."
  114. :group 'tooltip
  115. :group 'basic-faces)
  116. (defcustom tooltip-use-echo-area nil
  117. "Use the echo area instead of tooltip frames for help and GUD tooltips.
  118. This variable is obsolete; instead of setting it to t, disable
  119. `tooltip-mode' (which has a similar effect)."
  120. :type 'boolean
  121. :group 'tooltip)
  122. (make-obsolete-variable 'tooltip-use-echo-area
  123. "disable Tooltip mode instead" "24.1" 'set)
  124. ;;; Variables that are not customizable.
  125. (define-obsolete-variable-alias 'tooltip-hook 'tooltip-functions "23.1")
  126. (defvar tooltip-functions nil
  127. "Functions to call to display tooltips.
  128. Each function is called with one argument EVENT which is a copy
  129. of the last mouse movement event that occurred. If one of these
  130. functions displays the tooltip, it should return non-nil and the
  131. rest are not called.")
  132. (defvar tooltip-timeout-id nil
  133. "The id of the timeout started when Emacs becomes idle.")
  134. (defvar tooltip-last-mouse-motion-event nil
  135. "A copy of the last mouse motion event seen.")
  136. (defvar tooltip-hide-time nil
  137. "Time when the last tooltip was hidden.")
  138. (defvar gud-tooltip-mode) ;; Prevent warning.
  139. ;;; Event accessors
  140. (defun tooltip-event-buffer (event)
  141. "Return the buffer over which event EVENT occurred.
  142. This might return nil if the event did not occur over a buffer."
  143. (let ((window (posn-window (event-end event))))
  144. (and window (window-buffer window))))
  145. ;;; Timeout for tooltip display
  146. (defun tooltip-delay ()
  147. "Return the delay in seconds for the next tooltip."
  148. (if (and tooltip-hide-time
  149. (< (- (float-time) tooltip-hide-time) tooltip-recent-seconds))
  150. tooltip-short-delay
  151. tooltip-delay))
  152. (defun tooltip-cancel-delayed-tip ()
  153. "Disable the tooltip timeout."
  154. (when tooltip-timeout-id
  155. (disable-timeout tooltip-timeout-id)
  156. (setq tooltip-timeout-id nil)))
  157. (defun tooltip-start-delayed-tip ()
  158. "Add a one-shot timeout to call function `tooltip-timeout'."
  159. (setq tooltip-timeout-id
  160. (add-timeout (tooltip-delay) 'tooltip-timeout nil)))
  161. (defun tooltip-timeout (_object)
  162. "Function called when timer with id `tooltip-timeout-id' fires."
  163. (run-hook-with-args-until-success 'tooltip-functions
  164. tooltip-last-mouse-motion-event))
  165. ;;; Displaying tips
  166. (defun tooltip-set-param (alist key value)
  167. "Change the value of KEY in alist ALIST to VALUE.
  168. If there's no association for KEY in ALIST, add one, otherwise
  169. change the existing association. Value is the resulting alist."
  170. (declare (obsolete "use (setf (alist-get ..) ..) instead" "25.1"))
  171. (setf (alist-get key alist) value)
  172. alist)
  173. (declare-function x-show-tip "xfns.c"
  174. (string &optional frame parms timeout dx dy))
  175. (defun tooltip-show (text &optional use-echo-area)
  176. "Show a tooltip window displaying TEXT.
  177. Text larger than `x-max-tooltip-size' is clipped.
  178. If the alist in `tooltip-frame-parameters' includes `left' and `top'
  179. parameters, they determine the x and y position where the tooltip
  180. is displayed. Otherwise, the tooltip pops at offsets specified by
  181. `tooltip-x-offset' and `tooltip-y-offset' from the current mouse
  182. position.
  183. Optional second arg USE-ECHO-AREA non-nil means to show tooltip
  184. in echo area."
  185. (if use-echo-area
  186. (tooltip-show-help-non-mode text)
  187. (condition-case error
  188. (let ((params (copy-sequence tooltip-frame-parameters))
  189. (fg (face-attribute 'tooltip :foreground))
  190. (bg (face-attribute 'tooltip :background)))
  191. (when (stringp fg)
  192. (setf (alist-get 'foreground-color params) fg)
  193. (setf (alist-get 'border-color params) fg))
  194. (when (stringp bg)
  195. (setf (alist-get 'background-color params) bg))
  196. (x-show-tip (propertize text 'face 'tooltip)
  197. (selected-frame)
  198. params
  199. tooltip-hide-delay
  200. tooltip-x-offset
  201. tooltip-y-offset))
  202. (error
  203. (message "Error while displaying tooltip: %s" error)
  204. (sit-for 1)
  205. (message "%s" text)))))
  206. (declare-function x-hide-tip "xfns.c" ())
  207. (defun tooltip-hide (&optional _ignored-arg)
  208. "Hide a tooltip, if one is displayed.
  209. Value is non-nil if tooltip was open."
  210. (tooltip-cancel-delayed-tip)
  211. (when (x-hide-tip)
  212. (setq tooltip-hide-time (float-time))))
  213. ;;; Debugger-related functions
  214. (defun tooltip-identifier-from-point (point)
  215. "Extract the identifier at POINT, if any.
  216. Value is nil if no identifier exists at point. Identifier extraction
  217. is based on the current syntax table."
  218. (save-excursion
  219. (goto-char point)
  220. (let* ((start (progn (skip-syntax-backward "w_") (point)))
  221. (pstate (syntax-ppss)))
  222. (unless (or (looking-at "[0-9]")
  223. (nth 3 pstate)
  224. (nth 4 pstate))
  225. (skip-syntax-forward "w_")
  226. (when (> (point) start)
  227. (buffer-substring start (point)))))))
  228. (defun tooltip-expr-to-print (event)
  229. "Return an expression that should be printed for EVENT.
  230. If a region is active and the mouse is inside the region, print
  231. the region. Otherwise, figure out the identifier around the point
  232. where the mouse is."
  233. (with-current-buffer (tooltip-event-buffer event)
  234. (let ((point (posn-point (event-end event))))
  235. (if (use-region-p)
  236. (when (and (<= (region-beginning) point) (<= point (region-end)))
  237. (buffer-substring (region-beginning) (region-end)))
  238. (tooltip-identifier-from-point point)))))
  239. (defun tooltip-process-prompt-regexp (process)
  240. "Return regexp matching the prompt of PROCESS at the end of a string.
  241. The prompt is taken from the value of `comint-prompt-regexp' in
  242. the buffer of PROCESS."
  243. (let ((prompt-regexp (with-current-buffer (process-buffer process)
  244. comint-prompt-regexp)))
  245. (concat "\n*"
  246. ;; Most start with `^' but the one for `sdb' cannot be easily
  247. ;; stripped. Code the prompt for `sdb' fixed here.
  248. (if (= (aref prompt-regexp 0) ?^)
  249. (substring prompt-regexp 1)
  250. "\\*")
  251. "$")))
  252. (defun tooltip-strip-prompt (process output)
  253. "Return OUTPUT with any prompt of PROCESS stripped from its end."
  254. (save-match-data
  255. (if (string-match (tooltip-process-prompt-regexp process) output)
  256. (substring output 0 (match-beginning 0))
  257. output)))
  258. ;;; Tooltip help.
  259. (defvar tooltip-help-message nil
  260. "The last help message received via `show-help-function'.
  261. This is used by `tooltip-show-help' and
  262. `tooltip-show-help-non-mode'.")
  263. (defvar tooltip-previous-message nil
  264. "The previous content of the echo area.")
  265. (defun tooltip-show-help-non-mode (help)
  266. "Function installed as `show-help-function' when Tooltip mode is off.
  267. It is also called if Tooltip mode is on, for text-only displays."
  268. (when (and (not (window-minibuffer-p)) ;Don't overwrite minibuffer contents.
  269. (not cursor-in-echo-area)) ;Don't overwrite a prompt.
  270. (cond
  271. ((stringp help)
  272. (setq help (replace-regexp-in-string "\n" ", " help))
  273. (unless (or tooltip-previous-message
  274. (equal-including-properties help (current-message))
  275. (and (stringp tooltip-help-message)
  276. (equal-including-properties tooltip-help-message
  277. (current-message))))
  278. (setq tooltip-previous-message (current-message)))
  279. (setq tooltip-help-message help)
  280. (let ((message-truncate-lines t)
  281. (message-log-max nil))
  282. (message "%s" help)))
  283. ((stringp tooltip-previous-message)
  284. (let ((message-log-max nil))
  285. (message "%s" tooltip-previous-message)
  286. (setq tooltip-previous-message nil)))
  287. (t
  288. (message nil)))))
  289. (defun tooltip-show-help (msg)
  290. "Function installed as `show-help-function'.
  291. MSG is either a help string to display, or nil to cancel the display."
  292. (if (display-graphic-p)
  293. (let ((previous-help tooltip-help-message))
  294. (setq tooltip-help-message msg)
  295. (cond ((null msg)
  296. ;; Cancel display. This also cancels a delayed tip, if
  297. ;; there is one.
  298. (tooltip-hide))
  299. ((equal-including-properties previous-help msg)
  300. ;; Same help as before (but possibly the mouse has moved).
  301. ;; Keep what we have.
  302. )
  303. (t
  304. ;; A different help. Remove a previous tooltip, and
  305. ;; display a new one, with some delay.
  306. (tooltip-hide)
  307. (tooltip-start-delayed-tip))))
  308. ;; On text-only displays, try `tooltip-show-help-non-mode'.
  309. (tooltip-show-help-non-mode msg)))
  310. (defun tooltip-help-tips (_event)
  311. "Hook function to display a help tooltip.
  312. This is installed on the hook `tooltip-functions', which
  313. is run when the timer with id `tooltip-timeout-id' fires.
  314. Value is non-nil if this function handled the tip."
  315. (when (stringp tooltip-help-message)
  316. (tooltip-show tooltip-help-message tooltip-use-echo-area)
  317. t))
  318. (provide 'tooltip)
  319. ;;; tooltip.el ends here