summaryrefslogtreecommitdiff
path: root/lisp/erc/erc-match.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/erc/erc-match.el')
-rw-r--r--lisp/erc/erc-match.el658
1 files changed, 658 insertions, 0 deletions
diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el
new file mode 100644
index 00000000000..a5e3bf88ccf
--- /dev/null
+++ b/lisp/erc/erc-match.el
@@ -0,0 +1,658 @@
+;;; erc-match.el --- Highlight messages matching certain regexps
+
+;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+
+;; Author: Andreas Fuchs <asf@void.at>
+;; Keywords: comm, faces
+;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcMatch
+
+;; 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:
+
+;; This file includes stuff to work with pattern matching in ERC. If
+;; you were used to customizing erc-fools, erc-keywords, erc-pals,
+;; erc-dangerous-hosts and the like, this file contains these
+;; customizable variables.
+
+;; Usage:
+;; Put (erc-match-mode 1) into your ~/.emacs file.
+
+;;; Code:
+
+(require 'erc)
+(eval-when-compile (require 'cl))
+
+;; Customisation:
+
+(defgroup erc-match nil
+ "Keyword and Friend/Foe/... recognition.
+Group containing all things concerning pattern matching in ERC
+messages."
+ :group 'erc)
+
+;;;###autoload (autoload 'erc-match-mode "erc-match")
+(define-erc-module match nil
+ "This mode checks whether messages match certain patterns. If so,
+they are hidden or highlighted. This is controlled via the variables
+`erc-pals', `erc-fools', `erc-keywords', `erc-dangerous-hosts', and
+`erc-current-nick-highlight-type'. For all these highlighting types,
+you can decide whether the entire message or only the sending nick is
+highlighted."
+ ((add-hook 'erc-insert-modify-hook 'erc-match-message 'append))
+ ((remove-hook 'erc-insert-modify-hook 'erc-match-message)))
+
+;; Remaining customizations
+
+(defcustom erc-pals nil
+ "List of pals on IRC."
+ :group 'erc-match
+ :type '(repeat regexp))
+
+(defcustom erc-fools nil
+ "List of fools on IRC."
+ :group 'erc-match
+ :type '(repeat regexp))
+
+(defcustom erc-keywords nil
+ "List of keywords to highlight in all incoming messages.
+Each entry in the list is either a regexp, or a cons cell with the
+regexp in the car and the face to use in the cdr. If no face is
+specified, `erc-keyword-face' is used."
+ :group 'erc-match
+ :type '(repeat (choice regexp
+ (list regexp face))))
+
+(defcustom erc-dangerous-hosts nil
+ "List of regexps for hosts to highlight.
+Useful to mark nicks from dangerous hosts."
+ :group 'erc-match
+ :type '(repeat regexp))
+
+(defcustom erc-current-nick-highlight-type 'keyword
+ "*Determines how to highlight text in which your current nickname appears
+\(does not apply to text sent by you\).
+
+The following values are allowed:
+
+ nil - do not highlight the message at all
+ 'keyword - highlight all instances of current nickname in message
+ 'nick - highlight the nick of the user who typed your nickname
+ 'nick-or-keyword - highlight the nick of the user who typed your nickname,
+ or all instances of the current nickname if there was
+ no sending user
+ 'all - highlight the entire message where current nickname occurs
+
+Any other value disables highlighting of current nickname altogether."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const nick)
+ (const keyword)
+ (const nick-or-keyword)
+ (const all)))
+
+(defcustom erc-pal-highlight-type 'nick
+ "*Determines how to highlight messages by pals.
+See `erc-pals'.
+
+The following values are allowed:
+
+ nil - do not highlight the message at all
+ 'nick - highlight pal's nickname only
+ 'all - highlight the entire message from pal
+
+Any other value disables pal highlighting altogether."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const nick)
+ (const all)))
+
+(defcustom erc-fool-highlight-type 'nick
+ "*Determines how to highlight messages by fools.
+See `erc-fools'.
+
+The following values are allowed:
+
+ nil - do not highlight the message at all
+ 'nick - highlight fool's nickname only
+ 'all - highlight the entire message from fool
+
+Any other value disables fool highlighting altogether."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const nick)
+ (const all)))
+
+(defcustom erc-keyword-highlight-type 'keyword
+ "*Determines how to highlight messages containing keywords.
+See variable `erc-keywords'.
+
+The following values are allowed:
+
+ 'keyword - highlight keyword only
+ 'all - highlight the entire message containing keyword
+
+Any other value disables keyword highlighting altogether."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const keyword)
+ (const all)))
+
+(defcustom erc-dangerous-host-highlight-type 'nick
+ "*Determines how to highlight messages by nicks from dangerous-hosts.
+See `erc-dangerous-hosts'.
+
+The following values are allowed:
+
+ 'nick - highlight nick from dangerous-host only
+ 'all - highlight the entire message from dangerous-host
+
+Any other value disables dangerous-host highlighting altogether."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const nick)
+ (const all)))
+
+
+(defcustom erc-log-matches-types-alist '((keyword . "ERC Keywords"))
+ "Alist telling ERC where to log which match types.
+Valid match type keys are:
+- keyword
+- pal
+- dangerous-host
+- fool
+- current-nick
+
+The other element of each cons pair in this list is the buffer name to
+use for the logged message."
+ :group 'erc-match
+ :type '(repeat (cons (choice :tag "Key"
+ (const keyword)
+ (const pal)
+ (const dangerous-host)
+ (const fool)
+ (const current-nick))
+ (string :tag "Buffer name"))))
+
+(defcustom erc-log-matches-flag 'away
+ "Flag specifying when matched message logging should happen.
+When nil, don't log any matched messages.
+When t, log messages.
+When 'away, log messages only when away."
+ :group 'erc-match
+ :type '(choice (const nil)
+ (const away)
+ (const t)))
+
+(defcustom erc-log-match-format "%t<%n:%c> %m"
+ "Format for matched Messages.
+This variable specifies how messages in the corresponding log buffers will
+be formatted. The various format specs are:
+
+%t Timestamp (uses `erc-timestamp-format' if non-nil or \"[%Y-%m-%d %H:%M] \")
+%n Nickname of sender
+%u Nickname!user@host of sender
+%c Channel in which this was received
+%m Message"
+ :group 'erc-match
+ :type 'string)
+
+(defcustom erc-beep-match-types '(current-nick)
+ "Types of matches to beep for when a match occurs.
+The function `erc-beep-on-match' needs to be added to `erc-text-matched-hook'
+for beeping to work."
+ :group 'erc-match
+ :type '(choice (repeat :tag "Beep on match" (choice
+ (const current-nick)
+ (const keyword)
+ (const pal)
+ (const dangerous-host)
+ (const fool)))
+ (const :tag "Don't beep" nil)))
+
+(defcustom erc-text-matched-hook '(erc-log-matches)
+ "Hook run when text matches a given match-type.
+Functions in this hook are passed as arguments:
+\(match-type nick!user@host message) where MATCH-TYPE is a symbol of:
+current-nick, keyword, pal, dangerous-host, fool"
+ :options '(erc-log-matches erc-hide-fools erc-beep-on-match)
+ :group 'erc-match
+ :type 'hook)
+
+;; Internal variables:
+
+;; This is exactly the same as erc-button-syntax-table. Should we
+;; just put it in erc.el
+(defvar erc-match-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 highlighting messages.
+This syntax table should make all the legal nick characters word
+constituents.")
+
+;; Faces:
+
+(defface erc-current-nick-face '((t (:bold t :foreground "DarkTurquoise")))
+ "ERC face for occurrences of your current nickname."
+ :group 'erc-faces)
+
+(defface erc-dangerous-host-face '((t (:foreground "red")))
+ "ERC face for people on dangerous hosts.
+See `erc-dangerous-hosts'."
+ :group 'erc-faces)
+
+(defface erc-pal-face '((t (:bold t :foreground "Magenta")))
+ "ERC face for your pals.
+See `erc-pals'."
+ :group 'erc-faces)
+
+(defface erc-fool-face '((t (:foreground "dim gray")))
+ "ERC face for fools on the channel.
+See `erc-fools'."
+ :group 'erc-faces)
+
+(defface erc-keyword-face '((t (:bold t :foreground "pale green")))
+ "ERC face for your keywords.
+Note that this is the default face to use if
+`erc-keywords' does not specify another."
+ :group 'erc-faces)
+
+;; Functions:
+
+(defun erc-add-entry-to-list (list prompt &optional completions)
+ "Add an entry interactively to a list.
+LIST must be passed as a symbol
+The query happens using PROMPT.
+Completion is performed on the optional alist COMPLETIONS."
+ (let ((entry (completing-read
+ prompt
+ completions
+ (lambda (x)
+ (not (erc-member-ignore-case (car x) (symbol-value list)))))))
+ (if (erc-member-ignore-case entry (symbol-value list))
+ (error (format "\"%s\" is already on the list" entry))
+ (set list (cons entry (symbol-value list))))))
+
+(defun erc-remove-entry-from-list (list prompt)
+ "Remove an entry interactively from a list.
+LIST must be passed as a symbol.
+The elements of LIST can be strings, or cons cells where the
+car is the string."
+ (let* ((alist (mapcar (lambda (x)
+ (if (listp x)
+ x
+ (list x)))
+ (symbol-value list)))
+ (entry (completing-read
+ prompt
+ alist
+ nil
+ t)))
+ (if (erc-member-ignore-case entry (symbol-value list))
+ ;; plain string
+ (set list (delete entry (symbol-value list)))
+ ;; cons cell
+ (set list (delete (assoc entry (symbol-value list))
+ (symbol-value list))))))
+
+;;;###autoload
+(defun erc-add-pal ()
+ "Add pal interactively to `erc-pals'."
+ (interactive)
+ (erc-add-entry-to-list 'erc-pals "Add pal: " (erc-get-server-nickname-alist)))
+
+;;;###autoload
+(defun erc-delete-pal ()
+ "Delete pal interactively to `erc-pals'."
+ (interactive)
+ (erc-remove-entry-from-list 'erc-pals "Delete pal: "))
+
+;;;###autoload
+(defun erc-add-fool ()
+ "Add fool interactively to `erc-fools'."
+ (interactive)
+ (erc-add-entry-to-list 'erc-fools "Add fool: "
+ (erc-get-server-nickname-alist)))
+
+;;;###autoload
+(defun erc-delete-fool ()
+ "Delete fool interactively to `erc-fools'."
+ (interactive)
+ (erc-remove-entry-from-list 'erc-fools "Delete fool: "))
+
+;;;###autoload
+(defun erc-add-keyword ()
+ "Add keyword interactively to `erc-keywords'."
+ (interactive)
+ (erc-add-entry-to-list 'erc-keywords "Add keyword: "))
+
+;;;###autoload
+(defun erc-delete-keyword ()
+ "Delete keyword interactively to `erc-keywords'."
+ (interactive)
+ (erc-remove-entry-from-list 'erc-keywords "Delete keyword: "))
+
+;;;###autoload
+(defun erc-add-dangerous-host ()
+ "Add dangerous-host interactively to `erc-dangerous-hosts'."
+ (interactive)
+ (erc-add-entry-to-list 'erc-dangerous-hosts "Add dangerous-host: "))
+
+;;;###autoload
+(defun erc-delete-dangerous-host ()
+ "Delete dangerous-host interactively to `erc-dangerous-hosts'."
+ (interactive)
+ (erc-remove-entry-from-list 'erc-dangerous-hosts "Delete dangerous-host: "))
+
+(defun erc-match-current-nick-p (nickuserhost msg)
+ "Check whether the current nickname is in MSG.
+NICKUSERHOST will be ignored."
+ (with-syntax-table erc-match-syntax-table
+ (and msg
+ (string-match (concat "\\b"
+ (regexp-quote (erc-current-nick))
+ "\\b")
+ msg))))
+
+(defun erc-match-pal-p (nickuserhost msg)
+ "Check whether NICKUSERHOST is in `erc-pals'.
+MSG will be ignored."
+ (and nickuserhost
+ (erc-list-match erc-pals nickuserhost)))
+
+(defun erc-match-fool-p (nickuserhost msg)
+ "Check whether NICKUSERHOST is in `erc-fools' or MSG is directed at a fool."
+ (and msg nickuserhost
+ (or (erc-list-match erc-fools nickuserhost)
+ (erc-match-directed-at-fool-p msg))))
+
+(defun erc-match-keyword-p (nickuserhost msg)
+ "Check whether any keyword of `erc-keywords' matches for MSG.
+NICKUSERHOST will be ignored."
+ (and msg
+ (erc-list-match
+ (mapcar (lambda (x)
+ (if (listp x)
+ (car x)
+ x))
+ erc-keywords)
+ msg)))
+
+(defun erc-match-dangerous-host-p (nickuserhost msg)
+ "Check whether NICKUSERHOST is in `erc-dangerous-hosts'.
+MSG will be ignored."
+ (and nickuserhost
+ (erc-list-match erc-dangerous-hosts nickuserhost)))
+
+(defun erc-match-directed-at-fool-p (msg)
+ "Check whether MSG is directed at a fool.
+In order to do this, every entry in `erc-fools' will be used.
+In any of the following situations, MSG is directed at an entry FOOL:
+
+- MSG starts with \"FOOL: \" or \"FOO, \"
+- MSG contains \", FOOL.\" (actually, \"\\s. FOOL\\s.\")"
+ (let ((fools-beg (mapcar (lambda (entry)
+ (concat "^" entry "[:,] "))
+ erc-fools))
+ (fools-end (mapcar (lambda (entry)
+ (concat "\\s. " entry "\\s."))
+ erc-fools)))
+ (or (erc-list-match fools-beg msg)
+ (erc-list-match fools-end msg))))
+
+(defun erc-get-parsed-vector (point)
+ "Return the whole parsed vector on POINT."
+ (get-text-property point 'erc-parsed))
+
+(defun erc-get-parsed-vector-nick (vect)
+ "Return nickname in the parsed vector VECT."
+ (let* ((untreated-nick (and vect (erc-response.sender vect)))
+ (maybe-nick (when untreated-nick
+ (car (split-string untreated-nick "!")))))
+ (when (and (not (null maybe-nick))
+ (erc-is-valid-nick-p maybe-nick))
+ untreated-nick)))
+
+(defun erc-get-parsed-vector-type (vect)
+ "Return message type in the parsed vector VECT."
+ (and vect
+ (erc-response.command vect)))
+
+(defun erc-match-message ()
+ "Mark certain keywords in a region.
+Use this defun with `erc-insert-modify-hook'."
+ ;; This needs some refactoring.
+ (goto-char (point-min))
+ (let* ((to-match-nick-dep '("pal" "fool" "dangerous-host"))
+ (to-match-nick-indep '("keyword" "current-nick"))
+ (vector (erc-get-parsed-vector (point-min)))
+ (nickuserhost (erc-get-parsed-vector-nick vector))
+ (nickname (and nickuserhost
+ (nth 0 (erc-parse-user nickuserhost))))
+ (old-pt (point))
+ (nick-beg (and nickname
+ (re-search-forward (regexp-quote nickname)
+ (point-max) t)
+ (match-beginning 0)))
+ (nick-end (when nick-beg
+ (match-end 0)))
+ (message (buffer-substring (if (and nick-end
+ (<= (+ 2 nick-end) (point-max)))
+ (+ 2 nick-end)
+ (point-min))
+ (point-max))))
+ (when vector
+ (mapc
+ (lambda (match-type)
+ (goto-char (point-min))
+ (let* ((match-prefix (concat "erc-" match-type))
+ (match-pred (intern (concat "erc-match-" match-type "-p")))
+ (match-htype (eval (intern (concat match-prefix
+ "-highlight-type"))))
+ (match-regex (if (string= match-type "current-nick")
+ (regexp-quote (erc-current-nick))
+ (eval (intern (concat match-prefix "s")))))
+ (match-face (intern (concat match-prefix "-face"))))
+ (when (funcall match-pred nickuserhost message)
+ (cond
+ ;; Highlight the nick of the message
+ ((and (eq match-htype 'nick)
+ nick-end)
+ (erc-put-text-property
+ nick-beg nick-end
+ 'face match-face (current-buffer)))
+ ;; Highlight the nick of the message, or the current
+ ;; nick if there's no nick in the message (e.g. /NAMES
+ ;; output)
+ ((and (string= match-type "current-nick")
+ (eq match-htype 'nick-or-keyword))
+ (if nick-end
+ (erc-put-text-property
+ nick-beg nick-end
+ 'face match-face (current-buffer))
+ (goto-char (+ 2 (or nick-end
+ (point-min))))
+ (while (re-search-forward match-regex nil t)
+ (erc-put-text-property (match-beginning 0) (match-end 0)
+ 'face match-face))))
+ ;; Highlight the whole message
+ ((eq match-htype 'all)
+ (erc-put-text-property
+ (point-min) (point-max)
+ 'face match-face (current-buffer)))
+ ;; Highlight all occurrences of the word to be
+ ;; highlighted.
+ ((and (string= match-type "keyword")
+ (eq match-htype 'keyword))
+ (mapc (lambda (elt)
+ (let ((regex elt)
+ (face match-face))
+ (when (consp regex)
+ (setq regex (car elt)
+ face (cdr elt)))
+ (goto-char (+ 2 (or nick-end
+ (point-min))))
+ (while (re-search-forward regex nil t)
+ (erc-put-text-property
+ (match-beginning 0) (match-end 0)
+ 'face face))))
+ match-regex))
+ ;; Highlight all occurrences of our nick.
+ ((and (string= match-type "current-nick")
+ (eq match-htype 'keyword))
+ (goto-char (+ 2 (or nick-end
+ (point-min))))
+ (while (re-search-forward match-regex nil t)
+ (erc-put-text-property (match-beginning 0) (match-end 0)
+ 'face match-face)))
+ ;; Else twiddle your thumbs.
+ (t nil))
+ (run-hook-with-args
+ 'erc-text-matched-hook
+ (intern match-type)
+ (or nickuserhost
+ (concat "Server:" (erc-get-parsed-vector-type vector)))
+ message))))
+ (if nickuserhost
+ (append to-match-nick-dep to-match-nick-indep)
+ to-match-nick-indep)))))
+
+(defun erc-log-matches (match-type nickuserhost message)
+ "Log matches in a separate buffer, determined by MATCH-TYPE.
+The behaviour of this function is controlled by the variables
+`erc-log-matches-types-alist' and `erc-log-matches-flag'. Specify the
+match types which should be logged in the former, and
+deactivate/activate match logging in the latter. See
+`erc-log-match-format'."
+ (let ((match-buffer-name (cdr (assq match-type
+ erc-log-matches-types-alist)))
+ (nick (nth 0 (erc-parse-user nickuserhost))))
+ (when (and
+ (or (eq erc-log-matches-flag t)
+ (and (eq erc-log-matches-flag 'away)
+ erc-away))
+ match-buffer-name)
+ (let ((line (format-spec erc-log-match-format
+ (format-spec-make
+ ?n nick
+ ?t (format-time-string
+ (or (and (boundp 'erc-timestamp-format)
+ erc-timestamp-format)
+ "[%Y-%m-%d %H:%M] "))
+ ?c (or (erc-default-target) "")
+ ?m message
+ ?u nickuserhost))))
+ (with-current-buffer (erc-log-matches-make-buffer match-buffer-name)
+ (toggle-read-only -1)
+ (point-max)
+ (insert line)
+ (toggle-read-only 1))))))
+
+(defun erc-log-matches-make-buffer (name)
+ "Create or get a log-matches buffer named NAME and return it."
+ (let* ((buffer-already (get-buffer name))
+ (buffer (or buffer-already
+ (get-buffer-create name))))
+ (with-current-buffer buffer
+ (unless buffer-already
+ (insert " == Type \"q\" to dismiss messages ==\n")
+ (erc-view-mode-enter nil (lambda (buffer)
+ (when (y-or-n-p "Discard messages?")
+ (kill-buffer buffer)))))
+ buffer)))
+
+(defun erc-log-matches-come-back (proc parsed)
+ "Display a notice that messages were logged while away."
+ (when (and erc-away
+ (eq erc-log-matches-flag 'away))
+ (mapc
+ (lambda (match-type)
+ (let ((buffer (get-buffer (cdr match-type)))
+ (buffer-name (cdr match-type)))
+ (when buffer
+ (let* ((last-msg-time (erc-emacs-time-to-erc-time
+ (with-current-buffer buffer
+ (get-text-property (1- (point-max))
+ 'timestamp))))
+ (away-time (erc-emacs-time-to-erc-time erc-away)))
+ (when (and away-time last-msg-time
+ (erc-time-gt last-msg-time away-time))
+ (erc-display-message
+ nil 'notice 'active
+ (format "You have logged messages waiting in \"%s\"."
+ buffer-name))
+ (erc-display-message
+ nil 'notice 'active
+ (format "Type \"C-c C-k %s RET\" to view them."
+ buffer-name)))))))
+ erc-log-matches-types-alist))
+ nil)
+
+; This handler must be run _before_ erc-process-away is.
+(add-hook 'erc-server-305-functions 'erc-log-matches-come-back nil)
+
+(defun erc-go-to-log-matches-buffer ()
+ "Interactively open an erc-log-matches buffer."
+ (interactive)
+ (let ((buffer-name (completing-read "Switch to ERC Log buffer: "
+ (mapcar (lambda (x)
+ (cons (cdr x) t))
+ erc-log-matches-types-alist)
+ (lambda (buffer-cons)
+ (get-buffer (car buffer-cons))))))
+ (switch-to-buffer buffer-name)))
+
+(define-key erc-mode-map "\C-c\C-k" 'erc-go-to-log-matches-buffer)
+
+(defun erc-hide-fools (match-type nickuserhost message)
+ "Hide foolish comments.
+This function should be called from `erc-text-matched-hook'."
+ (when (eq match-type 'fool)
+ (erc-put-text-properties (point-min) (point-max)
+ '(invisible intangible)
+ (current-buffer))))
+
+(defun erc-beep-on-match (match-type nickuserhost message)
+ "Beep when text matches.
+This function is meant to be called from `erc-text-matched-hook'."
+ (when (member match-type erc-beep-match-types)
+ (beep)))
+
+(provide 'erc-match)
+
+;;; erc-match.el ends here
+;;
+;; Local Variables:
+;; indent-tabs-mode: t
+;; tab-width: 8
+;; End:
+
+;; arch-tag: 1f1f595e-abcc-4b0b-83db-598a1d3f0f82