1 ;;; erc-match.el --- Highlight messages matching certain regexps
3 ;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
5 ;; Author: Andreas Fuchs <asf@void.at>
6 ;; Keywords: comm, faces
7 ;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcMatch
9 ;; This file is part of GNU Emacs.
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 2, or (at your option)
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
28 ;; This file includes stuff to work with pattern matching in ERC. If
29 ;; you were used to customizing erc-fools, erc-keywords, erc-pals,
30 ;; erc-dangerous-hosts and the like, this file contains these
31 ;; customizable variables.
34 ;; Put (erc-match-mode 1) into your ~/.emacs file.
39 (eval-when-compile (require 'cl))
43 (defconst erc-match-version "$Revision: 1.51.2.1 $"
44 "ERC match mode revision.")
46 (defgroup erc-match nil
47 "Keyword and Friend/Foe/... recognition.
48 Group containing all things concerning pattern matching in ERC
52 ;;;###autoload (autoload 'erc-match-mode "erc-match")
53 (define-erc-module match nil
54 "This mode checks whether messages match certain patterns. If so,
55 they are hidden or highlighted. This is controlled via the variables
56 `erc-pals', `erc-fools', `erc-keywords', `erc-dangerous-hosts', and
57 `erc-current-nick-highlight-type'. For all these highlighting types,
58 you can decide whether the entire message or only the sending nick is
60 ((add-hook 'erc-insert-modify-hook 'erc-match-message 'append))
61 ((remove-hook 'erc-insert-modify-hook 'erc-match-message)))
63 ;; Remaining customizations
65 (defcustom erc-pals nil
66 "List of pals on IRC."
68 :type '(repeat regexp))
70 (defcustom erc-fools nil
71 "List of fools on IRC."
73 :type '(repeat regexp))
75 (defcustom erc-keywords nil
76 "List of keywords to highlight in all incoming messages.
77 Each entry in the list is either a regexp, or a cons cell with the
78 regexp in the car and the face to use in the cdr. If no face is
79 specified, `erc-keyword-face' is used."
81 :type '(repeat (choice regexp
84 (defcustom erc-dangerous-hosts nil
85 "List of regexps for hosts to highlight.
86 Useful to mark nicks from dangerous hosts."
88 :type '(repeat regexp))
90 (defcustom erc-current-nick-highlight-type 'keyword
91 "*Determines how to highlight text in which your current nickname appears
92 \(does not apply to text sent by you\).
94 The following values are allowed:
96 nil - do not highlight the message at all
97 'keyword - highlight all instances of current nickname in message
98 'nick - highlight the nick of the user who typed your nickname
99 'nick-or-keyword - highlight the nick of the user who typed your nickname,
100 or all instances of the current nickname if there was
102 'all - highlight the entire message where current nickname occurs
104 Any other value disables highlighting of current nickname altogether."
106 :type '(choice (const nil)
109 (const nick-or-keyword)
112 (defcustom erc-pal-highlight-type 'nick
113 "*Determines how to highlight messages by pals.
116 The following values are allowed:
118 nil - do not highlight the message at all
119 'nick - highlight pal's nickname only
120 'all - highlight the entire message from pal
122 Any other value disables pal highlighting altogether."
124 :type '(choice (const nil)
128 (defcustom erc-fool-highlight-type 'nick
129 "*Determines how to highlight messages by fools.
132 The following values are allowed:
134 nil - do not highlight the message at all
135 'nick - highlight fool's nickname only
136 'all - highlight the entire message from fool
138 Any other value disables fool highlighting altogether."
140 :type '(choice (const nil)
144 (defcustom erc-keyword-highlight-type 'keyword
145 "*Determines how to highlight messages containing keywords.
146 See variable `erc-keywords'.
148 The following values are allowed:
150 'keyword - highlight keyword only
151 'all - highlight the entire message containing keyword
153 Any other value disables keyword highlighting altogether."
155 :type '(choice (const nil)
159 (defcustom erc-dangerous-host-highlight-type 'nick
160 "*Determines how to highlight messages by nicks from dangerous-hosts.
161 See `erc-dangerous-hosts'.
163 The following values are allowed:
165 'nick - highlight nick from dangerous-host only
166 'all - highlight the entire message from dangerous-host
168 Any other value disables dangerous-host highlighting altogether."
170 :type '(choice (const nil)
175 (defcustom erc-log-matches-types-alist '((keyword . "ERC Keywords"))
176 "Alist telling ERC where to log which match types.
177 Valid match type keys are:
184 The other element of each cons pair in this list is the buffer name to
185 use for the logged message."
187 :type '(repeat (cons (choice :tag "Key"
190 (const dangerous-host)
192 (const current-nick))
193 (string :tag "Buffer name"))))
195 (defcustom erc-log-matches-flag 'away
196 "Flag specifying when matched message logging should happen.
197 When nil, don't log any matched messages.
198 When t, log messages.
199 When 'away, log messages only when away."
201 :type '(choice (const nil)
205 (defcustom erc-log-match-format "%t<%n:%c> %m"
206 "Format for matched Messages.
207 This variable specifies how messages in the corresponding log buffers will
208 be formatted. The various format specs are:
210 %t Timestamp (uses `erc-timestamp-format' if non-nil or \"[%Y-%m-%d %H:%M] \")
211 %n Nickname of sender
212 %u Nickname!user@host of sender
213 %c Channel in which this was received
218 (defcustom erc-beep-match-types '(current-nick)
219 "Types of matches to beep for when a match occurs.
220 The function `erc-beep-on-match' needs to be added to `erc-text-matched-hook'
221 for beeping to work."
223 :type '(choice (repeat :tag "Beep on match" (choice
227 (const dangerous-host)
229 (const :tag "Don't beep" nil)))
231 (defcustom erc-text-matched-hook '(erc-log-matches)
232 "Hook run when text matches a given match-type.
233 Functions in this hook are passed as arguments:
234 \(match-type nick!user@host message) where MATCH-TYPE is a symbol of:
235 current-nick, keyword, pal, dangerous-host, fool"
236 :options '(erc-log-matches erc-hide-fools erc-beep-on-match)
240 ;; Internal variables:
242 ;; This is exactly the same as erc-button-syntax-table. Should we
243 ;; just put it in erc.el
244 (defvar erc-match-syntax-table
245 (let ((table (make-syntax-table)))
246 (modify-syntax-entry ?\( "w" table)
247 (modify-syntax-entry ?\) "w" table)
248 (modify-syntax-entry ?\[ "w" table)
249 (modify-syntax-entry ?\] "w" table)
250 (modify-syntax-entry ?\{ "w" table)
251 (modify-syntax-entry ?\} "w" table)
252 (modify-syntax-entry ?` "w" table)
253 (modify-syntax-entry ?' "w" table)
254 (modify-syntax-entry ?^ "w" table)
255 (modify-syntax-entry ?- "w" table)
256 (modify-syntax-entry ?_ "w" table)
257 (modify-syntax-entry ?| "w" table)
258 (modify-syntax-entry ?\\ "w" table)
260 "Syntax table used when highlighting messages.
261 This syntax table should make all the legal nick characters word
266 (defface erc-current-nick-face '((t (:bold t :foreground "DarkTurquoise")))
267 "ERC face for occurrences of your current nickname."
270 (defface erc-dangerous-host-face '((t (:foreground "red")))
271 "ERC face for people on dangerous hosts.
272 See `erc-dangerous-hosts'."
275 (defface erc-pal-face '((t (:bold t :foreground "Magenta")))
276 "ERC face for your pals.
280 (defface erc-fool-face '((t (:foreground "dim gray")))
281 "ERC face for fools on the channel.
285 (defface erc-keyword-face '((t (:bold t :foreground "pale green")))
286 "ERC face for your keywords.
287 Note that this is the default face to use if
288 `erc-keywords' does not specify another."
293 (defun erc-add-entry-to-list (list prompt &optional completions)
294 "Add an entry interactively to a list.
295 LIST must be passed as a symbol
296 The query happens using PROMPT.
297 Completion is performed on the optional alist COMPLETIONS."
298 (let ((entry (completing-read
302 (not (erc-member-ignore-case (car x) (symbol-value list)))))))
303 (if (erc-member-ignore-case entry (symbol-value list))
304 (error (format "\"%s\" is already on the list" entry))
305 (set list (cons entry (symbol-value list))))))
307 (defun erc-remove-entry-from-list (list prompt)
308 "Remove an entry interactively from a list.
309 LIST must be passed as a symbol.
310 The elements of LIST can be strings, or cons cells where the
312 (let* ((alist (mapcar (lambda (x)
316 (symbol-value list)))
317 (entry (completing-read
322 (if (erc-member-ignore-case entry (symbol-value list))
324 (set list (delete entry (symbol-value list)))
326 (set list (delete (assoc entry (symbol-value list))
327 (symbol-value list))))))
330 (defun erc-add-pal ()
331 "Add pal interactively to `erc-pals'."
333 (erc-add-entry-to-list 'erc-pals "Add pal: " (erc-get-server-nickname-alist)))
336 (defun erc-delete-pal ()
337 "Delete pal interactively to `erc-pals'."
339 (erc-remove-entry-from-list 'erc-pals "Delete pal: "))
342 (defun erc-add-fool ()
343 "Add fool interactively to `erc-fools'."
345 (erc-add-entry-to-list 'erc-fools "Add fool: "
346 (erc-get-server-nickname-alist)))
349 (defun erc-delete-fool ()
350 "Delete fool interactively to `erc-fools'."
352 (erc-remove-entry-from-list 'erc-fools "Delete fool: "))
355 (defun erc-add-keyword ()
356 "Add keyword interactively to `erc-keywords'."
358 (erc-add-entry-to-list 'erc-keywords "Add keyword: "))
361 (defun erc-delete-keyword ()
362 "Delete keyword interactively to `erc-keywords'."
364 (erc-remove-entry-from-list 'erc-keywords "Delete keyword: "))
367 (defun erc-add-dangerous-host ()
368 "Add dangerous-host interactively to `erc-dangerous-hosts'."
370 (erc-add-entry-to-list 'erc-dangerous-hosts "Add dangerous-host: "))
373 (defun erc-delete-dangerous-host ()
374 "Delete dangerous-host interactively to `erc-dangerous-hosts'."
376 (erc-remove-entry-from-list 'erc-dangerous-hosts "Delete dangerous-host: "))
378 (defun erc-match-current-nick-p (nickuserhost msg)
379 "Check whether the current nickname is in MSG.
380 NICKUSERHOST will be ignored."
381 (with-syntax-table erc-match-syntax-table
383 (string-match (concat "\\b"
384 (regexp-quote (erc-current-nick))
388 (defun erc-match-pal-p (nickuserhost msg)
389 "Check whether NICKUSERHOST is in `erc-pals'.
390 MSG will be ignored."
392 (erc-list-match erc-pals nickuserhost)))
394 (defun erc-match-fool-p (nickuserhost msg)
395 "Check whether NICKUSERHOST is in `erc-fools' or MSG is directed at a fool."
396 (and msg nickuserhost
397 (or (erc-list-match erc-fools nickuserhost)
398 (erc-match-directed-at-fool-p msg))))
400 (defun erc-match-keyword-p (nickuserhost msg)
401 "Check whether any keyword of `erc-keywords' matches for MSG.
402 NICKUSERHOST will be ignored."
412 (defun erc-match-dangerous-host-p (nickuserhost msg)
413 "Check whether NICKUSERHOST is in `erc-dangerous-hosts'.
414 MSG will be ignored."
416 (erc-list-match erc-dangerous-hosts nickuserhost)))
418 (defun erc-match-directed-at-fool-p (msg)
419 "Check whether MSG is directed at a fool.
420 In order to do this, every entry in `erc-fools' will be used.
421 In any of the following situations, MSG is directed at an entry FOOL:
423 - MSG starts with \"FOOL: \" or \"FOO, \"
424 - MSG contains \", FOOL.\" (actually, \"\\s. FOOL\\s.\")"
425 (let ((fools-beg (mapcar (lambda (entry)
426 (concat "^" entry "[:,] "))
428 (fools-end (mapcar (lambda (entry)
429 (concat "\\s. " entry "\\s."))
431 (or (erc-list-match fools-beg msg)
432 (erc-list-match fools-end msg))))
434 (defun erc-get-parsed-vector (point)
435 "Return the whole parsed vector on POINT."
436 (get-text-property point 'erc-parsed))
438 (defun erc-get-parsed-vector-nick (vect)
439 "Return nickname in the parsed vector VECT."
440 (let* ((untreated-nick (and vect (erc-response.sender vect)))
441 (maybe-nick (when untreated-nick
442 (car (split-string untreated-nick "!")))))
443 (when (and (not (null maybe-nick))
444 (erc-is-valid-nick-p maybe-nick))
447 (defun erc-get-parsed-vector-type (vect)
448 "Return message type in the parsed vector VECT."
450 (erc-response.command vect)))
452 (defun erc-match-message ()
453 "Mark certain keywords in a region.
454 Use this defun with `erc-insert-modify-hook'."
455 ;; This needs some refactoring.
456 (goto-char (point-min))
457 (let* ((to-match-nick-dep '("pal" "fool" "dangerous-host"))
458 (to-match-nick-indep '("keyword" "current-nick"))
459 (vector (erc-get-parsed-vector (point-min)))
460 (nickuserhost (erc-get-parsed-vector-nick vector))
461 (nickname (and nickuserhost
462 (nth 0 (erc-parse-user nickuserhost))))
464 (nick-beg (and nickname
465 (re-search-forward (regexp-quote nickname)
467 (match-beginning 0)))
468 (nick-end (when nick-beg
470 (message (buffer-substring (if (and nick-end
471 (<= (+ 2 nick-end) (point-max)))
478 (goto-char (point-min))
479 (let* ((match-prefix (concat "erc-" match-type))
480 (match-pred (intern (concat "erc-match-" match-type "-p")))
481 (match-htype (eval (intern (concat match-prefix
482 "-highlight-type"))))
483 (match-regex (if (string= match-type "current-nick")
484 (regexp-quote (erc-current-nick))
485 (eval (intern (concat match-prefix "s")))))
486 (match-face (intern (concat match-prefix "-face"))))
487 (when (funcall match-pred nickuserhost message)
489 ;; Highlight the nick of the message
490 ((and (eq match-htype 'nick)
492 (erc-put-text-property
494 'face match-face (current-buffer)))
495 ;; Highlight the nick of the message, or the current
496 ;; nick if there's no nick in the message (e.g. /NAMES
498 ((and (string= match-type "current-nick")
499 (eq match-htype 'nick-or-keyword))
501 (erc-put-text-property
503 'face match-face (current-buffer))
504 (goto-char (+ 2 (or nick-end
506 (while (re-search-forward match-regex nil t)
507 (erc-put-text-property (match-beginning 0) (match-end 0)
509 ;; Highlight the whole message
510 ((eq match-htype 'all)
511 (erc-put-text-property
512 (point-min) (point-max)
513 'face match-face (current-buffer)))
514 ;; Highlight all occurrences of the word to be
516 ((and (string= match-type "keyword")
517 (eq match-htype 'keyword))
522 (setq regex (car elt)
524 (goto-char (+ 2 (or nick-end
526 (while (re-search-forward regex nil t)
527 (erc-put-text-property
528 (match-beginning 0) (match-end 0)
531 ;; Highlight all occurrences of our nick.
532 ((and (string= match-type "current-nick")
533 (eq match-htype 'keyword))
534 (goto-char (+ 2 (or nick-end
536 (while (re-search-forward match-regex nil t)
537 (erc-put-text-property (match-beginning 0) (match-end 0)
539 ;; Else twiddle your thumbs.
542 'erc-text-matched-hook
545 (concat "Server:" (erc-get-parsed-vector-type vector)))
548 (append to-match-nick-dep to-match-nick-indep)
549 to-match-nick-indep)))))
551 (defun erc-log-matches (match-type nickuserhost message)
552 "Log matches in a separate buffer, determined by MATCH-TYPE.
553 The behaviour of this function is controlled by the variables
554 `erc-log-matches-types-alist' and `erc-log-matches-flag'. Specify the
555 match types which should be logged in the former, and
556 deactivate/activate match logging in the latter. See
557 `erc-log-match-format'."
558 (let ((match-buffer-name (cdr (assq match-type
559 erc-log-matches-types-alist)))
560 (nick (nth 0 (erc-parse-user nickuserhost))))
562 (or (eq erc-log-matches-flag t)
563 (and (eq erc-log-matches-flag 'away)
566 (let ((line (format-spec erc-log-match-format
569 ?t (format-time-string
570 (or (and (boundp 'erc-timestamp-format)
571 erc-timestamp-format)
572 "[%Y-%m-%d %H:%M] "))
573 ?c (or (erc-default-target) "")
576 (with-current-buffer (erc-log-matches-make-buffer match-buffer-name)
577 (toggle-read-only -1)
580 (toggle-read-only 1))))))
582 (defun erc-log-matches-make-buffer (name)
583 "Create or get a log-matches buffer named NAME and return it."
584 (let* ((buffer-already (get-buffer name))
585 (buffer (or buffer-already
586 (get-buffer-create name))))
587 (with-current-buffer buffer
588 (unless buffer-already
589 (insert " == Type \"q\" to dismiss messages ==\n")
590 (erc-view-mode-enter nil (lambda (buffer)
591 (when (y-or-n-p "Discard messages?")
592 (kill-buffer buffer)))))
595 (defun erc-log-matches-come-back (proc parsed)
596 "Display a notice that messages were logged while away."
598 (eq erc-log-matches-flag 'away))
601 (let ((buffer (get-buffer (cdr match-type)))
602 (buffer-name (cdr match-type)))
604 (let* ((last-msg-time (erc-emacs-time-to-erc-time
605 (with-current-buffer buffer
606 (get-text-property (1- (point-max))
608 (away-time (erc-emacs-time-to-erc-time erc-away)))
609 (when (and away-time last-msg-time
610 (erc-time-gt last-msg-time away-time))
613 (format "You have logged messages waiting in \"%s\"."
617 (format "Type \"C-c C-k %s RET\" to view them."
619 erc-log-matches-types-alist))
622 ; This handler must be run _before_ erc-process-away is.
623 (add-hook 'erc-server-305-functions 'erc-log-matches-come-back nil)
625 (defun erc-go-to-log-matches-buffer ()
626 "Interactively open an erc-log-matches buffer."
628 (let ((buffer-name (completing-read "Switch to ERC Log buffer: "
631 erc-log-matches-types-alist)
632 (lambda (buffer-cons)
633 (get-buffer (car buffer-cons))))))
634 (switch-to-buffer buffer-name)))
636 (define-key erc-mode-map "\C-c\C-k" 'erc-go-to-log-matches-buffer)
638 (defun erc-hide-fools (match-type nickuserhost message)
639 "Hide foolish comments.
640 This function should be called from `erc-text-matched-hook'."
641 (when (eq match-type 'fool)
642 (erc-put-text-properties (point-min) (point-max)
643 '(invisible intangible)
646 (defun erc-beep-on-match (match-type nickuserhost message)
647 "Beep when text matches.
648 This function is meant to be called from `erc-text-matched-hook'."
649 (when (member match-type erc-beep-match-types)
654 ;;; erc-match.el ends here
657 ;; indent-tabs-mode: t
661 ;; arch-tag: 1f1f595e-abcc-4b0b-83db-598a1d3f0f82