diff options
author | Miles Bader <miles@gnu.org> | 2006-01-29 13:08:58 +0000 |
---|---|---|
committer | Miles Bader <miles@gnu.org> | 2006-01-29 13:08:58 +0000 |
commit | 597993cf4433604ea65e40d33ad6cfe83dab2fb7 (patch) | |
tree | 9e9cc6dbc0968bc83d7657c17ecade6b56691f89 /lisp/erc/erc-button.el | |
parent | 33c7860d38eb0f5416630b54a7a1b878810a5d3b (diff) | |
download | emacs-597993cf4433604ea65e40d33ad6cfe83dab2fb7.tar.gz emacs-597993cf4433604ea65e40d33ad6cfe83dab2fb7.tar.bz2 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.el | 504 |
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 @@ +;; erc-button.el --- A way of buttonizing certain things in ERC buffers + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, +;; 2006 Free Software Foundation, Inc. + +;; Author: Mario Lang <mlang@delysid.org> +;; Keywords: irc, button, url, regexp +;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcButton + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; Heavily borrowed from gnus-art.el. Thanks to the original authors. +;; This buttonizes nicks and other stuff to make it all clickable. +;; To enable, add to your ~/.emacs: +;; (require 'erc-button) +;; (erc-button-mode 1) +;; +;; Todo: +;; * Rewrite all this to do the same, but use button.el from GNU Emacs +;; if it's available for xemacs too. Why? button.el is much faster, +;; and much more elegant, and solves the problem we get with large buffers +;; and a large erc-button-marker-list. + + +;;; Code: + +(require 'erc) +(require 'wid-edit) +(require 'erc-fill) + +;;; Minor Mode + +(defgroup erc-button nil + "Define how text can be turned into clickable buttons." + :group 'erc) + +;;;###autoload (autoload 'erc-button-mode "erc-button" nil t) +(define-erc-module button nil + "This mode buttonizes all messages according to `erc-button-alist'." + ((add-hook 'erc-insert-modify-hook 'erc-button-add-buttons 'append) + (add-hook 'erc-send-modify-hook 'erc-button-add-buttons 'append) + (add-hook 'erc-complete-functions 'erc-button-next)) + ((remove-hook 'erc-insert-modify-hook 'erc-button-add-buttons) + (remove-hook 'erc-send-modify-hook 'erc-button-add-buttons) + (remove-hook 'erc-complete-functions 'erc-button-next))) + +;; Make XEmacs use `erc-button-face'. +(when (featurep 'xemacs) + (add-hook 'erc-mode-hook + (lambda () (set (make-local-variable 'widget-button-face) nil)))) + +;;; Variables + +(defface erc-button '((t (:bold t))) + "ERC button face." + :group 'erc-faces) + +(defcustom erc-button-face 'erc-button + "Face used for highlighting buttons in ERC buffers. + +A button is a piece of text that you can activate by pressing +`RET' or `mouse-2' above it. See also `erc-button-keymap'." + :type 'face + :group 'erc-faces) + +(defcustom erc-button-nickname-face 'erc-nick-default-face + "Face used for ERC nickname buttons." + :type 'face + :group 'erc-faces) + +(defcustom erc-button-mouse-face 'highlight + "Face used for mouse highlighting in ERC buffers. + +Buttons will be displayed in this face when the mouse cursor is +above them." + :type 'face + :group 'erc-faces) + +(defcustom erc-button-url-regexp + (concat "\\(www\\.\\|\\(s?https?\\|" + "ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\)" + "\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?" + "[-a-zA-Z0-9_=!?#$@~`%&*+\\/:;.,]+[-a-zA-Z0-9_=#$@~`%&*+\\/]") + "Regular expression that matches URLs." + :group 'erc-button + :type 'regexp) + +(defcustom erc-button-wrap-long-urls nil + "If non-nil, \"long\" URLs matching `erc-button-url-regexp' will be wrapped. + +If this variable is a number, consider URLs longer than its value to +be \"long\". If t, URLs will be considered \"long\" if they are +longer than `erc-fill-column'." + :group 'erc-button + :type '(choice integer boolean)) + +(defcustom erc-button-buttonize-nicks t + "Flag indicating whether nicks should be buttonized or not." + :group 'erc-button + :type 'boolean) + +(defcustom erc-button-rfc-url "http://www.faqs.org/rfcs/rfc%s.html" + "*URL used to browse rfc references. +%s is replaced by the number." + :group 'erc-button + :type 'string) + +(defcustom erc-button-google-url "http://www.google.com/search?q=%s" + "*URL used to browse Google search references. +%s is replaced by the search string." + :group 'erc-button + :type 'string) + +(defcustom erc-button-alist + ;; Since the callback is only executed when the user is clicking on + ;; a button, it makes no sense to optimize performance by + ;; bytecompiling lambdas in this alist. On the other hand, it makes + ;; things hard to maintain. + '(('nicknames 0 erc-button-buttonize-nicks erc-nick-popup 0) + (erc-button-url-regexp 0 t browse-url 0) + ("<URL: *\\([^<> ]+\\) *>" 0 t browse-url 1) + ("(\\(\\([^~\n \t@][^\n \t@]*\\)@\\([a-zA-Z0-9.:-]+\\)\\)" 1 t finger 2 3) + ;; emacs internal + ("[`]\\([a-zA-Z][-a-zA-Z_0-9]+\\)[']" 1 t erc-button-describe-symbol 1) + ;; pseudo links + ("\\bInfo:[\"]\\([^\"]+\\)[\"]" 0 t Info-goto-node 1) + ("\\b\\(Ward\\|Wiki\\|WardsWiki\\|TheWiki\\):\\([A-Z][a-z]+\\([A-Z][a-z]+\\)+\\)" + 0 t (lambda (page) + (browse-url (concat "http://c2.com/cgi-bin/wiki?" page))) + 2) + ("EmacsWiki:\\([A-Z][a-z]+\\([A-Z][a-z]+\\)+\\)" 0 t erc-browse-emacswiki 1) + ("Lisp:\\([a-zA-Z.+-]+\\)" 0 t erc-browse-emacswiki-lisp 1) + ("\\bGoogle:\\([^ \t\n\r\f]+\\)" + 0 t (lambda (keywords) + (browse-url (format erc-button-google-url keywords))) + 1) + ("\\brfc[#: ]?\\([0-9]+\\)" + 0 t (lambda (num) + (browse-url (format erc-button-rfc-url num))) + 1) + ;; other + ("\\s-\\(@\\([0-9][0-9][0-9]\\)\\)" 1 t erc-button-beats-to-time 2)) + "*Alist of regexps matching buttons in ERC buffers. +Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where + +REGEXP is the string matching text around the button or a symbol + indicating a variable holding that string, or a list of + strings, or an alist with the strings in the car. Note that + entries in lists or alists are considered to be nicks or other + complete words. Therefore they are enclosed in \\< and \\> + while searching. REGEXP can also be the quoted symbol + 'nicknames, which matches the nickname of any user on the + current server. + +BUTTON is the number of the regexp grouping actually matching the + button, This is ignored if REGEXP is 'nicknames. + +FORM is a lisp expression which must eval to true for the button to + be added, + +CALLBACK is the function to call when the user push this button. + CALLBACK can also be a symbol. Its variable value will be used + as the callback function. + +PAR is a number of a regexp grouping whose text will be passed to + CALLBACK. There can be several PAR arguments. If REGEXP is + 'nicknames, these are ignored, and CALLBACK will be called with + the nickname matched as the argument." + :group 'erc-button + :type '(repeat + (list :tag "Button" + (choice :tag "Matches" + regexp + (variable :tag "Variable containing regexp") + (const :tag "Nicknames" 'nicknames)) + (integer :tag "Number of the regexp section that matches") + (choice :tag "When to buttonize" + (const :tag "Always" t) + (sexp :tag "Only when this evaluates to non-nil")) + (function :tag "Function to call when button is pressed") + (repeat :tag "Sections of regexp to send to the function" + :inline t + (integer :tag "Regexp section number"))))) + +(defcustom erc-emacswiki-url "http://www.emacswiki.org/cgi-bin/wiki.pl?" + "*URL of the EmacsWiki Homepage." + :group 'erc-button + :type 'string) + +(defcustom erc-emacswiki-lisp-url "http://www.emacswiki.org/elisp/" + "*URL of the EmacsWiki ELisp area." + :group 'erc-button + :type 'string) + +(defvar erc-button-keymap + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") 'erc-button-press-button) + (if (featurep 'xemacs) + (define-key map (kbd "<button2>") 'erc-button-click-button) + (define-key map (kbd "<mouse-2>") 'erc-button-click-button)) + (define-key map (kbd "TAB") 'erc-button-next) + (set-keymap-parent map erc-mode-map) + map) + "Local keymap for ERC buttons.") + +(defvar erc-button-syntax-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?\( "w" table) + (modify-syntax-entry ?\) "w" table) + (modify-syntax-entry ?\[ "w" table) + (modify-syntax-entry ?\] "w" table) + (modify-syntax-entry ?\{ "w" table) + (modify-syntax-entry ?\} "w" table) + (modify-syntax-entry ?` "w" table) + (modify-syntax-entry ?' "w" table) + (modify-syntax-entry ?^ "w" table) + (modify-syntax-entry ?- "w" table) + (modify-syntax-entry ?_ "w" table) + (modify-syntax-entry ?| "w" table) + (modify-syntax-entry ?\\ "w" table) + table) + "Syntax table used when buttonizing messages. +This syntax table should make all the legal nick characters word +constituents.") + +(defun erc-button-add-buttons () + "Find external references in the current buffer and make buttons of them. +\"External references\" are things like URLs, as +specified by `erc-button-alist'." + (interactive) + (save-excursion + (with-syntax-table erc-button-syntax-table + (let ((buffer-read-only nil) + (inhibit-point-motion-hooks t) + (inhibit-field-text-motion t) + (alist erc-button-alist) + entry regexp data) + (erc-button-remove-old-buttons) + (dolist (entry alist) + (if (equal (car entry) (quote (quote nicknames))) + (erc-button-add-nickname-buttons entry) + (progn + (setq regexp (or (and (stringp (car entry)) (car entry)) + (and (boundp (car entry)) + (symbol-value (car entry))))) + (cond ((stringp regexp) + (erc-button-add-buttons-1 regexp entry)) + ((and (listp regexp) (stringp (car regexp))) + (dolist (r regexp) + (erc-button-add-buttons-1 + (concat "\\<" (regexp-quote r) "\\>") + entry))) + ((and (listp regexp) (listp (car regexp)) + (stringp (caar regexp))) + (dolist (elem regexp) + (erc-button-add-buttons-1 + (concat "\\<" (regexp-quote (car elem)) "\\>") + entry))))))))))) + +(defun erc-button-add-nickname-buttons (entry) + "Search through the buffer for nicknames, and add buttons." + (let ((form (nth 2 entry)) + (fun (nth 3 entry)) + bounds word) + (when (or (eq t form) + (eval form)) + (goto-char (point-min)) + (while (forward-word 1) + (setq bounds (bounds-of-thing-at-point 'word)) + (setq word (buffer-substring-no-properties + (car bounds) (cdr bounds))) + (if (erc-get-server-user word) + (erc-button-add-button (car bounds) (cdr bounds) + fun t (list word))))))) + +(defun erc-button-add-buttons-1 (regexp entry) + "Search through the buffer for matches to ENTRY and add buttons." + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (let ((start (match-beginning (nth 1 entry))) + (end (match-end (nth 1 entry))) + (form (nth 2 entry)) + (fun (nth 3 entry)) + (data (mapcar 'match-string (nthcdr 4 entry)))) + (when (or (eq t form) + (eval form)) + (erc-button-add-button start end fun nil data regexp))))) + +(defun erc-button-remove-old-buttons () + "Remove all existing buttons. +This is called with narrowing in effect, just before the text is +buttonized again. Removing a button means to remove all the properties +that `erc-button-add-button' adds, except for the face." + (remove-text-properties + (point-min) (point-max) + '(erc-callback nil + erc-data nil + mouse-face nil + keymap nil))) + +(defun erc-button-add-button (from to fun nick-p &optional data regexp) + "Create a button between FROM and TO with callback FUN and data DATA. +NICK-P specifies if this is a nickname button. +REGEXP is the regular expression which matched for this button." + ;; Really nasty hack to <URL: > ise urls, and line-wrap them if + ;; they're going to be wider than `erc-fill-column'. + ;; This could be a lot cleaner, but it works for me -- lawrence. + (let (fill-column) + (when (and erc-button-wrap-long-urls + (string= regexp erc-button-url-regexp) + (> (- to from) + (setq fill-column (- (if (numberp erc-button-wrap-long-urls) + erc-button-wrap-long-urls + erc-fill-column) + (length erc-fill-prefix))))) + (setq to (prog1 (point-marker) (insert ">")) + from (prog2 (goto-char from) (point-marker) (insert "<URL: "))) + (let ((pos (copy-marker from))) + (while (> (- to pos) fill-column) + (goto-char (+ pos fill-column)) + (insert "\n" erc-fill-prefix) ; This ought to figure out + ; what type of filling we're + ; doing, and indent accordingly. + (move-marker pos (point)))))) + (if nick-p + (when erc-button-nickname-face + (erc-button-add-face from to erc-button-nickname-face)) + (when erc-button-face + (erc-button-add-face from to erc-button-face))) + (add-text-properties + from to + (nconc (and erc-button-mouse-face + (list 'mouse-face erc-button-mouse-face)) + (list 'erc-callback fun) + (list 'keymap erc-button-keymap) + (list 'rear-nonsticky t) + (and data (list 'erc-data data)))) + (widget-convert-button 'link from to :action 'erc-button-press-button + :suppress-face t + ;; Make XEmacs use our faces. + :button-face (if nick-p + erc-button-nickname-face + erc-button-face) + ;; Make XEmacs behave with mouse-clicks, for + ;; some reason, widget stuff overrides the + ;; 'keymap text-property. + :mouse-down-action 'erc-button-click-button)) + +(defun erc-button-add-face (from to face) + "Add FACE to the region between FROM and TO." + ;; If we just use `add-text-property', then this will overwrite any + ;; face text property already used for the button. It will not be + ;; merged correctly. If we use overlays, then redisplay will be + ;; very slow with lots of buttons. This is why we manually merge + ;; face text properties. + (let ((old (erc-list (get-text-property from 'face))) + (pos from) + (end (next-single-property-change from 'face nil to)) + new) + ;; old is the face at pos, in list form. It is nil if there is no + ;; face at pos. If nil, the new face is FACE. If not nil, the + ;; new face is a list containing FACE and the old stuff. end is + ;; where this face changes. + (while (< pos to) + (setq new (if old (cons face old) face)) + (put-text-property pos end 'face new) + (setq pos end + old (erc-list (get-text-property pos 'face)) + end (next-single-property-change pos 'face nil to))))) + +;; widget-button-click calls with two args, we ignore the first. +;; Since Emacs runs this directly, rather than with +;; widget-button-click, we need to fake an extra arg in the +;; interactive spec. +(defun erc-button-click-button (ignore event) + "Call `erc-button-press-button'." + (interactive "P\ne") + (save-excursion + (mouse-set-point event) + (erc-button-press-button))) + +;; XEmacs calls this via widget-button-press with a bunch of arguments +;; which we don't care about. +(defun erc-button-press-button (&rest ignore) + "Check text at point for a callback function. +If the text at point has a `erc-callback' property, +call it with the value of the `erc-data' text property." + (interactive) + (let* ((data (get-text-property (point) 'erc-data)) + (fun (get-text-property (point) 'erc-callback))) + (unless fun + (message "No button at point")) + (when (and fun (symbolp fun) (not (fboundp fun))) + (error "Function %S is not bound" fun)) + (apply fun data))) + +(defun erc-button-next () + "Go to the next button in this buffer." + (interactive) + (let ((here (point))) + (when (< here (erc-beg-of-input-line)) + (while (and (get-text-property here 'erc-callback) + (not (= here (point-max)))) + (setq here (1+ here))) + (while (and (not (get-text-property here 'erc-callback)) + (not (= here (point-max)))) + (setq here (1+ here))) + (if (< here (point-max)) + (goto-char here) + (error "No next button")) + t))) + +(defun erc-browse-emacswiki (thing) + "Browse to thing in the emacs-wiki." + (browse-url (concat erc-emacswiki-url thing))) + +(defun erc-browse-emacswiki-lisp (thing) + "Browse to THING in the emacs-wiki elisp area." + (browse-url (concat erc-emacswiki-lisp-url thing))) + +;;; Nickname buttons: + +(defcustom erc-nick-popup-alist + '(("DeOp" . (erc-cmd-DEOP nick)) + ("Kick" . (erc-cmd-KICK (concat nick " " + (read-from-minibuffer + (concat "Kick " nick ", reason: "))))) + ("Msg" . (erc-cmd-MSG (concat nick " " + (read-from-minibuffer + (concat "Message to " nick ": "))))) + ("Op" . (erc-cmd-OP nick)) + ("Query" . (erc-cmd-QUERY nick)) + ("Whois" . (erc-cmd-WHOIS nick)) + ("Lastlog" . (erc-cmd-LASTLOG nick))) + "*An alist of possible actions to take on a nickname. +An entry looks like (\"Action\" . SEXP) where SEXP is evaluated with +the variable `nick' bound to the nick in question. + +Examples: + (\"DebianDB\" . + (shell-command + (format + \"ldapsearch -x -P 2 -h db.debian.org -b dc=debian,dc=org ircnick=%s\" + nick)))" + :group 'erc-button + :type '(repeat (cons (string :tag "Op") + sexp))) + +(defun erc-nick-popup (nick) + (let* ((completion-ignore-case t) + (action (completing-read (concat "What action to take on '" nick "'? ") + erc-nick-popup-alist)) + (code (cdr (assoc action erc-nick-popup-alist)))) + (when code + (erc-set-active-buffer (current-buffer)) + (eval code)))) + +;;; Callback functions +(defun erc-button-describe-symbol (symbol-name) + "Describe SYMBOL-NAME. +Use `describe-function' for functions, `describe-variable' for variables, +and `apropos' for other symbols." + (let ((symbol (intern-soft symbol-name))) + (cond ((and symbol (fboundp symbol)) + (describe-function symbol)) + ((and symbol (boundp symbol)) + (describe-variable symbol)) + (t (apropos symbol-name))))) + +(defun erc-button-beats-to-time (beats) + "Display BEATS in a readable time format." + (let* ((seconds (- (* (string-to-number beats) 86.4) + 3600 + (- (car (current-time-zone))))) + (hours (mod (floor seconds 3600) 24)) + (minutes (mod (round seconds 60) 60))) + (message (format "@%s is %d:%02d local time" + beats hours minutes)))) + +(provide 'erc-button) + +;;; erc-button.el ends here +;; Local Variables: +;; indent-tabs-mode: nil +;; End: + +;; arch-tag: 7d23bed4-2f30-4273-a03f-d7a274c605c4 |