Set maintainer of most lisp/erc/*.el files to FSF
[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>
df5d5f59 6;; Maintainer: FSF
597993cf
MB
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:
865fe16f 33;; Put (erc-match-mode 1) into your init file.
597993cf
MB
34
35;;; Code:
36
37(require 'erc)
38(eval-when-compile (require 'cl))
39
e1dbe924 40;; Customization:
597993cf
MB
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
fb7ada5f 87 "Determines how to highlight text in which your current nickname appears
597993cf
MB
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
fb7ada5f 109 "Determines how to highlight messages by pals.
597993cf
MB
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
fb7ada5f 125 "Determines how to highlight messages by fools.
597993cf
MB
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
fb7ada5f 141 "Determines how to highlight messages containing keywords.
597993cf
MB
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
fb7ada5f 156 "Determines how to highlight messages by nicks from dangerous-hosts.
597993cf
MB
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
24835558
JD
236(defcustom erc-match-exclude-server-buffer nil
237 "If true, don't perform match on the server buffer; this is
238useful for excluding all the things like MOTDs from the server
239and other miscellaneous functions."
240 :group 'erc-match
2a1e2476 241 :version "24.3"
24835558
JD
242 :type 'boolean)
243
597993cf
MB
244;; Internal variables:
245
246;; This is exactly the same as erc-button-syntax-table. Should we
247;; just put it in erc.el
248(defvar erc-match-syntax-table
249 (let ((table (make-syntax-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 (modify-syntax-entry ?\\ "w" table)
263 table)
264 "Syntax table used when highlighting messages.
42c28f29 265This syntax table should make all the valid nick characters word
597993cf
MB
266constituents.")
267
268;; Faces:
269
4b56d0fe 270(defface erc-current-nick-face '((t :weight bold :foreground "DarkTurquoise"))
597993cf
MB
271 "ERC face for occurrences of your current nickname."
272 :group 'erc-faces)
273
4b56d0fe 274(defface erc-dangerous-host-face '((t :foreground "red"))
597993cf
MB
275 "ERC face for people on dangerous hosts.
276See `erc-dangerous-hosts'."
277 :group 'erc-faces)
278
4b56d0fe 279(defface erc-pal-face '((t :weight bold :foreground "Magenta"))
597993cf
MB
280 "ERC face for your pals.
281See `erc-pals'."
282 :group 'erc-faces)
283
4b56d0fe 284(defface erc-fool-face '((t :foreground "dim gray"))
597993cf
MB
285 "ERC face for fools on the channel.
286See `erc-fools'."
287 :group 'erc-faces)
288
4b56d0fe 289(defface erc-keyword-face '((t :weight bold :foreground "pale green"))
597993cf
MB
290 "ERC face for your keywords.
291Note that this is the default face to use if
292`erc-keywords' does not specify another."
293 :group 'erc-faces)
294
295;; Functions:
296
297(defun erc-add-entry-to-list (list prompt &optional completions)
298 "Add an entry interactively to a list.
299LIST must be passed as a symbol
300The query happens using PROMPT.
301Completion is performed on the optional alist COMPLETIONS."
302 (let ((entry (completing-read
303 prompt
304 completions
305 (lambda (x)
306 (not (erc-member-ignore-case (car x) (symbol-value list)))))))
307 (if (erc-member-ignore-case entry (symbol-value list))
2e564b0d 308 (error "\"%s\" is already on the list" entry)
597993cf
MB
309 (set list (cons entry (symbol-value list))))))
310
311(defun erc-remove-entry-from-list (list prompt)
312 "Remove an entry interactively from a list.
313LIST must be passed as a symbol.
314The elements of LIST can be strings, or cons cells where the
315car is the string."
316 (let* ((alist (mapcar (lambda (x)
317 (if (listp x)
318 x
319 (list x)))
320 (symbol-value list)))
321 (entry (completing-read
322 prompt
323 alist
324 nil
325 t)))
326 (if (erc-member-ignore-case entry (symbol-value list))
327 ;; plain string
328 (set list (delete entry (symbol-value list)))
329 ;; cons cell
330 (set list (delete (assoc entry (symbol-value list))
331 (symbol-value list))))))
332
333;;;###autoload
334(defun erc-add-pal ()
335 "Add pal interactively to `erc-pals'."
336 (interactive)
337 (erc-add-entry-to-list 'erc-pals "Add pal: " (erc-get-server-nickname-alist)))
338
339;;;###autoload
340(defun erc-delete-pal ()
341 "Delete pal interactively to `erc-pals'."
342 (interactive)
343 (erc-remove-entry-from-list 'erc-pals "Delete pal: "))
344
345;;;###autoload
346(defun erc-add-fool ()
347 "Add fool interactively to `erc-fools'."
348 (interactive)
349 (erc-add-entry-to-list 'erc-fools "Add fool: "
350 (erc-get-server-nickname-alist)))
351
352;;;###autoload
353(defun erc-delete-fool ()
354 "Delete fool interactively to `erc-fools'."
355 (interactive)
356 (erc-remove-entry-from-list 'erc-fools "Delete fool: "))
357
358;;;###autoload
359(defun erc-add-keyword ()
360 "Add keyword interactively to `erc-keywords'."
361 (interactive)
362 (erc-add-entry-to-list 'erc-keywords "Add keyword: "))
363
364;;;###autoload
365(defun erc-delete-keyword ()
366 "Delete keyword interactively to `erc-keywords'."
367 (interactive)
368 (erc-remove-entry-from-list 'erc-keywords "Delete keyword: "))
369
370;;;###autoload
371(defun erc-add-dangerous-host ()
372 "Add dangerous-host interactively to `erc-dangerous-hosts'."
373 (interactive)
374 (erc-add-entry-to-list 'erc-dangerous-hosts "Add dangerous-host: "))
375
376;;;###autoload
377(defun erc-delete-dangerous-host ()
378 "Delete dangerous-host interactively to `erc-dangerous-hosts'."
379 (interactive)
380 (erc-remove-entry-from-list 'erc-dangerous-hosts "Delete dangerous-host: "))
381
382(defun erc-match-current-nick-p (nickuserhost msg)
383 "Check whether the current nickname is in MSG.
384NICKUSERHOST will be ignored."
385 (with-syntax-table erc-match-syntax-table
386 (and msg
387 (string-match (concat "\\b"
388 (regexp-quote (erc-current-nick))
389 "\\b")
390 msg))))
391
392(defun erc-match-pal-p (nickuserhost msg)
393 "Check whether NICKUSERHOST is in `erc-pals'.
394MSG will be ignored."
395 (and nickuserhost
396 (erc-list-match erc-pals nickuserhost)))
397
398(defun erc-match-fool-p (nickuserhost msg)
399 "Check whether NICKUSERHOST is in `erc-fools' or MSG is directed at a fool."
400 (and msg nickuserhost
401 (or (erc-list-match erc-fools nickuserhost)
402 (erc-match-directed-at-fool-p msg))))
403
404(defun erc-match-keyword-p (nickuserhost msg)
405 "Check whether any keyword of `erc-keywords' matches for MSG.
406NICKUSERHOST will be ignored."
407 (and msg
408 (erc-list-match
409 (mapcar (lambda (x)
410 (if (listp x)
411 (car x)
412 x))
413 erc-keywords)
414 msg)))
415
416(defun erc-match-dangerous-host-p (nickuserhost msg)
417 "Check whether NICKUSERHOST is in `erc-dangerous-hosts'.
418MSG will be ignored."
419 (and nickuserhost
420 (erc-list-match erc-dangerous-hosts nickuserhost)))
421
422(defun erc-match-directed-at-fool-p (msg)
423 "Check whether MSG is directed at a fool.
424In order to do this, every entry in `erc-fools' will be used.
425In any of the following situations, MSG is directed at an entry FOOL:
426
427- MSG starts with \"FOOL: \" or \"FOO, \"
428- MSG contains \", FOOL.\" (actually, \"\\s. FOOL\\s.\")"
429 (let ((fools-beg (mapcar (lambda (entry)
430 (concat "^" entry "[:,] "))
431 erc-fools))
432 (fools-end (mapcar (lambda (entry)
433 (concat "\\s. " entry "\\s."))
434 erc-fools)))
435 (or (erc-list-match fools-beg msg)
436 (erc-list-match fools-end msg))))
437
597993cf
MB
438(defun erc-match-message ()
439 "Mark certain keywords in a region.
440Use this defun with `erc-insert-modify-hook'."
441 ;; This needs some refactoring.
442 (goto-char (point-min))
443 (let* ((to-match-nick-dep '("pal" "fool" "dangerous-host"))
444 (to-match-nick-indep '("keyword" "current-nick"))
445 (vector (erc-get-parsed-vector (point-min)))
446 (nickuserhost (erc-get-parsed-vector-nick vector))
447 (nickname (and nickuserhost
448 (nth 0 (erc-parse-user nickuserhost))))
449 (old-pt (point))
450 (nick-beg (and nickname
451 (re-search-forward (regexp-quote nickname)
452 (point-max) t)
453 (match-beginning 0)))
454 (nick-end (when nick-beg
455 (match-end 0)))
456 (message (buffer-substring (if (and nick-end
457 (<= (+ 2 nick-end) (point-max)))
458 (+ 2 nick-end)
459 (point-min))
460 (point-max))))
24835558 461 (when (and vector
a878d0f2 462 (not (and erc-match-exclude-server-buffer
24835558 463 (erc-server-buffer-p))))
597993cf
MB
464 (mapc
465 (lambda (match-type)
466 (goto-char (point-min))
467 (let* ((match-prefix (concat "erc-" match-type))
468 (match-pred (intern (concat "erc-match-" match-type "-p")))
469 (match-htype (eval (intern (concat match-prefix
470 "-highlight-type"))))
471 (match-regex (if (string= match-type "current-nick")
472 (regexp-quote (erc-current-nick))
473 (eval (intern (concat match-prefix "s")))))
474 (match-face (intern (concat match-prefix "-face"))))
475 (when (funcall match-pred nickuserhost message)
476 (cond
477 ;; Highlight the nick of the message
478 ((and (eq match-htype 'nick)
479 nick-end)
480 (erc-put-text-property
481 nick-beg nick-end
482 'face match-face (current-buffer)))
483 ;; Highlight the nick of the message, or the current
484 ;; nick if there's no nick in the message (e.g. /NAMES
485 ;; output)
486 ((and (string= match-type "current-nick")
487 (eq match-htype 'nick-or-keyword))
488 (if nick-end
489 (erc-put-text-property
490 nick-beg nick-end
491 'face match-face (current-buffer))
492 (goto-char (+ 2 (or nick-end
493 (point-min))))
494 (while (re-search-forward match-regex nil t)
495 (erc-put-text-property (match-beginning 0) (match-end 0)
496 'face match-face))))
497 ;; Highlight the whole message
498 ((eq match-htype 'all)
499 (erc-put-text-property
500 (point-min) (point-max)
501 'face match-face (current-buffer)))
502 ;; Highlight all occurrences of the word to be
503 ;; highlighted.
504 ((and (string= match-type "keyword")
505 (eq match-htype 'keyword))
506 (mapc (lambda (elt)
507 (let ((regex elt)
508 (face match-face))
509 (when (consp regex)
510 (setq regex (car elt)
511 face (cdr elt)))
512 (goto-char (+ 2 (or nick-end
513 (point-min))))
514 (while (re-search-forward regex nil t)
515 (erc-put-text-property
516 (match-beginning 0) (match-end 0)
517 'face face))))
518 match-regex))
519 ;; Highlight all occurrences of our nick.
520 ((and (string= match-type "current-nick")
521 (eq match-htype 'keyword))
522 (goto-char (+ 2 (or nick-end
523 (point-min))))
524 (while (re-search-forward match-regex nil t)
525 (erc-put-text-property (match-beginning 0) (match-end 0)
526 'face match-face)))
527 ;; Else twiddle your thumbs.
528 (t nil))
529 (run-hook-with-args
530 'erc-text-matched-hook
531 (intern match-type)
532 (or nickuserhost
533 (concat "Server:" (erc-get-parsed-vector-type vector)))
534 message))))
535 (if nickuserhost
536 (append to-match-nick-dep to-match-nick-indep)
537 to-match-nick-indep)))))
538
539(defun erc-log-matches (match-type nickuserhost message)
540 "Log matches in a separate buffer, determined by MATCH-TYPE.
da2a6e44
JB
541The behavior of this function is controlled by the variables
542`erc-log-matches-types-alist' and `erc-log-matches-flag'.
543Specify the match types which should be logged in the former,
544and deactivate/activate match logging in the latter.
545See `erc-log-match-format'."
597993cf
MB
546 (let ((match-buffer-name (cdr (assq match-type
547 erc-log-matches-types-alist)))
548 (nick (nth 0 (erc-parse-user nickuserhost))))
549 (when (and
550 (or (eq erc-log-matches-flag t)
551 (and (eq erc-log-matches-flag 'away)
ff59d266 552 (erc-away-time)))
597993cf
MB
553 match-buffer-name)
554 (let ((line (format-spec erc-log-match-format
555 (format-spec-make
556 ?n nick
557 ?t (format-time-string
558 (or (and (boundp 'erc-timestamp-format)
559 erc-timestamp-format)
560 "[%Y-%m-%d %H:%M] "))
561 ?c (or (erc-default-target) "")
562 ?m message
563 ?u nickuserhost))))
564 (with-current-buffer (erc-log-matches-make-buffer match-buffer-name)
0b6bb130
MB
565 (let ((inhibit-read-only t))
566 (goto-char (point-max))
567 (insert line)))))))
597993cf
MB
568
569(defun erc-log-matches-make-buffer (name)
570 "Create or get a log-matches buffer named NAME and return it."
571 (let* ((buffer-already (get-buffer name))
572 (buffer (or buffer-already
573 (get-buffer-create name))))
574 (with-current-buffer buffer
575 (unless buffer-already
576 (insert " == Type \"q\" to dismiss messages ==\n")
577 (erc-view-mode-enter nil (lambda (buffer)
35dbb6cf 578 (when (y-or-n-p "Discard messages? ")
597993cf
MB
579 (kill-buffer buffer)))))
580 buffer)))
581
582(defun erc-log-matches-come-back (proc parsed)
583 "Display a notice that messages were logged while away."
ff59d266 584 (when (and (erc-away-time)
597993cf
MB
585 (eq erc-log-matches-flag 'away))
586 (mapc
587 (lambda (match-type)
588 (let ((buffer (get-buffer (cdr match-type)))
589 (buffer-name (cdr match-type)))
590 (when buffer
591 (let* ((last-msg-time (erc-emacs-time-to-erc-time
592 (with-current-buffer buffer
593 (get-text-property (1- (point-max))
594 'timestamp))))
ff59d266 595 (away-time (erc-emacs-time-to-erc-time (erc-away-time))))
597993cf
MB
596 (when (and away-time last-msg-time
597 (erc-time-gt last-msg-time away-time))
598 (erc-display-message
599 nil 'notice 'active
600 (format "You have logged messages waiting in \"%s\"."
601 buffer-name))
602 (erc-display-message
603 nil 'notice 'active
604 (format "Type \"C-c C-k %s RET\" to view them."
605 buffer-name)))))))
606 erc-log-matches-types-alist))
607 nil)
608
609; This handler must be run _before_ erc-process-away is.
610(add-hook 'erc-server-305-functions 'erc-log-matches-come-back nil)
611
612(defun erc-go-to-log-matches-buffer ()
613 "Interactively open an erc-log-matches buffer."
614 (interactive)
615 (let ((buffer-name (completing-read "Switch to ERC Log buffer: "
616 (mapcar (lambda (x)
617 (cons (cdr x) t))
618 erc-log-matches-types-alist)
619 (lambda (buffer-cons)
620 (get-buffer (car buffer-cons))))))
621 (switch-to-buffer buffer-name)))
622
623(define-key erc-mode-map "\C-c\C-k" 'erc-go-to-log-matches-buffer)
624
625(defun erc-hide-fools (match-type nickuserhost message)
626 "Hide foolish comments.
627This function should be called from `erc-text-matched-hook'."
628 (when (eq match-type 'fool)
629 (erc-put-text-properties (point-min) (point-max)
630 '(invisible intangible)
631 (current-buffer))))
632
633(defun erc-beep-on-match (match-type nickuserhost message)
634 "Beep when text matches.
635This function is meant to be called from `erc-text-matched-hook'."
636 (when (member match-type erc-beep-match-types)
637 (beep)))
638
639(provide 'erc-match)
640
641;;; erc-match.el ends here
642;;
643;; Local Variables:
644;; indent-tabs-mode: t
645;; tab-width: 8
646;; End: