]> code.delx.au - gnu-emacs/blob - lisp/erc/erc-match.el
Add 2012 to FSF copyright years for Emacs files (do not merge to trunk)
[gnu-emacs] / lisp / erc / erc-match.el
1 ;;; erc-match.el --- Highlight messages matching certain regexps
2
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006,
4 ;; 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
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
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 of the License, or
15 ;; (at your option) any later version.
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
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
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.
44 Group containing all things concerning pattern matching in ERC
45 messages."
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,
51 they 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,
54 you can decide whether the entire message or only the sending nick is
55 highlighted."
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.
73 Each entry in the list is either a regexp, or a cons cell with the
74 regexp in the car and the face to use in the cdr. If no face is
75 specified, `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.
82 Useful 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
90 The 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
100 Any 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.
110 See `erc-pals'.
111
112 The 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
118 Any 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.
126 See `erc-fools'.
127
128 The 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
134 Any 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.
142 See variable `erc-keywords'.
143
144 The following values are allowed:
145
146 'keyword - highlight keyword only
147 'all - highlight the entire message containing keyword
148
149 Any 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.
157 See `erc-dangerous-hosts'.
158
159 The following values are allowed:
160
161 'nick - highlight nick from dangerous-host only
162 'all - highlight the entire message from dangerous-host
163
164 Any 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.
173 Valid match type keys are:
174 - keyword
175 - pal
176 - dangerous-host
177 - fool
178 - current-nick
179
180 The other element of each cons pair in this list is the buffer name to
181 use 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.
193 When nil, don't log any matched messages.
194 When t, log messages.
195 When '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.
203 This variable specifies how messages in the corresponding log buffers will
204 be 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.
216 The function `erc-beep-on-match' needs to be added to `erc-text-matched-hook'
217 for 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.
229 Functions in this hook are passed as arguments:
230 \(match-type nick!user@host message) where MATCH-TYPE is a symbol of:
231 current-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.
257 This syntax table should make all the valid nick characters word
258 constituents.")
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.
268 See `erc-dangerous-hosts'."
269 :group 'erc-faces)
270
271 (defface erc-pal-face '((t (:bold t :foreground "Magenta")))
272 "ERC face for your pals.
273 See `erc-pals'."
274 :group 'erc-faces)
275
276 (defface erc-fool-face '((t (:foreground "dim gray")))
277 "ERC face for fools on the channel.
278 See `erc-fools'."
279 :group 'erc-faces)
280
281 (defface erc-keyword-face '((t (:bold t :foreground "pale green")))
282 "ERC face for your keywords.
283 Note 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.
291 LIST must be passed as a symbol
292 The query happens using PROMPT.
293 Completion 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))
300 (error "\"%s\" is already on the list" entry)
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.
305 LIST must be passed as a symbol.
306 The elements of LIST can be strings, or cons cells where the
307 car 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.
376 NICKUSERHOST 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'.
386 MSG 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.
398 NICKUSERHOST 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'.
410 MSG 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.
416 In order to do this, every entry in `erc-fools' will be used.
417 In 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
430 (defun erc-match-message ()
431 "Mark certain keywords in a region.
432 Use 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.
531 The behavior of this function is controlled by the variables
532 `erc-log-matches-types-alist' and `erc-log-matches-flag'.
533 Specify the match types which should be logged in the former,
534 and deactivate/activate match logging in the latter.
535 See `erc-log-match-format'."
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)
542 (erc-away-time)))
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)
555 (let ((inhibit-read-only t))
556 (goto-char (point-max))
557 (insert line)))))))
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)
568 (when (y-or-n-p "Discard messages? ")
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."
574 (when (and (erc-away-time)
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))))
585 (away-time (erc-emacs-time-to-erc-time (erc-away-time))))
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.
617 This 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.
625 This 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