1 ;;; erc-match.el --- Highlight messages matching certain regexps
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006,
4 ;; 2007 Free Software Foundation, Inc.
6 ;; Author: Andreas Fuchs <asf@void.at>
7 ;; Keywords: comm, faces
8 ;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcMatch
10 ;; This file is part of GNU Emacs.
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 3, or (at your option)
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.
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.
29 ;; This file includes stuff to work with pattern matching in ERC. If
30 ;; you were used to customizing erc-fools, erc-keywords, erc-pals,
31 ;; erc-dangerous-hosts and the like, this file contains these
32 ;; customizable variables.
35 ;; Put (erc-match-mode 1) into your ~/.emacs file.
40 (eval-when-compile (require 'cl
))
44 (defgroup erc-match nil
45 "Keyword and Friend/Foe/... recognition.
46 Group containing all things concerning pattern matching in ERC
50 ;;;###autoload (autoload 'erc-match-mode "erc-match")
51 (define-erc-module match nil
52 "This mode checks whether messages match certain patterns. If so,
53 they are hidden or highlighted. This is controlled via the variables
54 `erc-pals', `erc-fools', `erc-keywords', `erc-dangerous-hosts', and
55 `erc-current-nick-highlight-type'. For all these highlighting types,
56 you can decide whether the entire message or only the sending nick is
58 ((add-hook 'erc-insert-modify-hook
'erc-match-message
'append
))
59 ((remove-hook 'erc-insert-modify-hook
'erc-match-message
)))
61 ;; Remaining customizations
63 (defcustom erc-pals nil
64 "List of pals on IRC."
66 :type
'(repeat regexp
))
68 (defcustom erc-fools nil
69 "List of fools on IRC."
71 :type
'(repeat regexp
))
73 (defcustom erc-keywords nil
74 "List of keywords to highlight in all incoming messages.
75 Each entry in the list is either a regexp, or a cons cell with the
76 regexp in the car and the face to use in the cdr. If no face is
77 specified, `erc-keyword-face' is used."
79 :type
'(repeat (choice regexp
82 (defcustom erc-dangerous-hosts nil
83 "List of regexps for hosts to highlight.
84 Useful to mark nicks from dangerous hosts."
86 :type
'(repeat regexp
))
88 (defcustom erc-current-nick-highlight-type
'keyword
89 "*Determines how to highlight text in which your current nickname appears
90 \(does not apply to text sent by you\).
92 The following values are allowed:
94 nil - do not highlight the message at all
95 'keyword - highlight all instances of current nickname in message
96 'nick - highlight the nick of the user who typed your nickname
97 'nick-or-keyword - highlight the nick of the user who typed your nickname,
98 or all instances of the current nickname if there was
100 'all - highlight the entire message where current nickname occurs
102 Any other value disables highlighting of current nickname altogether."
104 :type
'(choice (const nil
)
107 (const nick-or-keyword
)
110 (defcustom erc-pal-highlight-type
'nick
111 "*Determines how to highlight messages by pals.
114 The following values are allowed:
116 nil - do not highlight the message at all
117 'nick - highlight pal's nickname only
118 'all - highlight the entire message from pal
120 Any other value disables pal highlighting altogether."
122 :type
'(choice (const nil
)
126 (defcustom erc-fool-highlight-type
'nick
127 "*Determines how to highlight messages by fools.
130 The following values are allowed:
132 nil - do not highlight the message at all
133 'nick - highlight fool's nickname only
134 'all - highlight the entire message from fool
136 Any other value disables fool highlighting altogether."
138 :type
'(choice (const nil
)
142 (defcustom erc-keyword-highlight-type
'keyword
143 "*Determines how to highlight messages containing keywords.
144 See variable `erc-keywords'.
146 The following values are allowed:
148 'keyword - highlight keyword only
149 'all - highlight the entire message containing keyword
151 Any other value disables keyword highlighting altogether."
153 :type
'(choice (const nil
)
157 (defcustom erc-dangerous-host-highlight-type
'nick
158 "*Determines how to highlight messages by nicks from dangerous-hosts.
159 See `erc-dangerous-hosts'.
161 The following values are allowed:
163 'nick - highlight nick from dangerous-host only
164 'all - highlight the entire message from dangerous-host
166 Any other value disables dangerous-host highlighting altogether."
168 :type
'(choice (const nil
)
173 (defcustom erc-log-matches-types-alist
'((keyword .
"ERC Keywords"))
174 "Alist telling ERC where to log which match types.
175 Valid match type keys are:
182 The other element of each cons pair in this list is the buffer name to
183 use for the logged message."
185 :type
'(repeat (cons (choice :tag
"Key"
188 (const dangerous-host
)
190 (const current-nick
))
191 (string :tag
"Buffer name"))))
193 (defcustom erc-log-matches-flag
'away
194 "Flag specifying when matched message logging should happen.
195 When nil, don't log any matched messages.
196 When t, log messages.
197 When 'away, log messages only when away."
199 :type
'(choice (const nil
)
203 (defcustom erc-log-match-format
"%t<%n:%c> %m"
204 "Format for matched Messages.
205 This variable specifies how messages in the corresponding log buffers will
206 be formatted. The various format specs are:
208 %t Timestamp (uses `erc-timestamp-format' if non-nil or \"[%Y-%m-%d %H:%M] \")
209 %n Nickname of sender
210 %u Nickname!user@host of sender
211 %c Channel in which this was received
216 (defcustom erc-beep-match-types
'(current-nick)
217 "Types of matches to beep for when a match occurs.
218 The function `erc-beep-on-match' needs to be added to `erc-text-matched-hook'
219 for beeping to work."
221 :type
'(choice (repeat :tag
"Beep on match" (choice
225 (const dangerous-host
)
227 (const :tag
"Don't beep" nil
)))
229 (defcustom erc-text-matched-hook
'(erc-log-matches)
230 "Hook run when text matches a given match-type.
231 Functions in this hook are passed as arguments:
232 \(match-type nick!user@host message) where MATCH-TYPE is a symbol of:
233 current-nick, keyword, pal, dangerous-host, fool"
234 :options
'(erc-log-matches erc-hide-fools erc-beep-on-match
)
238 ;; Internal variables:
240 ;; This is exactly the same as erc-button-syntax-table. Should we
241 ;; just put it in erc.el
242 (defvar erc-match-syntax-table
243 (let ((table (make-syntax-table)))
244 (modify-syntax-entry ?\
( "w" table
)
245 (modify-syntax-entry ?\
) "w" 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
)
258 "Syntax table used when highlighting messages.
259 This syntax table should make all the legal nick characters word
264 (defface erc-current-nick-face
'((t (:bold t
:foreground
"DarkTurquoise")))
265 "ERC face for occurrences of your current nickname."
268 (defface erc-dangerous-host-face
'((t (:foreground
"red")))
269 "ERC face for people on dangerous hosts.
270 See `erc-dangerous-hosts'."
273 (defface erc-pal-face
'((t (:bold t
:foreground
"Magenta")))
274 "ERC face for your pals.
278 (defface erc-fool-face
'((t (:foreground
"dim gray")))
279 "ERC face for fools on the channel.
283 (defface erc-keyword-face
'((t (:bold t
:foreground
"pale green")))
284 "ERC face for your keywords.
285 Note that this is the default face to use if
286 `erc-keywords' does not specify another."
291 (defun erc-add-entry-to-list (list prompt
&optional completions
)
292 "Add an entry interactively to a list.
293 LIST must be passed as a symbol
294 The query happens using PROMPT.
295 Completion is performed on the optional alist COMPLETIONS."
296 (let ((entry (completing-read
300 (not (erc-member-ignore-case (car x
) (symbol-value list
)))))))
301 (if (erc-member-ignore-case entry
(symbol-value list
))
302 (error (format "\"%s\" is already on the list" entry
))
303 (set list
(cons entry
(symbol-value list
))))))
305 (defun erc-remove-entry-from-list (list prompt
)
306 "Remove an entry interactively from a list.
307 LIST must be passed as a symbol.
308 The elements of LIST can be strings, or cons cells where the
310 (let* ((alist (mapcar (lambda (x)
314 (symbol-value list
)))
315 (entry (completing-read
320 (if (erc-member-ignore-case entry
(symbol-value list
))
322 (set list
(delete entry
(symbol-value list
)))
324 (set list
(delete (assoc entry
(symbol-value list
))
325 (symbol-value list
))))))
328 (defun erc-add-pal ()
329 "Add pal interactively to `erc-pals'."
331 (erc-add-entry-to-list 'erc-pals
"Add pal: " (erc-get-server-nickname-alist)))
334 (defun erc-delete-pal ()
335 "Delete pal interactively to `erc-pals'."
337 (erc-remove-entry-from-list 'erc-pals
"Delete pal: "))
340 (defun erc-add-fool ()
341 "Add fool interactively to `erc-fools'."
343 (erc-add-entry-to-list 'erc-fools
"Add fool: "
344 (erc-get-server-nickname-alist)))
347 (defun erc-delete-fool ()
348 "Delete fool interactively to `erc-fools'."
350 (erc-remove-entry-from-list 'erc-fools
"Delete fool: "))
353 (defun erc-add-keyword ()
354 "Add keyword interactively to `erc-keywords'."
356 (erc-add-entry-to-list 'erc-keywords
"Add keyword: "))
359 (defun erc-delete-keyword ()
360 "Delete keyword interactively to `erc-keywords'."
362 (erc-remove-entry-from-list 'erc-keywords
"Delete keyword: "))
365 (defun erc-add-dangerous-host ()
366 "Add dangerous-host interactively to `erc-dangerous-hosts'."
368 (erc-add-entry-to-list 'erc-dangerous-hosts
"Add dangerous-host: "))
371 (defun erc-delete-dangerous-host ()
372 "Delete dangerous-host interactively to `erc-dangerous-hosts'."
374 (erc-remove-entry-from-list 'erc-dangerous-hosts
"Delete dangerous-host: "))
376 (defun erc-match-current-nick-p (nickuserhost msg
)
377 "Check whether the current nickname is in MSG.
378 NICKUSERHOST will be ignored."
379 (with-syntax-table erc-match-syntax-table
381 (string-match (concat "\\b"
382 (regexp-quote (erc-current-nick))
386 (defun erc-match-pal-p (nickuserhost msg
)
387 "Check whether NICKUSERHOST is in `erc-pals'.
388 MSG will be ignored."
390 (erc-list-match erc-pals nickuserhost
)))
392 (defun erc-match-fool-p (nickuserhost msg
)
393 "Check whether NICKUSERHOST is in `erc-fools' or MSG is directed at a fool."
394 (and msg nickuserhost
395 (or (erc-list-match erc-fools nickuserhost
)
396 (erc-match-directed-at-fool-p msg
))))
398 (defun erc-match-keyword-p (nickuserhost msg
)
399 "Check whether any keyword of `erc-keywords' matches for MSG.
400 NICKUSERHOST will be ignored."
410 (defun erc-match-dangerous-host-p (nickuserhost msg
)
411 "Check whether NICKUSERHOST is in `erc-dangerous-hosts'.
412 MSG will be ignored."
414 (erc-list-match erc-dangerous-hosts nickuserhost
)))
416 (defun erc-match-directed-at-fool-p (msg)
417 "Check whether MSG is directed at a fool.
418 In order to do this, every entry in `erc-fools' will be used.
419 In any of the following situations, MSG is directed at an entry FOOL:
421 - MSG starts with \"FOOL: \" or \"FOO, \"
422 - MSG contains \", FOOL.\" (actually, \"\\s. FOOL\\s.\")"
423 (let ((fools-beg (mapcar (lambda (entry)
424 (concat "^" entry
"[:,] "))
426 (fools-end (mapcar (lambda (entry)
427 (concat "\\s. " entry
"\\s."))
429 (or (erc-list-match fools-beg msg
)
430 (erc-list-match fools-end msg
))))
432 (defun erc-match-message ()
433 "Mark certain keywords in a region.
434 Use this defun with `erc-insert-modify-hook'."
435 ;; This needs some refactoring.
436 (goto-char (point-min))
437 (let* ((to-match-nick-dep '("pal" "fool" "dangerous-host"))
438 (to-match-nick-indep '("keyword" "current-nick"))
439 (vector (erc-get-parsed-vector (point-min)))
440 (nickuserhost (erc-get-parsed-vector-nick vector
))
441 (nickname (and nickuserhost
442 (nth 0 (erc-parse-user nickuserhost
))))
444 (nick-beg (and nickname
445 (re-search-forward (regexp-quote nickname
)
447 (match-beginning 0)))
448 (nick-end (when nick-beg
450 (message (buffer-substring (if (and nick-end
451 (<= (+ 2 nick-end
) (point-max)))
458 (goto-char (point-min))
459 (let* ((match-prefix (concat "erc-" match-type
))
460 (match-pred (intern (concat "erc-match-" match-type
"-p")))
461 (match-htype (eval (intern (concat match-prefix
462 "-highlight-type"))))
463 (match-regex (if (string= match-type
"current-nick")
464 (regexp-quote (erc-current-nick))
465 (eval (intern (concat match-prefix
"s")))))
466 (match-face (intern (concat match-prefix
"-face"))))
467 (when (funcall match-pred nickuserhost message
)
469 ;; Highlight the nick of the message
470 ((and (eq match-htype
'nick
)
472 (erc-put-text-property
474 'face match-face
(current-buffer)))
475 ;; Highlight the nick of the message, or the current
476 ;; nick if there's no nick in the message (e.g. /NAMES
478 ((and (string= match-type
"current-nick")
479 (eq match-htype
'nick-or-keyword
))
481 (erc-put-text-property
483 'face match-face
(current-buffer))
484 (goto-char (+ 2 (or nick-end
486 (while (re-search-forward match-regex nil t
)
487 (erc-put-text-property (match-beginning 0) (match-end 0)
489 ;; Highlight the whole message
490 ((eq match-htype
'all
)
491 (erc-put-text-property
492 (point-min) (point-max)
493 'face match-face
(current-buffer)))
494 ;; Highlight all occurrences of the word to be
496 ((and (string= match-type
"keyword")
497 (eq match-htype
'keyword
))
502 (setq regex
(car elt
)
504 (goto-char (+ 2 (or nick-end
506 (while (re-search-forward regex nil t
)
507 (erc-put-text-property
508 (match-beginning 0) (match-end 0)
511 ;; Highlight all occurrences of our nick.
512 ((and (string= match-type
"current-nick")
513 (eq match-htype
'keyword
))
514 (goto-char (+ 2 (or nick-end
516 (while (re-search-forward match-regex nil t
)
517 (erc-put-text-property (match-beginning 0) (match-end 0)
519 ;; Else twiddle your thumbs.
522 'erc-text-matched-hook
525 (concat "Server:" (erc-get-parsed-vector-type vector
)))
528 (append to-match-nick-dep to-match-nick-indep
)
529 to-match-nick-indep
)))))
531 (defun erc-log-matches (match-type nickuserhost message
)
532 "Log matches in a separate buffer, determined by MATCH-TYPE.
533 The behavior of this function is controlled by the variables
534 `erc-log-matches-types-alist' and `erc-log-matches-flag'.
535 Specify the match types which should be logged in the former,
536 and deactivate/activate match logging in the latter.
537 See `erc-log-match-format'."
538 (let ((match-buffer-name (cdr (assq match-type
539 erc-log-matches-types-alist
)))
540 (nick (nth 0 (erc-parse-user nickuserhost
))))
542 (or (eq erc-log-matches-flag t
)
543 (and (eq erc-log-matches-flag
'away
)
546 (let ((line (format-spec erc-log-match-format
549 ?t
(format-time-string
550 (or (and (boundp 'erc-timestamp-format
)
551 erc-timestamp-format
)
552 "[%Y-%m-%d %H:%M] "))
553 ?c
(or (erc-default-target) "")
556 (with-current-buffer (erc-log-matches-make-buffer match-buffer-name
)
557 (let ((inhibit-read-only t
))
558 (goto-char (point-max))
561 (defun erc-log-matches-make-buffer (name)
562 "Create or get a log-matches buffer named NAME and return it."
563 (let* ((buffer-already (get-buffer name
))
564 (buffer (or buffer-already
565 (get-buffer-create name
))))
566 (with-current-buffer buffer
567 (unless buffer-already
568 (insert " == Type \"q\" to dismiss messages ==\n")
569 (erc-view-mode-enter nil
(lambda (buffer)
570 (when (y-or-n-p "Discard messages? ")
571 (kill-buffer buffer
)))))
574 (defun erc-log-matches-come-back (proc parsed
)
575 "Display a notice that messages were logged while away."
576 (when (and (erc-away-time)
577 (eq erc-log-matches-flag
'away
))
580 (let ((buffer (get-buffer (cdr match-type
)))
581 (buffer-name (cdr match-type
)))
583 (let* ((last-msg-time (erc-emacs-time-to-erc-time
584 (with-current-buffer buffer
585 (get-text-property (1- (point-max))
587 (away-time (erc-emacs-time-to-erc-time (erc-away-time))))
588 (when (and away-time last-msg-time
589 (erc-time-gt last-msg-time away-time
))
592 (format "You have logged messages waiting in \"%s\"."
596 (format "Type \"C-c C-k %s RET\" to view them."
598 erc-log-matches-types-alist
))
601 ; This handler must be run _before_ erc-process-away is.
602 (add-hook 'erc-server-305-functions
'erc-log-matches-come-back nil
)
604 (defun erc-go-to-log-matches-buffer ()
605 "Interactively open an erc-log-matches buffer."
607 (let ((buffer-name (completing-read "Switch to ERC Log buffer: "
610 erc-log-matches-types-alist
)
611 (lambda (buffer-cons)
612 (get-buffer (car buffer-cons
))))))
613 (switch-to-buffer buffer-name
)))
615 (define-key erc-mode-map
"\C-c\C-k" 'erc-go-to-log-matches-buffer
)
617 (defun erc-hide-fools (match-type nickuserhost message
)
618 "Hide foolish comments.
619 This function should be called from `erc-text-matched-hook'."
620 (when (eq match-type
'fool
)
621 (erc-put-text-properties (point-min) (point-max)
622 '(invisible intangible
)
625 (defun erc-beep-on-match (match-type nickuserhost message
)
626 "Beep when text matches.
627 This function is meant to be called from `erc-text-matched-hook'."
628 (when (member match-type erc-beep-match-types
)
633 ;;; erc-match.el ends here
636 ;; indent-tabs-mode: t
640 ;; arch-tag: 1f1f595e-abcc-4b0b-83db-598a1d3f0f82