elisp: Clickable Text
31.19.8 Defining Clickable Text
-------------------------------
“Clickable text” is text that can be clicked, with either the mouse or
via a keyboard command, to produce some result. Many major modes use
clickable text to implement textual hyper-links, or “links” for short.
The easiest way to insert and manipulate links is to use the ‘button’
package. Buttons. In this section, we will explain how to
manually set up clickable text in a buffer, using text properties. For
simplicity, we will refer to the clickable text as a “link”.
Implementing a link involves three separate steps: (1) indicating
clickability when the mouse moves over the link; (2) making <RET> or
‘mouse-2’ on that link do something; and (3) setting up a ‘follow-link’
condition so that the link obeys ‘mouse-1-click-follows-link’.
To indicate clickability, add the ‘mouse-face’ text property to the
text of the link; then Emacs will highlight the link when the mouse
moves over it. In addition, you should define a tooltip or echo area
message, using the ‘help-echo’ text property. Special
Properties. For instance, here is how Dired indicates that file names
are clickable:
(if (dired-move-to-filename)
(add-text-properties
(point)
(save-excursion
(dired-move-to-end-of-filename)
(point))
'(mouse-face highlight
help-echo "mouse-2: visit this file in other window")))
To make the link clickable, bind <RET> and ‘mouse-2’ to commands that
perform the desired action. Each command should check to see whether it
was called on a link, and act accordingly. For instance, Dired’s major
mode keymap binds ‘mouse-2’ to the following command:
(defun dired-mouse-find-file-other-window (event)
"In Dired, visit the file or directory name you click on."
(interactive "e")
(let ((window (posn-window (event-end event)))
(pos (posn-point (event-end event)))
file)
(if (not (windowp window))
(error "No file chosen"))
(with-current-buffer (window-buffer window)
(goto-char pos)
(setq file (dired-get-file-for-visit)))
(if (file-directory-p file)
(or (and (cdr dired-subdir-alist)
(dired-goto-subdir file))
(progn
(select-window window)
(dired-other-window file)))
(select-window window)
(find-file-other-window (file-name-sans-versions file t)))))
This command uses the functions ‘posn-window’ and ‘posn-point’ to
determine where the click occurred, and ‘dired-get-file-for-visit’ to
determine which file to visit.
Instead of binding the mouse command in a major mode keymap, you can
bind it within the link text, using the ‘keymap’ text property (
Special Properties). For instance:
(let ((map (make-sparse-keymap)))
(define-key map [mouse-2] 'operate-this-button)
(put-text-property link-start link-end 'keymap map))
With this method, you can easily define different commands for different
links. Furthermore, the global definition of <RET> and ‘mouse-2’ remain
available for the rest of the text in the buffer.
The basic Emacs command for clicking on links is ‘mouse-2’. However,
for compatibility with other graphical applications, Emacs also
recognizes ‘mouse-1’ clicks on links, provided the user clicks on the
link quickly without moving the mouse. This behavior is controlled by
the user option ‘mouse-1-click-follows-link’. (emacs)Mouse
References.
To set up the link so that it obeys ‘mouse-1-click-follows-link’, you
must either (1) apply a ‘follow-link’ text or overlay property to the
link text, or (2) bind the ‘follow-link’ event to a keymap (which can be
a major mode keymap or a local keymap specified via the ‘keymap’ text
property). The value of the ‘follow-link’ property, or the binding for
the ‘follow-link’ event, acts as a condition for the link action. This
condition tells Emacs two things: the circumstances under which a
‘mouse-1’ click should be regarded as occurring inside the link, and how
to compute an action code that says what to translate the ‘mouse-1’
click into. The link action condition can be one of the following:
‘mouse-face’
If the condition is the symbol ‘mouse-face’, a position is inside a
link if there is a non-‘nil’ ‘mouse-face’ property at that
position. The action code is always ‘t’.
For example, here is how Info mode handles <mouse-1>:
(define-key Info-mode-map [follow-link] 'mouse-face)
a function
If the condition is a function, FUNC, then a position POS is inside
a link if ‘(FUNC POS)’ evaluates to non-‘nil’. The value returned
by FUNC serves as the action code.
For example, here is how pcvs enables ‘mouse-1’ to follow links on
file names only:
(define-key map [follow-link]
(lambda (pos)
(eq (get-char-property pos 'face) 'cvs-filename-face)))
anything else
If the condition value is anything else, then the position is
inside a link and the condition itself is the action code.
Clearly, you should specify this kind of condition only when
applying the condition via a text or property overlay on the link
text (so that it does not apply to the entire buffer).
The action code tells ‘mouse-1’ how to follow the link:
a string or vector
If the action code is a string or vector, the ‘mouse-1’ event is
translated into the first element of the string or vector; i.e.,
the action of the ‘mouse-1’ click is the local or global binding of
that character or symbol. Thus, if the action code is ‘"foo"’,
‘mouse-1’ translates into ‘f’. If it is ‘[foo]’, ‘mouse-1’
translates into <foo>.
anything else
For any other non-‘nil’ action code, the ‘mouse-1’ event is
translated into a ‘mouse-2’ event at the same position.
To define ‘mouse-1’ to activate a button defined with
‘define-button-type’, give the button a ‘follow-link’ property. The
property value should be a link action condition, as described above.
Buttons. For example, here is how Help mode handles ‘mouse-1’:
(define-button-type 'help-xref
'follow-link t
'action #'help-button-action)
To define ‘mouse-1’ on a widget defined with ‘define-widget’, give
the widget a ‘:follow-link’ property. The property value should be a
link action condition, as described above. For example, here is how the
‘link’ widget specifies that a <mouse-1> click shall be translated to
<RET>:
(define-widget 'link 'item
"An embedded link."
:button-prefix 'widget-link-prefix
:button-suffix 'widget-link-suffix
:follow-link "\C-m"
:help-echo "Follow the link."
:format "%[%t%]")
-- Function: mouse-on-link-p pos
This function returns non-‘nil’ if position POS in the current
buffer is on a link. POS can also be a mouse event location, as
returned by ‘event-start’ (Accessing Mouse).