Add 2010 to copyright years.
[bpt/emacs.git] / lisp / erc / erc-match.el
CommitLineData
597993cf
MB
1;;; erc-match.el --- Highlight messages matching certain regexps
2
ff59d266 3;; Copyright (C) 2002, 2003, 2004, 2005, 2006,
114f9c96 4;; 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
597993cf
MB
5
6;; Author: Andreas Fuchs <asf@void.at>
7;; Keywords: comm, faces
8;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?ErcMatch
9
10;; This file is part of GNU Emacs.
11
4ee57b2a 12;; GNU Emacs is free software: you can redistribute it and/or modify
597993cf 13;; it under the terms of the GNU General Public License as published by
4ee57b2a
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
597993cf
MB
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
4ee57b2a 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
597993cf
MB
24
25;;; Commentary:
26
27;; This file includes stuff to work with pattern matching in ERC. If
28;; you were used to customizing erc-fools, erc-keywords, erc-pals,
29;; erc-dangerous-hosts and the like, this file contains these
30;; customizable variables.
31
32;; Usage:
33;; Put (erc-match-mode 1) into your ~/.emacs file.
34
35;;; Code:
36
37(require 'erc)
38(eval-when-compile (require 'cl))
39
40;; Customisation:
41
42(defgroup erc-match nil
43 "Keyword and Friend/Foe/... recognition.
44Group containing all things concerning pattern matching in ERC
45messages."
46 :group 'erc)
47
48;;;###autoload (autoload 'erc-match-mode "erc-match")
49(define-erc-module match nil
50 "This mode checks whether messages match certain patterns. If so,
51they are hidden or highlighted. This is controlled via the variables
52`erc-pals', `erc-fools', `erc-keywords', `erc-dangerous-hosts', and
53`erc-current-nick-highlight-type'. For all these highlighting types,
54you can decide whether the entire message or only the sending nick is
55highlighted."
56 ((add-hook 'erc-insert-modify-hook 'erc-match-message 'append))
57 ((remove-hook 'erc-insert-modify-hook 'erc-match-message)))
58
59;; Remaining customizations
60
61(defcustom erc-pals nil
62 "List of pals on IRC."
63 :group 'erc-match
64 :type '(repeat regexp))
65
66(defcustom erc-fools nil
67 "List of fools on IRC."
68 :group 'erc-match
69 :type '(repeat regexp))
70
71(defcustom erc-keywords nil
72 "List of keywords to highlight in all incoming messages.
73Each entry in the list is either a regexp, or a cons cell with the
74regexp in the car and the face to use in the cdr. If no face is
75specified, `erc-keyword-face' is used."
76 :group 'erc-match
77 :type '(repeat (choice regexp
78 (list regexp face))))
79
80(defcustom erc-dangerous-hosts nil
81 "List of regexps for hosts to highlight.
82Useful to mark nicks from dangerous hosts."
83 :group 'erc-match
84 :type '(repeat regexp))
85
86(defcustom erc-current-nick-highlight-type 'keyword
87 "*Determines how to highlight text in which your current nickname appears
88\(does not apply to text sent by you\).
89
90The following values are allowed:
91
92 nil - do not highlight the message at all
93 'keyword - highlight all instances of current nickname in message
94 'nick - highlight the nick of the user who typed your nickname
95 'nick-or-keyword - highlight the nick of the user who typed your nickname,
96 or all instances of the current nickname if there was
97 no sending user
98 'all - highlight the entire message where current nickname occurs
99
100Any other value disables highlighting of current nickname altogether."
101 :group 'erc-match
102 :type '(choice (const nil)
103 (const nick)
104 (const keyword)
105 (const nick-or-keyword)
106 (const all)))
107
108(defcustom erc-pal-highlight-type 'nick
109 "*Determines how to highlight messages by pals.
110See `erc-pals'.
111
112The following values are allowed:
113
114 nil - do not highlight the message at all
115 'nick - highlight pal's nickname only
116 'all - highlight the entire message from pal
117
118Any other value disables pal highlighting altogether."
119 :group 'erc-match
120 :type '(choice (const nil)
121 (const nick)
122 (const all)))
123
124(defcustom erc-fool-highlight-type 'nick
125 "*Determines how to highlight messages by fools.
126See `erc-fools'.
127
128The following values are allowed:
129
130 nil - do not highlight the message at all
131 'nick - highlight fool's nickname only
132 'all - highlight the entire message from fool
133
134Any other value disables fool highlighting altogether."
135 :group 'erc-match
136 :type '(choice (const nil)
137 (const nick)
138 (const all)))
139
140(defcustom erc-keyword-highlight-type 'keyword
141 "*Determines how to highlight messages containing keywords.
142See variable `erc-keywords'.
143
144The following values are allowed:
145
146 'keyword - highlight keyword only
147 'all - highlight the entire message containing keyword
148
149Any other value disables keyword highlighting altogether."
150 :group 'erc-match
151 :type '(choice (const nil)
152 (const keyword)
153 (const all)))
154
155(defcustom erc-dangerous-host-highlight-type 'nick
156 "*Determines how to highlight messages by nicks from dangerous-hosts.
157See `erc-dangerous-hosts'.
158
159The following values are allowed:
160
161 'nick - highlight nick from dangerous-host only
162 'all - highlight the entire message from dangerous-host
163
164Any other value disables dangerous-host highlighting altogether."
165 :group 'erc-match
166 :type '(choice (const nil)
167 (const nick)
168 (const all)))
169
170
171(defcustom erc-log-matches-types-alist '((keyword . "ERC Keywords"))
172 "Alist telling ERC where to log which match types.
173Valid match type keys are:
174- keyword
175- pal
176- dangerous-host
177- fool
178- current-nick
179
180The other element of each cons pair in this list is the buffer name to
181use for the logged message."
182 :group 'erc-match
183 :type '(repeat (cons (choice :tag "Key"
184 (const keyword)
185 (const pal)
186 (const dangerous-host)
187 (const fool)
188 (const current-nick))
189 (string :tag "Buffer name"))))
190
191(defcustom erc-log-matches-flag 'away
192 "Flag specifying when matched message logging should happen.
193When nil, don't log any matched messages.
194When t, log messages.
195When 'away, log messages only when away."
196 :group 'erc-match
197 :type '(choice (const nil)
198 (const away)
199 (const t)))
200
201(defcustom erc-log-match-format "%t<%n:%c> %m"
202 "Format for matched Messages.
203This variable specifies how messages in the corresponding log buffers will
204be formatted. The various format specs are:
205
206%t Timestamp (uses `erc-timestamp-format' if non-nil or \"[%Y-%m-%d %H:%M] \")
207%n Nickname of sender
208%u Nickname!user@host of sender
209%c Channel in which this was received
210%m Message"
211 :group 'erc-match
212 :type 'string)
213
214(defcustom erc-beep-match-types '(current-nick)
215 "Types of matches to beep for when a match occurs.
216The function `erc-beep-on-match' needs to be added to `erc-text-matched-hook'
217for beeping to work."
218 :group 'erc-match
219 :type '(choice (repeat :tag "Beep on match" (choice
220 (const current-nick)
221 (const keyword)
222 (const pal)
223 (const dangerous-host)
224 (const fool)))
225 (const :tag "Don't beep" nil)))
226
227(defcustom erc-text-matched-hook '(erc-log-matches)
228 "Hook run when text matches a given match-type.
229Functions in this hook are passed as arguments:
230\(match-type nick!user@host message) where MATCH-TYPE is a symbol of:
231current-nick, keyword, pal, dangerous-host, fool"
232 :options '(erc-log-matches erc-hide-fools erc-beep-on-match)
233 :group 'erc-match
234 :type 'hook)
235
236;; Internal variables:
237
238;; This is exactly the same as erc-button-syntax-table. Should we
239;; just put it in erc.el
240(defvar erc-match-syntax-table
241 (let ((table (make-syntax-table)))
242 (modify-syntax-entry ?\( "w" table)
243 (modify-syntax-entry ?\) "w" 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 table)
256 "Syntax table used when highlighting messages.
42c28f29 257This syntax table should make all the valid nick characters word
597993cf
MB
258constituents.")
259
260;; Faces:
261
262(defface erc-current-nick-face '((t (:bold t :foreground "DarkTurquoise")))
263 "ERC face for occurrences of your current nickname."
264 :group 'erc-faces)
265
266(defface erc-dangerous-host-face '((t (:foreground "red")))
267 "ERC face for people on dangerous hosts.
268See `erc-dangerous-hosts'."
269 :group 'erc-faces)
270
271(defface erc-pal-face '((t (:bold t :foreground "Magenta")))
272 "ERC face for your pals.
273See `erc-pals'."
274 :group 'erc-faces)
275
276(defface erc-fool-face '((t (:foreground "dim gray")))
277 "ERC face for fools on the channel.
278See `erc-fools'."
279 :group 'erc-faces)
280
281(defface erc-keyword-face '((t (:bold t :foreground "pale green")))
282 "ERC face for your keywords.
283Note that this is the default face to use if
284`erc-keywords' does not specify another."
285 :group 'erc-faces)
286
287;; Functions:
288
289(defun erc-add-entry-to-list (list prompt &optional completions)
290 "Add an entry interactively to a list.
291LIST must be passed as a symbol
292The query happens using PROMPT.
293Completion is performed on the optional alist COMPLETIONS."
294 (let ((entry (completing-read
295 prompt
296 completions
297 (lambda (x)
298 (not (erc-member-ignore-case (car x) (symbol-value list)))))))
299 (if (erc-member-ignore-case entry (symbol-value list))
2e564b0d 300 (error "\"%s\" is already on the list" entry)
597993cf
MB
301 (set list (cons entry (symbol-value list))))))
302
303(defun erc-remove-entry-from-list (list prompt)
304 "Remove an entry interactively from a list.
305LIST must be passed as a symbol.
306The elements of LIST can be strings, or cons cells where the
307car is the string."
308 (let* ((alist (mapcar (lambda (x)
309 (if (listp x)
310 x
311 (list x)))
312 (symbol-value list)))
313 (entry (completing-read
314 prompt
315 alist
316 nil
317 t)))
318 (if (erc-member-ignore-case entry (symbol-value list))
319 ;; plain string
320 (set list (delete entry (symbol-value list)))
321 ;; cons cell
322 (set list (delete (assoc entry (symbol-value list))
323 (symbol-value list))))))
324
325;;;###autoload
326(defun erc-add-pal ()
327 "Add pal interactively to `erc-pals'."
328 (interactive)
329 (erc-add-entry-to-list 'erc-pals "Add pal: " (erc-get-server-nickname-alist)))
330
331;;;###autoload
332(defun erc-delete-pal ()
333 "Delete pal interactively to `erc-pals'."
334 (interactive)
335 (erc-remove-entry-from-list 'erc-pals "Delete pal: "))
336
337;;;###autoload
338(defun erc-add-fool ()
339 "Add fool interactively to `erc-fools'."
340 (interactive)
341 (erc-add-entry-to-list 'erc-fools "Add fool: "
342 (erc-get-server-nickname-alist)))
343
344;;;###autoload
345(defun erc-delete-fool ()
346 "Delete fool interactively to `erc-fools'."
347 (interactive)
348 (erc-remove-entry-from-list 'erc-fools "Delete fool: "))
349
350;;;###autoload
351(defun erc-add-keyword ()
352 "Add keyword interactively to `erc-keywords'."
353 (interactive)
354 (erc-add-entry-to-list 'erc-keywords "Add keyword: "))
355
356;;;###autoload
357(defun erc-delete-keyword ()
358 "Delete keyword interactively to `erc-keywords'."
359 (interactive)
360 (erc-remove-entry-from-list 'erc-keywords "Delete keyword: "))
361
362;;;###autoload
363(defun erc-add-dangerous-host ()
364 "Add dangerous-host interactively to `erc-dangerous-hosts'."
365 (interactive)
366 (erc-add-entry-to-list 'erc-dangerous-hosts "Add dangerous-host: "))
367
368;;;###autoload
369(defun erc-delete-dangerous-host ()
370 "Delete dangerous-host interactively to `erc-dangerous-hosts'."
371 (interactive)
372 (erc-remove-entry-from-list 'erc-dangerous-hosts "Delete dangerous-host: "))
373
374(defun erc-match-current-nick-p (nickuserhost msg)
375 "Check whether the current nickname is in MSG.
376NICKUSERHOST will be ignored."
377 (with-syntax-table erc-match-syntax-table
378 (and msg
379 (string-match (concat "\\b"
380 (regexp-quote (erc-current-nick))
381 "\\b")
382 msg))))
383
384(defun erc-match-pal-p (nickuserhost msg)
385 "Check whether NICKUSERHOST is in `erc-pals'.
386MSG will be ignored."
387 (and nickuserhost
388 (erc-list-match erc-pals nickuserhost)))
389
390(defun erc-match-fool-p (nickuserhost msg)
391 "Check whether NICKUSERHOST is in `erc-fools' or MSG is directed at a fool."
392 (and msg nickuserhost
393 (or (erc-list-match erc-fools nickuserhost)
394 (erc-match-directed-at-fool-p msg))))
395
396(defun erc-match-keyword-p (nickuserhost msg)
397 "Check whether any keyword of `erc-keywords' matches for MSG.
398NICKUSERHOST will be ignored."
399 (and msg
400 (erc-list-match
401 (mapcar (lambda (x)
402 (if (listp x)
403 (car x)
404 x))
405 erc-keywords)
406 msg)))
407
408(defun erc-match-dangerous-host-p (nickuserhost msg)
409 "Check whether NICKUSERHOST is in `erc-dangerous-hosts'.
410MSG will be ignored."
411 (and nickuserhost
412 (erc-list-match erc-dangerous-hosts nickuserhost)))
413
414(defun erc-match-directed-at-fool-p (msg)
415 "Check whether MSG is directed at a fool.
416In order to do this, every entry in `erc-fools' will be used.
417In any of the following situations, MSG is directed at an entry FOOL:
418
419- MSG starts with \"FOOL: \" or \"FOO, \"
420- MSG contains \", FOOL.\" (actually, \"\\s. FOOL\\s.\")"
421 (let ((fools-beg (mapcar (lambda (entry)
422 (concat "^" entry "[:,] "))
423 erc-fools))
424 (fools-end (mapcar (lambda (entry)
425 (concat "\\s. " entry "\\s."))
426 erc-fools)))
427 (or (erc-list-match fools-beg msg)
428 (erc-list-match fools-end msg))))
429
597993cf
MB
430(defun erc-match-message ()
431 "Mark certain keywords in a region.
432Use this defun with `erc-insert-modify-hook'."
433 ;; This needs some refactoring.
434 (goto-char (point-min))
435 (let* ((to-match-nick-dep '("pal" "fool" "dangerous-host"))
436 (to-match-nick-indep '("keyword" "current-nick"))
437 (vector (erc-get-parsed-vector (point-min)))
438 (nickuserhost (erc-get-parsed-vector-nick vector))
439 (nickname (and nickuserhost
440 (nth 0 (erc-parse-user nickuserhost))))
441 (old-pt (point))
442 (nick-beg (and nickname
443 (re-search-forward (regexp-quote nickname)
444 (point-max) t)
445 (match-beginning 0)))
446 (nick-end (when nick-beg
447 (match-end 0)))
448 (message (buffer-substring (if (and nick-end
449 (<= (+ 2 nick-end) (point-max)))
450 (+ 2 nick-end)
451 (point-min))
452 (point-max))))
453 (when vector
454 (mapc
455 (lambda (match-type)
456 (goto-char (point-min))
457 (let* ((match-prefix (concat "erc-" match-type))
458 (match-pred (intern (concat "erc-match-" match-type "-p")))
459 (match-htype (eval (intern (concat match-prefix
460 "-highlight-type"))))
461 (match-regex (if (string= match-type "current-nick")
462 (regexp-quote (erc-current-nick))
463 (eval (intern (concat match-prefix "s")))))
464 (match-face (intern (concat match-prefix "-face"))))
465 (when (funcall match-pred nickuserhost message)
466 (cond
467 ;; Highlight the nick of the message
468 ((and (eq match-htype 'nick)
469 nick-end)
470 (erc-put-text-property
471 nick-beg nick-end
472 'face match-face (current-buffer)))
473 ;; Highlight the nick of the message, or the current
474 ;; nick if there's no nick in the message (e.g. /NAMES
475 ;; output)
476 ((and (string= match-type "current-nick")
477 (eq match-htype 'nick-or-keyword))
478 (if nick-end
479 (erc-put-text-property
480 nick-beg nick-end
481 'face match-face (current-buffer))
482 (goto-char (+ 2 (or nick-end
483 (point-min))))
484 (while (re-search-forward match-regex nil t)
485 (erc-put-text-property (match-beginning 0) (match-end 0)
486 'face match-face))))
487 ;; Highlight the whole message
488 ((eq match-htype 'all)
489 (erc-put-text-property
490 (point-min) (point-max)
491 'face match-face (current-buffer)))
492 ;; Highlight all occurrences of the word to be
493 ;; highlighted.
494 ((and (string= match-type "keyword")
495 (eq match-htype 'keyword))
496 (mapc (lambda (elt)
497 (let ((regex elt)
498 (face match-face))
499 (when (consp regex)
500 (setq regex (car elt)
501 face (cdr elt)))
502 (goto-char (+ 2 (or nick-end
503 (point-min))))
504 (while (re-search-forward regex nil t)
505 (erc-put-text-property
506 (match-beginning 0) (match-end 0)
507 'face face))))
508 match-regex))
509 ;; Highlight all occurrences of our nick.
510 ((and (string= match-type "current-nick")
511 (eq match-htype 'keyword))
512 (goto-char (+ 2 (or nick-end
513 (point-min))))
514 (while (re-search-forward match-regex nil t)
515 (erc-put-text-property (match-beginning 0) (match-end 0)
516 'face match-face)))
517 ;; Else twiddle your thumbs.
518 (t nil))
519 (run-hook-with-args
520 'erc-text-matched-hook
521 (intern match-type)
522 (or nickuserhost
523 (concat "Server:" (erc-get-parsed-vector-type vector)))
524 message))))
525 (if nickuserhost
526 (append to-match-nick-dep to-match-nick-indep)
527 to-match-nick-indep)))))
528
529(defun erc-log-matches (match-type nickuserhost message)
530 "Log matches in a separate buffer, determined by MATCH-TYPE.
da2a6e44
JB
531The behavior of this function is controlled by the variables
532`erc-log-matches-types-alist' and `erc-log-matches-flag'.
533Specify the match types which should be logged in the former,
534and deactivate/activate match logging in the latter.
535See `erc-log-match-format'."
597993cf
MB
536 (let ((match-buffer-name (cdr (assq match-type
537 erc-log-matches-types-alist)))
538 (nick (nth 0 (erc-parse-user nickuserhost))))
539 (when (and
540 (or (eq erc-log-matches-flag t)
541 (and (eq erc-log-matches-flag 'away)
ff59d266 542 (erc-away-time)))
597993cf
MB
543 match-buffer-name)
544 (let ((line (format-spec erc-log-match-format
545 (format-spec-make
546 ?n nick
547 ?t (format-time-string
548 (or (and (boundp 'erc-timestamp-format)
549 erc-timestamp-format)
550 "[%Y-%m-%d %H:%M] "))
551 ?c (or (erc-default-target) "")
552 ?m message
553 ?u nickuserhost))))
554 (with-current-buffer (erc-log-matches-make-buffer match-buffer-name)
0b6bb130
MB
555 (let ((inhibit-read-only t))
556 (goto-char (point-max))
557 (insert line)))))))
597993cf
MB
558
559(defun erc-log-matches-make-buffer (name)
560 "Create or get a log-matches buffer named NAME and return it."
561 (let* ((buffer-already (get-buffer name))
562 (buffer (or buffer-already
563 (get-buffer-create name))))
564 (with-current-buffer buffer
565 (unless buffer-already
566 (insert " == Type \"q\" to dismiss messages ==\n")
567 (erc-view-mode-enter nil (lambda (buffer)
35dbb6cf 568 (when (y-or-n-p "Discard messages? ")
597993cf
MB
569 (kill-buffer buffer)))))
570 buffer)))
571
572(defun erc-log-matches-come-back (proc parsed)
573 "Display a notice that messages were logged while away."
ff59d266 574 (when (and (erc-away-time)
597993cf
MB
575 (eq erc-log-matches-flag 'away))
576 (mapc
577 (lambda (match-type)
578 (let ((buffer (get-buffer (cdr match-type)))
579 (buffer-name (cdr match-type)))
580 (when buffer
581 (let* ((last-msg-time (erc-emacs-time-to-erc-time
582 (with-current-buffer buffer
583 (get-text-property (1- (point-max))
584 'timestamp))))
ff59d266 585 (away-time (erc-emacs-time-to-erc-time (erc-away-time))))
597993cf
MB
586 (when (and away-time last-msg-time
587 (erc-time-gt last-msg-time away-time))
588 (erc-display-message
589 nil 'notice 'active
590 (format "You have logged messages waiting in \"%s\"."
591 buffer-name))
592 (erc-display-message
593 nil 'notice 'active
594 (format "Type \"C-c C-k %s RET\" to view them."
595 buffer-name)))))))
596 erc-log-matches-types-alist))
597 nil)
598
599; This handler must be run _before_ erc-process-away is.
600(add-hook 'erc-server-305-functions 'erc-log-matches-come-back nil)
601
602(defun erc-go-to-log-matches-buffer ()
603 "Interactively open an erc-log-matches buffer."
604 (interactive)
605 (let ((buffer-name (completing-read "Switch to ERC Log buffer: "
606 (mapcar (lambda (x)
607 (cons (cdr x) t))
608 erc-log-matches-types-alist)
609 (lambda (buffer-cons)
610 (get-buffer (car buffer-cons))))))
611 (switch-to-buffer buffer-name)))
612
613(define-key erc-mode-map "\C-c\C-k" 'erc-go-to-log-matches-buffer)
614
615(defun erc-hide-fools (match-type nickuserhost message)
616 "Hide foolish comments.
617This function should be called from `erc-text-matched-hook'."
618 (when (eq match-type 'fool)
619 (erc-put-text-properties (point-min) (point-max)
620 '(invisible intangible)
621 (current-buffer))))
622
623(defun erc-beep-on-match (match-type nickuserhost message)
624 "Beep when text matches.
625This function is meant to be called from `erc-text-matched-hook'."
626 (when (member match-type erc-beep-match-types)
627 (beep)))
628
629(provide 'erc-match)
630
631;;; erc-match.el ends here
632;;
633;; Local Variables:
634;; indent-tabs-mode: t
635;; tab-width: 8
636;; End:
637
638;; arch-tag: 1f1f595e-abcc-4b0b-83db-598a1d3f0f82