(diff-font-lock-keywords): Fix M. Kifer's last change.
[bpt/emacs.git] / lisp / net / rcirc.el
CommitLineData
bd43c990
RS
1;;; rcirc.el --- default, simple IRC client.
2
d7a0267c 3;; Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
bd43c990
RS
4
5;; Author: Ryan Yeske
bd43c990
RS
6;; URL: http://www.nongnu.org/rcirc
7;; Keywords: comm
8
b71cef5c 9;; This file is part of GNU Emacs.
bd43c990
RS
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
92c4adc1 18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
bd43c990
RS
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
b71cef5c
RF
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.
bd43c990
RS
25
26;;; Commentary:
27
adf794e4
EZ
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.
bd43c990 39
bd43c990
RS
40;; Open a new irc connection with:
41;; M-x irc RET
42
7faa3f8c
MB
43;;; Todo:
44
bd43c990
RS
45;;; Code:
46
47(require 'ring)
48(require 'time-date)
49(eval-when-compile (require 'cl))
50
adf794e4
EZ
51(defgroup rcirc nil
52 "Simple IRC client."
53 :version "22.1"
2fbed782 54 :prefix "rcirc-"
e8f10ddb 55 :link '(custom-manual "(rcirc)")
adf794e4
EZ
56 :group 'applications)
57
a2524d26 58(defcustom rcirc-default-server "irc.freenode.net"
adf794e4
EZ
59 "The default server to connect to."
60 :type 'string
61 :group 'rcirc)
bd43c990 62
a2524d26 63(defcustom rcirc-default-port 6667
adf794e4
EZ
64 "The default port to connect to."
65 :type 'integer
66 :group 'rcirc)
bd43c990 67
a2524d26 68(defcustom rcirc-default-nick (user-login-name)
adf794e4
EZ
69 "Your nick."
70 :type 'string
71 :group 'rcirc)
bd43c990 72
a2524d26 73(defcustom rcirc-default-user-name (user-login-name)
adf794e4
EZ
74 "Your user name sent to the server when connecting."
75 :type 'string
76 :group 'rcirc)
bd43c990 77
a2524d26 78(defcustom rcirc-default-user-full-name (if (string= (user-full-name) "")
18aa2c90 79 rcirc-default-user-name
02f47e86 80 (user-full-name))
adf794e4
EZ
81 "The full name sent to the server when connecting."
82 :type 'string
83 :group 'rcirc)
bd43c990 84
02f47e86 85(defcustom rcirc-startup-channels-alist '(("^irc.freenode.net$" "#rcirc"))
bd43c990 86 "Alist of channels to join at startup.
adf794e4
EZ
87Each element looks like (SERVER-REGEXP . CHANNEL-LIST)."
88 :type '(alist :key-type string :value-type (repeat string))
89 :group 'rcirc)
bd43c990 90
adf794e4
EZ
91(defcustom rcirc-fill-flag t
92 "*Non-nil means line-wrap messages printed in channel buffers."
93 :type 'boolean
94 :group 'rcirc)
bd43c990 95
adf794e4
EZ
96(defcustom rcirc-fill-column nil
97 "*Column beyond which automatic line-wrapping should happen.
d51f146b
RS
98If nil, use value of `fill-column'.
99If `window-width', use the window's width as maximum.
100If `frame-width', use the frame's width as maximum."
adf794e4 101 :type '(choice (const :tag "Value of `fill-column'")
d51f146b 102 (const :tag "Full window width" window-width)
adf794e4
EZ
103 (const :tag "Full frame width" frame-width)
104 (integer :tag "Number of columns"))
105 :group 'rcirc)
bd43c990 106
adf794e4 107(defcustom rcirc-fill-prefix nil
bd43c990
RS
108 "*Text to insert before filled lines.
109If nil, calculate the prefix dynamically to line up text
adf794e4
EZ
110underneath each nick."
111 :type '(choice (const :tag "Dynamic" nil)
112 (string :tag "Prefix text"))
113 :group 'rcirc)
bd43c990 114
adf794e4
EZ
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)
bd43c990 118
a2524d26
EZ
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
adf794e4 123(defcustom rcirc-time-format "%H:%M "
bd43c990 124 "*Describes how timestamps are printed.
adf794e4
EZ
125Used as the first arg to `format-time-string'."
126 :type 'string
127 :group 'rcirc)
bd43c990 128
adf794e4
EZ
129(defcustom rcirc-input-ring-size 1024
130 "*Size of input history ring."
131 :type 'integer
132 :group 'rcirc)
bd43c990 133
adf794e4 134(defcustom rcirc-read-only-flag t
2e398771 135 "*Non-nil means make text in IRC buffers read-only."
adf794e4
EZ
136 :type 'boolean
137 :group 'rcirc)
bd43c990 138
adf794e4 139(defcustom rcirc-buffer-maximum-lines nil
bd43c990
RS
140 "*The maximum size in lines for rcirc buffers.
141Channel buffers are truncated from the top to be no greater than this
92c4adc1 142number. If zero or nil, no truncating is done."
adf794e4
EZ
143 :type '(choice (const :tag "No truncation" nil)
144 (integer :tag "Number of lines"))
145 :group 'rcirc)
bd43c990 146
d40ac716 147(defcustom rcirc-scroll-show-maximum-output t
d51f146b 148 "*If non-nil, scroll buffer to keep the point at the bottom of the window."
f8db61b2
EZ
149 :type 'boolean
150 :group 'rcirc)
7faa3f8c 151
db58efbf
EZ
152(defcustom rcirc-authinfo nil
153 "List of authentication passwords.
154Each element of the list is a list with a SERVER-REGEXP string
155and a method symbol followed by method specific arguments.
156
157The valid METHOD symbols are `nickserv', `chanserv' and
158`bitlbee'.
bd43c990
RS
159
160The required ARGUMENTS for each METHOD symbol are:
db58efbf
EZ
161 `nickserv': NICK PASSWORD
162 `chanserv': NICK CHANNEL PASSWORD
163 `bitlbee': NICK PASSWORD
bd43c990
RS
164
165Example:
db58efbf
EZ
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"))))
adf794e4 183 :group 'rcirc)
bd43c990 184
db58efbf 185(defcustom rcirc-auto-authenticate-flag t
bd43c990 186 "*Non-nil means automatically send authentication string to server.
db58efbf 187See also `rcirc-authinfo'."
adf794e4
EZ
188 :type 'boolean
189 :group 'rcirc)
bd43c990 190
adf794e4 191(defcustom rcirc-prompt "> "
2e398771 192 "Prompt string to use in IRC buffers.
bd43c990
RS
193
194The 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
adf794e4
EZ
199Setting this alone will not affect the prompt;
200use 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
f8db61b2
EZ
206(defcustom rcirc-keywords nil
207 "List of keywords to highlight in message text."
208 :type '(repeat string)
209 :group 'rcirc)
210
2c8abe90
AS
211(defcustom rcirc-ignore-list ()
212 "List of ignored nicks.
213Use /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.
219When an ignored person renames, their nick is added to both lists.
220Nicks will be removed from the automatic list on follow-up renamings or
221parts.")
222
f8db61b2
EZ
223(defcustom rcirc-bright-nicks nil
224 "List of nicks to be emphasized.
02f47e86 225See `rcirc-bright-nick' face."
f8db61b2 226 :type '(repeat string)
02f47e86
MB
227 :group 'rcirc)
228
f8db61b2
EZ
229(defcustom rcirc-dim-nicks nil
230 "List of nicks to be deemphasized.
02f47e86 231See `rcirc-dim-nick' face."
f8db61b2 232 :type '(repeat string)
02f47e86
MB
233 :group 'rcirc)
234
adf794e4
EZ
235(defcustom rcirc-print-hooks nil
236 "Hook run after text is printed.
237Called with 5 arguments, PROCESS, SENDER, RESPONSE, TARGET and TEXT."
238 :type 'hook
239 :group 'rcirc)
bd43c990 240
db58efbf
EZ
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
02f47e86 246(defcustom rcirc-decode-coding-system 'utf-8
a2524d26
EZ
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
f8db61b2 257 "Alist to decide a coding system to use for a channel I/O operation.
a2524d26
EZ
258The format is ((PATTERN . VAL) ...).
259PATTERN is either a string or a cons of strings.
260If PATTERN is a string, it is used to match a target.
261If PATTERN is a cons of strings, the car part is used to match a
262target, and the cdr part is used to match a server.
263VAL is either a coding system or a cons of coding systems.
264If VAL is a coding system, it is used for both decoding and encoding
265messages.
266If VAL is a cons of coding systems, the car part is used for decoding,
267and 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
bd43c990
RS
283(defvar rcirc-prompt-start-marker nil)
284(defvar rcirc-prompt-end-marker nil)
285
286(defvar rcirc-nick-table nil)
287
2c8abe90
AS
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
adf794e4
EZ
296;; each process has an alist of (target . buffer) pairs
297(defvar rcirc-buffer-alist nil)
298
bd43c990 299(defvar rcirc-activity nil
a2524d26 300 "List of buffers with unviewed activity.")
bd43c990
RS
301
302(defvar rcirc-activity-string ""
303 "String displayed in modeline representing `rcirc-activity'.")
304(put 'rcirc-activity-string 'risky-local-variable t)
305
a2524d26
EZ
306(defvar rcirc-server-buffer nil
307 "The server buffer associated with this channel buffer.")
bd43c990
RS
308
309(defvar rcirc-target nil
310 "The channel or user associated with this buffer.")
311
bd43c990
RS
312(defvar rcirc-urls nil
313 "List of urls seen in the current buffer.")
7faa3f8c 314(put 'rcirc-urls 'permanent-local t)
bd43c990 315
2e875089 316(defvar rcirc-timeout-seconds 600
8216fbaf 317 "Kill connection after this many seconds if there is no activity.")
bd43c990 318
adf794e4 319(defconst rcirc-id-string (concat "rcirc on GNU Emacs " emacs-version))
bd43c990 320\f
bd43c990
RS
321(defvar rcirc-startup-channels nil)
322;;;###autoload
db58efbf 323(defun rcirc (arg)
bd43c990 324 "Connect to IRC.
db58efbf
EZ
325If ARG is non-nil, prompt for a server to connect to."
326 (interactive "P")
327 (if arg
a2524d26
EZ
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))
db58efbf
EZ
331 (channels (split-string
332 (read-string "IRC Channels: "
a2524d26 333 (mapconcat 'identity (rcirc-startup-channels server) " "))
db58efbf 334 "[, ]+" t)))
a2524d26 335 (rcirc-connect server port nick rcirc-default-user-name rcirc-default-user-full-name
db58efbf
EZ
336 channels))
337 ;; make new connection using defaults unless already connected to
338 ;; the default rcirc-server
a2524d26 339 (let (connected)
db58efbf 340 (dolist (p (rcirc-process-list))
a2524d26 341 (when (string= rcirc-default-server (process-name p))
db58efbf
EZ
342 (setq connected p)))
343 (if (not connected)
92c4adc1 344 (rcirc-connect rcirc-default-server rcirc-default-port
a2524d26
EZ
345 rcirc-default-nick rcirc-default-user-name
346 rcirc-default-user-full-name
347 (rcirc-startup-channels rcirc-default-server))
db58efbf 348 (switch-to-buffer (process-buffer connected))
92c4adc1 349 (message "Connected to %s"
a2524d26
EZ
350 (process-contact (get-buffer-process (current-buffer))
351 :host))))))
bd43c990
RS
352;;;###autoload
353(defalias 'irc 'rcirc)
354
355\f
356(defvar rcirc-process-output nil)
bd43c990
RS
357(defvar rcirc-topic nil)
358(defvar rcirc-keepalive-timer nil)
ad8121fe 359(defvar rcirc-last-server-message-time nil)
8216fbaf
EZ
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)
8d214091
RF
366
367;;;###autoload
2fbed782 368(defun rcirc-connect (&optional server port nick user-name full-name startup-channels)
bd43c990
RS
369 (save-excursion
370 (message "Connecting to %s..." server)
371 (let* ((inhibit-eol-conversion)
2fbed782
EZ
372 (port-number (if port
373 (if (stringp port)
374 (string-to-number port)
375 port)
a2524d26
EZ
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)
8216fbaf 382 (process (make-network-process :name server :host server :service port-number)))
bd43c990
RS
383 ;; set up process
384 (set-process-coding-system process 'raw-text 'raw-text)
adf794e4 385 (switch-to-buffer (rcirc-generate-new-buffer-name process nil))
bd43c990 386 (set-process-buffer process (current-buffer))
bd43c990 387 (rcirc-mode process nil)
a2524d26
EZ
388 (set-process-sentinel process 'rcirc-sentinel)
389 (set-process-filter process 'rcirc-filter)
8216fbaf
EZ
390 (make-local-variable 'rcirc-process)
391 (setq rcirc-process process)
a2524d26
EZ
392 (make-local-variable 'rcirc-server)
393 (setq rcirc-server server)
8216fbaf
EZ
394 (make-local-variable 'rcirc-server-name)
395 (setq rcirc-server-name server) ; update when we get 001 response
adf794e4
EZ
396 (make-local-variable 'rcirc-buffer-alist)
397 (setq rcirc-buffer-alist nil)
bd43c990
RS
398 (make-local-variable 'rcirc-nick-table)
399 (setq rcirc-nick-table (make-hash-table :test 'equal))
bd43c990
RS
400 (make-local-variable 'rcirc-nick)
401 (setq rcirc-nick nick)
402 (make-local-variable 'rcirc-process-output)
403 (setq rcirc-process-output nil)
bd43c990
RS
404 (make-local-variable 'rcirc-startup-channels)
405 (setq rcirc-startup-channels startup-channels)
ad8121fe
EZ
406 (make-local-variable 'rcirc-last-server-message-time)
407 (setq rcirc-last-server-message-time (current-time))
8216fbaf
EZ
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)
bd43c990
RS
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
8216fbaf
EZ
422 (unless rcirc-keepalive-timer
423 (setq rcirc-keepalive-timer
424 (run-at-time 0 (/ rcirc-timeout-seconds 2) 'rcirc-keepalive)))
bd43c990
RS
425
426 (message "Connecting to %s...done" server)
427
428 ;; return process object
429 process)))
430
adf794e4
EZ
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
a2524d26
EZ
436(defmacro with-rcirc-server-buffer (&rest body)
437 (declare (indent 0) (debug t))
438 `(with-current-buffer rcirc-server-buffer
439 ,@body))
440
bd43c990 441(defun rcirc-keepalive ()
ad8121fe
EZ
442 "Send keep alive pings to active rcirc processes.
443Kill processes that have not received a server message since the
444last ping."
bd43c990
RS
445 (if (rcirc-process-list)
446 (mapc (lambda (process)
8216fbaf
EZ
447 (with-rcirc-process-buffer process
448 (when (not rcirc-connecting)
449 (rcirc-send-string process (concat "PING " (rcirc-server-name process))))))
bd43c990 450 (rcirc-process-list))
8216fbaf 451 ;; no processes, clean up timer
bd43c990
RS
452 (cancel-timer rcirc-keepalive-timer)
453 (setq rcirc-keepalive-timer nil)))
454
adf794e4
EZ
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)
bd43c990 459 "Add an entry to the debug log including PROCESS and TEXT.
2e398771 460Debug text is written to `rcirc-debug-buffer' if `rcirc-debug-flag'
adf794e4
EZ
461is non-nil."
462 (when rcirc-debug-flag
bd43c990
RS
463 (save-excursion
464 (save-window-excursion
adf794e4 465 (set-buffer (get-buffer-create rcirc-debug-buffer))
bd43c990
RS
466 (goto-char (point-max))
467 (insert (concat
468 "["
469 (format-time-string "%Y-%m-%dT%T ") (process-name process)
470 "] "
471 text))))))
adf794e4 472
bd43c990
RS
473(defvar rcirc-sentinel-hooks nil
474 "Hook functions called when the process sentinel is called.
475Functions 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)))
adf794e4
EZ
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)))
adf794e4 483 (with-current-buffer (or buffer (current-buffer))
db58efbf
EZ
484 (rcirc-print process "rcirc.el" "ERROR" rcirc-target
485 (format "%s: %s (%S)"
486 (process-name process)
487 sentinel
8216fbaf 488 (process-status process)) (not rcirc-target))
db58efbf 489 ;; remove the prompt from buffers
bd43c990
RS
490 (let ((inhibit-read-only t))
491 (delete-region rcirc-prompt-start-marker
8216fbaf
EZ
492 rcirc-prompt-end-marker))))
493 (run-hook-with-args 'rcirc-sentinel-hooks process sentinel))))
bd43c990
RS
494
495(defun rcirc-process-list ()
496 "Return a list of rcirc processes."
497 (let (ps)
498 (mapc (lambda (p)
18aa2c90 499 (when (buffer-live-p (process-buffer p))
adf794e4 500 (with-rcirc-process-buffer p
bd43c990
RS
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
2e398771
JB
507 "Hook functions run when a message is received from server.
508Function is called with PROCESS, COMMAND, SENDER, ARGS and LINE.")
bd43c990
RS
509(defun rcirc-filter (process output)
510 "Called when PROCESS receives OUTPUT."
adf794e4 511 (rcirc-debug process output)
8216fbaf 512 (rcirc-reschedule-timeout process)
adf794e4 513 (with-rcirc-process-buffer process
ad8121fe 514 (setq rcirc-last-server-message-time (current-time))
bd43c990
RS
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))
adf794e4 520 (split-string rcirc-process-output "[\n\r]" t))
bd43c990
RS
521 (setq rcirc-process-output nil))))
522
8216fbaf
EZ
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
adf794e4 536(defvar rcirc-trap-errors-flag t)
bd43c990 537(defun rcirc-process-server-response (process text)
adf794e4 538 (if rcirc-trap-errors-flag
bd43c990
RS
539 (condition-case err
540 (rcirc-process-server-response-1 process text)
541 (error
542 (rcirc-print process "RCIRC" "ERROR" nil
adf794e4 543 (format "\"%s\" %s" text err) t)))
bd43c990
RS
544 (rcirc-process-server-response-1 process text)))
545
546(defun rcirc-process-server-response-1 (process text)
547 (if (string-match "^\\(:\\([^ ]+\\) \\)?\\([^ ]+\\) \\(.+\\)$" text)
db58efbf
EZ
548 (let* ((user (match-string 2 text))
549 (sender (rcirc-user-nick user))
bd43c990
RS
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))
adf794e4
EZ
556 (args (delq nil (append (split-string args1 " " t)
557 (list args2)))))
bd43c990
RS
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
f8db61b2
EZ
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)
bd43c990 569 "Generic server response handler."
f8db61b2
EZ
570 (rcirc-print process sender response nil
571 (mapconcat 'identity (cdr args) " ")
572 (not (member response rcirc-responses-no-activity))))
bd43c990
RS
573
574(defun rcirc-send-string (process string)
575 "Send PROCESS a STRING plus a newline."
a2524d26 576 (let ((string (concat (encode-coding-string string rcirc-encode-coding-system)
bd43c990 577 "\n")))
a2524d26 578 (unless (eq (process-status process) 'open)
53f831f3 579 (error "Network connection to %s is not open"
a2524d26 580 (process-name process)))
adf794e4 581 (rcirc-debug process string)
bd43c990
RS
582 (process-send-string process string)))
583
a2524d26
EZ
584(defun rcirc-buffer-process (&optional buffer)
585 "Return the process associated with channel BUFFER.
586With no argument or nil as argument, use the current buffer."
8216fbaf
EZ
587 (or (get-buffer-process (if buffer
588 (with-current-buffer buffer
589 rcirc-server-buffer)
590 rcirc-server-buffer))
591 rcirc-process))
a2524d26
EZ
592
593(defun rcirc-server-name (process)
594 "Return PROCESS server name, given by the 001 response."
adf794e4 595 (with-rcirc-process-buffer process
8216fbaf 596 (or rcirc-server-name rcirc-default-server)))
bd43c990
RS
597
598(defun rcirc-nick (process)
599 "Return PROCESS nick."
92c4adc1 600 (with-rcirc-process-buffer process
a2524d26
EZ
601 (or rcirc-nick rcirc-default-nick)))
602
603(defun rcirc-buffer-nick (&optional buffer)
604 "Return the nick associated with BUFFER.
605With 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))))
bd43c990 609
02f47e86 610(defvar rcirc-max-message-length 420
bd43c990
RS
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.
615If 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))))
db58efbf
EZ
627 (rcirc-get-buffer-create process target)
628 (rcirc-print process (rcirc-nick process) response target text)
bd43c990 629 (rcirc-send-string process (concat response " " target " :" text))
db58efbf 630 (when more (rcirc-send-message process target more noticep))))
bd43c990
RS
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)
7faa3f8c 653
bd43c990
RS
654(defun rcirc-complete-nick ()
655 "Cycle through nick completions from list of nicks in channel."
656 (interactive)
7faa3f8c 657 (if (eq last-command this-command)
bd43c990
RS
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))
adf794e4
EZ
669 (all-completions
670 (buffer-substring
bd43c990
RS
671 (+ rcirc-prompt-end-marker
672 rcirc-nick-completion-start-offset)
673 (point))
674 (mapcar (lambda (x) (cons x nil))
a2524d26
EZ
675 (rcirc-channel-nicks (rcirc-buffer-process)
676 rcirc-target))))))
bd43c990
RS
677 (let ((completion (car rcirc-nick-completions)))
678 (when completion
7faa3f8c 679 (rcirc-put-nick-channel (rcirc-buffer-process) completion rcirc-target)
adf794e4 680 (delete-region (+ rcirc-prompt-end-marker
7faa3f8c
MB
681 rcirc-nick-completion-start-offset)
682 (point))
bd43c990 683 (insert (concat completion
adf794e4 684 (if (= (+ rcirc-prompt-end-marker
bd43c990
RS
685 rcirc-nick-completion-start-offset)
686 rcirc-prompt-end-marker)
687 ": "))))))
688
a2524d26
EZ
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))
bd43c990
RS
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)
a2524d26 710(define-key rcirc-mode-map (kbd "C-c C-l") 'rcirc-toggle-low-priority)
bd43c990
RS
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
adf794e4 722 'rcirc-toggle-ignore-buffer-activity)
bd43c990
RS
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
adf794e4 726(defvar rcirc-browse-url-map (make-sparse-keymap)
2e398771 727 "Keymap used for browsing URLs in `rcirc-mode'.")
adf794e4
EZ
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
bd43c990
RS
735(defvar rcirc-mode-hook nil
736 "Hook run when setting up rcirc buffer.")
737
a2524d26
EZ
738(defvar rcirc-last-post-time nil)
739
bd43c990 740(defun rcirc-mode (process target)
2e398771 741 "Major mode for IRC channel buffers.
bd43c990
RS
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))
a2524d26
EZ
751 (make-local-variable 'rcirc-server-buffer)
752 (setq rcirc-server-buffer (process-buffer process))
bd43c990
RS
753 (make-local-variable 'rcirc-target)
754 (setq rcirc-target target)
ad8121fe
EZ
755 (make-local-variable 'rcirc-topic)
756 (setq rcirc-topic nil)
a2524d26
EZ
757 (make-local-variable 'rcirc-last-post-time)
758 (setq rcirc-last-post-time (current-time))
adf794e4
EZ
759
760 (make-local-variable 'rcirc-short-buffer-name)
761 (setq rcirc-short-buffer-name nil)
bd43c990 762 (make-local-variable 'rcirc-urls)
bd43c990 763 (setq use-hard-newlines t)
bd43c990 764
a2524d26
EZ
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)))
aac5d1fd
EZ
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))))))
a2524d26 774
bd43c990
RS
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
a2524d26
EZ
788 ;; if the user changes the major mode or kills the buffer, there is
789 ;; cleanup work to do
f8db61b2
EZ
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)
a2524d26 792
adf794e4
EZ
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
bd43c990
RS
801 (run-hooks 'rcirc-mode-hook))
802
adf794e4
EZ
803(defun rcirc-update-prompt (&optional all)
804 "Reset the prompt string in the current buffer.
c18a54de 805
adf794e4
EZ
806If 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
a2524d26
EZ
819 (replace-regexp-in-string (car rep) (cdr rep) prompt)))
820 (list (cons "%n" (rcirc-buffer-nick))
8216fbaf 821 (cons "%s" (with-rcirc-server-buffer rcirc-server-name))
adf794e4
EZ
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))))
bd43c990
RS
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)
a2524d26
EZ
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)
adf794e4
EZ
862 (let ((buffer (current-buffer)))
863 (rcirc-clear-activity buffer)
a2524d26
EZ
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)))
adf794e4 869 (rcirc-update-short-buffer-names)
bd43c990 870 (if (rcirc-channel-p rcirc-target)
a2524d26
EZ
871 (rcirc-send-string (rcirc-buffer-process)
872 (concat "PART " rcirc-target " :" reason))
adf794e4 873 (when rcirc-target
a2524d26
EZ
874 (rcirc-remove-nick-channel (rcirc-buffer-process)
875 (rcirc-buffer-nick)
adf794e4
EZ
876 rcirc-target))))))
877
adf794e4
EZ
878(defun rcirc-generate-new-buffer-name (process target)
879 "Return a buffer name based on PROCESS and TARGET.
2e398771 880This is used for the initial name given to IRC buffers."
adf794e4
EZ
881 (if target
882 (concat target "@" (process-name process))
883 (concat "*" (process-name process) "*")))
bd43c990 884
adf794e4 885(defun rcirc-get-buffer (process target &optional server)
bd43c990 886 "Return the buffer associated with the PROCESS and TARGET.
adf794e4 887
adf794e4
EZ
888If optional argument SERVER is non-nil, return the server buffer
889if 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)))))))
bd43c990
RS
895
896(defun rcirc-get-buffer-create (process target)
adf794e4
EZ
897 "Return the buffer associated with the PROCESS and TARGET.
898Create the buffer if it doesn't exist."
899 (let ((buffer (rcirc-get-buffer process target)))
a2524d26 900 (if (and buffer (buffer-live-p buffer))
2fbed782 901 (with-current-buffer buffer
db58efbf 902 (when (not rcirc-target)
2fbed782 903 (setq rcirc-target target))
db58efbf 904 buffer)
adf794e4
EZ
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)))))
bd43c990
RS
913
914(defun rcirc-send-input ()
915 "Send input to target associated with the current buffer."
916 (interactive)
53f831f3
AS
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))
a2524d26
EZ
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)
db58efbf
EZ
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)
a2524d26 955 (message "Not joined (no target)")
db58efbf 956 (delete-region rcirc-prompt-end-marker (point))
a2524d26
EZ
957 (rcirc-send-message (rcirc-buffer-process) rcirc-target line)
958 (setq rcirc-last-post-time (current-time))))
db58efbf
EZ
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))
a2524d26
EZ
964 (let ((fun (intern-soft (concat "rcirc-cmd-" command)))
965 (process (rcirc-buffer-process)))
db58efbf
EZ
966 (newline)
967 (with-current-buffer (current-buffer)
968 (delete-region rcirc-prompt-end-marker (point))
969 (if (string= command "me")
a2524d26 970 (rcirc-print process (rcirc-buffer-nick)
db58efbf 971 "ACTION" rcirc-target args)
a2524d26 972 (rcirc-print process (rcirc-buffer-nick)
db58efbf
EZ
973 "COMMAND" rcirc-target line))
974 (set-marker rcirc-prompt-end-marker (point))
975 (if (fboundp fun)
a2524d26
EZ
976 (funcall fun args process rcirc-target)
977 (rcirc-send-string process
f8db61b2 978 (concat command " :" args)))))))
db58efbf 979
bd43c990
RS
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)))
a2524d26 988 (parent (buffer-name)))
bd43c990
RS
989 (delete-region rcirc-prompt-end-marker (point))
990 (setq rcirc-window-configuration (current-window-configuration))
991 (pop-to-buffer (concat "*multiline " parent "*"))
a2524d26
EZ
992 (funcall rcirc-multiline-major-mode)
993 (rcirc-multiline-minor-mode 1)
bd43c990 994 (setq rcirc-parent-buffer parent)
bd43c990 995 (insert text)
db58efbf
EZ
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))))
bd43c990 998
a2524d26
EZ
999(defvar rcirc-multiline-minor-mode-map (make-sparse-keymap)
1000 "Keymap for multiline mode in rcirc.")
92c4adc1 1001(define-key rcirc-multiline-minor-mode-map
a2524d26
EZ
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
bd43c990 1017 (make-local-variable 'rcirc-parent-buffer)
02f47e86
MB
1018 (put 'rcirc-parent-buffer 'permanent-local t)
1019 (setq fill-column rcirc-max-message-length))
a2524d26
EZ
1020
1021(defun rcirc-multiline-minor-submit ()
bd43c990
RS
1022 "Send the text in buffer back to parent buffer."
1023 (interactive)
bd43c990 1024 (assert rcirc-parent-buffer)
adf794e4 1025 (untabify (point-min) (point-max))
bd43c990
RS
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)
bd43c990 1032 (kill-buffer buffer)
adf794e4
EZ
1033 (set-window-configuration rcirc-window-configuration)
1034 (goto-char (+ rcirc-prompt-end-marker (1- pos)))))
bd43c990 1035
a2524d26 1036(defun rcirc-multiline-minor-cancel ()
bd43c990
RS
1037 "Cancel the multiline edit."
1038 (interactive)
bd43c990
RS
1039 (kill-buffer (current-buffer))
1040 (set-window-configuration rcirc-window-configuration))
1041
2fbed782 1042(defun rcirc-any-buffer (process)
adf794e4 1043 "Return a buffer for PROCESS, either the one selected or the process buffer."
2fbed782
EZ
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)
a2524d26 1050 (eq (rcirc-buffer-process) process))))
2fbed782
EZ
1051 buffer
1052 (process-buffer process)))))
bd43c990 1053
324e4da7 1054(defcustom rcirc-response-formats
2fbed782
EZ
1055 '(("PRIVMSG" . "%T<%N> %m")
1056 ("NOTICE" . "%T-%N- %m")
1057 ("ACTION" . "%T[%N %m]")
324e4da7
MB
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.
1062The format is looked up using the response-type as a key;
1063if no match is found, the default entry (with a key of `t') is used.
1064
1065The entry's value part should be a string, which is inserted with
1066the of the following escape sequences replaced by the described values:
1067
1068 %m The message text
2fbed782
EZ
1069 %n The sender's nick
1070 %N The sender's nick (with face `rcirc-my-nick' or `rcirc-other-nick')
324e4da7
MB
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
3715419e 1078 %f- Following text uses the default face
a2524d26 1079 %% A literal `%' character"
324e4da7
MB
1080 :type '(alist :key-type (choice (string :tag "Type")
1081 (const :tag "Default" t))
1082 :value-type string)
1083 :group 'rcirc)
1084
bd43c990 1085(defun rcirc-format-response-string (process sender response target text)
324e4da7
MB
1086 "Return a nicely-formatted response string, incorporating TEXT
1087\(and perhaps other arguments). The specific formatting used
1088is 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 "%"))
02f47e86 1093 (sender (or sender ""))
324e4da7
MB
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 ?%)
3715419e 1106 ;; %% -- literal % character
324e4da7 1107 "%")
2fbed782
EZ
1108 ((or (eq key ?n) (eq key ?N))
1109 ;; %n/%N -- nick
8216fbaf 1110 (let ((nick (concat (if (string= (rcirc-server-name process)
2fbed782
EZ
1111 sender)
1112 ""
54aba1ee 1113 sender)
2fbed782
EZ
1114 (and target (concat "," target)))))
1115 (rcirc-facify nick
1116 (if (eq key ?n)
1117 face
02f47e86
MB
1118 (cond ((string= sender (rcirc-nick process))
1119 'rcirc-my-nick)
f8db61b2 1120 ((and rcirc-bright-nicks
92c4adc1 1121 (string-match
f8db61b2
EZ
1122 (regexp-opt rcirc-bright-nicks)
1123 sender))
02f47e86 1124 'rcirc-bright-nick)
f8db61b2
EZ
1125 ((and rcirc-dim-nicks
1126 (string-match
1127 (regexp-opt rcirc-dim-nicks)
1128 sender))
02f47e86
MB
1129 'rcirc-dim-nick)
1130 (t
1131 'rcirc-other-nick))))))
f8db61b2 1132 ((eq key ?T)
3715419e 1133 ;; %T -- timestamp
324e4da7
MB
1134 (rcirc-facify
1135 (format-time-string rcirc-time-format (current-time))
1136 'rcirc-timestamp))
1137 ((eq key ?m)
3715419e 1138 ;; %m -- message text
f8db61b2 1139 (rcirc-markup-text process sender response (rcirc-facify text face)))
324e4da7 1140 ((eq key ?t)
3715419e 1141 ;; %t -- target
324e4da7
MB
1142 (rcirc-facify (or rcirc-target "") face))
1143 ((eq key ?r)
3715419e 1144 ;; %r -- response
324e4da7
MB
1145 (rcirc-facify response face))
1146 ((eq key ?f)
3715419e 1147 ;; %f -- change face
324e4da7 1148 (setq face-key (aref chunk 0))
3715419e 1149 (setq chunk (substring chunk 1))
324e4da7 1150 (cond ((eq face-key ?w)
3715419e 1151 ;; %fw -- warning face
324e4da7
MB
1152 (setq face 'font-lock-warning-face))
1153 ((eq face-key ?p)
3715419e 1154 ;; %fp -- server-prefix face
324e4da7
MB
1155 (setq face 'rcirc-server-prefix))
1156 ((eq face-key ?s)
3715419e 1157 ;; %fs -- warning face
324e4da7
MB
1158 (setq face 'rcirc-server))
1159 ((eq face-key ?-)
3715419e 1160 ;; %fs -- warning face
324e4da7
MB
1161 (setq face nil))
1162 ((and (eq face-key ?\[)
3715419e 1163 (string-match "^\\([^]]*\\)[]]" chunk)
324e4da7 1164 (facep (match-string 1 chunk)))
3715419e 1165 ;; %f[...] -- named face
324e4da7 1166 (setq face (intern (match-string 1 chunk)))
3715419e
MB
1167 (setq chunk (substring chunk (match-end 0)))))
1168 "")))
324e4da7
MB
1169 (setq result (concat result repl (rcirc-facify chunk face))))
1170 result))
bd43c990 1171
db58efbf
EZ
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)
2fbed782 1177 (rcirc-any-buffer process))
db58efbf
EZ
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)
2fbed782 1186 (rcirc-any-buffer process))))))
db58efbf 1187
f8db61b2
EZ
1188(defvar rcirc-activity-types nil)
1189(make-variable-buffer-local 'rcirc-activity-types)
a2524d26
EZ
1190(defvar rcirc-last-sender nil)
1191(make-variable-buffer-local 'rcirc-last-sender)
7faa3f8c 1192
bd43c990
RS
1193(defun rcirc-print (process sender response target text &optional activity)
1194 "Print TEXT in the buffer associated with TARGET.
1195Format based on SENDER and RESPONSE. If ACTIVITY is non-nil,
1196record activity."
a2524d26 1197 (or text (setq text ""))
db58efbf 1198 (unless (or (member sender rcirc-ignore-list)
2c8abe90 1199 (member (with-syntax-table rcirc-nick-syntax-table
02f47e86
MB
1200 (when (string-match "^\\([^/]\\w*\\)[:,]" text)
1201 (match-string 1 text)))
1202 rcirc-ignore-list))
db58efbf 1203 (let* ((buffer (rcirc-target-buffer process sender response target text))
2c8abe90
AS
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
a2524d26 1212 (setq text (decode-coding-string text rcirc-decode-coding-system))
2c8abe90
AS
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)
324e4da7
MB
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
2fbed782
EZ
1233 (let ((text-start (make-marker)))
1234 (set-marker text-start
92c4adc1 1235 (or (next-single-property-change fill-start
2fbed782 1236 'rcirc-text)
e8f10ddb 1237 rcirc-prompt-end-marker))
2fbed782
EZ
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)))
d51f146b
RS
1249 ((eq rcirc-fill-column 'window-width)
1250 (1- (window-width)))
2fbed782
EZ
1251 (rcirc-fill-column
1252 rcirc-fill-column)
1253 (t fill-column))))
1254 (fill-region fill-start rcirc-prompt-start-marker 'left t)))))
2c8abe90
AS
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)
d40ac716
CY
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))))
2c8abe90
AS
1279 nil t)
1280
1281 ;; restore the point
1282 (goto-char (if moving rcirc-prompt-end-marker old-point))
1283
d40ac716
CY
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
b96572ff
CY
1291 (when (<= (- (window-height)
1292 (count-screen-lines
1293 (window-point)
1294 (window-start))
d40ac716
CY
1295 1)
1296 0)
1297 (recenter -1)))))))
1298 nil t))
1299
2c8abe90
AS
1300 ;; flush undo (can we do something smarter here?)
1301 (buffer-disable-undo)
1302 (buffer-enable-undo))
1303
1304 ;; record modeline activity
f8db61b2
EZ
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)))
2c8abe90
AS
1312
1313 (sit-for 0) ; displayed text before hook
1314 (run-hook-with-args 'rcirc-print-hooks
1315 process sender response target text)))))
bd43c990
RS
1316
1317(defun rcirc-startup-channels (server)
2e398771 1318 "Return the list of startup channels for SERVER."
bd43c990
RS
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
db58efbf
EZ
1328 (dolist (channel channels)
1329 (with-rcirc-process-buffer process
1330 (rcirc-cmd-join channel process)))))
bd43c990
RS
1331\f
1332;;; nick management
8216fbaf 1333(defvar rcirc-nick-prefix-chars "~&@%+")
bd43c990
RS
1334(defun rcirc-user-nick (user)
1335 "Return the nick from USER. Remove any non-nick junk."
db58efbf 1336 (save-match-data
8216fbaf
EZ
1337 (if (string-match (concat "^[" rcirc-nick-prefix-chars
1338 "]?\\([^! ]+\\)!?") (or user ""))
db58efbf
EZ
1339 (match-string 1 user)
1340 user)))
bd43c990 1341
bd43c990
RS
1342(defun rcirc-nick-channels (process nick)
1343 "Return list of channels for NICK."
db58efbf
EZ
1344 (with-rcirc-process-buffer process
1345 (mapcar (lambda (x) (car x))
1346 (gethash nick rcirc-nick-table))))
bd43c990
RS
1347
1348(defun rcirc-put-nick-channel (process nick channel)
1349 "Add CHANNEL to list associated with NICK."
2fbed782
EZ
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))))))
bd43c990
RS
1359
1360(defun rcirc-nick-remove (process nick)
1361 "Remove NICK from table."
adf794e4 1362 (with-rcirc-process-buffer process
bd43c990
RS
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."
adf794e4 1367 (with-rcirc-process-buffer process
db58efbf 1368 (let* ((chans (gethash nick rcirc-nick-table))
adf794e4
EZ
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)))))
bd43c990
RS
1375 (if newchans
1376 (puthash nick newchans rcirc-nick-table)
1377 (remhash nick rcirc-nick-table)))))
1378
a2524d26
EZ
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))))
2c8abe90
AS
1394
1395(defun rcirc-ignore-update-automatic (nick)
2e398771
JB
1396 "Remove NICK from `rcirc-ignore-list'
1397if NICK is also on `rcirc-ignore-list-automatic'."
2c8abe90
AS
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))))
bd43c990
RS
1403\f
1404;;; activity tracking
db58efbf
EZ
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
e8f10ddb 1412;;;###autoload
db58efbf
EZ
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
a2524d26
EZ
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))
92c4adc1 1429 (setq global-mode-string
a2524d26
EZ
1430 (delete 'rcirc-activity-string global-mode-string))
1431 (remove-hook 'window-configuration-change-hook
1432 'rcirc-window-configuration-change)))
db58efbf 1433
adf794e4 1434(or (assq 'rcirc-ignore-buffer-activity-flag minor-mode-alist)
bd43c990 1435 (setq minor-mode-alist
adf794e4 1436 (cons '(rcirc-ignore-buffer-activity-flag " Ignore") minor-mode-alist)))
a2524d26
EZ
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)))
bd43c990 1440
db58efbf
EZ
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"))
bd43c990
RS
1449 (force-mode-line-update))
1450
a2524d26 1451(defun rcirc-toggle-low-priority ()
02f47e86 1452 "Toggle the value of `rcirc-low-priority-flag'."
a2524d26
EZ
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
bd43c990
RS
1461(defvar rcirc-switch-to-buffer-function 'switch-to-buffer
1462 "Function to use when switching buffers.
1463Possible 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)
a2524d26 1469 (funcall rcirc-switch-to-buffer-function rcirc-server-buffer))
bd43c990
RS
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)
a2524d26
EZ
1481 "Go to the next rcirc buffer with activity.
1482With prefix ARG, go to the next low priority buffer with activity.
bd43c990
RS
1483The function given by `rcirc-switch-to-buffer-function' is used to
1484show the buffer."
a2524d26
EZ
1485 (interactive "P")
1486 (let* ((pair (rcirc-split-activity rcirc-activity))
1487 (lopri (car pair))
92c4adc1 1488 (hipri (cdr pair)))
a2524d26
EZ
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))
92c4adc1 1503 (message (concat
a2524d26
EZ
1504 "No IRC activity."
1505 (when lopri
92c4adc1 1506 (concat
a2524d26
EZ
1507 " Type C-u "
1508 (key-description (this-command-keys))
1509 " for low priority activity."))))))))
bd43c990
RS
1510
1511(defvar rcirc-activity-hooks nil
1512 "Hook to be run when there is channel activity.
1513
1514Functions are called with a single argument, the buffer with the
1515activity. Only run if the buffer is not visible and
adf794e4 1516`rcirc-ignore-buffer-activity-flag' is non-nil.")
bd43c990 1517
a2524d26 1518(defun rcirc-record-activity (buffer &optional type)
bd43c990
RS
1519 "Record BUFFER activity with TYPE."
1520 (with-current-buffer buffer
1521 (when (not (get-buffer-window (current-buffer) t))
a2524d26
EZ
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)))))
f8db61b2 1528 (pushnew type rcirc-activity-types)
bd43c990
RS
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
f8db61b2 1536 (setq rcirc-activity-types nil)))
bd43c990 1537
a2524d26
EZ
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
f8db61b2 1544 (not (member 'nick rcirc-activity-types)))
a2524d26
EZ
1545 (add-to-list 'lopri buf t)
1546 (add-to-list 'hipri buf t))))
1547 (cons lopri hipri)))
1548
adf794e4 1549;; TODO: add mouse properties
bd43c990
RS
1550(defun rcirc-update-activity-string ()
1551 "Update mode-line string."
a2524d26
EZ
1552 (let* ((pair (rcirc-split-activity rcirc-activity))
1553 (lopri (car pair))
1554 (hipri (cdr pair)))
1555 (setq rcirc-activity-string
7faa3f8c
MB
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 "")))))
a2524d26
EZ
1570
1571(defun rcirc-activity-string (buffers)
1572 (mapconcat (lambda (b)
f8db61b2 1573 (let ((s (substring-no-properties (rcirc-short-buffer-name b))))
a2524d26 1574 (with-current-buffer b
f8db61b2
EZ
1575 (dolist (type rcirc-activity-types)
1576 (rcirc-add-face 0 (length s)
1577 (case type
82741a5e
CY
1578 (nick 'rcirc-track-nick)
1579 (keyword 'rcirc-track-keyword))
f8db61b2
EZ
1580 s)))
1581 s))
a2524d26 1582 buffers ","))
bd43c990
RS
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
adf794e4
EZ
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.
1592Also, 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)))
f8db61b2
EZ
1596 (with-current-buffer buf
1597 (when (eq major-mode 'rcirc-mode)
1598 (rcirc-clear-activity buf)))
a2524d26 1599 (when (eq buf rcirc-current-buffer)
f8db61b2 1600 (setq current-now-hidden nil)))))
a2524d26 1601 ;; add overlay arrow if the buffer isn't displayed
f8db61b2
EZ
1602 (when (and current-now-hidden
1603 rcirc-current-buffer
1604 (buffer-live-p rcirc-current-buffer))
adf794e4 1605 (with-current-buffer rcirc-current-buffer
f8db61b2
EZ
1606 (when (and (eq major-mode 'rcirc-mode)
1607 (marker-position overlay-arrow-position))
adf794e4
EZ
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)))
bd43c990
RS
1616
1617\f
adf794e4
EZ
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))
a2524d26
EZ
1626 (when (buffer-live-p (cdr i))
1627 (with-current-buffer (cdr i)
1628 (setq rcirc-short-buffer-name (car i)))))))
adf794e4
EZ
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
bd43c990
RS
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
adf794e4 1678(defmacro defun-rcirc-command (command argument docstring interactive-form
bd43c990
RS
1679 &rest body)
1680 "Define a command."
1681 `(defun ,(intern (concat "rcirc-cmd-" (symbol-name command)))
1682 (,@argument &optional process target)
a2524d26
EZ
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.")
bd43c990 1685 ,interactive-form
a2524d26 1686 (let ((process (or process (rcirc-buffer-process)))
bd43c990
RS
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: "
92c4adc1 1696 (with-rcirc-server-buffer
a2524d26 1697 rcirc-nick-table)))
bd43c990
RS
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: "
a2524d26 1711 (with-rcirc-server-buffer rcirc-nick-table))))
adf794e4
EZ
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)
bd43c990
RS
1716 (rcirc-cmd-whois nick))))
1717
f161b079 1718(defun-rcirc-command join (channel)
bd43c990
RS
1719 "Join CHANNEL."
1720 (interactive "sJoin channel: ")
f161b079
JB
1721 (let ((buffer (rcirc-get-buffer-create process
1722 (car (split-string channel)))))
a2524d26 1723 (rcirc-send-string process (concat "JOIN " channel))
bd43c990 1724 (when (not (eq (selected-window) (minibuffer-window)))
a2524d26 1725 (funcall rcirc-switch-to-buffer-function buffer))))
bd43c990
RS
1726
1727(defun-rcirc-command part (channel)
1728 "Part CHANNEL."
1729 (interactive "sPart channel: ")
1730 (let ((channel (if (> (length channel) 0) channel target)))
adf794e4 1731 (rcirc-send-string process (concat "PART " channel " :" rcirc-id-string))))
bd43c990
RS
1732
1733(defun-rcirc-command quit (reason)
1734 "Send a quit message to server with REASON."
1735 (interactive "sQuit reason: ")
adf794e4
EZ
1736 (rcirc-send-string process (concat "QUIT :"
1737 (if (not (zerop (length reason)))
1738 reason
1739 rcirc-id-string))))
bd43c990
RS
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.
1750If 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.
1762With 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: "
a2524d26 1774 (with-rcirc-server-buffer rcirc-nick-table))))
bd43c990
RS
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: "
92c4adc1 1802 (rcirc-channel-nicks
a2524d26
EZ
1803 (rcirc-buffer-process)
1804 rcirc-target))
bd43c990
RS
1805 (read-from-minibuffer "Kick reason: "))))
1806 (let* ((arglist (split-string arg))
adf794e4 1807 (argstring (concat (car arglist) " :"
bd43c990
RS
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)))
adf794e4
EZ
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
bd43c990
RS
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)))
2c8abe90 1824
f8db61b2
EZ
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
2c8abe90
AS
1832(defun-rcirc-command ignore (nick)
1833 "Manage the ignore list.
1834Ignore NICK, unignore NICK if already ignored, or list ignored
1835nicks when no NICK is given. When listing ignored nicks, the
2e398771 1836ones added to the list automatically are marked with an asterisk."
2c8abe90 1837 (interactive "sToggle ignoring of nick: ")
f8db61b2 1838 (setq rcirc-ignore-list (rcirc-add-or-remove rcirc-ignore-list nick))
92c4adc1 1839 (rcirc-print process nil "IGNORE" target
db58efbf
EZ
1840 (mapconcat
1841 (lambda (nick)
1842 (concat nick
1843 (if (member nick rcirc-ignore-list-automatic)
1844 "*" "")))
1845 rcirc-ignore-list " ")))
2c8abe90 1846
f8db61b2
EZ
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))
92c4adc1 1851 (rcirc-print process nil "BRIGHT" target
f8db61b2
EZ
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))
92c4adc1 1858 (rcirc-print process nil "DIM" target
f8db61b2
EZ
1859 (mapconcat 'identity rcirc-dim-nicks " ")))
1860
1861(defun-rcirc-command keyword (keyword)
1862 "Manage the keyword list.
1863Mark KEYWORD, unmark KEYWORD if already marked, or list marked
1864keywords when no KEYWORD is given."
1865 (interactive "sToggle highlighting of keyword: ")
1866 (setq rcirc-keywords (rcirc-add-or-remove rcirc-keywords keyword))
92c4adc1 1867 (rcirc-print process nil "KEYWORD" target
f8db61b2
EZ
1868 (mapconcat 'identity rcirc-keywords " ")))
1869
bd43c990 1870\f
f8db61b2
EZ
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)))))
adf794e4 1882
bd43c990
RS
1883(defun rcirc-facify (string face)
1884 "Return a copy of STRING with FACE property added."
f8db61b2
EZ
1885 (let ((string (or string "")))
1886 (rcirc-add-face 0 (length string) face string)
1887 string))
bd43c990 1888
bd43c990 1889(defvar rcirc-url-regexp
2fbed782
EZ
1890 (rx-to-string
1891 `(and word-boundary
92c4adc1
JB
1892 (or (and
1893 (or (and (or "http" "https" "ftp" "file" "gopher" "news"
019ed9c7
EZ
1894 "telnet" "wais" "mailto")
1895 "://")
1896 "www.")
1897 (1+ (char "-a-zA-Z0-9_."))
7faa3f8c 1898 (1+ (char "-a-zA-Z0-9_"))
019ed9c7 1899 (optional ":" (1+ (char "0-9"))))
2fbed782
EZ
1900 (and (1+ (char "-a-zA-Z0-9_."))
1901 (or ".com" ".net" ".org")
1902 word-boundary))
92c4adc1 1903 (optional
2fbed782 1904 (and "/"
f8db61b2
EZ
1905 (1+ (char "-a-zA-Z0-9_=!?#$\@~`%&*+|\\/:;.,{}[]()"))
1906 (char "-a-zA-Z0-9_=#$\@~`%&*+|\\/:;{}[]()")))))
2e398771 1907 "Regexp matching URLs. Set to nil to disable URL features in rcirc.")
bd43c990
RS
1908
1909(defun rcirc-browse-url (&optional arg)
2e398771 1910 "Prompt for URL to browse based on URLs in buffer."
02f47e86 1911 (interactive "P")
bd43c990
RS
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
adf794e4
EZ
1919(defun rcirc-browse-url-at-point (point)
1920 "Send URL at point to `browse-url'."
1921 (interactive "d")
7faa3f8c 1922 (let ((beg (previous-single-property-change (1+ point) 'mouse-face))
adf794e4
EZ
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
f8db61b2
EZ
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
1943Each function takes three arguments, PROCESS, SENDER, RESPONSE
1944and CHANNEL-BUFFER. The current buffer is temporary buffer that
1945contains the text to manipulate. Each function works on the text
1946in this buffer.")
1947
1948(defun rcirc-markup-text (process sender response text)
bd43c990 1949 "Return TEXT with properties added based on various patterns."
f8db61b2
EZ
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
92c4adc1 1984 (while (re-search-forward (concat "\\b"
f8db61b2
EZ
1985 (regexp-quote (rcirc-nick process))
1986 "\\b")
1987 nil t)
92c4adc1 1988 (rcirc-add-face (match-beginning 0) (match-end 0)
f8db61b2
EZ
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)))))
bd43c990
RS
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
adf794e4 2035 (with-rcirc-process-buffer process
8216fbaf
EZ
2036 (setq rcirc-connecting nil)
2037 (rcirc-reschedule-timeout process)
2038 (setq rcirc-server-name sender)
bd43c990
RS
2039 (setq rcirc-nick (car args))
2040 (rcirc-update-prompt)
2041 (when rcirc-auto-authenticate-flag (rcirc-authenticate))
adf794e4 2042 (rcirc-join-channels process rcirc-startup-channels)))
bd43c990
RS
2043
2044(defun rcirc-handler-PRIVMSG (process sender args text)
2045 (let ((target (if (rcirc-channel-p (car args))
2046 (car args)
db58efbf 2047 sender))
bd43c990
RS
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)))
adf794e4
EZ
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...
02f47e86 2066 ((string-match "\\[\\(#[^\] ]+\\)\\]" message)
adf794e4
EZ
2067 (match-string 1 message))
2068 (sender
a2524d26 2069 (if (string= sender (rcirc-server-name process))
db58efbf
EZ
2070 nil ; server notice
2071 sender)))
adf794e4 2072 message t))))
bd43c990
RS
2073
2074(defun rcirc-handler-WALLOPS (process sender args text)
db58efbf 2075 (rcirc-print process sender "WALLOPS" sender (car args) t))
bd43c990
RS
2076
2077(defun rcirc-handler-JOIN (process sender args text)
db58efbf 2078 (let ((channel (car args)))
bd43c990
RS
2079 (rcirc-get-buffer-create process channel)
2080 (rcirc-print process sender "JOIN" channel "")
2081
2082 ;; print in private chat buffer if it exists
a2524d26 2083 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
db58efbf 2084 (rcirc-print process sender "JOIN" sender channel))
bd43c990 2085
adf794e4 2086 (rcirc-put-nick-channel process sender channel)))
bd43c990
RS
2087
2088;; PART and KICK are handled the same way
2089(defun rcirc-handler-PART-or-KICK (process response channel sender nick args)
a2524d26 2090 (rcirc-ignore-update-automatic nick)
bd43c990
RS
2091 (if (not (string= nick (rcirc-nick process)))
2092 ;; this is someone else leaving
adf794e4
EZ
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))))))
bd43c990
RS
2104
2105(defun rcirc-handler-PART (process sender args text)
a2524d26
EZ
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)))
bd43c990
RS
2115
2116(defun rcirc-handler-KICK (process sender args text)
a2524d26
EZ
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)))
bd43c990
RS
2127
2128(defun rcirc-handler-QUIT (process sender args text)
db58efbf
EZ
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))
bd43c990 2133
db58efbf 2134 ;; print in private chat buffer if it exists
a2524d26 2135 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
db58efbf 2136 (rcirc-print process sender "QUIT" sender (apply 'concat args)))
bd43c990 2137
db58efbf 2138 (rcirc-nick-remove process sender))
bd43c990
RS
2139
2140(defun rcirc-handler-NICK (process sender args text)
db58efbf 2141 (let* ((old-nick sender)
bd43c990
RS
2142 (new-nick (car args))
2143 (channels (rcirc-nick-channels process old-nick)))
2c8abe90
AS
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))
bd43c990
RS
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
adf794e4
EZ
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)))))
bd43c990 2159 ;; remove old nick and add new one
adf794e4 2160 (with-rcirc-process-buffer process
bd43c990
RS
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)
adf794e4 2167 (rcirc-update-prompt t)
bd43c990
RS
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
a2524d26
EZ
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
bd43c990
RS
2199(defun rcirc-handler-332 (process sender args text)
2200 "RPL_TOPIC"
adf794e4
EZ
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)))))
bd43c990
RS
2205
2206(defun rcirc-handler-333 (process sender args text)
2207 "Not in rfc1459.txt"
adf794e4
EZ
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))))))
bd43c990
RS
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)
db58efbf
EZ
2233 (when (rcirc-get-buffer process nick)
2234 (rcirc-print process sender "MODE" nick msg)))
adf794e4 2235 (cddr args))))
bd43c990
RS
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"
adf794e4 2244 (let ((channel (caddr args)))
bd43c990
RS
2245 (mapc (lambda (nick)
2246 (rcirc-put-nick-channel process nick channel))
adf794e4 2247 (split-string (cadddr args) " " t))
bd43c990
RS
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) "`")))
adf794e4 2265 (with-rcirc-process-buffer process
bd43c990
RS
2266 (rcirc-cmd-nick new-nick nil process))))
2267
2268(defun rcirc-authenticate ()
2269 "Send authentication to process associated with current buffer.
db58efbf 2270Passwords are stored in `rcirc-authinfo' (which see)."
bd43c990 2271 (interactive)
a2524d26 2272 (with-rcirc-server-buffer
db58efbf 2273 (dolist (i rcirc-authinfo)
a2524d26
EZ
2274 (let ((process (rcirc-buffer-process))
2275 (server (car i))
db58efbf
EZ
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
a2524d26 2283 process
db58efbf
EZ
2284 (concat
2285 "PRIVMSG nickserv :identify "
2286 (car args))))
2287 ((equal method 'chanserv)
2288 (rcirc-send-string
a2524d26 2289 process
db58efbf
EZ
2290 (concat
2291 "PRIVMSG chanserv :identify "
2292 (cadr args) " " (car args))))
2293 ((equal method 'bitlbee)
2294 (rcirc-send-string
a2524d26 2295 process
db58efbf
EZ
2296 (concat "PRIVMSG &bitlbee :identify " (car args))))
2297 (t
2298 (message "No %S authentication method defined"
2299 method))))))))
adf794e4 2300
bd43c990
RS
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))
bd43c990
RS
2311 (handler (intern-soft (concat "rcirc-handler-ctcp-" request))))
2312 (if (not (fboundp handler))
db58efbf
EZ
2313 (rcirc-print process sender "ERROR" target
2314 (format "%s sent unsupported ctcp: %s" sender text)
adf794e4 2315 t)
bd43c990
RS
2316 (funcall handler process target sender args)
2317 (if (not (string= request "ACTION"))
db58efbf 2318 (rcirc-print process sender "CTCP" target
adf794e4 2319 (format "%s" text) t))))))
bd43c990
RS
2320
2321(defun rcirc-handler-ctcp-VERSION (process target sender args)
2322 (rcirc-send-string process
db58efbf 2323 (concat "NOTICE " sender
adf794e4 2324 " :\C-aVERSION " rcirc-id-string
bd43c990
RS
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
db58efbf 2332 (concat "NOTICE " sender
bd43c990 2333 " :\C-aTIME " (current-time-string) "\C-a")))
adf794e4
EZ
2334
2335(defun rcirc-handler-CTCP-response (process target sender message)
2336 (rcirc-print process sender "CTCP" nil message t))
bd43c990 2337\f
adf794e4
EZ
2338(defgroup rcirc-faces nil
2339 "Faces for rcirc."
2340 :group 'rcirc
2341 :group 'faces)
2342
ad8121fe
EZ
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)))
adf794e4
EZ
2350 "The face used to highlight my messages."
2351 :group 'rcirc-faces)
bd43c990 2352
ad8121fe
EZ
2353(defface rcirc-other-nick ; font-lock-variable-name-face
2354 '((((class grayscale) (background light))
2355 (:foreground "Gray90" :weight bold :slant italic))
bd43c990 2356 (((class grayscale) (background dark))
ad8121fe
EZ
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)))
adf794e4
EZ
2364 "The face used to highlight other messages."
2365 :group 'rcirc-faces)
bd43c990 2366
02f47e86
MB
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)))
f8db61b2 2378 "Face used for nicks matched by `rcirc-bright-nicks'."
02f47e86
MB
2379 :group 'rcirc-faces)
2380
2381(defface rcirc-dim-nick
2382 '((t :inherit default))
f8db61b2 2383 "Face used for nicks in `rcirc-dim-nicks'."
02f47e86
MB
2384 :group 'rcirc-faces)
2385
ad8121fe
EZ
2386(defface rcirc-server ; font-lock-comment-face
2387 '((((class grayscale) (background light))
2388 (:foreground "DimGray" :weight bold :slant italic))
bd43c990 2389 (((class grayscale) (background dark))
ad8121fe
EZ
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)))
adf794e4
EZ
2404 "The face used to highlight server messages."
2405 :group 'rcirc-faces)
bd43c990 2406
ad8121fe 2407(defface rcirc-server-prefix ; font-lock-comment-delimiter-face
db58efbf 2408 '((default :inherit rcirc-server)
ad8121fe
EZ
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)))
f8db61b2 2432 "The face used to highlight instances of your nick within messages."
adf794e4 2433 :group 'rcirc-faces)
bd43c990 2434
f8db61b2
EZ
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."
92c4adc1 2438 :group 'rcirc-faces)
f8db61b2 2439
ad8121fe
EZ
2440(defface rcirc-prompt ; comint-highlight-prompt
2441 '((((min-colors 88) (background dark)) (:foreground "cyan1"))
2442 (((background dark)) (:foreground "cyan"))
bd43c990 2443 (t (:foreground "dark blue")))
2e398771 2444 "The face used to highlight prompts."
adf794e4 2445 :group 'rcirc-faces)
bd43c990 2446
f8db61b2 2447(defface rcirc-track-nick
8216fbaf
EZ
2448 '((((type tty)) (:inherit default))
2449 (t (:inverse-video t)))
f8db61b2
EZ
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
bd43c990 2459 '((t (:bold t)))
f8db61b2
EZ
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."
adf794e4 2466 :group 'rcirc-faces)
a2524d26 2467
bd43c990 2468\f
adf794e4 2469;; When using M-x flyspell-mode, only check words after the prompt
bd43c990
RS
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)
e636ae15
MB
2477
2478;; arch-tag: b471b7e8-6b5a-4399-b2c6-a3c78dfc8ffb
bd43c990 2479;;; rcirc.el ends here