aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/erc/erc-button.el
diff options
context:
space:
mode:
authorMiles Bader2006-01-29 13:08:58 +0000
committerMiles Bader2006-01-29 13:08:58 +0000
commit597993cf4433604ea65e40d33ad6cfe83dab2fb7 (patch)
tree9e9cc6dbc0968bc83d7657c17ecade6b56691f89 /lisp/erc/erc-button.el
parent33c7860d38eb0f5416630b54a7a1b878810a5d3b (diff)
downloademacs-597993cf4433604ea65e40d33ad6cfe83dab2fb7.tar.gz
emacs-597993cf4433604ea65e40d33ad6cfe83dab2fb7.zip
Revision: emacs@sv.gnu.org/emacs--devo--0--patch-22
Creator: Michael Olson <mwolson@gnu.org> Install ERC.
Diffstat (limited to 'lisp/erc/erc-button.el')
-rw-r--r--lisp/erc/erc-button.el504
1 files changed, 504 insertions, 0 deletions
diff --git a/lisp/erc/erc-button.el b/lisp/erc/erc-button.el
new file mode 100644
index 00000000000..6c6998a3afc
--- /dev/null
+++ b/lisp/erc/erc-button.el
@@ -0,0 +1,504 @@
1;; erc-button.el --- A way of buttonizing certain things in ERC buffers
2
3;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4;; 2006 Free Software Foundation, Inc.
5
6;; Author: Mario Lang <mlang@delysid.org>
7;; Keywords: irc, button, url, regexp
8;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcButton
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25;; Boston, MA 02110-1301, USA.
26
27;;; Commentary:
28
29;; Heavily borrowed from gnus-art.el. Thanks to the original authors.
30;; This buttonizes nicks and other stuff to make it all clickable.
31;; To enable, add to your ~/.emacs:
32;; (require 'erc-button)
33;; (erc-button-mode 1)
34;;
35;; Todo:
36;; * Rewrite all this to do the same, but use button.el from GNU Emacs
37;; if it's available for xemacs too. Why? button.el is much faster,
38;; and much more elegant, and solves the problem we get with large buffers
39;; and a large erc-button-marker-list.
40
41
42;;; Code:
43
44(require 'erc)
45(require 'wid-edit)
46(require 'erc-fill)
47
48;;; Minor Mode
49
50(defgroup erc-button nil
51 "Define how text can be turned into clickable buttons."
52 :group 'erc)
53
54;;;###autoload (autoload 'erc-button-mode "erc-button" nil t)
55(define-erc-module button nil
56 "This mode buttonizes all messages according to `erc-button-alist'."
57 ((add-hook 'erc-insert-modify-hook 'erc-button-add-buttons 'append)
58 (add-hook 'erc-send-modify-hook 'erc-button-add-buttons 'append)
59 (add-hook 'erc-complete-functions 'erc-button-next))
60 ((remove-hook 'erc-insert-modify-hook 'erc-button-add-buttons)
61 (remove-hook 'erc-send-modify-hook 'erc-button-add-buttons)
62 (remove-hook 'erc-complete-functions 'erc-button-next)))
63
64;; Make XEmacs use `erc-button-face'.
65(when (featurep 'xemacs)
66 (add-hook 'erc-mode-hook
67 (lambda () (set (make-local-variable 'widget-button-face) nil))))
68
69;;; Variables
70
71(defface erc-button '((t (:bold t)))
72 "ERC button face."
73 :group 'erc-faces)
74
75(defcustom erc-button-face 'erc-button
76 "Face used for highlighting buttons in ERC buffers.
77
78A button is a piece of text that you can activate by pressing
79`RET' or `mouse-2' above it. See also `erc-button-keymap'."
80 :type 'face
81 :group 'erc-faces)
82
83(defcustom erc-button-nickname-face 'erc-nick-default-face
84 "Face used for ERC nickname buttons."
85 :type 'face
86 :group 'erc-faces)
87
88(defcustom erc-button-mouse-face 'highlight
89 "Face used for mouse highlighting in ERC buffers.
90
91Buttons will be displayed in this face when the mouse cursor is
92above them."
93 :type 'face
94 :group 'erc-faces)
95
96(defcustom erc-button-url-regexp
97 (concat "\\(www\\.\\|\\(s?https?\\|"
98 "ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\)"
99 "\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?"
100 "[-a-zA-Z0-9_=!?#$@~`%&*+\\/:;.,]+[-a-zA-Z0-9_=#$@~`%&*+\\/]")
101 "Regular expression that matches URLs."
102 :group 'erc-button
103 :type 'regexp)
104
105(defcustom erc-button-wrap-long-urls nil
106 "If non-nil, \"long\" URLs matching `erc-button-url-regexp' will be wrapped.
107
108If this variable is a number, consider URLs longer than its value to
109be \"long\". If t, URLs will be considered \"long\" if they are
110longer than `erc-fill-column'."
111 :group 'erc-button
112 :type '(choice integer boolean))
113
114(defcustom erc-button-buttonize-nicks t
115 "Flag indicating whether nicks should be buttonized or not."
116 :group 'erc-button
117 :type 'boolean)
118
119(defcustom erc-button-rfc-url "http://www.faqs.org/rfcs/rfc%s.html"
120 "*URL used to browse rfc references.
121%s is replaced by the number."
122 :group 'erc-button
123 :type 'string)
124
125(defcustom erc-button-google-url "http://www.google.com/search?q=%s"
126 "*URL used to browse Google search references.
127%s is replaced by the search string."
128 :group 'erc-button
129 :type 'string)
130
131(defcustom erc-button-alist
132 ;; Since the callback is only executed when the user is clicking on
133 ;; a button, it makes no sense to optimize performance by
134 ;; bytecompiling lambdas in this alist. On the other hand, it makes
135 ;; things hard to maintain.
136 '(('nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0)
137 (erc-button-url-regexp 0 t browse-url 0)
138 ("<URL: *\\([^<> ]+\\) *>" 0 t browse-url 1)
139 ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger 2 3)
140 ;; emacs internal
141 ("[`]\\([a-zA-Z][-a-zA-Z_0-9]+\\)[']" 1 t erc-button-describe-symbol 1)
142 ;; pseudo links
143 ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t Info-goto-node 1)
144 ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-z]+\\)+\\)"
145 0 t (lambda (page)
146 (browse-url (concat "http://c2.com/cgi-bin/wiki?" page)))
147 2)
148 ("EmacsWiki:\\([A-Z][a-z]+\\([A-Z][a-z]+\\)+\\)" 0 t erc-browse-emacswiki 1)
149 ("Lisp:\\([a-zA-Z.+-]+\\)" 0 t erc-browse-emacswiki-lisp 1)
150 ("\\bGoogle:\\([^ \t\n\r\f]+\\)"
151 0 t (lambda (keywords)
152 (browse-url (format erc-button-google-url keywords)))
153 1)
154 ("\\brfc[#: ]?\\([0-9]+\\)"
155 0 t (lambda (num)
156 (browse-url (format erc-button-rfc-url num)))
157 1)
158 ;; other
159 ("\\s-\\(@\\([0-9][0-9][0-9]\\)\\)" 1 t erc-button-beats-to-time 2))
160 "*Alist of regexps matching buttons in ERC buffers.
161Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
162
163REGEXP is the string matching text around the button or a symbol
164 indicating a variable holding that string, or a list of
165 strings, or an alist with the strings in the car. Note that
166 entries in lists or alists are considered to be nicks or other
167 complete words. Therefore they are enclosed in \\< and \\>
168 while searching. REGEXP can also be the quoted symbol
169 'nicknames, which matches the nickname of any user on the
170 current server.
171
172BUTTON is the number of the regexp grouping actually matching the
173 button, This is ignored if REGEXP is 'nicknames.
174
175FORM is a lisp expression which must eval to true for the button to
176 be added,
177
178CALLBACK is the function to call when the user push this button.
179 CALLBACK can also be a symbol. Its variable value will be used
180 as the callback function.
181
182PAR is a number of a regexp grouping whose text will be passed to
183 CALLBACK. There can be several PAR arguments. If REGEXP is
184 'nicknames, these are ignored, and CALLBACK will be called with
185 the nickname matched as the argument."
186 :group 'erc-button
187 :type '(repeat
188 (list :tag "Button"
189 (choice :tag "Matches"
190 regexp
191 (variable :tag "Variable containing regexp")
192 (const :tag "Nicknames" 'nicknames))
193 (integer :tag "Number of the regexp section that matches")
194 (choice :tag "When to buttonize"
195 (const :tag "Always" t)
196 (sexp :tag "Only when this evaluates to non-nil"))
197 (function :tag "Function to call when button is pressed")
198 (repeat :tag "Sections of regexp to send to the function"
199 :inline t
200 (integer :tag "Regexp section number")))))
201
202(defcustom erc-emacswiki-url "http://www.emacswiki.org/cgi-bin/wiki.pl?"
203 "*URL of the EmacsWiki Homepage."
204 :group 'erc-button
205 :type 'string)
206
207(defcustom erc-emacswiki-lisp-url "http://www.emacswiki.org/elisp/"
208 "*URL of the EmacsWiki ELisp area."
209 :group 'erc-button
210 :type 'string)
211
212(defvar erc-button-keymap
213 (let ((map (make-sparse-keymap)))
214 (define-key map (kbd "RET") 'erc-button-press-button)
215 (if (featurep 'xemacs)
216 (define-key map (kbd "<button2>") 'erc-button-click-button)
217 (define-key map (kbd "<mouse-2>") 'erc-button-click-button))
218 (define-key map (kbd "TAB") 'erc-button-next)
219 (set-keymap-parent map erc-mode-map)
220 map)
221 "Local keymap for ERC buttons.")
222
223(defvar erc-button-syntax-table
224 (let ((table (make-syntax-table)))
225 (modify-syntax-entry ?\( "w" table)
226 (modify-syntax-entry ?\) "w" table)
227 (modify-syntax-entry ?\[ "w" table)
228 (modify-syntax-entry ?\] "w" table)
229 (modify-syntax-entry ?\{ "w" table)
230 (modify-syntax-entry ?\} "w" table)
231 (modify-syntax-entry ?` "w" table)
232 (modify-syntax-entry ?' "w" table)
233 (modify-syntax-entry ?^ "w" table)
234 (modify-syntax-entry ?- "w" table)
235 (modify-syntax-entry ?_ "w" table)
236 (modify-syntax-entry ?| "w" table)
237 (modify-syntax-entry ?\\ "w" table)
238 table)
239 "Syntax table used when buttonizing messages.
240This syntax table should make all the legal nick characters word
241constituents.")
242
243(defun erc-button-add-buttons ()
244 "Find external references in the current buffer and make buttons of them.
245\"External references\" are things like URLs, as
246specified by `erc-button-alist'."
247 (interactive)
248 (save-excursion
249 (with-syntax-table erc-button-syntax-table
250 (let ((buffer-read-only nil)
251 (inhibit-point-motion-hooks t)
252 (inhibit-field-text-motion t)
253 (alist erc-button-alist)
254 entry regexp data)
255 (erc-button-remove-old-buttons)
256 (dolist (entry alist)
257 (if (equal (car entry) (quote (quote nicknames)))
258 (erc-button-add-nickname-buttons entry)
259 (progn
260 (setq regexp (or (and (stringp (car entry)) (car entry))
261 (and (boundp (car entry))
262 (symbol-value (car entry)))))
263 (cond ((stringp regexp)
264 (erc-button-add-buttons-1 regexp entry))
265 ((and (listp regexp) (stringp (car regexp)))
266 (dolist (r regexp)
267 (erc-button-add-buttons-1
268 (concat "\\<" (regexp-quote r) "\\>")
269 entry)))
270 ((and (listp regexp) (listp (car regexp))
271 (stringp (caar regexp)))
272 (dolist (elem regexp)
273 (erc-button-add-buttons-1
274 (concat "\\<" (regexp-quote (car elem)) "\\>")
275 entry)))))))))))
276
277(defun erc-button-add-nickname-buttons (entry)
278 "Search through the buffer for nicknames, and add buttons."
279 (let ((form (nth 2 entry))
280 (fun (nth 3 entry))
281 bounds word)
282 (when (or (eq t form)
283 (eval form))
284 (goto-char (point-min))
285 (while (forward-word 1)
286 (setq bounds (bounds-of-thing-at-point 'word))
287 (setq word (buffer-substring-no-properties
288 (car bounds) (cdr bounds)))
289 (if (erc-get-server-user word)
290 (erc-button-add-button (car bounds) (cdr bounds)
291 fun t (list word)))))))
292
293(defun erc-button-add-buttons-1 (regexp entry)
294 "Search through the buffer for matches to ENTRY and add buttons."
295 (goto-char (point-min))
296 (while (re-search-forward regexp nil t)
297 (let ((start (match-beginning (nth 1 entry)))
298 (end (match-end (nth 1 entry)))
299 (form (nth 2 entry))
300 (fun (nth 3 entry))
301 (data (mapcar 'match-string (nthcdr 4 entry))))
302 (when (or (eq t form)
303 (eval form))
304 (erc-button-add-button start end fun nil data regexp)))))
305
306(defun erc-button-remove-old-buttons ()
307 "Remove all existing buttons.
308This is called with narrowing in effect, just before the text is
309buttonized again. Removing a button means to remove all the properties
310that `erc-button-add-button' adds, except for the face."
311 (remove-text-properties
312 (point-min) (point-max)
313 '(erc-callback nil
314 erc-data nil
315 mouse-face nil
316 keymap nil)))
317
318(defun erc-button-add-button (from to fun nick-p &optional data regexp)
319 "Create a button between FROM and TO with callback FUN and data DATA.
320NICK-P specifies if this is a nickname button.
321REGEXP is the regular expression which matched for this button."
322 ;; Really nasty hack to <URL: > ise urls, and line-wrap them if
323 ;; they're going to be wider than `erc-fill-column'.
324 ;; This could be a lot cleaner, but it works for me -- lawrence.
325 (let (fill-column)
326 (when (and erc-button-wrap-long-urls
327 (string= regexp erc-button-url-regexp)
328 (> (- to from)
329 (setq fill-column (- (if (numberp erc-button-wrap-long-urls)
330 erc-button-wrap-long-urls
331 erc-fill-column)
332 (length erc-fill-prefix)))))
333 (setq to (prog1 (point-marker) (insert ">"))
334 from (prog2 (goto-char from) (point-marker) (insert "<URL: ")))
335 (let ((pos (copy-marker from)))
336 (while (> (- to pos) fill-column)
337 (goto-char (+ pos fill-column))
338 (insert "\n" erc-fill-prefix) ; This ought to figure out
339 ; what type of filling we're
340 ; doing, and indent accordingly.
341 (move-marker pos (point))))))
342 (if nick-p
343 (when erc-button-nickname-face
344 (erc-button-add-face from to erc-button-nickname-face))
345 (when erc-button-face
346 (erc-button-add-face from to erc-button-face)))
347 (add-text-properties
348 from to
349 (nconc (and erc-button-mouse-face
350 (list 'mouse-face erc-button-mouse-face))
351 (list 'erc-callback fun)
352 (list 'keymap erc-button-keymap)
353 (list 'rear-nonsticky t)
354 (and data (list 'erc-data data))))
355 (widget-convert-button 'link from to :action 'erc-button-press-button
356 :suppress-face t
357 ;; Make XEmacs use our faces.
358 :button-face (if nick-p
359 erc-button-nickname-face
360 erc-button-face)
361 ;; Make XEmacs behave with mouse-clicks, for
362 ;; some reason, widget stuff overrides the
363 ;; 'keymap text-property.
364 :mouse-down-action 'erc-button-click-button))
365
366(defun erc-button-add-face (from to face)
367 "Add FACE to the region between FROM and TO."
368 ;; If we just use `add-text-property', then this will overwrite any
369 ;; face text property already used for the button. It will not be
370 ;; merged correctly. If we use overlays, then redisplay will be
371 ;; very slow with lots of buttons. This is why we manually merge
372 ;; face text properties.
373 (let ((old (erc-list (get-text-property from 'face)))
374 (pos from)
375 (end (next-single-property-change from 'face nil to))
376 new)
377 ;; old is the face at pos, in list form. It is nil if there is no
378 ;; face at pos. If nil, the new face is FACE. If not nil, the
379 ;; new face is a list containing FACE and the old stuff. end is
380 ;; where this face changes.
381 (while (< pos to)
382 (setq new (if old (cons face old) face))
383 (put-text-property pos end 'face new)
384 (setq pos end
385 old (erc-list (get-text-property pos 'face))
386 end (next-single-property-change pos 'face nil to)))))
387
388;; widget-button-click calls with two args, we ignore the first.
389;; Since Emacs runs this directly, rather than with
390;; widget-button-click, we need to fake an extra arg in the
391;; interactive spec.
392(defun erc-button-click-button (ignore event)
393 "Call `erc-button-press-button'."
394 (interactive "P\ne")
395 (save-excursion
396 (mouse-set-point event)
397 (erc-button-press-button)))
398
399;; XEmacs calls this via widget-button-press with a bunch of arguments
400;; which we don't care about.
401(defun erc-button-press-button (&rest ignore)
402 "Check text at point for a callback function.
403If the text at point has a `erc-callback' property,
404call it with the value of the `erc-data' text property."
405 (interactive)
406 (let* ((data (get-text-property (point) 'erc-data))
407 (fun (get-text-property (point) 'erc-callback)))
408 (unless fun
409 (message "No button at point"))
410 (when (and fun (symbolp fun) (not (fboundp fun)))
411 (error "Function %S is not bound" fun))
412 (apply fun data)))
413
414(defun erc-button-next ()
415 "Go to the next button in this buffer."
416 (interactive)
417 (let ((here (point)))
418 (when (< here (erc-beg-of-input-line))
419 (while (and (get-text-property here 'erc-callback)
420 (not (= here (point-max))))
421 (setq here (1+ here)))
422 (while (and (not (get-text-property here 'erc-callback))
423 (not (= here (point-max))))
424 (setq here (1+ here)))
425 (if (< here (point-max))
426 (goto-char here)
427 (error "No next button"))
428 t)))
429
430(defun erc-browse-emacswiki (thing)
431 "Browse to thing in the emacs-wiki."
432 (browse-url (concat erc-emacswiki-url thing)))
433
434(defun erc-browse-emacswiki-lisp (thing)
435 "Browse to THING in the emacs-wiki elisp area."
436 (browse-url (concat erc-emacswiki-lisp-url thing)))
437
438;;; Nickname buttons:
439
440(defcustom erc-nick-popup-alist
441 '(("DeOp" . (erc-cmd-DEOP nick))
442 ("Kick" . (erc-cmd-KICK (concat nick " "
443 (read-from-minibuffer
444 (concat "Kick " nick ", reason: ")))))
445 ("Msg" . (erc-cmd-MSG (concat nick " "
446 (read-from-minibuffer
447 (concat "Message to " nick ": ")))))
448 ("Op" . (erc-cmd-OP nick))
449 ("Query" . (erc-cmd-QUERY nick))
450 ("Whois" . (erc-cmd-WHOIS nick))
451 ("Lastlog" . (erc-cmd-LASTLOG nick)))
452 "*An alist of possible actions to take on a nickname.
453An entry looks like (\"Action\" . SEXP) where SEXP is evaluated with
454the variable `nick' bound to the nick in question.
455
456Examples:
457 (\"DebianDB\" .
458 (shell-command
459 (format
460 \"ldapsearch -x -P 2 -h db.debian.org -b dc=debian,dc=org ircnick=%s\"
461 nick)))"
462 :group 'erc-button
463 :type '(repeat (cons (string :tag "Op")
464 sexp)))
465
466(defun erc-nick-popup (nick)
467 (let* ((completion-ignore-case t)
468 (action (completing-read (concat "What action to take on '" nick "'? ")
469 erc-nick-popup-alist))
470 (code (cdr (assoc action erc-nick-popup-alist))))
471 (when code
472 (erc-set-active-buffer (current-buffer))
473 (eval code))))
474
475;;; Callback functions
476(defun erc-button-describe-symbol (symbol-name)
477 "Describe SYMBOL-NAME.
478Use `describe-function' for functions, `describe-variable' for variables,
479and `apropos' for other symbols."
480 (let ((symbol (intern-soft symbol-name)))
481 (cond ((and symbol (fboundp symbol))
482 (describe-function symbol))
483 ((and symbol (boundp symbol))
484 (describe-variable symbol))
485 (t (apropos symbol-name)))))
486
487(defun erc-button-beats-to-time (beats)
488 "Display BEATS in a readable time format."
489 (let* ((seconds (- (* (string-to-number beats) 86.4)
490 3600
491 (- (car (current-time-zone)))))
492 (hours (mod (floor seconds 3600) 24))
493 (minutes (mod (round seconds 60) 60)))
494 (message (format "@%s is %d:%02d local time"
495 beats hours minutes))))
496
497(provide 'erc-button)
498
499;;; erc-button.el ends here
500;; Local Variables:
501;; indent-tabs-mode: nil
502;; End:
503
504;; arch-tag: 7d23bed4-2f30-4273-a03f-d7a274c605c4