(rcirc-fill-column): Allow `window-width'.
[bpt/emacs.git] / lisp / net / rcirc.el
1 ;;; rcirc.el --- default, simple IRC client.
2
3 ;; Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
4
5 ;; Author: Ryan Yeske
6 ;; URL: http://www.nongnu.org/rcirc
7 ;; Keywords: comm
8
9 ;; This file is part of GNU Emacs.
10
11 ;; This file is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; This file 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
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;; Internet Relay Chat (IRC) is a form of instant communication over
29 ;; the Internet. It is mainly designed for group (many-to-many)
30 ;; communication in discussion forums called channels, but also allows
31 ;; one-to-one communication.
32
33 ;; Rcirc has simple defaults and clear and consistent behaviour.
34 ;; Message arrival timestamps, activity notification on the modeline,
35 ;; message filling, nick completion, and keepalive pings are all
36 ;; enabled by default, but can easily be adjusted or turned off. Each
37 ;; discussion takes place in its own buffer and there is a single
38 ;; server buffer per connection.
39
40 ;; Open a new irc connection with:
41 ;; M-x irc RET
42
43 ;;; Todo:
44
45 ;;; Code:
46
47 (require 'ring)
48 (require 'time-date)
49 (eval-when-compile (require 'cl))
50
51 (defgroup rcirc nil
52 "Simple IRC client."
53 :version "22.1"
54 :prefix "rcirc-"
55 :link '(custom-manual "(rcirc)")
56 :group 'applications)
57
58 (defcustom rcirc-default-server "irc.freenode.net"
59 "The default server to connect to."
60 :type 'string
61 :group 'rcirc)
62
63 (defcustom rcirc-default-port 6667
64 "The default port to connect to."
65 :type 'integer
66 :group 'rcirc)
67
68 (defcustom rcirc-default-nick (user-login-name)
69 "Your nick."
70 :type 'string
71 :group 'rcirc)
72
73 (defcustom rcirc-default-user-name (user-login-name)
74 "Your user name sent to the server when connecting."
75 :type 'string
76 :group 'rcirc)
77
78 (defcustom rcirc-default-user-full-name (if (string= (user-full-name) "")
79 rcirc-default-user-name
80 (user-full-name))
81 "The full name sent to the server when connecting."
82 :type 'string
83 :group 'rcirc)
84
85 (defcustom rcirc-startup-channels-alist '(("^irc.freenode.net$" "#rcirc"))
86 "Alist of channels to join at startup.
87 Each element looks like (SERVER-REGEXP . CHANNEL-LIST)."
88 :type '(alist :key-type string :value-type (repeat string))
89 :group 'rcirc)
90
91 (defcustom rcirc-fill-flag t
92 "*Non-nil means line-wrap messages printed in channel buffers."
93 :type 'boolean
94 :group 'rcirc)
95
96 (defcustom rcirc-fill-column nil
97 "*Column beyond which automatic line-wrapping should happen.
98 If nil, use value of `fill-column'.
99 If `window-width', use the window's width as maximum.
100 If `frame-width', use the frame's width as maximum."
101 :type '(choice (const :tag "Value of `fill-column'")
102 (const :tag "Full window width" window-width)
103 (const :tag "Full frame width" frame-width)
104 (integer :tag "Number of columns"))
105 :group 'rcirc)
106
107 (defcustom rcirc-fill-prefix nil
108 "*Text to insert before filled lines.
109 If nil, calculate the prefix dynamically to line up text
110 underneath each nick."
111 :type '(choice (const :tag "Dynamic" nil)
112 (string :tag "Prefix text"))
113 :group 'rcirc)
114
115 (defvar rcirc-ignore-buffer-activity-flag nil
116 "If non-nil, ignore activity in this buffer.")
117 (make-variable-buffer-local 'rcirc-ignore-buffer-activity-flag)
118
119 (defvar rcirc-low-priority-flag nil
120 "If non-nil, activity in this buffer is considered low priority.")
121 (make-variable-buffer-local 'rcirc-low-priority-flag)
122
123 (defcustom rcirc-time-format "%H:%M "
124 "*Describes how timestamps are printed.
125 Used as the first arg to `format-time-string'."
126 :type 'string
127 :group 'rcirc)
128
129 (defcustom rcirc-input-ring-size 1024
130 "*Size of input history ring."
131 :type 'integer
132 :group 'rcirc)
133
134 (defcustom rcirc-read-only-flag t
135 "*Non-nil means make text in IRC buffers read-only."
136 :type 'boolean
137 :group 'rcirc)
138
139 (defcustom rcirc-buffer-maximum-lines nil
140 "*The maximum size in lines for rcirc buffers.
141 Channel buffers are truncated from the top to be no greater than this
142 number. If zero or nil, no truncating is done."
143 :type '(choice (const :tag "No truncation" nil)
144 (integer :tag "Number of lines"))
145 :group 'rcirc)
146
147 (defcustom rcirc-scroll-show-maximum-output t
148 "*If non-nil, scroll buffer to keep the point at the bottom of the window."
149 :type 'boolean
150 :group 'rcirc)
151
152 (defcustom rcirc-authinfo nil
153 "List of authentication passwords.
154 Each element of the list is a list with a SERVER-REGEXP string
155 and a method symbol followed by method specific arguments.
156
157 The valid METHOD symbols are `nickserv', `chanserv' and
158 `bitlbee'.
159
160 The required ARGUMENTS for each METHOD symbol are:
161 `nickserv': NICK PASSWORD
162 `chanserv': NICK CHANNEL PASSWORD
163 `bitlbee': NICK PASSWORD
164
165 Example:
166 ((\"freenode\" nickserv \"bob\" \"p455w0rd\")
167 (\"freenode\" chanserv \"bob\" \"#bobland\" \"passwd99\")
168 (\"bitlbee\" bitlbee \"robert\" \"sekrit\"))"
169 :type '(alist :key-type (string :tag "Server")
170 :value-type (choice (list :tag "NickServ"
171 (const nickserv)
172 (string :tag "Nick")
173 (string :tag "Password"))
174 (list :tag "ChanServ"
175 (const chanserv)
176 (string :tag "Nick")
177 (string :tag "Channel")
178 (string :tag "Password"))
179 (list :tag "BitlBee"
180 (const bitlbee)
181 (string :tag "Nick")
182 (string :tag "Password"))))
183 :group 'rcirc)
184
185 (defcustom rcirc-auto-authenticate-flag t
186 "*Non-nil means automatically send authentication string to server.
187 See also `rcirc-authinfo'."
188 :type 'boolean
189 :group 'rcirc)
190
191 (defcustom rcirc-prompt "> "
192 "Prompt string to use in IRC buffers.
193
194 The following replacements are made:
195 %n is your nick.
196 %s is the server.
197 %t is the buffer target, a channel or a user.
198
199 Setting this alone will not affect the prompt;
200 use either M-x customize or also call `rcirc-update-prompt'."
201 :type 'string
202 :set 'rcirc-set-changed
203 :initialize 'custom-initialize-default
204 :group 'rcirc)
205
206 (defcustom rcirc-keywords nil
207 "List of keywords to highlight in message text."
208 :type '(repeat string)
209 :group 'rcirc)
210
211 (defcustom rcirc-ignore-list ()
212 "List of ignored nicks.
213 Use /ignore to list them, use /ignore NICK to add or remove a nick."
214 :type '(repeat string)
215 :group 'rcirc)
216
217 (defvar rcirc-ignore-list-automatic ()
218 "List of ignored nicks added to `rcirc-ignore-list' because of renaming.
219 When an ignored person renames, their nick is added to both lists.
220 Nicks will be removed from the automatic list on follow-up renamings or
221 parts.")
222
223 (defcustom rcirc-bright-nicks nil
224 "List of nicks to be emphasized.
225 See `rcirc-bright-nick' face."
226 :type '(repeat string)
227 :group 'rcirc)
228
229 (defcustom rcirc-dim-nicks nil
230 "List of nicks to be deemphasized.
231 See `rcirc-dim-nick' face."
232 :type '(repeat string)
233 :group 'rcirc)
234
235 (defcustom rcirc-print-hooks nil
236 "Hook run after text is printed.
237 Called with 5 arguments, PROCESS, SENDER, RESPONSE, TARGET and TEXT."
238 :type 'hook
239 :group 'rcirc)
240
241 (defcustom rcirc-always-use-server-buffer-flag nil
242 "Non-nil means messages without a channel target will go to the server buffer."
243 :type 'boolean
244 :group 'rcirc)
245
246 (defcustom rcirc-decode-coding-system 'utf-8
247 "Coding system used to decode incoming irc messages."
248 :type 'coding-system
249 :group 'rcirc)
250
251 (defcustom rcirc-encode-coding-system 'utf-8
252 "Coding system used to encode outgoing irc messages."
253 :type 'coding-system
254 :group 'rcirc)
255
256 (defcustom rcirc-coding-system-alist nil
257 "Alist to decide a coding system to use for a channel I/O operation.
258 The format is ((PATTERN . VAL) ...).
259 PATTERN is either a string or a cons of strings.
260 If PATTERN is a string, it is used to match a target.
261 If PATTERN is a cons of strings, the car part is used to match a
262 target, and the cdr part is used to match a server.
263 VAL is either a coding system or a cons of coding systems.
264 If VAL is a coding system, it is used for both decoding and encoding
265 messages.
266 If VAL is a cons of coding systems, the car part is used for decoding,
267 and the cdr part is used for encoding."
268 :type '(alist :key-type (choice (string :tag "Channel Regexp")
269 (cons (string :tag "Channel Regexp")
270 (string :tag "Server Regexp")))
271 :value-type (choice coding-system
272 (cons (coding-system :tag "Decode")
273 (coding-system :tag "Encode"))))
274 :group 'rcirc)
275
276 (defcustom rcirc-multiline-major-mode 'fundamental-mode
277 "Major-mode function to use in multiline edit buffers."
278 :type 'function
279 :group 'rcirc)
280
281 (defvar rcirc-nick nil)
282
283 (defvar rcirc-prompt-start-marker nil)
284 (defvar rcirc-prompt-end-marker nil)
285
286 (defvar rcirc-nick-table nil)
287
288 (defvar rcirc-nick-syntax-table
289 (let ((table (make-syntax-table text-mode-syntax-table)))
290 (mapc (lambda (c) (modify-syntax-entry c "w" table))
291 "[]\\`_^{|}-")
292 (modify-syntax-entry ?' "_" table)
293 table)
294 "Syntax table which includes all nick characters as word constituents.")
295
296 ;; each process has an alist of (target . buffer) pairs
297 (defvar rcirc-buffer-alist nil)
298
299 (defvar rcirc-activity nil
300 "List of buffers with unviewed activity.")
301
302 (defvar rcirc-activity-string ""
303 "String displayed in modeline representing `rcirc-activity'.")
304 (put 'rcirc-activity-string 'risky-local-variable t)
305
306 (defvar rcirc-server-buffer nil
307 "The server buffer associated with this channel buffer.")
308
309 (defvar rcirc-target nil
310 "The channel or user associated with this buffer.")
311
312 (defvar rcirc-urls nil
313 "List of urls seen in the current buffer.")
314 (put 'rcirc-urls 'permanent-local t)
315
316 (defvar rcirc-timeout-seconds 600
317 "Kill connection after this many seconds if there is no activity.")
318
319 (defconst rcirc-id-string (concat "rcirc on GNU Emacs " emacs-version))
320 \f
321 (defvar rcirc-startup-channels nil)
322 ;;;###autoload
323 (defun rcirc (arg)
324 "Connect to IRC.
325 If ARG is non-nil, prompt for a server to connect to."
326 (interactive "P")
327 (if arg
328 (let* ((server (read-string "IRC Server: " rcirc-default-server))
329 (port (read-string "IRC Port: " (number-to-string rcirc-default-port)))
330 (nick (read-string "IRC Nick: " rcirc-default-nick))
331 (channels (split-string
332 (read-string "IRC Channels: "
333 (mapconcat 'identity (rcirc-startup-channels server) " "))
334 "[, ]+" t)))
335 (rcirc-connect server port nick rcirc-default-user-name rcirc-default-user-full-name
336 channels))
337 ;; make new connection using defaults unless already connected to
338 ;; the default rcirc-server
339 (let (connected)
340 (dolist (p (rcirc-process-list))
341 (when (string= rcirc-default-server (process-name p))
342 (setq connected p)))
343 (if (not connected)
344 (rcirc-connect rcirc-default-server rcirc-default-port
345 rcirc-default-nick rcirc-default-user-name
346 rcirc-default-user-full-name
347 (rcirc-startup-channels rcirc-default-server))
348 (switch-to-buffer (process-buffer connected))
349 (message "Connected to %s"
350 (process-contact (get-buffer-process (current-buffer))
351 :host))))))
352 ;;;###autoload
353 (defalias 'irc 'rcirc)
354
355 \f
356 (defvar rcirc-process-output nil)
357 (defvar rcirc-topic nil)
358 (defvar rcirc-keepalive-timer nil)
359 (defvar rcirc-last-server-message-time nil)
360 (defvar rcirc-server nil) ; server provided by server
361 (defvar rcirc-server-name nil) ; server name given by 001 response
362 (defvar rcirc-timeout-timer nil)
363 (defvar rcirc-user-disconnect nil)
364 (defvar rcirc-connecting nil)
365 (defvar rcirc-process nil)
366
367 ;;;###autoload
368 (defun rcirc-connect (&optional server port nick user-name full-name startup-channels)
369 (save-excursion
370 (message "Connecting to %s..." server)
371 (let* ((inhibit-eol-conversion)
372 (port-number (if port
373 (if (stringp port)
374 (string-to-number port)
375 port)
376 rcirc-default-port))
377 (server (or server rcirc-default-server))
378 (nick (or nick rcirc-default-nick))
379 (user-name (or user-name rcirc-default-user-name))
380 (full-name (or full-name rcirc-default-user-full-name))
381 (startup-channels startup-channels)
382 (process (make-network-process :name server :host server :service port-number)))
383 ;; set up process
384 (set-process-coding-system process 'raw-text 'raw-text)
385 (switch-to-buffer (rcirc-generate-new-buffer-name process nil))
386 (set-process-buffer process (current-buffer))
387 (rcirc-mode process nil)
388 (set-process-sentinel process 'rcirc-sentinel)
389 (set-process-filter process 'rcirc-filter)
390 (make-local-variable 'rcirc-process)
391 (setq rcirc-process process)
392 (make-local-variable 'rcirc-server)
393 (setq rcirc-server server)
394 (make-local-variable 'rcirc-server-name)
395 (setq rcirc-server-name server) ; update when we get 001 response
396 (make-local-variable 'rcirc-buffer-alist)
397 (setq rcirc-buffer-alist nil)
398 (make-local-variable 'rcirc-nick-table)
399 (setq rcirc-nick-table (make-hash-table :test 'equal))
400 (make-local-variable 'rcirc-nick)
401 (setq rcirc-nick nick)
402 (make-local-variable 'rcirc-process-output)
403 (setq rcirc-process-output nil)
404 (make-local-variable 'rcirc-startup-channels)
405 (setq rcirc-startup-channels startup-channels)
406 (make-local-variable 'rcirc-last-server-message-time)
407 (setq rcirc-last-server-message-time (current-time))
408 (make-local-variable 'rcirc-timeout-timer)
409 (setq rcirc-timeout-timer nil)
410 (make-local-variable 'rcirc-user-disconnect)
411 (setq rcirc-user-disconnect nil)
412 (make-local-variable 'rcirc-connecting)
413 (setq rcirc-connecting t)
414
415 ;; identify
416 (rcirc-send-string process (concat "NICK " nick))
417 (rcirc-send-string process (concat "USER " user-name
418 " hostname servername :"
419 full-name))
420
421 ;; setup ping timer if necessary
422 (unless rcirc-keepalive-timer
423 (setq rcirc-keepalive-timer
424 (run-at-time 0 (/ rcirc-timeout-seconds 2) 'rcirc-keepalive)))
425
426 (message "Connecting to %s...done" server)
427
428 ;; return process object
429 process)))
430
431 (defmacro with-rcirc-process-buffer (process &rest body)
432 (declare (indent 1) (debug t))
433 `(with-current-buffer (process-buffer ,process)
434 ,@body))
435
436 (defmacro with-rcirc-server-buffer (&rest body)
437 (declare (indent 0) (debug t))
438 `(with-current-buffer rcirc-server-buffer
439 ,@body))
440
441 (defun rcirc-keepalive ()
442 "Send keep alive pings to active rcirc processes.
443 Kill processes that have not received a server message since the
444 last ping."
445 (if (rcirc-process-list)
446 (mapc (lambda (process)
447 (with-rcirc-process-buffer process
448 (when (not rcirc-connecting)
449 (rcirc-send-string process (concat "PING " (rcirc-server-name process))))))
450 (rcirc-process-list))
451 ;; no processes, clean up timer
452 (cancel-timer rcirc-keepalive-timer)
453 (setq rcirc-keepalive-timer nil)))
454
455 (defvar rcirc-debug-buffer " *rcirc debug*")
456 (defvar rcirc-debug-flag nil
457 "If non-nil, write information to `rcirc-debug-buffer'.")
458 (defun rcirc-debug (process text)
459 "Add an entry to the debug log including PROCESS and TEXT.
460 Debug text is written to `rcirc-debug-buffer' if `rcirc-debug-flag'
461 is non-nil."
462 (when rcirc-debug-flag
463 (save-excursion
464 (save-window-excursion
465 (set-buffer (get-buffer-create rcirc-debug-buffer))
466 (goto-char (point-max))
467 (insert (concat
468 "["
469 (format-time-string "%Y-%m-%dT%T ") (process-name process)
470 "] "
471 text))))))
472
473 (defvar rcirc-sentinel-hooks nil
474 "Hook functions called when the process sentinel is called.
475 Functions are called with PROCESS and SENTINEL arguments.")
476
477 (defun rcirc-sentinel (process sentinel)
478 "Called when PROCESS receives SENTINEL."
479 (let ((sentinel (replace-regexp-in-string "\n" "" sentinel)))
480 (rcirc-debug process (format "SENTINEL: %S %S\n" process sentinel))
481 (with-rcirc-process-buffer process
482 (dolist (buffer (cons nil (mapcar 'cdr rcirc-buffer-alist)))
483 (with-current-buffer (or buffer (current-buffer))
484 (rcirc-print process "rcirc.el" "ERROR" rcirc-target
485 (format "%s: %s (%S)"
486 (process-name process)
487 sentinel
488 (process-status process)) (not rcirc-target))
489 ;; remove the prompt from buffers
490 (let ((inhibit-read-only t))
491 (delete-region rcirc-prompt-start-marker
492 rcirc-prompt-end-marker))))
493 (run-hook-with-args 'rcirc-sentinel-hooks process sentinel))))
494
495 (defun rcirc-process-list ()
496 "Return a list of rcirc processes."
497 (let (ps)
498 (mapc (lambda (p)
499 (when (buffer-live-p (process-buffer p))
500 (with-rcirc-process-buffer p
501 (when (eq major-mode 'rcirc-mode)
502 (setq ps (cons p ps))))))
503 (process-list))
504 ps))
505
506 (defvar rcirc-receive-message-hooks nil
507 "Hook functions run when a message is received from server.
508 Function is called with PROCESS, COMMAND, SENDER, ARGS and LINE.")
509 (defun rcirc-filter (process output)
510 "Called when PROCESS receives OUTPUT."
511 (rcirc-debug process output)
512 (rcirc-reschedule-timeout process)
513 (with-rcirc-process-buffer process
514 (setq rcirc-last-server-message-time (current-time))
515 (setq rcirc-process-output (concat rcirc-process-output output))
516 (when (= (aref rcirc-process-output
517 (1- (length rcirc-process-output))) ?\n)
518 (mapc (lambda (line)
519 (rcirc-process-server-response process line))
520 (split-string rcirc-process-output "[\n\r]" t))
521 (setq rcirc-process-output nil))))
522
523 (defun rcirc-reschedule-timeout (process)
524 (with-rcirc-process-buffer process
525 (when (not rcirc-connecting)
526 (with-rcirc-process-buffer process
527 (when rcirc-timeout-timer (cancel-timer rcirc-timeout-timer))
528 (setq rcirc-timeout-timer (run-at-time rcirc-timeout-seconds nil
529 'rcirc-delete-process
530 process))))))
531
532 (defun rcirc-delete-process (process)
533 (message "delete process %S" process)
534 (delete-process process))
535
536 (defvar rcirc-trap-errors-flag t)
537 (defun rcirc-process-server-response (process text)
538 (if rcirc-trap-errors-flag
539 (condition-case err
540 (rcirc-process-server-response-1 process text)
541 (error
542 (rcirc-print process "RCIRC" "ERROR" nil
543 (format "\"%s\" %s" text err) t)))
544 (rcirc-process-server-response-1 process text)))
545
546 (defun rcirc-process-server-response-1 (process text)
547 (if (string-match "^\\(:\\([^ ]+\\) \\)?\\([^ ]+\\) \\(.+\\)$" text)
548 (let* ((user (match-string 2 text))
549 (sender (rcirc-user-nick user))
550 (cmd (match-string 3 text))
551 (args (match-string 4 text))
552 (handler (intern-soft (concat "rcirc-handler-" cmd))))
553 (string-match "^\\([^:]*\\):?\\(.+\\)?$" args)
554 (let* ((args1 (match-string 1 args))
555 (args2 (match-string 2 args))
556 (args (delq nil (append (split-string args1 " " t)
557 (list args2)))))
558 (if (not (fboundp handler))
559 (rcirc-handler-generic process cmd sender args text)
560 (funcall handler process sender args text))
561 (run-hook-with-args 'rcirc-receive-message-hooks
562 process cmd sender args text)))
563 (message "UNHANDLED: %s" text)))
564
565 (defvar rcirc-responses-no-activity '("305" "306")
566 "Responses that don't trigger activity in the mode-line indicator.")
567
568 (defun rcirc-handler-generic (process response sender args text)
569 "Generic server response handler."
570 (rcirc-print process sender response nil
571 (mapconcat 'identity (cdr args) " ")
572 (not (member response rcirc-responses-no-activity))))
573
574 (defun rcirc-send-string (process string)
575 "Send PROCESS a STRING plus a newline."
576 (let ((string (concat (encode-coding-string string rcirc-encode-coding-system)
577 "\n")))
578 (unless (eq (process-status process) 'open)
579 (error "Network connection to %s is not open"
580 (process-name process)))
581 (rcirc-debug process string)
582 (process-send-string process string)))
583
584 (defun rcirc-buffer-process (&optional buffer)
585 "Return the process associated with channel BUFFER.
586 With no argument or nil as argument, use the current buffer."
587 (or (get-buffer-process (if buffer
588 (with-current-buffer buffer
589 rcirc-server-buffer)
590 rcirc-server-buffer))
591 rcirc-process))
592
593 (defun rcirc-server-name (process)
594 "Return PROCESS server name, given by the 001 response."
595 (with-rcirc-process-buffer process
596 (or rcirc-server-name rcirc-default-server)))
597
598 (defun rcirc-nick (process)
599 "Return PROCESS nick."
600 (with-rcirc-process-buffer process
601 (or rcirc-nick rcirc-default-nick)))
602
603 (defun rcirc-buffer-nick (&optional buffer)
604 "Return the nick associated with BUFFER.
605 With no argument or nil as argument, use the current buffer."
606 (with-current-buffer (or buffer (current-buffer))
607 (with-current-buffer rcirc-server-buffer
608 (or rcirc-nick rcirc-default-nick))))
609
610 (defvar rcirc-max-message-length 420
611 "Messages longer than this value will be split.")
612
613 (defun rcirc-send-message (process target message &optional noticep)
614 "Send TARGET associated with PROCESS a privmsg with text MESSAGE.
615 If NOTICEP is non-nil, send a notice instead of privmsg."
616 ;; max message length is 512 including CRLF
617 (let* ((response (if noticep "NOTICE" "PRIVMSG"))
618 (oversize (> (length message) rcirc-max-message-length))
619 (text (if oversize
620 (substring message 0 rcirc-max-message-length)
621 message))
622 (text (if (string= text "")
623 " "
624 text))
625 (more (if oversize
626 (substring message rcirc-max-message-length))))
627 (rcirc-get-buffer-create process target)
628 (rcirc-print process (rcirc-nick process) response target text)
629 (rcirc-send-string process (concat response " " target " :" text))
630 (when more (rcirc-send-message process target more noticep))))
631
632 (defvar rcirc-input-ring nil)
633 (defvar rcirc-input-ring-index 0)
634 (defun rcirc-prev-input-string (arg)
635 (ring-ref rcirc-input-ring (+ rcirc-input-ring-index arg)))
636
637 (defun rcirc-insert-prev-input (arg)
638 (interactive "p")
639 (when (<= rcirc-prompt-end-marker (point))
640 (delete-region rcirc-prompt-end-marker (point-max))
641 (insert (rcirc-prev-input-string 0))
642 (setq rcirc-input-ring-index (1+ rcirc-input-ring-index))))
643
644 (defun rcirc-insert-next-input (arg)
645 (interactive "p")
646 (when (<= rcirc-prompt-end-marker (point))
647 (delete-region rcirc-prompt-end-marker (point-max))
648 (setq rcirc-input-ring-index (1- rcirc-input-ring-index))
649 (insert (rcirc-prev-input-string -1))))
650
651 (defvar rcirc-nick-completions nil)
652 (defvar rcirc-nick-completion-start-offset nil)
653
654 (defun rcirc-complete-nick ()
655 "Cycle through nick completions from list of nicks in channel."
656 (interactive)
657 (if (eq last-command this-command)
658 (setq rcirc-nick-completions
659 (append (cdr rcirc-nick-completions)
660 (list (car rcirc-nick-completions))))
661 (setq rcirc-nick-completion-start-offset
662 (- (save-excursion
663 (if (re-search-backward " " rcirc-prompt-end-marker t)
664 (1+ (point))
665 rcirc-prompt-end-marker))
666 rcirc-prompt-end-marker))
667 (setq rcirc-nick-completions
668 (let ((completion-ignore-case t))
669 (all-completions
670 (buffer-substring
671 (+ rcirc-prompt-end-marker
672 rcirc-nick-completion-start-offset)
673 (point))
674 (mapcar (lambda (x) (cons x nil))
675 (rcirc-channel-nicks (rcirc-buffer-process)
676 rcirc-target))))))
677 (let ((completion (car rcirc-nick-completions)))
678 (when completion
679 (rcirc-put-nick-channel (rcirc-buffer-process) completion rcirc-target)
680 (delete-region (+ rcirc-prompt-end-marker
681 rcirc-nick-completion-start-offset)
682 (point))
683 (insert (concat completion
684 (if (= (+ rcirc-prompt-end-marker
685 rcirc-nick-completion-start-offset)
686 rcirc-prompt-end-marker)
687 ": "))))))
688
689 (defun set-rcirc-decode-coding-system (coding-system)
690 "Set the decode coding system used in this channel."
691 (interactive "zCoding system for incoming messages: ")
692 (setq rcirc-decode-coding-system coding-system))
693
694 (defun set-rcirc-encode-coding-system (coding-system)
695 "Set the encode coding system used in this channel."
696 (interactive "zCoding system for outgoing messages: ")
697 (setq rcirc-encode-coding-system coding-system))
698
699 (defvar rcirc-mode-map (make-sparse-keymap)
700 "Keymap for rcirc mode.")
701
702 (define-key rcirc-mode-map (kbd "RET") 'rcirc-send-input)
703 (define-key rcirc-mode-map (kbd "M-p") 'rcirc-insert-prev-input)
704 (define-key rcirc-mode-map (kbd "M-n") 'rcirc-insert-next-input)
705 (define-key rcirc-mode-map (kbd "TAB") 'rcirc-complete-nick)
706 (define-key rcirc-mode-map (kbd "C-c C-b") 'rcirc-browse-url)
707 (define-key rcirc-mode-map (kbd "C-c C-c") 'rcirc-edit-multiline)
708 (define-key rcirc-mode-map (kbd "C-c C-j") 'rcirc-cmd-join)
709 (define-key rcirc-mode-map (kbd "C-c C-k") 'rcirc-cmd-kick)
710 (define-key rcirc-mode-map (kbd "C-c C-l") 'rcirc-toggle-low-priority)
711 (define-key rcirc-mode-map (kbd "C-c C-d") 'rcirc-cmd-mode)
712 (define-key rcirc-mode-map (kbd "C-c C-m") 'rcirc-cmd-msg)
713 (define-key rcirc-mode-map (kbd "C-c C-r") 'rcirc-cmd-nick) ; rename
714 (define-key rcirc-mode-map (kbd "C-c C-o") 'rcirc-cmd-oper)
715 (define-key rcirc-mode-map (kbd "C-c C-p") 'rcirc-cmd-part)
716 (define-key rcirc-mode-map (kbd "C-c C-q") 'rcirc-cmd-query)
717 (define-key rcirc-mode-map (kbd "C-c C-t") 'rcirc-cmd-topic)
718 (define-key rcirc-mode-map (kbd "C-c C-n") 'rcirc-cmd-names)
719 (define-key rcirc-mode-map (kbd "C-c C-w") 'rcirc-cmd-whois)
720 (define-key rcirc-mode-map (kbd "C-c C-x") 'rcirc-cmd-quit)
721 (define-key rcirc-mode-map (kbd "C-c TAB") ; C-i
722 'rcirc-toggle-ignore-buffer-activity)
723 (define-key rcirc-mode-map (kbd "C-c C-s") 'rcirc-switch-to-server-buffer)
724 (define-key rcirc-mode-map (kbd "C-c C-a") 'rcirc-jump-to-first-unread-line)
725
726 (defvar rcirc-browse-url-map (make-sparse-keymap)
727 "Keymap used for browsing URLs in `rcirc-mode'.")
728
729 (define-key rcirc-browse-url-map (kbd "RET") 'rcirc-browse-url-at-point)
730 (define-key rcirc-browse-url-map (kbd "<mouse-2>") 'rcirc-browse-url-at-mouse)
731
732 (defvar rcirc-short-buffer-name nil
733 "Generated abbreviation to use to indicate buffer activity.")
734
735 (defvar rcirc-mode-hook nil
736 "Hook run when setting up rcirc buffer.")
737
738 (defvar rcirc-last-post-time nil)
739
740 (defun rcirc-mode (process target)
741 "Major mode for IRC channel buffers.
742
743 \\{rcirc-mode-map}"
744 (kill-all-local-variables)
745 (use-local-map rcirc-mode-map)
746 (setq mode-name "rcirc")
747 (setq major-mode 'rcirc-mode)
748
749 (make-local-variable 'rcirc-input-ring)
750 (setq rcirc-input-ring (make-ring rcirc-input-ring-size))
751 (make-local-variable 'rcirc-server-buffer)
752 (setq rcirc-server-buffer (process-buffer process))
753 (make-local-variable 'rcirc-target)
754 (setq rcirc-target target)
755 (make-local-variable 'rcirc-topic)
756 (setq rcirc-topic nil)
757 (make-local-variable 'rcirc-last-post-time)
758 (setq rcirc-last-post-time (current-time))
759
760 (make-local-variable 'rcirc-short-buffer-name)
761 (setq rcirc-short-buffer-name nil)
762 (make-local-variable 'rcirc-urls)
763 (setq use-hard-newlines t)
764
765 (make-local-variable 'rcirc-decode-coding-system)
766 (make-local-variable 'rcirc-encode-coding-system)
767 (dolist (i rcirc-coding-system-alist)
768 (let ((chan (if (consp (car i)) (caar i) (car i)))
769 (serv (if (consp (car i)) (cdar i) "")))
770 (when (and (string-match chan (or target ""))
771 (string-match serv (rcirc-server-name process)))
772 (setq rcirc-decode-coding-system (if (consp (cdr i)) (cadr i) (cdr i))
773 rcirc-encode-coding-system (if (consp (cdr i)) (cddr i) (cdr i))))))
774
775 ;; setup the prompt and markers
776 (make-local-variable 'rcirc-prompt-start-marker)
777 (setq rcirc-prompt-start-marker (make-marker))
778 (set-marker rcirc-prompt-start-marker (point-max))
779 (make-local-variable 'rcirc-prompt-end-marker)
780 (setq rcirc-prompt-end-marker (make-marker))
781 (set-marker rcirc-prompt-end-marker (point-max))
782 (rcirc-update-prompt)
783 (goto-char rcirc-prompt-end-marker)
784 (make-local-variable 'overlay-arrow-position)
785 (setq overlay-arrow-position (make-marker))
786 (set-marker overlay-arrow-position nil)
787
788 ;; if the user changes the major mode or kills the buffer, there is
789 ;; cleanup work to do
790 (add-hook 'change-major-mode-hook 'rcirc-change-major-mode-hook nil t)
791 (add-hook 'kill-buffer-hook 'rcirc-kill-buffer-hook nil t)
792
793 ;; add to buffer list, and update buffer abbrevs
794 (when target ; skip server buffer
795 (let ((buffer (current-buffer)))
796 (with-rcirc-process-buffer process
797 (setq rcirc-buffer-alist (cons (cons target buffer)
798 rcirc-buffer-alist))))
799 (rcirc-update-short-buffer-names))
800
801 (run-hooks 'rcirc-mode-hook))
802
803 (defun rcirc-update-prompt (&optional all)
804 "Reset the prompt string in the current buffer.
805
806 If ALL is non-nil, update prompts in all IRC buffers."
807 (if all
808 (mapc (lambda (process)
809 (mapc (lambda (buffer)
810 (with-current-buffer buffer
811 (rcirc-update-prompt)))
812 (with-rcirc-process-buffer process
813 (mapcar 'cdr rcirc-buffer-alist))))
814 (rcirc-process-list))
815 (let ((inhibit-read-only t)
816 (prompt (or rcirc-prompt "")))
817 (mapc (lambda (rep)
818 (setq prompt
819 (replace-regexp-in-string (car rep) (cdr rep) prompt)))
820 (list (cons "%n" (rcirc-buffer-nick))
821 (cons "%s" (with-rcirc-server-buffer rcirc-server-name))
822 (cons "%t" (or rcirc-target ""))))
823 (save-excursion
824 (delete-region rcirc-prompt-start-marker rcirc-prompt-end-marker)
825 (goto-char rcirc-prompt-start-marker)
826 (let ((start (point)))
827 (insert-before-markers prompt)
828 (set-marker rcirc-prompt-start-marker start)
829 (when (not (zerop (- rcirc-prompt-end-marker
830 rcirc-prompt-start-marker)))
831 (add-text-properties rcirc-prompt-start-marker
832 rcirc-prompt-end-marker
833 (list 'face 'rcirc-prompt
834 'read-only t 'field t
835 'front-sticky t 'rear-nonsticky t))))))))
836
837 (defun rcirc-set-changed (option value)
838 "Set OPTION to VALUE and do updates after a customization change."
839 (set-default option value)
840 (cond ((eq option 'rcirc-prompt)
841 (rcirc-update-prompt 'all))
842 (t
843 (error "Bad option %s" option))))
844
845 (defun rcirc-channel-p (target)
846 "Return t if TARGET is a channel name."
847 (and target
848 (not (zerop (length target)))
849 (or (eq (aref target 0) ?#)
850 (eq (aref target 0) ?&))))
851
852 (defun rcirc-kill-buffer-hook ()
853 "Part the channel when killing an rcirc buffer."
854 (when (eq major-mode 'rcirc-mode)
855 (rcirc-clean-up-buffer "Killed buffer")))
856
857 (defun rcirc-change-major-mode-hook ()
858 "Part the channel when changing the major-mode."
859 (rcirc-clean-up-buffer "Changed major mode"))
860
861 (defun rcirc-clean-up-buffer (reason)
862 (let ((buffer (current-buffer)))
863 (rcirc-clear-activity buffer)
864 (when (and (rcirc-buffer-process)
865 (eq (process-status (rcirc-buffer-process)) 'open))
866 (with-rcirc-server-buffer
867 (setq rcirc-buffer-alist
868 (rassq-delete-all buffer rcirc-buffer-alist)))
869 (rcirc-update-short-buffer-names)
870 (if (rcirc-channel-p rcirc-target)
871 (rcirc-send-string (rcirc-buffer-process)
872 (concat "PART " rcirc-target " :" reason))
873 (when rcirc-target
874 (rcirc-remove-nick-channel (rcirc-buffer-process)
875 (rcirc-buffer-nick)
876 rcirc-target))))))
877
878 (defun rcirc-generate-new-buffer-name (process target)
879 "Return a buffer name based on PROCESS and TARGET.
880 This is used for the initial name given to IRC buffers."
881 (if target
882 (concat target "@" (process-name process))
883 (concat "*" (process-name process) "*")))
884
885 (defun rcirc-get-buffer (process target &optional server)
886 "Return the buffer associated with the PROCESS and TARGET.
887
888 If optional argument SERVER is non-nil, return the server buffer
889 if there is no existing buffer for TARGET, otherwise return nil."
890 (with-rcirc-process-buffer process
891 (if (null target)
892 (current-buffer)
893 (let ((buffer (cdr (assoc-string target rcirc-buffer-alist t))))
894 (or buffer (when server (current-buffer)))))))
895
896 (defun rcirc-get-buffer-create (process target)
897 "Return the buffer associated with the PROCESS and TARGET.
898 Create the buffer if it doesn't exist."
899 (let ((buffer (rcirc-get-buffer process target)))
900 (if (and buffer (buffer-live-p buffer))
901 (with-current-buffer buffer
902 (when (not rcirc-target)
903 (setq rcirc-target target))
904 buffer)
905 ;; create the buffer
906 (with-rcirc-process-buffer process
907 (let ((new-buffer (get-buffer-create
908 (rcirc-generate-new-buffer-name process target))))
909 (with-current-buffer new-buffer
910 (rcirc-mode process target))
911 (rcirc-put-nick-channel process (rcirc-nick process) target)
912 new-buffer)))))
913
914 (defun rcirc-send-input ()
915 "Send input to target associated with the current buffer."
916 (interactive)
917 (if (< (point) rcirc-prompt-end-marker)
918 ;; copy the line down to the input area
919 (progn
920 (forward-line 0)
921 (let ((start (if (eq (point) (point-min))
922 (point)
923 (if (get-text-property (1- (point)) 'hard)
924 (point)
925 (previous-single-property-change (point) 'hard))))
926 (end (next-single-property-change (1+ (point)) 'hard)))
927 (goto-char (point-max))
928 (insert (replace-regexp-in-string
929 "\n\\s-+" " "
930 (buffer-substring-no-properties start end)))))
931 ;; process input
932 (goto-char (point-max))
933 (when (not (equal 0 (- (point) rcirc-prompt-end-marker)))
934 ;; delete a trailing newline
935 (when (eq (point) (point-at-bol))
936 (delete-backward-char 1))
937 (let ((input (buffer-substring-no-properties
938 rcirc-prompt-end-marker (point))))
939 (dolist (line (split-string input "\n"))
940 (rcirc-process-input-line line))
941 ;; add to input-ring
942 (save-excursion
943 (ring-insert rcirc-input-ring input)
944 (setq rcirc-input-ring-index 0))))))
945
946 (defun rcirc-process-input-line (line)
947 (if (string-match "^/\\([^ ]+\\) ?\\(.*\\)$" line)
948 (rcirc-process-command (match-string 1 line)
949 (match-string 2 line)
950 line)
951 (rcirc-process-message line)))
952
953 (defun rcirc-process-message (line)
954 (if (not rcirc-target)
955 (message "Not joined (no target)")
956 (delete-region rcirc-prompt-end-marker (point))
957 (rcirc-send-message (rcirc-buffer-process) rcirc-target line)
958 (setq rcirc-last-post-time (current-time))))
959
960 (defun rcirc-process-command (command args line)
961 (if (eq (aref command 0) ?/)
962 ;; "//text" will send "/text" as a message
963 (rcirc-process-message (substring line 1))
964 (let ((fun (intern-soft (concat "rcirc-cmd-" command)))
965 (process (rcirc-buffer-process)))
966 (newline)
967 (with-current-buffer (current-buffer)
968 (delete-region rcirc-prompt-end-marker (point))
969 (if (string= command "me")
970 (rcirc-print process (rcirc-buffer-nick)
971 "ACTION" rcirc-target args)
972 (rcirc-print process (rcirc-buffer-nick)
973 "COMMAND" rcirc-target line))
974 (set-marker rcirc-prompt-end-marker (point))
975 (if (fboundp fun)
976 (funcall fun args process rcirc-target)
977 (rcirc-send-string process
978 (concat command " :" args)))))))
979
980 (defvar rcirc-parent-buffer nil)
981 (defvar rcirc-window-configuration nil)
982 (defun rcirc-edit-multiline ()
983 "Move current edit to a dedicated buffer."
984 (interactive)
985 (let ((pos (1+ (- (point) rcirc-prompt-end-marker))))
986 (goto-char (point-max))
987 (let ((text (buffer-substring rcirc-prompt-end-marker (point)))
988 (parent (buffer-name)))
989 (delete-region rcirc-prompt-end-marker (point))
990 (setq rcirc-window-configuration (current-window-configuration))
991 (pop-to-buffer (concat "*multiline " parent "*"))
992 (funcall rcirc-multiline-major-mode)
993 (rcirc-multiline-minor-mode 1)
994 (setq rcirc-parent-buffer parent)
995 (insert text)
996 (and (> pos 0) (goto-char pos))
997 (message "Type C-c C-c to return text to %s, or C-c C-k to cancel" parent))))
998
999 (defvar rcirc-multiline-minor-mode-map (make-sparse-keymap)
1000 "Keymap for multiline mode in rcirc.")
1001 (define-key rcirc-multiline-minor-mode-map
1002 (kbd "C-c C-c") 'rcirc-multiline-minor-submit)
1003 (define-key rcirc-multiline-minor-mode-map
1004 (kbd "C-x C-s") 'rcirc-multiline-minor-submit)
1005 (define-key rcirc-multiline-minor-mode-map
1006 (kbd "C-c C-k") 'rcirc-multiline-minor-cancel)
1007 (define-key rcirc-multiline-minor-mode-map
1008 (kbd "ESC ESC ESC") 'rcirc-multiline-minor-cancel)
1009
1010 (define-minor-mode rcirc-multiline-minor-mode
1011 "Minor mode for editing multiple lines in rcirc."
1012 :init-value nil
1013 :lighter " rcirc-mline"
1014 :keymap rcirc-multiline-minor-mode-map
1015 :global nil
1016 :group 'rcirc
1017 (make-local-variable 'rcirc-parent-buffer)
1018 (put 'rcirc-parent-buffer 'permanent-local t)
1019 (setq fill-column rcirc-max-message-length))
1020
1021 (defun rcirc-multiline-minor-submit ()
1022 "Send the text in buffer back to parent buffer."
1023 (interactive)
1024 (assert rcirc-parent-buffer)
1025 (untabify (point-min) (point-max))
1026 (let ((text (buffer-substring (point-min) (point-max)))
1027 (buffer (current-buffer))
1028 (pos (point)))
1029 (set-buffer rcirc-parent-buffer)
1030 (goto-char (point-max))
1031 (insert text)
1032 (kill-buffer buffer)
1033 (set-window-configuration rcirc-window-configuration)
1034 (goto-char (+ rcirc-prompt-end-marker (1- pos)))))
1035
1036 (defun rcirc-multiline-minor-cancel ()
1037 "Cancel the multiline edit."
1038 (interactive)
1039 (kill-buffer (current-buffer))
1040 (set-window-configuration rcirc-window-configuration))
1041
1042 (defun rcirc-any-buffer (process)
1043 "Return a buffer for PROCESS, either the one selected or the process buffer."
1044 (if rcirc-always-use-server-buffer-flag
1045 (process-buffer process)
1046 (let ((buffer (window-buffer (selected-window))))
1047 (if (and buffer
1048 (with-current-buffer buffer
1049 (and (eq major-mode 'rcirc-mode)
1050 (eq (rcirc-buffer-process) process))))
1051 buffer
1052 (process-buffer process)))))
1053
1054 (defcustom rcirc-response-formats
1055 '(("PRIVMSG" . "%T<%N> %m")
1056 ("NOTICE" . "%T-%N- %m")
1057 ("ACTION" . "%T[%N %m]")
1058 ("COMMAND" . "%T%m")
1059 ("ERROR" . "%T%fw!!! %m")
1060 (t . "%T%fp*** %fs%n %r %m"))
1061 "An alist of formats used for printing responses.
1062 The format is looked up using the response-type as a key;
1063 if no match is found, the default entry (with a key of `t') is used.
1064
1065 The entry's value part should be a string, which is inserted with
1066 the of the following escape sequences replaced by the described values:
1067
1068 %m The message text
1069 %n The sender's nick
1070 %N The sender's nick (with face `rcirc-my-nick' or `rcirc-other-nick')
1071 %r The response-type
1072 %T The timestamp (with face `rcirc-timestamp')
1073 %t The target
1074 %fw Following text uses the face `font-lock-warning-face'
1075 %fp Following text uses the face `rcirc-server-prefix'
1076 %fs Following text uses the face `rcirc-server'
1077 %f[FACE] Following text uses the face FACE
1078 %f- Following text uses the default face
1079 %% A literal `%' character"
1080 :type '(alist :key-type (choice (string :tag "Type")
1081 (const :tag "Default" t))
1082 :value-type string)
1083 :group 'rcirc)
1084
1085 (defun rcirc-format-response-string (process sender response target text)
1086 "Return a nicely-formatted response string, incorporating TEXT
1087 \(and perhaps other arguments). The specific formatting used
1088 is found by looking up RESPONSE in `rcirc-response-formats'."
1089 (let ((chunks
1090 (split-string (or (cdr (assoc response rcirc-response-formats))
1091 (cdr (assq t rcirc-response-formats)))
1092 "%"))
1093 (sender (or sender ""))
1094 (result "")
1095 (face nil)
1096 key face-key repl)
1097 (when (equal (car chunks) "")
1098 (pop chunks))
1099 (dolist (chunk chunks)
1100 (if (equal chunk "")
1101 (setq key ?%)
1102 (setq key (aref chunk 0))
1103 (setq chunk (substring chunk 1)))
1104 (setq repl
1105 (cond ((eq key ?%)
1106 ;; %% -- literal % character
1107 "%")
1108 ((or (eq key ?n) (eq key ?N))
1109 ;; %n/%N -- nick
1110 (let ((nick (concat (if (string= (rcirc-server-name process)
1111 sender)
1112 ""
1113 sender)
1114 (and target (concat "," target)))))
1115 (rcirc-facify nick
1116 (if (eq key ?n)
1117 face
1118 (cond ((string= sender (rcirc-nick process))
1119 'rcirc-my-nick)
1120 ((and rcirc-bright-nicks
1121 (string-match
1122 (regexp-opt rcirc-bright-nicks)
1123 sender))
1124 'rcirc-bright-nick)
1125 ((and rcirc-dim-nicks
1126 (string-match
1127 (regexp-opt rcirc-dim-nicks)
1128 sender))
1129 'rcirc-dim-nick)
1130 (t
1131 'rcirc-other-nick))))))
1132 ((eq key ?T)
1133 ;; %T -- timestamp
1134 (rcirc-facify
1135 (format-time-string rcirc-time-format (current-time))
1136 'rcirc-timestamp))
1137 ((eq key ?m)
1138 ;; %m -- message text
1139 (rcirc-markup-text process sender response (rcirc-facify text face)))
1140 ((eq key ?t)
1141 ;; %t -- target
1142 (rcirc-facify (or rcirc-target "") face))
1143 ((eq key ?r)
1144 ;; %r -- response
1145 (rcirc-facify response face))
1146 ((eq key ?f)
1147 ;; %f -- change face
1148 (setq face-key (aref chunk 0))
1149 (setq chunk (substring chunk 1))
1150 (cond ((eq face-key ?w)
1151 ;; %fw -- warning face
1152 (setq face 'font-lock-warning-face))
1153 ((eq face-key ?p)
1154 ;; %fp -- server-prefix face
1155 (setq face 'rcirc-server-prefix))
1156 ((eq face-key ?s)
1157 ;; %fs -- warning face
1158 (setq face 'rcirc-server))
1159 ((eq face-key ?-)
1160 ;; %fs -- warning face
1161 (setq face nil))
1162 ((and (eq face-key ?\[)
1163 (string-match "^\\([^]]*\\)[]]" chunk)
1164 (facep (match-string 1 chunk)))
1165 ;; %f[...] -- named face
1166 (setq face (intern (match-string 1 chunk)))
1167 (setq chunk (substring chunk (match-end 0)))))
1168 "")))
1169 (setq result (concat result repl (rcirc-facify chunk face))))
1170 result))
1171
1172 (defun rcirc-target-buffer (process sender response target text)
1173 "Return a buffer to print the server response."
1174 (assert (not (bufferp target)))
1175 (with-rcirc-process-buffer process
1176 (cond ((not target)
1177 (rcirc-any-buffer process))
1178 ((not (rcirc-channel-p target))
1179 ;; message from another user
1180 (if (string= response "PRIVMSG")
1181 (rcirc-get-buffer-create process (if (string= sender rcirc-nick)
1182 target
1183 sender))
1184 (rcirc-get-buffer process target t)))
1185 ((or (rcirc-get-buffer process target)
1186 (rcirc-any-buffer process))))))
1187
1188 (defvar rcirc-activity-types nil)
1189 (make-variable-buffer-local 'rcirc-activity-types)
1190 (defvar rcirc-last-sender nil)
1191 (make-variable-buffer-local 'rcirc-last-sender)
1192
1193 (defun rcirc-print (process sender response target text &optional activity)
1194 "Print TEXT in the buffer associated with TARGET.
1195 Format based on SENDER and RESPONSE. If ACTIVITY is non-nil,
1196 record activity."
1197 (or text (setq text ""))
1198 (unless (or (member sender rcirc-ignore-list)
1199 (member (with-syntax-table rcirc-nick-syntax-table
1200 (when (string-match "^\\([^/]\\w*\\)[:,]" text)
1201 (match-string 1 text)))
1202 rcirc-ignore-list))
1203 (let* ((buffer (rcirc-target-buffer process sender response target text))
1204 (inhibit-read-only t))
1205 (with-current-buffer buffer
1206 (let ((moving (= (point) rcirc-prompt-end-marker))
1207 (old-point (point-marker))
1208 (fill-start (marker-position rcirc-prompt-start-marker)))
1209
1210 (unless (string= sender (rcirc-nick process))
1211 ;; only decode text from other senders, not ours
1212 (setq text (decode-coding-string text rcirc-decode-coding-system))
1213 ;; mark the line with overlay arrow
1214 (unless (or (marker-position overlay-arrow-position)
1215 (get-buffer-window (current-buffer)))
1216 (set-marker overlay-arrow-position
1217 (marker-position rcirc-prompt-start-marker))))
1218
1219 ;; temporarily set the marker insertion-type because
1220 ;; insert-before-markers results in hidden text in new buffers
1221 (goto-char rcirc-prompt-start-marker)
1222 (set-marker-insertion-type rcirc-prompt-start-marker t)
1223 (set-marker-insertion-type rcirc-prompt-end-marker t)
1224
1225 (let ((fmted-text
1226 (rcirc-format-response-string process sender response nil
1227 text)))
1228
1229 (insert fmted-text (propertize "\n" 'hard t))
1230 (set-marker-insertion-type rcirc-prompt-start-marker nil)
1231 (set-marker-insertion-type rcirc-prompt-end-marker nil)
1232
1233 (let ((text-start (make-marker)))
1234 (set-marker text-start
1235 (or (next-single-property-change fill-start
1236 'rcirc-text)
1237 rcirc-prompt-end-marker))
1238 ;; squeeze spaces out of text before rcirc-text
1239 (fill-region fill-start (1- text-start))
1240
1241 ;; fill the text we just inserted, maybe
1242 (when (and rcirc-fill-flag
1243 (not (string= response "372"))) ;/motd
1244 (let ((fill-prefix
1245 (or rcirc-fill-prefix
1246 (make-string (- text-start fill-start) ?\s)))
1247 (fill-column (cond ((eq rcirc-fill-column 'frame-width)
1248 (1- (frame-width)))
1249 ((eq rcirc-fill-column 'window-width)
1250 (1- (window-width)))
1251 (rcirc-fill-column
1252 rcirc-fill-column)
1253 (t fill-column))))
1254 (fill-region fill-start rcirc-prompt-start-marker 'left t)))))
1255
1256 ;; set inserted text to be read-only
1257 (when rcirc-read-only-flag
1258 (put-text-property rcirc-prompt-start-marker fill-start 'read-only t)
1259 (let ((inhibit-read-only t))
1260 (put-text-property rcirc-prompt-start-marker fill-start
1261 'front-sticky t)
1262 (put-text-property (1- (point)) (point) 'rear-nonsticky t)))
1263
1264 ;; truncate buffer if it is very long
1265 (save-excursion
1266 (when (and rcirc-buffer-maximum-lines
1267 (> rcirc-buffer-maximum-lines 0)
1268 (= (forward-line (- rcirc-buffer-maximum-lines)) 0))
1269 (delete-region (point-min) (point))))
1270
1271 ;; set the window point for buffers show in windows
1272 (walk-windows (lambda (w)
1273 (when (and (not (eq (selected-window) w))
1274 (eq (current-buffer)
1275 (window-buffer w))
1276 (>= (window-point w)
1277 rcirc-prompt-end-marker))
1278 (set-window-point w (point-max))))
1279 nil t)
1280
1281 ;; restore the point
1282 (goto-char (if moving rcirc-prompt-end-marker old-point))
1283
1284 ;; keep window on bottom line if it was already there
1285 (when rcirc-scroll-show-maximum-output
1286 (walk-windows (lambda (w)
1287 (when (eq (window-buffer w) (current-buffer))
1288 (with-current-buffer (window-buffer w)
1289 (when (eq major-mode 'rcirc-mode)
1290 (with-selected-window w
1291 (when (<= (- (window-height)
1292 (count-screen-lines
1293 (window-point)
1294 (window-start))
1295 1)
1296 0)
1297 (recenter -1)))))))
1298 nil t))
1299
1300 ;; flush undo (can we do something smarter here?)
1301 (buffer-disable-undo)
1302 (buffer-enable-undo))
1303
1304 ;; record modeline activity
1305 (when (and activity
1306 (not rcirc-ignore-buffer-activity-flag)
1307 (not (and rcirc-dim-nicks sender
1308 (string-match (regexp-opt rcirc-dim-nicks) sender))))
1309 (rcirc-record-activity (current-buffer)
1310 (when (not (rcirc-channel-p rcirc-target))
1311 'nick)))
1312
1313 (sit-for 0) ; displayed text before hook
1314 (run-hook-with-args 'rcirc-print-hooks
1315 process sender response target text)))))
1316
1317 (defun rcirc-startup-channels (server)
1318 "Return the list of startup channels for SERVER."
1319 (let (channels)
1320 (dolist (i rcirc-startup-channels-alist)
1321 (if (string-match (car i) server)
1322 (setq channels (append channels (cdr i)))))
1323 channels))
1324
1325 (defun rcirc-join-channels (process channels)
1326 "Join CHANNELS."
1327 (save-window-excursion
1328 (dolist (channel channels)
1329 (with-rcirc-process-buffer process
1330 (rcirc-cmd-join channel process)))))
1331 \f
1332 ;;; nick management
1333 (defvar rcirc-nick-prefix-chars "~&@%+")
1334 (defun rcirc-user-nick (user)
1335 "Return the nick from USER. Remove any non-nick junk."
1336 (save-match-data
1337 (if (string-match (concat "^[" rcirc-nick-prefix-chars
1338 "]?\\([^! ]+\\)!?") (or user ""))
1339 (match-string 1 user)
1340 user)))
1341
1342 (defun rcirc-nick-channels (process nick)
1343 "Return list of channels for NICK."
1344 (with-rcirc-process-buffer process
1345 (mapcar (lambda (x) (car x))
1346 (gethash nick rcirc-nick-table))))
1347
1348 (defun rcirc-put-nick-channel (process nick channel)
1349 "Add CHANNEL to list associated with NICK."
1350 (let ((nick (rcirc-user-nick nick)))
1351 (with-rcirc-process-buffer process
1352 (let* ((chans (gethash nick rcirc-nick-table))
1353 (record (assoc-string channel chans t)))
1354 (if record
1355 (setcdr record (current-time))
1356 (puthash nick (cons (cons channel (current-time))
1357 chans)
1358 rcirc-nick-table))))))
1359
1360 (defun rcirc-nick-remove (process nick)
1361 "Remove NICK from table."
1362 (with-rcirc-process-buffer process
1363 (remhash nick rcirc-nick-table)))
1364
1365 (defun rcirc-remove-nick-channel (process nick channel)
1366 "Remove the CHANNEL from list associated with NICK."
1367 (with-rcirc-process-buffer process
1368 (let* ((chans (gethash nick rcirc-nick-table))
1369 (newchans
1370 ;; instead of assoc-string-delete-all:
1371 (let ((record (assoc-string channel chans t)))
1372 (when record
1373 (setcar record 'delete)
1374 (assq-delete-all 'delete chans)))))
1375 (if newchans
1376 (puthash nick newchans rcirc-nick-table)
1377 (remhash nick rcirc-nick-table)))))
1378
1379 (defun rcirc-channel-nicks (process target)
1380 "Return the list of nicks associated with TARGET sorted by last activity."
1381 (when target
1382 (if (rcirc-channel-p target)
1383 (with-rcirc-process-buffer process
1384 (let (nicks)
1385 (maphash
1386 (lambda (k v)
1387 (let ((record (assoc-string target v t)))
1388 (if record
1389 (setq nicks (cons (cons k (cdr record)) nicks)))))
1390 rcirc-nick-table)
1391 (mapcar (lambda (x) (car x))
1392 (sort nicks (lambda (x y) (time-less-p (cdr y) (cdr x)))))))
1393 (list target))))
1394
1395 (defun rcirc-ignore-update-automatic (nick)
1396 "Remove NICK from `rcirc-ignore-list'
1397 if NICK is also on `rcirc-ignore-list-automatic'."
1398 (when (member nick rcirc-ignore-list-automatic)
1399 (setq rcirc-ignore-list-automatic
1400 (delete nick rcirc-ignore-list-automatic)
1401 rcirc-ignore-list
1402 (delete nick rcirc-ignore-list))))
1403 \f
1404 ;;; activity tracking
1405 (defvar rcirc-track-minor-mode-map (make-sparse-keymap)
1406 "Keymap for rcirc track minor mode.")
1407
1408 (define-key rcirc-track-minor-mode-map (kbd "C-c `") 'rcirc-next-active-buffer)
1409 (define-key rcirc-track-minor-mode-map (kbd "C-c C-@") 'rcirc-next-active-buffer)
1410 (define-key rcirc-track-minor-mode-map (kbd "C-c C-SPC") 'rcirc-next-active-buffer)
1411
1412 ;;;###autoload
1413 (define-minor-mode rcirc-track-minor-mode
1414 "Global minor mode for tracking activity in rcirc buffers."
1415 :init-value nil
1416 :lighter ""
1417 :keymap rcirc-track-minor-mode-map
1418 :global t
1419 :group 'rcirc
1420 (or global-mode-string (setq global-mode-string '("")))
1421 ;; toggle the mode-line channel indicator
1422 (if rcirc-track-minor-mode
1423 (progn
1424 (and (not (memq 'rcirc-activity-string global-mode-string))
1425 (setq global-mode-string
1426 (append global-mode-string '(rcirc-activity-string))))
1427 (add-hook 'window-configuration-change-hook
1428 'rcirc-window-configuration-change))
1429 (setq global-mode-string
1430 (delete 'rcirc-activity-string global-mode-string))
1431 (remove-hook 'window-configuration-change-hook
1432 'rcirc-window-configuration-change)))
1433
1434 (or (assq 'rcirc-ignore-buffer-activity-flag minor-mode-alist)
1435 (setq minor-mode-alist
1436 (cons '(rcirc-ignore-buffer-activity-flag " Ignore") minor-mode-alist)))
1437 (or (assq 'rcirc-low-priority-flag minor-mode-alist)
1438 (setq minor-mode-alist
1439 (cons '(rcirc-low-priority-flag " LowPri") minor-mode-alist)))
1440
1441 (defun rcirc-toggle-ignore-buffer-activity ()
1442 "Toggle the value of `rcirc-ignore-buffer-activity-flag'."
1443 (interactive)
1444 (setq rcirc-ignore-buffer-activity-flag
1445 (not rcirc-ignore-buffer-activity-flag))
1446 (message (if rcirc-ignore-buffer-activity-flag
1447 "Ignore activity in this buffer"
1448 "Notice activity in this buffer"))
1449 (force-mode-line-update))
1450
1451 (defun rcirc-toggle-low-priority ()
1452 "Toggle the value of `rcirc-low-priority-flag'."
1453 (interactive)
1454 (setq rcirc-low-priority-flag
1455 (not rcirc-low-priority-flag))
1456 (message (if rcirc-low-priority-flag
1457 "Activity in this buffer is low priority"
1458 "Activity in this buffer is normal priority"))
1459 (force-mode-line-update))
1460
1461 (defvar rcirc-switch-to-buffer-function 'switch-to-buffer
1462 "Function to use when switching buffers.
1463 Possible values are `switch-to-buffer', `pop-to-buffer', and
1464 `display-buffer'.")
1465
1466 (defun rcirc-switch-to-server-buffer ()
1467 "Switch to the server buffer associated with current channel buffer."
1468 (interactive)
1469 (funcall rcirc-switch-to-buffer-function rcirc-server-buffer))
1470
1471 (defun rcirc-jump-to-first-unread-line ()
1472 "Move the point to the first unread line in this buffer."
1473 (interactive)
1474 (when (marker-position overlay-arrow-position)
1475 (goto-char overlay-arrow-position)))
1476
1477 (defvar rcirc-last-non-irc-buffer nil
1478 "The buffer to switch to when there is no more activity.")
1479
1480 (defun rcirc-next-active-buffer (arg)
1481 "Go to the next rcirc buffer with activity.
1482 With prefix ARG, go to the next low priority buffer with activity.
1483 The function given by `rcirc-switch-to-buffer-function' is used to
1484 show the buffer."
1485 (interactive "P")
1486 (let* ((pair (rcirc-split-activity rcirc-activity))
1487 (lopri (car pair))
1488 (hipri (cdr pair)))
1489 (if (or (and (not arg) hipri)
1490 (and arg lopri))
1491 (progn
1492 (unless (eq major-mode 'rcirc-mode)
1493 (setq rcirc-last-non-irc-buffer (current-buffer)))
1494 (funcall rcirc-switch-to-buffer-function
1495 (car (if arg lopri hipri))))
1496 (if (eq major-mode 'rcirc-mode)
1497 (if (not (and rcirc-last-non-irc-buffer
1498 (buffer-live-p rcirc-last-non-irc-buffer)))
1499 (message "No IRC activity. Start something.")
1500 (message "No more IRC activity. Go back to work.")
1501 (funcall rcirc-switch-to-buffer-function rcirc-last-non-irc-buffer)
1502 (setq rcirc-last-non-irc-buffer nil))
1503 (message (concat
1504 "No IRC activity."
1505 (when lopri
1506 (concat
1507 " Type C-u "
1508 (key-description (this-command-keys))
1509 " for low priority activity."))))))))
1510
1511 (defvar rcirc-activity-hooks nil
1512 "Hook to be run when there is channel activity.
1513
1514 Functions are called with a single argument, the buffer with the
1515 activity. Only run if the buffer is not visible and
1516 `rcirc-ignore-buffer-activity-flag' is non-nil.")
1517
1518 (defun rcirc-record-activity (buffer &optional type)
1519 "Record BUFFER activity with TYPE."
1520 (with-current-buffer buffer
1521 (when (not (get-buffer-window (current-buffer) t))
1522 (setq rcirc-activity
1523 (sort (add-to-list 'rcirc-activity (current-buffer))
1524 (lambda (b1 b2)
1525 (let ((t1 (with-current-buffer b1 rcirc-last-post-time))
1526 (t2 (with-current-buffer b2 rcirc-last-post-time)))
1527 (time-less-p t2 t1)))))
1528 (pushnew type rcirc-activity-types)
1529 (rcirc-update-activity-string)))
1530 (run-hook-with-args 'rcirc-activity-hooks buffer))
1531
1532 (defun rcirc-clear-activity (buffer)
1533 "Clear the BUFFER activity."
1534 (setq rcirc-activity (delete buffer rcirc-activity))
1535 (with-current-buffer buffer
1536 (setq rcirc-activity-types nil)))
1537
1538 (defun rcirc-split-activity (activity)
1539 "Return a cons cell with ACTIVITY split into (lopri . hipri)."
1540 (let (lopri hipri)
1541 (dolist (buf rcirc-activity)
1542 (with-current-buffer buf
1543 (if (and rcirc-low-priority-flag
1544 (not (member 'nick rcirc-activity-types)))
1545 (add-to-list 'lopri buf t)
1546 (add-to-list 'hipri buf t))))
1547 (cons lopri hipri)))
1548
1549 ;; TODO: add mouse properties
1550 (defun rcirc-update-activity-string ()
1551 "Update mode-line string."
1552 (let* ((pair (rcirc-split-activity rcirc-activity))
1553 (lopri (car pair))
1554 (hipri (cdr pair)))
1555 (setq rcirc-activity-string
1556 (cond ((or hipri lopri)
1557 (concat "-"
1558 (and hipri "[")
1559 (rcirc-activity-string hipri)
1560 (and hipri lopri ",")
1561 (and lopri
1562 (concat "("
1563 (rcirc-activity-string lopri)
1564 ")"))
1565 (and hipri "]")
1566 "-"))
1567 ((not (null (rcirc-process-list)))
1568 "-[]-")
1569 (t "")))))
1570
1571 (defun rcirc-activity-string (buffers)
1572 (mapconcat (lambda (b)
1573 (let ((s (substring-no-properties (rcirc-short-buffer-name b))))
1574 (with-current-buffer b
1575 (dolist (type rcirc-activity-types)
1576 (rcirc-add-face 0 (length s)
1577 (case type
1578 (nick 'rcirc-track-nick)
1579 (keyword 'rcirc-track-keyword))
1580 s)))
1581 s))
1582 buffers ","))
1583
1584 (defun rcirc-short-buffer-name (buffer)
1585 "Return a short name for BUFFER to use in the modeline indicator."
1586 (with-current-buffer buffer
1587 (or rcirc-short-buffer-name (buffer-name))))
1588
1589 (defvar rcirc-current-buffer nil)
1590 (defun rcirc-window-configuration-change ()
1591 "Go through visible windows and remove buffers from activity list.
1592 Also, clear the overlay arrow if the current buffer is now hidden."
1593 (let ((current-now-hidden t))
1594 (walk-windows (lambda (w)
1595 (let ((buf (window-buffer w)))
1596 (with-current-buffer buf
1597 (when (eq major-mode 'rcirc-mode)
1598 (rcirc-clear-activity buf)))
1599 (when (eq buf rcirc-current-buffer)
1600 (setq current-now-hidden nil)))))
1601 ;; add overlay arrow if the buffer isn't displayed
1602 (when (and current-now-hidden
1603 rcirc-current-buffer
1604 (buffer-live-p rcirc-current-buffer))
1605 (with-current-buffer rcirc-current-buffer
1606 (when (and (eq major-mode 'rcirc-mode)
1607 (marker-position overlay-arrow-position))
1608 (set-marker overlay-arrow-position nil)))))
1609
1610 ;; remove any killed buffers from list
1611 (setq rcirc-activity
1612 (delq nil (mapcar (lambda (buf) (when (buffer-live-p buf) buf))
1613 rcirc-activity)))
1614 (rcirc-update-activity-string)
1615 (setq rcirc-current-buffer (current-buffer)))
1616
1617 \f
1618 ;;; buffer name abbreviation
1619 (defun rcirc-update-short-buffer-names ()
1620 (let ((bufalist
1621 (apply 'append (mapcar (lambda (process)
1622 (with-rcirc-process-buffer process
1623 rcirc-buffer-alist))
1624 (rcirc-process-list)))))
1625 (dolist (i (rcirc-abbreviate bufalist))
1626 (when (buffer-live-p (cdr i))
1627 (with-current-buffer (cdr i)
1628 (setq rcirc-short-buffer-name (car i)))))))
1629
1630 (defun rcirc-abbreviate (pairs)
1631 (apply 'append (mapcar 'rcirc-rebuild-tree (rcirc-make-trees pairs))))
1632
1633 (defun rcirc-rebuild-tree (tree &optional acc)
1634 (let ((ch (char-to-string (car tree))))
1635 (dolist (x (cdr tree))
1636 (if (listp x)
1637 (setq acc (append acc
1638 (mapcar (lambda (y)
1639 (cons (concat ch (car y))
1640 (cdr y)))
1641 (rcirc-rebuild-tree x))))
1642 (setq acc (cons (cons ch x) acc))))
1643 acc))
1644
1645 (defun rcirc-make-trees (pairs)
1646 (let (alist)
1647 (mapc (lambda (pair)
1648 (if (consp pair)
1649 (let* ((str (car pair))
1650 (data (cdr pair))
1651 (char (unless (zerop (length str))
1652 (aref str 0)))
1653 (rest (unless (zerop (length str))
1654 (substring str 1)))
1655 (part (if char (assq char alist))))
1656 (if part
1657 ;; existing partition
1658 (setcdr part (cons (cons rest data) (cdr part)))
1659 ;; new partition
1660 (setq alist (cons (if char
1661 (list char (cons rest data))
1662 data)
1663 alist))))
1664 (setq alist (cons pair alist))))
1665 pairs)
1666 ;; recurse into cdrs of alist
1667 (mapc (lambda (x)
1668 (when (and (listp x) (listp (cadr x)))
1669 (setcdr x (if (> (length (cdr x)) 1)
1670 (rcirc-make-trees (cdr x))
1671 (setcdr x (list (cdadr x)))))))
1672 alist)))
1673 \f
1674 ;;; /commands these are called with 3 args: PROCESS, TARGET, which is
1675 ;; the current buffer/channel/user, and ARGS, which is a string
1676 ;; containing the text following the /cmd.
1677
1678 (defmacro defun-rcirc-command (command argument docstring interactive-form
1679 &rest body)
1680 "Define a command."
1681 `(defun ,(intern (concat "rcirc-cmd-" (symbol-name command)))
1682 (,@argument &optional process target)
1683 ,(concat docstring "\n\nNote: If PROCESS or TARGET are nil, the values given"
1684 "\nby `rcirc-buffer-process' and `rcirc-target' will be used.")
1685 ,interactive-form
1686 (let ((process (or process (rcirc-buffer-process)))
1687 (target (or target rcirc-target)))
1688 ,@body)))
1689
1690 (defun-rcirc-command msg (message)
1691 "Send private MESSAGE to TARGET."
1692 (interactive "i")
1693 (if (null message)
1694 (progn
1695 (setq target (completing-read "Message nick: "
1696 (with-rcirc-server-buffer
1697 rcirc-nick-table)))
1698 (when (> (length target) 0)
1699 (setq message (read-string (format "Message %s: " target)))
1700 (when (> (length message) 0)
1701 (rcirc-send-message process target message))))
1702 (if (not (string-match "\\([^ ]+\\) \\(.+\\)" message))
1703 (message "Not enough args, or something.")
1704 (setq target (match-string 1 message)
1705 message (match-string 2 message))
1706 (rcirc-send-message process target message))))
1707
1708 (defun-rcirc-command query (nick)
1709 "Open a private chat buffer to NICK."
1710 (interactive (list (completing-read "Query nick: "
1711 (with-rcirc-server-buffer rcirc-nick-table))))
1712 (let ((existing-buffer (rcirc-get-buffer process nick)))
1713 (switch-to-buffer (or existing-buffer
1714 (rcirc-get-buffer-create process nick)))
1715 (when (not existing-buffer)
1716 (rcirc-cmd-whois nick))))
1717
1718 (defun-rcirc-command join (channel)
1719 "Join CHANNEL."
1720 (interactive "sJoin channel: ")
1721 (let ((buffer (rcirc-get-buffer-create process
1722 (car (split-string channel)))))
1723 (rcirc-send-string process (concat "JOIN " channel))
1724 (when (not (eq (selected-window) (minibuffer-window)))
1725 (funcall rcirc-switch-to-buffer-function buffer))))
1726
1727 (defun-rcirc-command part (channel)
1728 "Part CHANNEL."
1729 (interactive "sPart channel: ")
1730 (let ((channel (if (> (length channel) 0) channel target)))
1731 (rcirc-send-string process (concat "PART " channel " :" rcirc-id-string))))
1732
1733 (defun-rcirc-command quit (reason)
1734 "Send a quit message to server with REASON."
1735 (interactive "sQuit reason: ")
1736 (rcirc-send-string process (concat "QUIT :"
1737 (if (not (zerop (length reason)))
1738 reason
1739 rcirc-id-string))))
1740
1741 (defun-rcirc-command nick (nick)
1742 "Change nick to NICK."
1743 (interactive "i")
1744 (when (null nick)
1745 (setq nick (read-string "New nick: " (rcirc-nick process))))
1746 (rcirc-send-string process (concat "NICK " nick)))
1747
1748 (defun-rcirc-command names (channel)
1749 "Display list of names in CHANNEL or in current channel if CHANNEL is nil.
1750 If called interactively, prompt for a channel when prefix arg is supplied."
1751 (interactive "P")
1752 (if (interactive-p)
1753 (if channel
1754 (setq channel (read-string "List names in channel: " target))))
1755 (let ((channel (if (> (length channel) 0)
1756 channel
1757 target)))
1758 (rcirc-send-string process (concat "NAMES " channel))))
1759
1760 (defun-rcirc-command topic (topic)
1761 "List TOPIC for the TARGET channel.
1762 With a prefix arg, prompt for new topic."
1763 (interactive "P")
1764 (if (and (interactive-p) topic)
1765 (setq topic (read-string "New Topic: " rcirc-topic)))
1766 (rcirc-send-string process (concat "TOPIC " target
1767 (when (> (length topic) 0)
1768 (concat " :" topic)))))
1769
1770 (defun-rcirc-command whois (nick)
1771 "Request information from server about NICK."
1772 (interactive (list
1773 (completing-read "Whois: "
1774 (with-rcirc-server-buffer rcirc-nick-table))))
1775 (rcirc-send-string process (concat "WHOIS " nick)))
1776
1777 (defun-rcirc-command mode (args)
1778 "Set mode with ARGS."
1779 (interactive (list (concat (read-string "Mode nick or channel: ")
1780 " " (read-string "Mode: "))))
1781 (rcirc-send-string process (concat "MODE " args)))
1782
1783 (defun-rcirc-command list (channels)
1784 "Request information on CHANNELS from server."
1785 (interactive "sList Channels: ")
1786 (rcirc-send-string process (concat "LIST " channels)))
1787
1788 (defun-rcirc-command oper (args)
1789 "Send operator command to server."
1790 (interactive "sOper args: ")
1791 (rcirc-send-string process (concat "OPER " args)))
1792
1793 (defun-rcirc-command quote (message)
1794 "Send MESSAGE literally to server."
1795 (interactive "sServer message: ")
1796 (rcirc-send-string process message))
1797
1798 (defun-rcirc-command kick (arg)
1799 "Kick NICK from current channel."
1800 (interactive (list
1801 (concat (completing-read "Kick nick: "
1802 (rcirc-channel-nicks
1803 (rcirc-buffer-process)
1804 rcirc-target))
1805 (read-from-minibuffer "Kick reason: "))))
1806 (let* ((arglist (split-string arg))
1807 (argstring (concat (car arglist) " :"
1808 (mapconcat 'identity (cdr arglist) " "))))
1809 (rcirc-send-string process (concat "KICK " target " " argstring))))
1810
1811 (defun rcirc-cmd-ctcp (args &optional process target)
1812 (if (string-match "^\\([^ ]+\\)\\s-+\\(.+\\)$" args)
1813 (let ((target (match-string 1 args))
1814 (request (match-string 2 args)))
1815 (rcirc-send-string process
1816 (format "PRIVMSG %s \C-a%s\C-a"
1817 target (upcase request))))
1818 (rcirc-print process (rcirc-nick process) "ERROR" nil
1819 "usage: /ctcp NICK REQUEST")))
1820
1821 (defun rcirc-cmd-me (args &optional process target)
1822 (rcirc-send-string process (format "PRIVMSG %s :\C-aACTION %s\C-a"
1823 target args)))
1824
1825 (defun rcirc-add-or-remove (set &optional elt)
1826 (if (and elt (not (string= "" elt)))
1827 (if (member-ignore-case elt set)
1828 (delete elt set)
1829 (cons elt set))
1830 set))
1831
1832 (defun-rcirc-command ignore (nick)
1833 "Manage the ignore list.
1834 Ignore NICK, unignore NICK if already ignored, or list ignored
1835 nicks when no NICK is given. When listing ignored nicks, the
1836 ones added to the list automatically are marked with an asterisk."
1837 (interactive "sToggle ignoring of nick: ")
1838 (setq rcirc-ignore-list (rcirc-add-or-remove rcirc-ignore-list nick))
1839 (rcirc-print process nil "IGNORE" target
1840 (mapconcat
1841 (lambda (nick)
1842 (concat nick
1843 (if (member nick rcirc-ignore-list-automatic)
1844 "*" "")))
1845 rcirc-ignore-list " ")))
1846
1847 (defun-rcirc-command bright (nick)
1848 "Manage the bright nick list."
1849 (interactive "sToggle emphasis of nick: ")
1850 (setq rcirc-bright-nicks (rcirc-add-or-remove rcirc-bright-nicks nick))
1851 (rcirc-print process nil "BRIGHT" target
1852 (mapconcat 'identity rcirc-bright-nicks " ")))
1853
1854 (defun-rcirc-command dim (nick)
1855 "Manage the dim nick list."
1856 (interactive "sToggle deemphasis of nick: ")
1857 (setq rcirc-dim-nicks (rcirc-add-or-remove rcirc-dim-nicks nick))
1858 (rcirc-print process nil "DIM" target
1859 (mapconcat 'identity rcirc-dim-nicks " ")))
1860
1861 (defun-rcirc-command keyword (keyword)
1862 "Manage the keyword list.
1863 Mark KEYWORD, unmark KEYWORD if already marked, or list marked
1864 keywords when no KEYWORD is given."
1865 (interactive "sToggle highlighting of keyword: ")
1866 (setq rcirc-keywords (rcirc-add-or-remove rcirc-keywords keyword))
1867 (rcirc-print process nil "KEYWORD" target
1868 (mapconcat 'identity rcirc-keywords " ")))
1869
1870 \f
1871 (defun rcirc-add-face (start end name &optional object)
1872 "Add face NAME to the face text property of the text from START to END."
1873 (when name
1874 (let ((pos start)
1875 next prop)
1876 (while (< pos end)
1877 (setq prop (get-text-property pos 'face object)
1878 next (next-single-property-change pos 'face object end))
1879 (unless (member name (get-text-property pos 'face object))
1880 (add-text-properties pos next (list 'face (cons name prop)) object))
1881 (setq pos next)))))
1882
1883 (defun rcirc-facify (string face)
1884 "Return a copy of STRING with FACE property added."
1885 (let ((string (or string "")))
1886 (rcirc-add-face 0 (length string) face string)
1887 string))
1888
1889 (defvar rcirc-url-regexp
1890 (rx-to-string
1891 `(and word-boundary
1892 (or (and
1893 (or (and (or "http" "https" "ftp" "file" "gopher" "news"
1894 "telnet" "wais" "mailto")
1895 "://")
1896 "www.")
1897 (1+ (char "-a-zA-Z0-9_."))
1898 (1+ (char "-a-zA-Z0-9_"))
1899 (optional ":" (1+ (char "0-9"))))
1900 (and (1+ (char "-a-zA-Z0-9_."))
1901 (or ".com" ".net" ".org")
1902 word-boundary))
1903 (optional
1904 (and "/"
1905 (1+ (char "-a-zA-Z0-9_=!?#$\@~`%&*+|\\/:;.,{}[]()"))
1906 (char "-a-zA-Z0-9_=#$\@~`%&*+|\\/:;{}[]()")))))
1907 "Regexp matching URLs. Set to nil to disable URL features in rcirc.")
1908
1909 (defun rcirc-browse-url (&optional arg)
1910 "Prompt for URL to browse based on URLs in buffer."
1911 (interactive "P")
1912 (let ((completions (mapcar (lambda (x) (cons x nil)) rcirc-urls))
1913 (initial-input (car rcirc-urls))
1914 (history (cdr rcirc-urls)))
1915 (browse-url (completing-read "rcirc browse-url: "
1916 completions nil nil initial-input 'history)
1917 arg)))
1918
1919 (defun rcirc-browse-url-at-point (point)
1920 "Send URL at point to `browse-url'."
1921 (interactive "d")
1922 (let ((beg (previous-single-property-change (1+ point) 'mouse-face))
1923 (end (next-single-property-change point 'mouse-face)))
1924 (browse-url (buffer-substring-no-properties beg end))))
1925
1926 (defun rcirc-browse-url-at-mouse (event)
1927 "Send URL at mouse click to `browse-url'."
1928 (interactive "e")
1929 (let ((position (event-end event)))
1930 (with-current-buffer (window-buffer (posn-window position))
1931 (rcirc-browse-url-at-point (posn-point position)))))
1932
1933 \f
1934 (defvar rcirc-markup-text-functions
1935 '(rcirc-markup-body-text
1936 rcirc-markup-attributes
1937 rcirc-markup-my-nick
1938 rcirc-markup-urls
1939 rcirc-markup-keywords
1940 rcirc-markup-bright-nicks)
1941 "List of functions used to manipulate text before it is printed.
1942
1943 Each function takes three arguments, PROCESS, SENDER, RESPONSE
1944 and CHANNEL-BUFFER. The current buffer is temporary buffer that
1945 contains the text to manipulate. Each function works on the text
1946 in this buffer.")
1947
1948 (defun rcirc-markup-text (process sender response text)
1949 "Return TEXT with properties added based on various patterns."
1950 (let ((channel-buffer (current-buffer)))
1951 (with-temp-buffer
1952 (insert text)
1953 (goto-char (point-min))
1954 (dolist (fn rcirc-markup-text-functions)
1955 (save-excursion
1956 (funcall fn process sender response channel-buffer)))
1957 (buffer-substring (point-min) (point-max)))))
1958
1959 (defun rcirc-markup-body-text (process sender response channel-buffer)
1960 ;; We add the text property `rcirc-text' to identify this as the
1961 ;; body text.
1962 (add-text-properties (point-min) (point-max)
1963 (list 'rcirc-text (buffer-substring-no-properties
1964 (point-min) (point-max)))))
1965
1966 (defun rcirc-markup-attributes (process sender response channel-buffer)
1967 (while (re-search-forward "\\([\C-b\C-_\C-v]\\).*?\\(\\1\\|\C-o\\)" nil t)
1968 (rcirc-add-face (match-beginning 0) (match-end 0)
1969 (case (char-after (match-beginning 1))
1970 (?\C-b 'bold)
1971 (?\C-v 'italic)
1972 (?\C-_ 'underline)))
1973 ;; keep the ^O since it could terminate other attributes
1974 (when (not (eq ?\C-o (char-before (match-end 2))))
1975 (delete-region (match-beginning 2) (match-end 2)))
1976 (delete-region (match-beginning 1) (match-end 1))
1977 (goto-char (1+ (match-beginning 1))))
1978 ;; remove the ^O characters now
1979 (while (re-search-forward "\C-o+" nil t)
1980 (delete-region (match-beginning 0) (match-end 0))))
1981
1982 (defun rcirc-markup-my-nick (process sender response channel-buffer)
1983 (with-syntax-table rcirc-nick-syntax-table
1984 (while (re-search-forward (concat "\\b"
1985 (regexp-quote (rcirc-nick process))
1986 "\\b")
1987 nil t)
1988 (rcirc-add-face (match-beginning 0) (match-end 0)
1989 'rcirc-nick-in-message)
1990 (when (string= response "PRIVMSG")
1991 (rcirc-add-face (point-min) (point-max) 'rcirc-nick-in-message-full-line)
1992 (rcirc-record-activity channel-buffer 'nick)))))
1993
1994 (defun rcirc-markup-urls (process sender response channel-buffer)
1995 (while (re-search-forward rcirc-url-regexp nil t)
1996 (let ((start (match-beginning 0))
1997 (end (match-end 0)))
1998 (rcirc-add-face start end 'rcirc-url)
1999 (add-text-properties start end (list 'mouse-face 'highlight
2000 'keymap rcirc-browse-url-map))
2001 ;; record the url
2002 (let ((url (buffer-substring-no-properties start end)))
2003 (with-current-buffer channel-buffer
2004 (push url rcirc-urls))))))
2005
2006 (defun rcirc-markup-keywords (process sender response channel-buffer)
2007 (let* ((target (with-current-buffer channel-buffer (or rcirc-target "")))
2008 (keywords (delq nil (mapcar (lambda (keyword)
2009 (when (not (string-match keyword target))
2010 keyword))
2011 rcirc-keywords))))
2012 (when keywords
2013 (while (re-search-forward (regexp-opt keywords 'words) nil t)
2014 (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-keyword)
2015 (when (and (string= response "PRIVMSG")
2016 (not (string= sender (rcirc-nick process))))
2017 (rcirc-record-activity channel-buffer 'keyword))))))
2018
2019 (defun rcirc-markup-bright-nicks (process sender response channel-buffer)
2020 (when (and rcirc-bright-nicks
2021 (string= response "NAMES"))
2022 (with-syntax-table rcirc-nick-syntax-table
2023 (while (re-search-forward (regexp-opt rcirc-bright-nicks 'words) nil t)
2024 (rcirc-add-face (match-beginning 0) (match-end 0)
2025 'rcirc-bright-nick)))))
2026 \f
2027 ;;; handlers
2028 ;; these are called with the server PROCESS, the SENDER, which is a
2029 ;; server or a user, depending on the command, the ARGS, which is a
2030 ;; list of strings, and the TEXT, which is the original server text,
2031 ;; verbatim
2032 (defun rcirc-handler-001 (process sender args text)
2033 (rcirc-handler-generic process "001" sender args text)
2034 ;; set the real server name
2035 (with-rcirc-process-buffer process
2036 (setq rcirc-connecting nil)
2037 (rcirc-reschedule-timeout process)
2038 (setq rcirc-server-name sender)
2039 (setq rcirc-nick (car args))
2040 (rcirc-update-prompt)
2041 (when rcirc-auto-authenticate-flag (rcirc-authenticate))
2042 (rcirc-join-channels process rcirc-startup-channels)))
2043
2044 (defun rcirc-handler-PRIVMSG (process sender args text)
2045 (let ((target (if (rcirc-channel-p (car args))
2046 (car args)
2047 sender))
2048 (message (or (cadr args) "")))
2049 (if (string-match "^\C-a\\(.*\\)\C-a$" message)
2050 (rcirc-handler-CTCP process target sender (match-string 1 message))
2051 (rcirc-print process sender "PRIVMSG" target message t))
2052 ;; update nick timestamp
2053 (if (member target (rcirc-nick-channels process sender))
2054 (rcirc-put-nick-channel process sender target))))
2055
2056 (defun rcirc-handler-NOTICE (process sender args text)
2057 (let ((target (car args))
2058 (message (cadr args)))
2059 (if (string-match "^\C-a\\(.*\\)\C-a$" message)
2060 (rcirc-handler-CTCP-response process target sender
2061 (match-string 1 message))
2062 (rcirc-print process sender "NOTICE"
2063 (cond ((rcirc-channel-p target)
2064 target)
2065 ;;; -ChanServ- [#gnu] Welcome...
2066 ((string-match "\\[\\(#[^\] ]+\\)\\]" message)
2067 (match-string 1 message))
2068 (sender
2069 (if (string= sender (rcirc-server-name process))
2070 nil ; server notice
2071 sender)))
2072 message t))))
2073
2074 (defun rcirc-handler-WALLOPS (process sender args text)
2075 (rcirc-print process sender "WALLOPS" sender (car args) t))
2076
2077 (defun rcirc-handler-JOIN (process sender args text)
2078 (let ((channel (car args)))
2079 (rcirc-get-buffer-create process channel)
2080 (rcirc-print process sender "JOIN" channel "")
2081
2082 ;; print in private chat buffer if it exists
2083 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
2084 (rcirc-print process sender "JOIN" sender channel))
2085
2086 (rcirc-put-nick-channel process sender channel)))
2087
2088 ;; PART and KICK are handled the same way
2089 (defun rcirc-handler-PART-or-KICK (process response channel sender nick args)
2090 (rcirc-ignore-update-automatic nick)
2091 (if (not (string= nick (rcirc-nick process)))
2092 ;; this is someone else leaving
2093 (rcirc-remove-nick-channel process nick channel)
2094 ;; this is us leaving
2095 (mapc (lambda (n)
2096 (rcirc-remove-nick-channel process n channel))
2097 (rcirc-channel-nicks process channel))
2098
2099 ;; if the buffer is still around, make it inactive
2100 (let ((buffer (rcirc-get-buffer process channel)))
2101 (when buffer
2102 (with-current-buffer buffer
2103 (setq rcirc-target nil))))))
2104
2105 (defun rcirc-handler-PART (process sender args text)
2106 (let* ((channel (car args))
2107 (reason (cadr args))
2108 (message (concat channel " " reason)))
2109 (rcirc-print process sender "PART" channel message)
2110 ;; print in private chat buffer if it exists
2111 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
2112 (rcirc-print process sender "PART" sender message))
2113
2114 (rcirc-handler-PART-or-KICK process "PART" channel sender sender reason)))
2115
2116 (defun rcirc-handler-KICK (process sender args text)
2117 (let* ((channel (car args))
2118 (nick (cadr args))
2119 (reason (caddr args))
2120 (message (concat nick " " channel " " reason)))
2121 (rcirc-print process sender "KICK" channel message t)
2122 ;; print in private chat buffer if it exists
2123 (when (rcirc-get-buffer (rcirc-buffer-process) nick)
2124 (rcirc-print process sender "KICK" nick message))
2125
2126 (rcirc-handler-PART-or-KICK process "KICK" channel sender nick reason)))
2127
2128 (defun rcirc-handler-QUIT (process sender args text)
2129 (rcirc-ignore-update-automatic sender)
2130 (mapc (lambda (channel)
2131 (rcirc-print process sender "QUIT" channel (apply 'concat args)))
2132 (rcirc-nick-channels process sender))
2133
2134 ;; print in private chat buffer if it exists
2135 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
2136 (rcirc-print process sender "QUIT" sender (apply 'concat args)))
2137
2138 (rcirc-nick-remove process sender))
2139
2140 (defun rcirc-handler-NICK (process sender args text)
2141 (let* ((old-nick sender)
2142 (new-nick (car args))
2143 (channels (rcirc-nick-channels process old-nick)))
2144 ;; update list of ignored nicks
2145 (rcirc-ignore-update-automatic old-nick)
2146 (when (member old-nick rcirc-ignore-list)
2147 (add-to-list 'rcirc-ignore-list new-nick)
2148 (add-to-list 'rcirc-ignore-list-automatic new-nick))
2149 ;; print message to nick's channels
2150 (dolist (target channels)
2151 (rcirc-print process sender "NICK" target new-nick))
2152 ;; update private chat buffer, if it exists
2153 (let ((chat-buffer (rcirc-get-buffer process old-nick)))
2154 (when chat-buffer
2155 (with-current-buffer chat-buffer
2156 (rcirc-print process sender "NICK" old-nick new-nick)
2157 (setq rcirc-target new-nick)
2158 (rename-buffer (rcirc-generate-new-buffer-name process new-nick)))))
2159 ;; remove old nick and add new one
2160 (with-rcirc-process-buffer process
2161 (let ((v (gethash old-nick rcirc-nick-table)))
2162 (remhash old-nick rcirc-nick-table)
2163 (puthash new-nick v rcirc-nick-table))
2164 ;; if this is our nick...
2165 (when (string= old-nick rcirc-nick)
2166 (setq rcirc-nick new-nick)
2167 (rcirc-update-prompt t)
2168 ;; reauthenticate
2169 (when rcirc-auto-authenticate-flag (rcirc-authenticate))))))
2170
2171 (defun rcirc-handler-PING (process sender args text)
2172 (rcirc-send-string process (concat "PONG " (car args))))
2173
2174 (defun rcirc-handler-PONG (process sender args text)
2175 ;; do nothing
2176 )
2177
2178 (defun rcirc-handler-TOPIC (process sender args text)
2179 (let ((topic (cadr args)))
2180 (rcirc-print process sender "TOPIC" (car args) topic)
2181 (with-current-buffer (rcirc-get-buffer process (car args))
2182 (setq rcirc-topic topic))))
2183
2184 (defvar rcirc-nick-away-alist nil)
2185 (defun rcirc-handler-301 (process sender args text)
2186 "RPL_AWAY"
2187 (let* ((nick (cadr args))
2188 (rec (assoc-string nick rcirc-nick-away-alist))
2189 (away-message (caddr args)))
2190 (when (or (not rec)
2191 (not (string= (cdr rec) away-message)))
2192 ;; away message has changed
2193 (rcirc-handler-generic process "AWAY" nick (cdr args) text)
2194 (if rec
2195 (setcdr rec away-message)
2196 (setq rcirc-nick-away-alist (cons (cons nick away-message)
2197 rcirc-nick-away-alist))))))
2198
2199 (defun rcirc-handler-332 (process sender args text)
2200 "RPL_TOPIC"
2201 (let ((buffer (or (rcirc-get-buffer process (cadr args))
2202 (rcirc-get-temp-buffer-create process (cadr args)))))
2203 (with-current-buffer buffer
2204 (setq rcirc-topic (caddr args)))))
2205
2206 (defun rcirc-handler-333 (process sender args text)
2207 "Not in rfc1459.txt"
2208 (let ((buffer (or (rcirc-get-buffer process (cadr args))
2209 (rcirc-get-temp-buffer-create process (cadr args)))))
2210 (with-current-buffer buffer
2211 (let ((setter (caddr args))
2212 (time (current-time-string
2213 (seconds-to-time
2214 (string-to-number (cadddr args))))))
2215 (rcirc-print process sender "TOPIC" (cadr args)
2216 (format "%s (%s on %s)" rcirc-topic setter time))))))
2217
2218 (defun rcirc-handler-477 (process sender args text)
2219 "ERR_NOCHANMODES"
2220 (rcirc-print process sender "477" (cadr args) (caddr args)))
2221
2222 (defun rcirc-handler-MODE (process sender args text)
2223 (let ((target (car args))
2224 (msg (mapconcat 'identity (cdr args) " ")))
2225 (rcirc-print process sender "MODE"
2226 (if (string= target (rcirc-nick process))
2227 nil
2228 target)
2229 msg)
2230
2231 ;; print in private chat buffers if they exist
2232 (mapc (lambda (nick)
2233 (when (rcirc-get-buffer process nick)
2234 (rcirc-print process sender "MODE" nick msg)))
2235 (cddr args))))
2236
2237 (defun rcirc-get-temp-buffer-create (process channel)
2238 "Return a buffer based on PROCESS and CHANNEL."
2239 (let ((tmpnam (concat " " (downcase channel) "TMP" (process-name process))))
2240 (get-buffer-create tmpnam)))
2241
2242 (defun rcirc-handler-353 (process sender args text)
2243 "RPL_NAMREPLY"
2244 (let ((channel (caddr args)))
2245 (mapc (lambda (nick)
2246 (rcirc-put-nick-channel process nick channel))
2247 (split-string (cadddr args) " " t))
2248 (with-current-buffer (rcirc-get-temp-buffer-create process channel)
2249 (goto-char (point-max))
2250 (insert (car (last args)) " "))))
2251
2252 (defun rcirc-handler-366 (process sender args text)
2253 "RPL_ENDOFNAMES"
2254 (let* ((channel (cadr args))
2255 (buffer (rcirc-get-temp-buffer-create process channel)))
2256 (with-current-buffer buffer
2257 (rcirc-print process sender "NAMES" channel
2258 (buffer-substring (point-min) (point-max))))
2259 (kill-buffer buffer)))
2260
2261 (defun rcirc-handler-433 (process sender args text)
2262 "ERR_NICKNAMEINUSE"
2263 (rcirc-handler-generic process "433" sender args text)
2264 (let* ((new-nick (concat (cadr args) "`")))
2265 (with-rcirc-process-buffer process
2266 (rcirc-cmd-nick new-nick nil process))))
2267
2268 (defun rcirc-authenticate ()
2269 "Send authentication to process associated with current buffer.
2270 Passwords are stored in `rcirc-authinfo' (which see)."
2271 (interactive)
2272 (with-rcirc-server-buffer
2273 (dolist (i rcirc-authinfo)
2274 (let ((process (rcirc-buffer-process))
2275 (server (car i))
2276 (nick (caddr i))
2277 (method (cadr i))
2278 (args (cdddr i)))
2279 (when (and (string-match server rcirc-server)
2280 (string-match nick rcirc-nick))
2281 (cond ((equal method 'nickserv)
2282 (rcirc-send-string
2283 process
2284 (concat
2285 "PRIVMSG nickserv :identify "
2286 (car args))))
2287 ((equal method 'chanserv)
2288 (rcirc-send-string
2289 process
2290 (concat
2291 "PRIVMSG chanserv :identify "
2292 (cadr args) " " (car args))))
2293 ((equal method 'bitlbee)
2294 (rcirc-send-string
2295 process
2296 (concat "PRIVMSG &bitlbee :identify " (car args))))
2297 (t
2298 (message "No %S authentication method defined"
2299 method))))))))
2300
2301 (defun rcirc-handler-INVITE (process sender args text)
2302 (rcirc-print process sender "INVITE" nil (mapconcat 'identity args " ") t))
2303
2304 (defun rcirc-handler-ERROR (process sender args text)
2305 (rcirc-print process sender "ERROR" nil (mapconcat 'identity args " ")))
2306
2307 (defun rcirc-handler-CTCP (process target sender text)
2308 (if (string-match "^\\([^ ]+\\) *\\(.*\\)$" text)
2309 (let* ((request (upcase (match-string 1 text)))
2310 (args (match-string 2 text))
2311 (handler (intern-soft (concat "rcirc-handler-ctcp-" request))))
2312 (if (not (fboundp handler))
2313 (rcirc-print process sender "ERROR" target
2314 (format "%s sent unsupported ctcp: %s" sender text)
2315 t)
2316 (funcall handler process target sender args)
2317 (if (not (string= request "ACTION"))
2318 (rcirc-print process sender "CTCP" target
2319 (format "%s" text) t))))))
2320
2321 (defun rcirc-handler-ctcp-VERSION (process target sender args)
2322 (rcirc-send-string process
2323 (concat "NOTICE " sender
2324 " :\C-aVERSION " rcirc-id-string
2325 "\C-a")))
2326
2327 (defun rcirc-handler-ctcp-ACTION (process target sender args)
2328 (rcirc-print process sender "ACTION" target args t))
2329
2330 (defun rcirc-handler-ctcp-TIME (process target sender args)
2331 (rcirc-send-string process
2332 (concat "NOTICE " sender
2333 " :\C-aTIME " (current-time-string) "\C-a")))
2334
2335 (defun rcirc-handler-CTCP-response (process target sender message)
2336 (rcirc-print process sender "CTCP" nil message t))
2337 \f
2338 (defgroup rcirc-faces nil
2339 "Faces for rcirc."
2340 :group 'rcirc
2341 :group 'faces)
2342
2343 (defface rcirc-my-nick ; font-lock-function-name-face
2344 '((((class color) (min-colors 88) (background light)) (:foreground "Blue1"))
2345 (((class color) (min-colors 88) (background dark)) (:foreground "LightSkyBlue"))
2346 (((class color) (min-colors 16) (background light)) (:foreground "Blue"))
2347 (((class color) (min-colors 16) (background dark)) (:foreground "LightSkyBlue"))
2348 (((class color) (min-colors 8)) (:foreground "blue" :weight bold))
2349 (t (:inverse-video t :weight bold)))
2350 "The face used to highlight my messages."
2351 :group 'rcirc-faces)
2352
2353 (defface rcirc-other-nick ; font-lock-variable-name-face
2354 '((((class grayscale) (background light))
2355 (:foreground "Gray90" :weight bold :slant italic))
2356 (((class grayscale) (background dark))
2357 (:foreground "DimGray" :weight bold :slant italic))
2358 (((class color) (min-colors 88) (background light)) (:foreground "DarkGoldenrod"))
2359 (((class color) (min-colors 88) (background dark)) (:foreground "LightGoldenrod"))
2360 (((class color) (min-colors 16) (background light)) (:foreground "DarkGoldenrod"))
2361 (((class color) (min-colors 16) (background dark)) (:foreground "LightGoldenrod"))
2362 (((class color) (min-colors 8)) (:foreground "yellow" :weight light))
2363 (t (:weight bold :slant italic)))
2364 "The face used to highlight other messages."
2365 :group 'rcirc-faces)
2366
2367 (defface rcirc-bright-nick
2368 '((((class grayscale) (background light))
2369 (:foreground "LightGray" :weight bold :underline t))
2370 (((class grayscale) (background dark))
2371 (:foreground "Gray50" :weight bold :underline t))
2372 (((class color) (min-colors 88) (background light)) (:foreground "CadetBlue"))
2373 (((class color) (min-colors 88) (background dark)) (:foreground "Aquamarine"))
2374 (((class color) (min-colors 16) (background light)) (:foreground "CadetBlue"))
2375 (((class color) (min-colors 16) (background dark)) (:foreground "Aquamarine"))
2376 (((class color) (min-colors 8)) (:foreground "magenta"))
2377 (t (:weight bold :underline t)))
2378 "Face used for nicks matched by `rcirc-bright-nicks'."
2379 :group 'rcirc-faces)
2380
2381 (defface rcirc-dim-nick
2382 '((t :inherit default))
2383 "Face used for nicks in `rcirc-dim-nicks'."
2384 :group 'rcirc-faces)
2385
2386 (defface rcirc-server ; font-lock-comment-face
2387 '((((class grayscale) (background light))
2388 (:foreground "DimGray" :weight bold :slant italic))
2389 (((class grayscale) (background dark))
2390 (:foreground "LightGray" :weight bold :slant italic))
2391 (((class color) (min-colors 88) (background light))
2392 (:foreground "Firebrick"))
2393 (((class color) (min-colors 88) (background dark))
2394 (:foreground "chocolate1"))
2395 (((class color) (min-colors 16) (background light))
2396 (:foreground "red"))
2397 (((class color) (min-colors 16) (background dark))
2398 (:foreground "red1"))
2399 (((class color) (min-colors 8) (background light))
2400 )
2401 (((class color) (min-colors 8) (background dark))
2402 )
2403 (t (:weight bold :slant italic)))
2404 "The face used to highlight server messages."
2405 :group 'rcirc-faces)
2406
2407 (defface rcirc-server-prefix ; font-lock-comment-delimiter-face
2408 '((default :inherit rcirc-server)
2409 (((class grayscale)))
2410 (((class color) (min-colors 16)))
2411 (((class color) (min-colors 8) (background light))
2412 :foreground "red")
2413 (((class color) (min-colors 8) (background dark))
2414 :foreground "red1"))
2415 "The face used to highlight server prefixes."
2416 :group 'rcirc-faces)
2417
2418 (defface rcirc-timestamp
2419 '((t (:inherit default)))
2420 "The face used to highlight timestamps."
2421 :group 'rcirc-faces)
2422
2423 (defface rcirc-nick-in-message ; font-lock-keyword-face
2424 '((((class grayscale) (background light)) (:foreground "LightGray" :weight bold))
2425 (((class grayscale) (background dark)) (:foreground "DimGray" :weight bold))
2426 (((class color) (min-colors 88) (background light)) (:foreground "Purple"))
2427 (((class color) (min-colors 88) (background dark)) (:foreground "Cyan1"))
2428 (((class color) (min-colors 16) (background light)) (:foreground "Purple"))
2429 (((class color) (min-colors 16) (background dark)) (:foreground "Cyan"))
2430 (((class color) (min-colors 8)) (:foreground "cyan" :weight bold))
2431 (t (:weight bold)))
2432 "The face used to highlight instances of your nick within messages."
2433 :group 'rcirc-faces)
2434
2435 (defface rcirc-nick-in-message-full-line
2436 '((t (:bold t)))
2437 "The face used emphasize the entire message when your nick is mentioned."
2438 :group 'rcirc-faces)
2439
2440 (defface rcirc-prompt ; comint-highlight-prompt
2441 '((((min-colors 88) (background dark)) (:foreground "cyan1"))
2442 (((background dark)) (:foreground "cyan"))
2443 (t (:foreground "dark blue")))
2444 "The face used to highlight prompts."
2445 :group 'rcirc-faces)
2446
2447 (defface rcirc-track-nick
2448 '((((type tty)) (:inherit default))
2449 (t (:inverse-video t)))
2450 "The face used in the mode-line when your nick is mentioned."
2451 :group 'rcirc-faces)
2452
2453 (defface rcirc-track-keyword
2454 '((t (:bold t )))
2455 "The face used in the mode-line when keywords are mentioned."
2456 :group 'rcirc-faces)
2457
2458 (defface rcirc-url
2459 '((t (:bold t)))
2460 "The face used to highlight urls."
2461 :group 'rcirc-faces)
2462
2463 (defface rcirc-keyword
2464 '((t (:inherit highlight)))
2465 "The face used to highlight keywords."
2466 :group 'rcirc-faces)
2467
2468 \f
2469 ;; When using M-x flyspell-mode, only check words after the prompt
2470 (put 'rcirc-mode 'flyspell-mode-predicate 'rcirc-looking-at-input)
2471 (defun rcirc-looking-at-input ()
2472 "Returns true if point is past the input marker."
2473 (>= (point) rcirc-prompt-end-marker))
2474 \f
2475
2476 (provide 'rcirc)
2477
2478 ;; arch-tag: b471b7e8-6b5a-4399-b2c6-a3c78dfc8ffb
2479 ;;; rcirc.el ends here