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