button.el 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. ;;; button.el --- clickable buttons
  2. ;;
  3. ;; Copyright (C) 2001-2017 Free Software Foundation, Inc.
  4. ;;
  5. ;; Author: Miles Bader <miles@gnu.org>
  6. ;; Keywords: extensions
  7. ;; Package: emacs
  8. ;;
  9. ;; This file is part of GNU Emacs.
  10. ;;
  11. ;; GNU Emacs is free software: you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  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. ;; You should have received a copy of the GNU General Public License
  20. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;;
  23. ;; This package defines functions for inserting and manipulating
  24. ;; clickable buttons in Emacs buffers, such as might be used for help
  25. ;; hyperlinks, etc.
  26. ;;
  27. ;; In some ways it duplicates functionality also offered by the
  28. ;; `widget' package, but the button package has the advantage that it
  29. ;; is (1) much faster, (2) much smaller, and (3) much, much, simpler
  30. ;; (the code, that is, not the interface).
  31. ;;
  32. ;; Buttons can either use overlays, in which case the button is
  33. ;; represented by the overlay itself, or text-properties, in which case
  34. ;; the button is represented by a marker or buffer-position pointing
  35. ;; somewhere in the button. In the latter case, no markers into the
  36. ;; buffer are retained, which is important for speed if there are are
  37. ;; extremely large numbers of buttons. Note however that if there is
  38. ;; an existing face text-property at the site of the button, the
  39. ;; button face may not be visible. Using overlays avoids this.
  40. ;;
  41. ;; Using `define-button-type' to define default properties for buttons
  42. ;; is not necessary, but it is encouraged, since doing so makes the
  43. ;; resulting code clearer and more efficient.
  44. ;;
  45. ;;; Code:
  46. ;; Globals
  47. ;; Use color for the MS-DOS port because it doesn't support underline.
  48. ;; FIXME if MS-DOS correctly answers the (supports) question, it need
  49. ;; no longer be a special case.
  50. (defface button '((t :inherit link))
  51. "Default face used for buttons."
  52. :group 'basic-faces)
  53. (defvar button-map
  54. (let ((map (make-sparse-keymap)))
  55. ;; The following definition needs to avoid using escape sequences that
  56. ;; might get converted to ^M when building loaddefs.el
  57. (define-key map [(control ?m)] 'push-button)
  58. (define-key map [mouse-2] 'push-button)
  59. ;; FIXME: You'd think that for keymaps coming from text-properties on the
  60. ;; mode-line or header-line, the `mode-line' or `header-line' prefix
  61. ;; shouldn't be necessary!
  62. (define-key map [mode-line mouse-2] 'push-button)
  63. (define-key map [header-line mouse-2] 'push-button)
  64. map)
  65. "Keymap used by buttons.")
  66. (defvar button-buffer-map
  67. (let ((map (make-sparse-keymap)))
  68. (define-key map [?\t] 'forward-button)
  69. (define-key map "\e\t" 'backward-button)
  70. (define-key map [backtab] 'backward-button)
  71. map)
  72. "Keymap useful for buffers containing buttons.
  73. Mode-specific keymaps may want to use this as their parent keymap.")
  74. ;; Default properties for buttons
  75. (put 'default-button 'face 'button)
  76. (put 'default-button 'mouse-face 'highlight)
  77. (put 'default-button 'keymap button-map)
  78. (put 'default-button 'type 'button)
  79. ;; action may be either a function to call, or a marker to go to
  80. (put 'default-button 'action 'ignore)
  81. (put 'default-button 'help-echo (purecopy "mouse-2, RET: Push this button"))
  82. ;; Make overlay buttons go away if their underlying text is deleted.
  83. (put 'default-button 'evaporate t)
  84. ;; Prevent insertions adjacent to the text-property buttons from
  85. ;; inheriting its properties.
  86. (put 'default-button 'rear-nonsticky t)
  87. ;; A `category-symbol' property for the default button type
  88. (put 'button 'button-category-symbol 'default-button)
  89. ;; Button types (which can be used to hold default properties for buttons)
  90. ;; Because button-type properties are inherited by buttons using the
  91. ;; special `category' property (implemented by both overlays and
  92. ;; text-properties), we need to store them on a symbol to which the
  93. ;; `category' properties can point. Instead of using the symbol that's
  94. ;; the name of each button-type, however, we use a separate symbol (with
  95. ;; `-button' appended, and uninterned) to store the properties. This is
  96. ;; to avoid name clashes.
  97. ;; [this is an internal function]
  98. (defsubst button-category-symbol (type)
  99. "Return the symbol used by button-type TYPE to store properties.
  100. Buttons inherit them by setting their `category' property to that symbol."
  101. (or (get type 'button-category-symbol)
  102. (error "Unknown button type `%s'" type)))
  103. (defun define-button-type (name &rest properties)
  104. "Define a `button type' called NAME (a symbol).
  105. The remaining arguments form a sequence of PROPERTY VALUE pairs,
  106. specifying properties to use as defaults for buttons with this type
  107. \(a button's type may be set by giving it a `type' property when
  108. creating the button, using the :type keyword argument).
  109. In addition, the keyword argument :supertype may be used to specify a
  110. button-type from which NAME inherits its default property values
  111. \(however, the inheritance happens only when NAME is defined; subsequent
  112. changes to a supertype are not reflected in its subtypes)."
  113. (let ((catsym (make-symbol (concat (symbol-name name) "-button")))
  114. (super-catsym
  115. (button-category-symbol
  116. (or (plist-get properties 'supertype)
  117. (plist-get properties :supertype)
  118. 'button))))
  119. ;; Provide a link so that it's easy to find the real symbol.
  120. (put name 'button-category-symbol catsym)
  121. ;; Initialize NAME's properties using the global defaults.
  122. (let ((default-props (symbol-plist super-catsym)))
  123. (while default-props
  124. (put catsym (pop default-props) (pop default-props))))
  125. ;; Add NAME as the `type' property, which will then be returned as
  126. ;; the type property of individual buttons.
  127. (put catsym 'type name)
  128. ;; Add the properties in PROPERTIES to the real symbol.
  129. (while properties
  130. (let ((prop (pop properties)))
  131. (when (eq prop :supertype)
  132. (setq prop 'supertype))
  133. (put catsym prop (pop properties))))
  134. ;; Make sure there's a `supertype' property
  135. (unless (get catsym 'supertype)
  136. (put catsym 'supertype 'button))
  137. name))
  138. (defun button-type-put (type prop val)
  139. "Set the button-type TYPE's PROP property to VAL."
  140. (put (button-category-symbol type) prop val))
  141. (defun button-type-get (type prop)
  142. "Get the property of button-type TYPE named PROP."
  143. (get (button-category-symbol type) prop))
  144. (defun button-type-subtype-p (type supertype)
  145. "Return t if button-type TYPE is a subtype of SUPERTYPE."
  146. (or (eq type supertype)
  147. (and type
  148. (button-type-subtype-p (button-type-get type 'supertype)
  149. supertype))))
  150. ;; Button properties and other attributes
  151. (defun button-start (button)
  152. "Return the position at which BUTTON starts."
  153. (if (overlayp button)
  154. (overlay-start button)
  155. ;; Must be a text-property button.
  156. (or (previous-single-property-change (1+ button) 'button)
  157. (point-min))))
  158. (defun button-end (button)
  159. "Return the position at which BUTTON ends."
  160. (if (overlayp button)
  161. (overlay-end button)
  162. ;; Must be a text-property button.
  163. (or (next-single-property-change button 'button)
  164. (point-max))))
  165. (defun button-get (button prop)
  166. "Get the property of button BUTTON named PROP."
  167. (cond ((overlayp button)
  168. (overlay-get button prop))
  169. ((button--area-button-p button)
  170. (get-text-property (cdr button)
  171. prop (button--area-button-string button)))
  172. ((markerp button)
  173. (get-text-property button prop (marker-buffer button)))
  174. (t ; Must be a text-property button.
  175. (get-text-property button prop))))
  176. (defun button-put (button prop val)
  177. "Set BUTTON's PROP property to VAL."
  178. ;; Treat some properties specially.
  179. (cond ((memq prop '(type :type))
  180. ;; We translate a `type' property a `category' property, since
  181. ;; that's what's actually used by overlays/text-properties for
  182. ;; inheriting properties.
  183. (setq prop 'category)
  184. (setq val (button-category-symbol val)))
  185. ((eq prop 'category)
  186. ;; Disallow updating the `category' property directly.
  187. (error "Button `category' property may not be set directly")))
  188. ;; Add the property.
  189. (cond ((overlayp button)
  190. (overlay-put button prop val))
  191. ((button--area-button-p button)
  192. (setq button (button--area-button-string button))
  193. (put-text-property 0 (length button) prop val button))
  194. (t ; Must be a text-property button.
  195. (put-text-property
  196. (or (previous-single-property-change (1+ button) 'button)
  197. (point-min))
  198. (or (next-single-property-change button 'button)
  199. (point-max))
  200. prop val))))
  201. (defun button-activate (button &optional use-mouse-action)
  202. "Call BUTTON's `action' property.
  203. If USE-MOUSE-ACTION is non-nil, invoke the button's `mouse-action'
  204. property instead of `action'; if the button has no `mouse-action',
  205. the value of `action' is used instead.
  206. The action can either be a marker or a function. If it's a
  207. marker then goto it. Otherwise it it is a function then it is
  208. called with BUTTON as only argument. BUTTON is either an
  209. overlay, a buffer position, or (for buttons in the mode-line or
  210. header-line) a string."
  211. (let ((action (or (and use-mouse-action (button-get button 'mouse-action))
  212. (button-get button 'action))))
  213. (if (markerp action)
  214. (save-selected-window
  215. (select-window (display-buffer (marker-buffer action)))
  216. (goto-char action)
  217. (recenter 0))
  218. (funcall action button))))
  219. (defun button-label (button)
  220. "Return BUTTON's text label."
  221. (if (button--area-button-p button)
  222. (substring-no-properties (button--area-button-string button))
  223. (buffer-substring-no-properties (button-start button)
  224. (button-end button))))
  225. (defsubst button-type (button)
  226. "Return BUTTON's button-type."
  227. (button-get button 'type))
  228. (defun button-has-type-p (button type)
  229. "Return t if BUTTON has button-type TYPE, or one of TYPE's subtypes."
  230. (button-type-subtype-p (button-get button 'type) type))
  231. (defun button--area-button-p (b)
  232. "Return non-nil if BUTTON is an area button.
  233. Such area buttons are used for buttons in the mode-line and header-line."
  234. (stringp (car-safe b)))
  235. (defalias 'button--area-button-string #'car
  236. "Return area button BUTTON's button-string.")
  237. ;; Creating overlay buttons
  238. (defun make-button (beg end &rest properties)
  239. "Make a button from BEG to END in the current buffer.
  240. The remaining arguments form a sequence of PROPERTY VALUE pairs,
  241. specifying properties to add to the button.
  242. In addition, the keyword argument :type may be used to specify a
  243. button-type from which to inherit other properties; see
  244. `define-button-type'.
  245. Also see `make-text-button', `insert-button'."
  246. (let ((overlay (make-overlay beg end nil t nil)))
  247. (while properties
  248. (button-put overlay (pop properties) (pop properties)))
  249. ;; Put a pointer to the button in the overlay, so it's easy to get
  250. ;; when we don't actually have a reference to the overlay.
  251. (overlay-put overlay 'button overlay)
  252. ;; If the user didn't specify a type, use the default.
  253. (unless (overlay-get overlay 'category)
  254. (overlay-put overlay 'category 'default-button))
  255. ;; OVERLAY is the button, so return it
  256. overlay))
  257. (defun insert-button (label &rest properties)
  258. "Insert a button with the label LABEL.
  259. The remaining arguments form a sequence of PROPERTY VALUE pairs,
  260. specifying properties to add to the button.
  261. In addition, the keyword argument :type may be used to specify a
  262. button-type from which to inherit other properties; see
  263. `define-button-type'.
  264. Also see `insert-text-button', `make-button'."
  265. (apply #'make-button
  266. (prog1 (point) (insert label))
  267. (point)
  268. properties))
  269. ;; Creating text-property buttons
  270. (defun make-text-button (beg end &rest properties)
  271. "Make a button from BEG to END in the current buffer.
  272. The remaining arguments form a sequence of PROPERTY VALUE pairs,
  273. specifying properties to add to the button.
  274. In addition, the keyword argument :type may be used to specify a
  275. button-type from which to inherit other properties; see
  276. `define-button-type'.
  277. This function is like `make-button', except that the button is actually
  278. part of the text instead of being a property of the buffer. That is,
  279. this function uses text properties, the other uses overlays.
  280. Creating large numbers of buttons can also be somewhat faster
  281. using `make-text-button'. Note, however, that if there is an existing
  282. face property at the site of the button, the button face may not be visible.
  283. You may want to use `make-button' in that case.
  284. BEG can also be a string, in which case it is made into a button.
  285. Also see `insert-text-button'."
  286. (let ((object nil)
  287. (type-entry
  288. (or (plist-member properties 'type)
  289. (plist-member properties :type))))
  290. (when (stringp beg)
  291. (setq object beg beg 0 end (length object)))
  292. ;; Disallow setting the `category' property directly.
  293. (when (plist-get properties 'category)
  294. (error "Button `category' property may not be set directly"))
  295. (if (null type-entry)
  296. ;; The user didn't specify a `type' property, use the default.
  297. (setq properties (cons 'category (cons 'default-button properties)))
  298. ;; The user did specify a `type' property. Translate it into a
  299. ;; `category' property, which is what's actually used by
  300. ;; text-properties for inheritance.
  301. (setcar type-entry 'category)
  302. (setcar (cdr type-entry)
  303. (button-category-symbol (car (cdr type-entry)))))
  304. ;; Now add all the text properties at once
  305. (add-text-properties beg end
  306. ;; Each button should have a non-eq `button'
  307. ;; property so that next-single-property-change can
  308. ;; detect boundaries reliably.
  309. (cons 'button (cons (list t) properties))
  310. object)
  311. ;; Return something that can be used to get at the button.
  312. (or object beg)))
  313. (defun insert-text-button (label &rest properties)
  314. "Insert a button with the label LABEL.
  315. The remaining arguments form a sequence of PROPERTY VALUE pairs,
  316. specifying properties to add to the button.
  317. In addition, the keyword argument :type may be used to specify a
  318. button-type from which to inherit other properties; see
  319. `define-button-type'.
  320. This function is like `insert-button', except that the button is
  321. actually part of the text instead of being a property of the buffer.
  322. Creating large numbers of buttons can also be somewhat faster using
  323. `insert-text-button'.
  324. Also see `make-text-button'."
  325. (apply #'make-text-button
  326. (prog1 (point) (insert label))
  327. (point)
  328. properties))
  329. ;; Finding buttons in a buffer
  330. (defun button-at (pos)
  331. "Return the button at position POS in the current buffer, or nil.
  332. If the button at POS is a text property button, the return value
  333. is a marker pointing to POS."
  334. (let ((button (get-char-property pos 'button)))
  335. (if (or (overlayp button) (null button))
  336. button
  337. ;; Must be a text-property button; return a marker pointing to it.
  338. (copy-marker pos t))))
  339. (defun next-button (pos &optional count-current)
  340. "Return the next button after position POS in the current buffer.
  341. If COUNT-CURRENT is non-nil, count any button at POS in the search,
  342. instead of starting at the next button."
  343. (unless count-current
  344. ;; Search for the next button boundary.
  345. (setq pos (next-single-char-property-change pos 'button)))
  346. (and (< pos (point-max))
  347. (or (button-at pos)
  348. ;; We must have originally been on a button, and are now in
  349. ;; the inter-button space. Recurse to find a button.
  350. (next-button pos))))
  351. (defun previous-button (pos &optional count-current)
  352. "Return the previous button before position POS in the current buffer.
  353. If COUNT-CURRENT is non-nil, count any button at POS in the search,
  354. instead of starting at the next button."
  355. (let ((button (button-at pos)))
  356. (if button
  357. (if count-current
  358. button
  359. ;; We started out on a button, so move to its start and look
  360. ;; for the previous button boundary.
  361. (setq pos (previous-single-char-property-change
  362. (button-start button) 'button))
  363. (let ((new-button (button-at pos)))
  364. (if new-button
  365. ;; We are in a button again; this can happen if there
  366. ;; are adjacent buttons (or at bob).
  367. (unless (= pos (button-start button)) new-button)
  368. ;; We are now in the space between buttons.
  369. (previous-button pos))))
  370. ;; We started out in the space between buttons.
  371. (setq pos (previous-single-char-property-change pos 'button))
  372. (or (button-at pos)
  373. (and (> pos (point-min))
  374. (button-at (1- pos)))))))
  375. ;; User commands
  376. (defun push-button (&optional pos use-mouse-action)
  377. "Perform the action specified by a button at location POS.
  378. POS may be either a buffer position or a mouse-event. If
  379. USE-MOUSE-ACTION is non-nil, invoke the button's `mouse-action'
  380. property instead of its `action' property; if the button has no
  381. `mouse-action', the value of `action' is used instead.
  382. The action in both cases may be either a function to call or a
  383. marker to display and is invoked using `button-activate' (which
  384. see).
  385. POS defaults to point, except when `push-button' is invoked
  386. interactively as the result of a mouse-event, in which case, the
  387. mouse event is used.
  388. If there's no button at POS, do nothing and return nil, otherwise
  389. return t."
  390. (interactive
  391. (list (if (integerp last-command-event) (point) last-command-event)))
  392. (if (and (not (integerp pos)) (eventp pos))
  393. ;; POS is a mouse event; switch to the proper window/buffer
  394. (let ((posn (event-start pos)))
  395. (with-current-buffer (window-buffer (posn-window posn))
  396. (if (posn-string posn)
  397. ;; mode-line, header-line, or display string event.
  398. (button-activate (posn-string posn) t)
  399. (push-button (posn-point posn) t))))
  400. ;; POS is just normal position
  401. (let ((button (button-at (or pos (point)))))
  402. (when button
  403. (button-activate button use-mouse-action)
  404. t))))
  405. (defun forward-button (n &optional wrap display-message)
  406. "Move to the Nth next button, or Nth previous button if N is negative.
  407. If N is 0, move to the start of any button at point.
  408. If WRAP is non-nil, moving past either end of the buffer continues from the
  409. other end.
  410. If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
  411. Any button with a non-nil `skip' property is skipped over.
  412. Returns the button found."
  413. (interactive "p\nd\nd")
  414. (let (button)
  415. (if (zerop n)
  416. ;; Move to start of current button
  417. (if (setq button (button-at (point)))
  418. (goto-char (button-start button)))
  419. ;; Move to Nth next button
  420. (let ((iterator (if (> n 0) #'next-button #'previous-button))
  421. (wrap-start (if (> n 0) (point-min) (point-max)))
  422. opoint fail)
  423. (setq n (abs n))
  424. (setq button t) ; just to start the loop
  425. (while (and (null fail) (> n 0) button)
  426. (setq button (funcall iterator (point)))
  427. (when (and (not button) wrap)
  428. (setq button (funcall iterator wrap-start t)))
  429. (when button
  430. (goto-char (button-start button))
  431. ;; Avoid looping forever (e.g., if all the buttons have
  432. ;; the `skip' property).
  433. (cond ((null opoint)
  434. (setq opoint (point)))
  435. ((= opoint (point))
  436. (setq fail t)))
  437. (unless (button-get button 'skip)
  438. (setq n (1- n)))))))
  439. (if (null button)
  440. (user-error (if wrap "No buttons!" "No more buttons"))
  441. (let ((msg (and display-message (button-get button 'help-echo))))
  442. (when msg
  443. (message "%s" msg)))
  444. button)))
  445. (defun backward-button (n &optional wrap display-message)
  446. "Move to the Nth previous button, or Nth next button if N is negative.
  447. If N is 0, move to the start of any button at point.
  448. If WRAP is non-nil, moving past either end of the buffer continues from the
  449. other end.
  450. If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
  451. Any button with a non-nil `skip' property is skipped over.
  452. Returns the button found."
  453. (interactive "p\nd\nd")
  454. (forward-button (- n) wrap display-message))
  455. (provide 'button)
  456. ;;; button.el ends here