Replace version 24.2 with 24.3 where appropriate (hopefully)
[bpt/emacs.git] / lisp / net / rcirc.el
CommitLineData
bd43c990
RS
1;;; rcirc.el --- default, simple IRC client.
2
e1ac4066 3;; Copyright (C) 2005-2012 Free Software Foundation, Inc.
bd43c990 4
9cf56b2c
DD
5;; Author: Ryan Yeske <rcyeske@gmail.com>
6;; Maintainers: Ryan Yeske <rcyeske@gmail.com>,
b87a8200 7;; Deniz Dogan <deniz@dogan.se>
bd43c990
RS
8;; Keywords: comm
9
b71cef5c 10;; This file is part of GNU Emacs.
bd43c990 11
874a927a 12;; GNU Emacs is free software: you can redistribute it and/or modify
bd43c990 13;; it under the terms of the GNU General Public License as published by
874a927a
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
bd43c990 16
874a927a 17;; GNU Emacs is distributed in the hope that it will be useful,
bd43c990 18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
92c4adc1 19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
bd43c990
RS
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
874a927a 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
bd43c990
RS
24
25;;; Commentary:
26
adf794e4
EZ
27;; Internet Relay Chat (IRC) is a form of instant communication over
28;; the Internet. It is mainly designed for group (many-to-many)
29;; communication in discussion forums called channels, but also allows
30;; one-to-one communication.
31
fffa137c 32;; Rcirc has simple defaults and clear and consistent behavior.
37269466 33;; Message arrival timestamps, activity notification on the mode line,
adf794e4
EZ
34;; message filling, nick completion, and keepalive pings are all
35;; enabled by default, but can easily be adjusted or turned off. Each
36;; discussion takes place in its own buffer and there is a single
37;; server buffer per connection.
bd43c990 38
bd43c990
RS
39;; Open a new irc connection with:
40;; M-x irc RET
41
7faa3f8c
MB
42;;; Todo:
43
bd43c990
RS
44;;; Code:
45
46(require 'ring)
47(require 'time-date)
48(eval-when-compile (require 'cl))
49
adf794e4
EZ
50(defgroup rcirc nil
51 "Simple IRC client."
52 :version "22.1"
2fbed782 53 :prefix "rcirc-"
e8f10ddb 54 :link '(custom-manual "(rcirc)")
adf794e4
EZ
55 :group 'applications)
56
0ffab1eb 57(defcustom rcirc-server-alist
488086f4
SM
58 '(("irc.freenode.net" :channels ("#rcirc")
59 ;; Don't use the TLS port by default, in case gnutls is not available.
60 ;; :port 7000 :encryption tls
61 ))
195eca78
SM
62 "An alist of IRC connections to establish when running `rcirc'.
63Each element looks like (SERVER-NAME PARAMETERS).
64
65SERVER-NAME is a string describing the server to connect
66to.
67
0ffab1eb
TTN
68The optional PARAMETERS come in pairs PARAMETER VALUE.
69
70The following parameters are recognized:
71
72`:nick'
73
74VALUE must be a string. If absent, `rcirc-default-nick' is used
75for this connection.
76
77`:port'
78
79VALUE must be a number or string. If absent,
80`rcirc-default-port' is used.
81
82`:user-name'
83
84VALUE must be a string. If absent, `rcirc-default-user-name' is
85used.
86
a71832f7
SM
87`:password'
88
89VALUE must be a string. If absent, no PASS command will be sent
90to the server.
91
0ffab1eb
TTN
92`:full-name'
93
94VALUE must be a string. If absent, `rcirc-default-full-name' is
95used.
96
97`:channels'
98
99VALUE must be a list of strings describing which channels to join
100when connecting to this server. If absent, no channels will be
488086f4
SM
101connected to automatically.
102
103`:encryption'
104
105VALUE must be `plain' (the default) for unencrypted connections, or `tls'
106for connections using SSL/TLS."
0ffab1eb 107 :type '(alist :key-type string
488086f4
SM
108 :value-type (plist :options
109 ((:nick string)
110 (:port integer)
111 (:user-name string)
112 (:password string)
113 (:full-name string)
114 (:channels (repeat string))
115 (:encryption (choice (const tls)
116 (const plain))))))
adf794e4 117 :group 'rcirc)
bd43c990 118
a2524d26 119(defcustom rcirc-default-port 6667
adf794e4
EZ
120 "The default port to connect to."
121 :type 'integer
122 :group 'rcirc)
bd43c990 123
a2524d26 124(defcustom rcirc-default-nick (user-login-name)
adf794e4
EZ
125 "Your nick."
126 :type 'string
127 :group 'rcirc)
bd43c990 128
d26781af 129(defcustom rcirc-default-user-name "user"
adf794e4 130 "Your user name sent to the server when connecting."
6169260b 131 :version "24.1" ; changed default
adf794e4
EZ
132 :type 'string
133 :group 'rcirc)
bd43c990 134
d26781af 135(defcustom rcirc-default-full-name "unknown"
adf794e4 136 "The full name sent to the server when connecting."
6169260b 137 :version "24.1" ; changed default
adf794e4
EZ
138 :type 'string
139 :group 'rcirc)
bd43c990 140
adf794e4 141(defcustom rcirc-fill-flag t
fb7ada5f 142 "Non-nil means line-wrap messages printed in channel buffers."
adf794e4
EZ
143 :type 'boolean
144 :group 'rcirc)
bd43c990 145
adf794e4 146(defcustom rcirc-fill-column nil
fb7ada5f 147 "Column beyond which automatic line-wrapping should happen.
195eca78
SM
148If nil, use value of `fill-column'. If 'frame-width, use the
149maximum frame width."
adf794e4
EZ
150 :type '(choice (const :tag "Value of `fill-column'")
151 (const :tag "Full frame width" frame-width)
152 (integer :tag "Number of columns"))
153 :group 'rcirc)
bd43c990 154
adf794e4 155(defcustom rcirc-fill-prefix nil
fb7ada5f 156 "Text to insert before filled lines.
bd43c990 157If nil, calculate the prefix dynamically to line up text
adf794e4
EZ
158underneath each nick."
159 :type '(choice (const :tag "Dynamic" nil)
160 (string :tag "Prefix text"))
161 :group 'rcirc)
bd43c990 162
adf794e4
EZ
163(defvar rcirc-ignore-buffer-activity-flag nil
164 "If non-nil, ignore activity in this buffer.")
165(make-variable-buffer-local 'rcirc-ignore-buffer-activity-flag)
bd43c990 166
a2524d26
EZ
167(defvar rcirc-low-priority-flag nil
168 "If non-nil, activity in this buffer is considered low priority.")
169(make-variable-buffer-local 'rcirc-low-priority-flag)
170
195eca78
SM
171(defvar rcirc-omit-mode nil
172 "Non-nil if Rcirc-Omit mode is enabled.
173Use the command `rcirc-omit-mode' to change this variable.")
174(make-variable-buffer-local 'rcirc-omit-mode)
175
adf794e4 176(defcustom rcirc-time-format "%H:%M "
fb7ada5f 177 "Describes how timestamps are printed.
adf794e4
EZ
178Used as the first arg to `format-time-string'."
179 :type 'string
180 :group 'rcirc)
bd43c990 181
adf794e4 182(defcustom rcirc-input-ring-size 1024
fb7ada5f 183 "Size of input history ring."
adf794e4
EZ
184 :type 'integer
185 :group 'rcirc)
bd43c990 186
adf794e4 187(defcustom rcirc-read-only-flag t
fb7ada5f 188 "Non-nil means make text in IRC buffers read-only."
adf794e4
EZ
189 :type 'boolean
190 :group 'rcirc)
bd43c990 191
adf794e4 192(defcustom rcirc-buffer-maximum-lines nil
fb7ada5f 193 "The maximum size in lines for rcirc buffers.
bd43c990 194Channel buffers are truncated from the top to be no greater than this
92c4adc1 195number. If zero or nil, no truncating is done."
adf794e4
EZ
196 :type '(choice (const :tag "No truncation" nil)
197 (integer :tag "Number of lines"))
198 :group 'rcirc)
bd43c990 199
d40ac716 200(defcustom rcirc-scroll-show-maximum-output t
fb7ada5f 201 "If non-nil, scroll buffer to keep the point at the bottom of
195eca78 202the window."
f8db61b2
EZ
203 :type 'boolean
204 :group 'rcirc)
7faa3f8c 205
db58efbf
EZ
206(defcustom rcirc-authinfo nil
207 "List of authentication passwords.
208Each element of the list is a list with a SERVER-REGEXP string
209and a method symbol followed by method specific arguments.
210
211The valid METHOD symbols are `nickserv', `chanserv' and
212`bitlbee'.
bd43c990 213
d7a0fd6f
GM
214The ARGUMENTS for each METHOD symbol are:
215 `nickserv': NICK PASSWORD [NICKSERV-NICK]
db58efbf
EZ
216 `chanserv': NICK CHANNEL PASSWORD
217 `bitlbee': NICK PASSWORD
77f63d30 218 `quakenet': ACCOUNT PASSWORD
bd43c990 219
d7a0fd6f 220Examples:
db58efbf
EZ
221 ((\"freenode\" nickserv \"bob\" \"p455w0rd\")
222 (\"freenode\" chanserv \"bob\" \"#bobland\" \"passwd99\")
d7a0fd6f 223 (\"bitlbee\" bitlbee \"robert\" \"sekrit\")
77f63d30
DD
224 (\"dal.net\" nickserv \"bob\" \"sekrit\" \"NickServ@services.dal.net\")
225 (\"quakenet.org\" quakenet \"bobby\" \"sekrit\"))"
db58efbf
EZ
226 :type '(alist :key-type (string :tag "Server")
227 :value-type (choice (list :tag "NickServ"
228 (const nickserv)
229 (string :tag "Nick")
230 (string :tag "Password"))
231 (list :tag "ChanServ"
232 (const chanserv)
233 (string :tag "Nick")
234 (string :tag "Channel")
235 (string :tag "Password"))
236 (list :tag "BitlBee"
237 (const bitlbee)
238 (string :tag "Nick")
77f63d30
DD
239 (string :tag "Password"))
240 (list :tag "QuakeNet"
241 (const quakenet)
242 (string :tag "Account")
243 (string :tag "Password"))))
adf794e4 244 :group 'rcirc)
bd43c990 245
db58efbf 246(defcustom rcirc-auto-authenticate-flag t
fb7ada5f 247 "Non-nil means automatically send authentication string to server.
db58efbf 248See also `rcirc-authinfo'."
adf794e4
EZ
249 :type 'boolean
250 :group 'rcirc)
bd43c990 251
72d2c2e3 252(defcustom rcirc-authenticate-before-join t
fb7ada5f 253 "Non-nil means authenticate to services before joining channels.
72d2c2e3
DD
254Currently only works with NickServ on some networks."
255 :version "24.1"
256 :type 'boolean
257 :group 'rcirc)
258
adf794e4 259(defcustom rcirc-prompt "> "
2e398771 260 "Prompt string to use in IRC buffers.
bd43c990
RS
261
262The following replacements are made:
263%n is your nick.
264%s is the server.
265%t is the buffer target, a channel or a user.
266
adf794e4
EZ
267Setting this alone will not affect the prompt;
268use either M-x customize or also call `rcirc-update-prompt'."
269 :type 'string
270 :set 'rcirc-set-changed
271 :initialize 'custom-initialize-default
272 :group 'rcirc)
273
f8db61b2
EZ
274(defcustom rcirc-keywords nil
275 "List of keywords to highlight in message text."
276 :type '(repeat string)
277 :group 'rcirc)
278
2c8abe90
AS
279(defcustom rcirc-ignore-list ()
280 "List of ignored nicks.
281Use /ignore to list them, use /ignore NICK to add or remove a nick."
282 :type '(repeat string)
283 :group 'rcirc)
284
285(defvar rcirc-ignore-list-automatic ()
286 "List of ignored nicks added to `rcirc-ignore-list' because of renaming.
287When an ignored person renames, their nick is added to both lists.
288Nicks will be removed from the automatic list on follow-up renamings or
289parts.")
290
f8db61b2
EZ
291(defcustom rcirc-bright-nicks nil
292 "List of nicks to be emphasized.
02f47e86 293See `rcirc-bright-nick' face."
f8db61b2 294 :type '(repeat string)
02f47e86
MB
295 :group 'rcirc)
296
f8db61b2
EZ
297(defcustom rcirc-dim-nicks nil
298 "List of nicks to be deemphasized.
02f47e86 299See `rcirc-dim-nick' face."
f8db61b2 300 :type '(repeat string)
02f47e86
MB
301 :group 'rcirc)
302
adf794e4
EZ
303(defcustom rcirc-print-hooks nil
304 "Hook run after text is printed.
305Called with 5 arguments, PROCESS, SENDER, RESPONSE, TARGET and TEXT."
306 :type 'hook
307 :group 'rcirc)
bd43c990 308
72d2c2e3
DD
309(defvar rcirc-authenticated-hook nil
310 "Hook run after successfully authenticated.")
311
db58efbf
EZ
312(defcustom rcirc-always-use-server-buffer-flag nil
313 "Non-nil means messages without a channel target will go to the server buffer."
314 :type 'boolean
315 :group 'rcirc)
316
108bf785 317(defcustom rcirc-decode-coding-system 'utf-8
5ab33f2b 318 "Coding system used to decode incoming irc messages.
108bf785
LL
319Set to 'undecided if you want the encoding of the incoming
320messages autodetected."
a2524d26
EZ
321 :type 'coding-system
322 :group 'rcirc)
323
324(defcustom rcirc-encode-coding-system 'utf-8
325 "Coding system used to encode outgoing irc messages."
326 :type 'coding-system
327 :group 'rcirc)
328
329(defcustom rcirc-coding-system-alist nil
f8db61b2 330 "Alist to decide a coding system to use for a channel I/O operation.
a2524d26
EZ
331The format is ((PATTERN . VAL) ...).
332PATTERN is either a string or a cons of strings.
333If PATTERN is a string, it is used to match a target.
334If PATTERN is a cons of strings, the car part is used to match a
335target, and the cdr part is used to match a server.
336VAL is either a coding system or a cons of coding systems.
337If VAL is a coding system, it is used for both decoding and encoding
338messages.
339If VAL is a cons of coding systems, the car part is used for decoding,
340and the cdr part is used for encoding."
341 :type '(alist :key-type (choice (string :tag "Channel Regexp")
342 (cons (string :tag "Channel Regexp")
343 (string :tag "Server Regexp")))
344 :value-type (choice coding-system
345 (cons (coding-system :tag "Decode")
346 (coding-system :tag "Encode"))))
347 :group 'rcirc)
348
349(defcustom rcirc-multiline-major-mode 'fundamental-mode
350 "Major-mode function to use in multiline edit buffers."
351 :type 'function
352 :group 'rcirc)
353
2a4466ca
DD
354(defcustom rcirc-nick-completion-format "%s: "
355 "Format string to use in nick completions.
356
357The format string is only used when completing at the beginning
358of a line. The string is passed as the first argument to
359`format' with the nickname as the second argument."
2d7d6439 360 :version "24.1"
2a4466ca
DD
361 :type 'string
362 :group 'rcirc)
363
a63067fc
DD
364(defcustom rcirc-kill-channel-buffers nil
365 "When non-nil, kill channel buffers when the server buffer is killed.
366Only the channel buffers associated with the server in question
367will be killed."
2a1e2476 368 :version "24.3"
a63067fc
DD
369 :type 'boolean
370 :group 'rcirc)
371
a2524d26
EZ
372(defvar rcirc-nick nil)
373
bd43c990
RS
374(defvar rcirc-prompt-start-marker nil)
375(defvar rcirc-prompt-end-marker nil)
376
377(defvar rcirc-nick-table nil)
378
a0a5c583
GM
379(defvar rcirc-recent-quit-alist nil
380 "Alist of nicks that have recently quit or parted the channel.")
381
2c8abe90
AS
382(defvar rcirc-nick-syntax-table
383 (let ((table (make-syntax-table text-mode-syntax-table)))
384 (mapc (lambda (c) (modify-syntax-entry c "w" table))
385 "[]\\`_^{|}-")
386 (modify-syntax-entry ?' "_" table)
387 table)
388 "Syntax table which includes all nick characters as word constituents.")
389
adf794e4
EZ
390;; each process has an alist of (target . buffer) pairs
391(defvar rcirc-buffer-alist nil)
392
bd43c990 393(defvar rcirc-activity nil
a2524d26 394 "List of buffers with unviewed activity.")
bd43c990
RS
395
396(defvar rcirc-activity-string ""
37269466 397 "String displayed in mode line representing `rcirc-activity'.")
bd43c990
RS
398(put 'rcirc-activity-string 'risky-local-variable t)
399
a2524d26
EZ
400(defvar rcirc-server-buffer nil
401 "The server buffer associated with this channel buffer.")
bd43c990
RS
402
403(defvar rcirc-target nil
404 "The channel or user associated with this buffer.")
405
bd43c990
RS
406(defvar rcirc-urls nil
407 "List of urls seen in the current buffer.")
7faa3f8c 408(put 'rcirc-urls 'permanent-local t)
bd43c990 409
2e875089 410(defvar rcirc-timeout-seconds 600
8216fbaf 411 "Kill connection after this many seconds if there is no activity.")
bd43c990 412
adf794e4 413(defconst rcirc-id-string (concat "rcirc on GNU Emacs " emacs-version))
bd43c990 414\f
bd43c990 415(defvar rcirc-startup-channels nil)
195eca78 416
c0db3477
CY
417(defvar rcirc-server-name-history nil
418 "History variable for \\[rcirc] call.")
419
420(defvar rcirc-server-port-history nil
421 "History variable for \\[rcirc] call.")
422
423(defvar rcirc-nick-name-history nil
424 "History variable for \\[rcirc] call.")
425
d26781af
RY
426(defvar rcirc-user-name-history nil
427 "History variable for \\[rcirc] call.")
428
bd43c990 429;;;###autoload
db58efbf 430(defun rcirc (arg)
0ffab1eb 431 "Connect to all servers in `rcirc-server-alist'.
195eca78
SM
432
433Do not connect to a server if it is already connected.
434
435If ARG is non-nil, instead prompt for connection parameters."
db58efbf
EZ
436 (interactive "P")
437 (if arg
0ffab1eb
TTN
438 (let* ((server (completing-read "IRC Server: "
439 rcirc-server-alist
195eca78 440 nil nil
c0db3477
CY
441 (caar rcirc-server-alist)
442 'rcirc-server-name-history))
0ffab1eb
TTN
443 (server-plist (cdr (assoc-string server rcirc-server-alist)))
444 (port (read-string "IRC Port: "
195eca78 445 (number-to-string
d8937064 446 (or (plist-get server-plist :port)
c0db3477
CY
447 rcirc-default-port))
448 'rcirc-server-port-history))
195eca78 449 (nick (read-string "IRC Nick: "
d8937064 450 (or (plist-get server-plist :nick)
c0db3477
CY
451 rcirc-default-nick)
452 'rcirc-nick-name-history))
d26781af
RY
453 (user-name (read-string "IRC Username: "
454 (or (plist-get server-plist :user-name)
455 rcirc-default-user-name)
456 'rcirc-user-name-history))
a71832f7
SM
457 (password (read-passwd "IRC Password: " nil
458 (plist-get server-plist :password)))
db58efbf
EZ
459 (channels (split-string
460 (read-string "IRC Channels: "
0ffab1eb 461 (mapconcat 'identity
195eca78 462 (plist-get server-plist
d8937064 463 :channels)
195eca78 464 " "))
488086f4 465 "[, ]+" t))
ac09b8a1 466 (encryption (rcirc-prompt-for-encryption server-plist)))
a71832f7 467 (rcirc-connect server port nick user-name
0ffab1eb 468 rcirc-default-full-name
488086f4 469 channels password encryption))
0ffab1eb 470 ;; connect to servers in `rcirc-server-alist'
195eca78 471 (let (connected-servers)
0ffab1eb 472 (dolist (c rcirc-server-alist)
195eca78 473 (let ((server (car c))
0ffab1eb
TTN
474 (nick (or (plist-get (cdr c) :nick) rcirc-default-nick))
475 (port (or (plist-get (cdr c) :port) rcirc-default-port))
476 (user-name (or (plist-get (cdr c) :user-name)
195eca78 477 rcirc-default-user-name))
0ffab1eb
TTN
478 (full-name (or (plist-get (cdr c) :full-name)
479 rcirc-default-full-name))
a71832f7 480 (channels (plist-get (cdr c) :channels))
488086f4 481 (password (plist-get (cdr c) :password))
d64a438f
LL
482 (encryption (plist-get (cdr c) :encryption))
483 contact)
195eca78
SM
484 (when server
485 (let (connected)
486 (dolist (p (rcirc-process-list))
487 (when (string= server (process-name p))
488 (setq connected p)))
489 (if (not connected)
490 (condition-case e
a71832f7 491 (rcirc-connect server port nick user-name
488086f4 492 full-name channels password encryption)
0ffab1eb 493 (quit (message "Quit connecting to %s" server)))
195eca78 494 (with-current-buffer (process-buffer connected)
d64a438f
LL
495 (setq contact (process-contact
496 (get-buffer-process (current-buffer)) :host))
497 (setq connected-servers
498 (cons (if (stringp contact) contact server)
499 connected-servers))))))))
195eca78
SM
500 (when connected-servers
501 (message "Already connected to %s"
a0a5c583
GM
502 (if (cdr connected-servers)
503 (concat (mapconcat 'identity (butlast connected-servers) ", ")
504 ", and "
505 (car (last connected-servers)))
506 (car connected-servers)))))))
195eca78 507
bd43c990
RS
508;;;###autoload
509(defalias 'irc 'rcirc)
510
511\f
512(defvar rcirc-process-output nil)
bd43c990
RS
513(defvar rcirc-topic nil)
514(defvar rcirc-keepalive-timer nil)
ad8121fe 515(defvar rcirc-last-server-message-time nil)
8216fbaf
EZ
516(defvar rcirc-server nil) ; server provided by server
517(defvar rcirc-server-name nil) ; server name given by 001 response
518(defvar rcirc-timeout-timer nil)
9882e214 519(defvar rcirc-user-authenticated nil)
8216fbaf
EZ
520(defvar rcirc-user-disconnect nil)
521(defvar rcirc-connecting nil)
522(defvar rcirc-process nil)
8d214091
RF
523
524;;;###autoload
a71832f7 525(defun rcirc-connect (server &optional port nick user-name
488086f4 526 full-name startup-channels password encryption)
bd43c990
RS
527 (save-excursion
528 (message "Connecting to %s..." server)
529 (let* ((inhibit-eol-conversion)
2fbed782
EZ
530 (port-number (if port
531 (if (stringp port)
532 (string-to-number port)
533 port)
a2524d26 534 rcirc-default-port))
a2524d26
EZ
535 (nick (or nick rcirc-default-nick))
536 (user-name (or user-name rcirc-default-user-name))
0ffab1eb 537 (full-name (or full-name rcirc-default-full-name))
a2524d26 538 (startup-channels startup-channels)
488086f4
SM
539 (process (open-network-stream
540 server nil server port-number
541 :type (or encryption 'plain))))
bd43c990
RS
542 ;; set up process
543 (set-process-coding-system process 'raw-text 'raw-text)
adf794e4 544 (switch-to-buffer (rcirc-generate-new-buffer-name process nil))
bd43c990 545 (set-process-buffer process (current-buffer))
bd43c990 546 (rcirc-mode process nil)
a2524d26
EZ
547 (set-process-sentinel process 'rcirc-sentinel)
548 (set-process-filter process 'rcirc-filter)
488086f4
SM
549
550 (set (make-local-variable 'rcirc-process) process)
551 (set (make-local-variable 'rcirc-server) server)
552 (set (make-local-variable 'rcirc-server-name) server) ; Update when we get 001 response.
553 (set (make-local-variable 'rcirc-buffer-alist) nil)
554 (set (make-local-variable 'rcirc-nick-table)
555 (make-hash-table :test 'equal))
556 (set (make-local-variable 'rcirc-nick) nick)
557 (set (make-local-variable 'rcirc-process-output) nil)
558 (set (make-local-variable 'rcirc-startup-channels) startup-channels)
559 (set (make-local-variable 'rcirc-last-server-message-time)
560 (current-time))
561
562 (set (make-local-variable 'rcirc-timeout-timer) nil)
563 (set (make-local-variable 'rcirc-user-disconnect) nil)
564 (set (make-local-variable 'rcirc-user-authenticated) nil)
565 (set (make-local-variable 'rcirc-connecting) t)
bd43c990 566
195eca78
SM
567 (add-hook 'auto-save-hook 'rcirc-log-write)
568
bd43c990 569 ;; identify
fa7062f6 570 (unless (zerop (length password))
a71832f7 571 (rcirc-send-string process (concat "PASS " password)))
bd43c990
RS
572 (rcirc-send-string process (concat "NICK " nick))
573 (rcirc-send-string process (concat "USER " user-name
d26781af 574 " 0 * :" full-name))
bd43c990
RS
575
576 ;; setup ping timer if necessary
8216fbaf
EZ
577 (unless rcirc-keepalive-timer
578 (setq rcirc-keepalive-timer
579 (run-at-time 0 (/ rcirc-timeout-seconds 2) 'rcirc-keepalive)))
bd43c990
RS
580
581 (message "Connecting to %s...done" server)
582
583 ;; return process object
584 process)))
585
adf794e4
EZ
586(defmacro with-rcirc-process-buffer (process &rest body)
587 (declare (indent 1) (debug t))
588 `(with-current-buffer (process-buffer ,process)
589 ,@body))
590
a2524d26
EZ
591(defmacro with-rcirc-server-buffer (&rest body)
592 (declare (indent 0) (debug t))
593 `(with-current-buffer rcirc-server-buffer
594 ,@body))
595
73057ba9
DD
596(defun rcirc-float-time ()
597 (if (featurep 'xemacs)
598 (time-to-seconds (current-time))
599 (float-time)))
600
ac09b8a1
DD
601(defun rcirc-prompt-for-encryption (server-plist)
602 "Prompt the user for the encryption method to use.
603SERVER-PLIST is the property list for the server."
604 (let ((msg "Encryption (default %s): ")
605 (choices '("plain" "tls"))
606 (default (or (plist-get server-plist :encryption)
7e821d0d 607 'plain)))
ac09b8a1
DD
608 (intern
609 (completing-read (format msg default)
7e821d0d 610 choices nil t nil nil (symbol-name default)))))
ac09b8a1 611
bd43c990 612(defun rcirc-keepalive ()
ad8121fe
EZ
613 "Send keep alive pings to active rcirc processes.
614Kill processes that have not received a server message since the
615last ping."
bd43c990
RS
616 (if (rcirc-process-list)
617 (mapc (lambda (process)
8216fbaf
EZ
618 (with-rcirc-process-buffer process
619 (when (not rcirc-connecting)
1be1d1e9
DD
620 (rcirc-send-ctcp process
621 rcirc-nick
622 (format "KEEPALIVE %f"
73057ba9 623 (rcirc-float-time))))))
bd43c990 624 (rcirc-process-list))
8216fbaf 625 ;; no processes, clean up timer
bd43c990
RS
626 (cancel-timer rcirc-keepalive-timer)
627 (setq rcirc-keepalive-timer nil)))
628
195eca78
SM
629(defun rcirc-handler-ctcp-KEEPALIVE (process target sender message)
630 (with-rcirc-process-buffer process
73057ba9 631 (setq header-line-format (format "%f" (- (rcirc-float-time)
195eca78
SM
632 (string-to-number message))))))
633
3767e706 634(defvar rcirc-debug-buffer "*rcirc debug*")
adf794e4
EZ
635(defvar rcirc-debug-flag nil
636 "If non-nil, write information to `rcirc-debug-buffer'.")
637(defun rcirc-debug (process text)
bd43c990 638 "Add an entry to the debug log including PROCESS and TEXT.
2e398771 639Debug text is written to `rcirc-debug-buffer' if `rcirc-debug-flag'
adf794e4
EZ
640is non-nil."
641 (when rcirc-debug-flag
9a529312 642 (with-current-buffer (get-buffer-create rcirc-debug-buffer)
195eca78
SM
643 (goto-char (point-max))
644 (insert (concat
645 "["
646 (format-time-string "%Y-%m-%dT%T ") (process-name process)
647 "] "
648 text)))))
adf794e4 649
bd43c990
RS
650(defvar rcirc-sentinel-hooks nil
651 "Hook functions called when the process sentinel is called.
652Functions are called with PROCESS and SENTINEL arguments.")
653
654(defun rcirc-sentinel (process sentinel)
655 "Called when PROCESS receives SENTINEL."
656 (let ((sentinel (replace-regexp-in-string "\n" "" sentinel)))
adf794e4
EZ
657 (rcirc-debug process (format "SENTINEL: %S %S\n" process sentinel))
658 (with-rcirc-process-buffer process
659 (dolist (buffer (cons nil (mapcar 'cdr rcirc-buffer-alist)))
adf794e4 660 (with-current-buffer (or buffer (current-buffer))
db58efbf
EZ
661 (rcirc-print process "rcirc.el" "ERROR" rcirc-target
662 (format "%s: %s (%S)"
663 (process-name process)
664 sentinel
8216fbaf 665 (process-status process)) (not rcirc-target))
195eca78 666 (rcirc-disconnect-buffer)))
8216fbaf 667 (run-hook-with-args 'rcirc-sentinel-hooks process sentinel))))
bd43c990 668
195eca78
SM
669(defun rcirc-disconnect-buffer (&optional buffer)
670 (with-current-buffer (or buffer (current-buffer))
671 ;; set rcirc-target to nil for each channel so cleanup
fe7a3057 672 ;; doesn't happen when we reconnect
195eca78 673 (setq rcirc-target nil)
0ffab1eb 674 (setq mode-line-process ":disconnected")))
195eca78 675
bd43c990
RS
676(defun rcirc-process-list ()
677 "Return a list of rcirc processes."
678 (let (ps)
679 (mapc (lambda (p)
18aa2c90 680 (when (buffer-live-p (process-buffer p))
adf794e4 681 (with-rcirc-process-buffer p
bd43c990
RS
682 (when (eq major-mode 'rcirc-mode)
683 (setq ps (cons p ps))))))
684 (process-list))
685 ps))
686
687(defvar rcirc-receive-message-hooks nil
2e398771
JB
688 "Hook functions run when a message is received from server.
689Function is called with PROCESS, COMMAND, SENDER, ARGS and LINE.")
bd43c990
RS
690(defun rcirc-filter (process output)
691 "Called when PROCESS receives OUTPUT."
adf794e4 692 (rcirc-debug process output)
8216fbaf 693 (rcirc-reschedule-timeout process)
adf794e4 694 (with-rcirc-process-buffer process
ad8121fe 695 (setq rcirc-last-server-message-time (current-time))
bd43c990
RS
696 (setq rcirc-process-output (concat rcirc-process-output output))
697 (when (= (aref rcirc-process-output
698 (1- (length rcirc-process-output))) ?\n)
699 (mapc (lambda (line)
700 (rcirc-process-server-response process line))
adf794e4 701 (split-string rcirc-process-output "[\n\r]" t))
bd43c990
RS
702 (setq rcirc-process-output nil))))
703
8216fbaf
EZ
704(defun rcirc-reschedule-timeout (process)
705 (with-rcirc-process-buffer process
706 (when (not rcirc-connecting)
707 (with-rcirc-process-buffer process
708 (when rcirc-timeout-timer (cancel-timer rcirc-timeout-timer))
709 (setq rcirc-timeout-timer (run-at-time rcirc-timeout-seconds nil
710 'rcirc-delete-process
711 process))))))
712
713(defun rcirc-delete-process (process)
8216fbaf
EZ
714 (delete-process process))
715
adf794e4 716(defvar rcirc-trap-errors-flag t)
bd43c990 717(defun rcirc-process-server-response (process text)
adf794e4 718 (if rcirc-trap-errors-flag
bd43c990
RS
719 (condition-case err
720 (rcirc-process-server-response-1 process text)
721 (error
722 (rcirc-print process "RCIRC" "ERROR" nil
adf794e4 723 (format "\"%s\" %s" text err) t)))
bd43c990
RS
724 (rcirc-process-server-response-1 process text)))
725
726(defun rcirc-process-server-response-1 (process text)
727 (if (string-match "^\\(:\\([^ ]+\\) \\)?\\([^ ]+\\) \\(.+\\)$" text)
db58efbf
EZ
728 (let* ((user (match-string 2 text))
729 (sender (rcirc-user-nick user))
bd43c990
RS
730 (cmd (match-string 3 text))
731 (args (match-string 4 text))
732 (handler (intern-soft (concat "rcirc-handler-" cmd))))
733 (string-match "^\\([^:]*\\):?\\(.+\\)?$" args)
734 (let* ((args1 (match-string 1 args))
735 (args2 (match-string 2 args))
adf794e4
EZ
736 (args (delq nil (append (split-string args1 " " t)
737 (list args2)))))
bd43c990
RS
738 (if (not (fboundp handler))
739 (rcirc-handler-generic process cmd sender args text)
740 (funcall handler process sender args text))
741 (run-hook-with-args 'rcirc-receive-message-hooks
742 process cmd sender args text)))
743 (message "UNHANDLED: %s" text)))
744
f8db61b2
EZ
745(defvar rcirc-responses-no-activity '("305" "306")
746 "Responses that don't trigger activity in the mode-line indicator.")
747
748(defun rcirc-handler-generic (process response sender args text)
bd43c990 749 "Generic server response handler."
f8db61b2
EZ
750 (rcirc-print process sender response nil
751 (mapconcat 'identity (cdr args) " ")
752 (not (member response rcirc-responses-no-activity))))
bd43c990 753
488086f4
SM
754(defun rcirc--connection-open-p (process)
755 (memq (process-status process) '(run open)))
756
bd43c990
RS
757(defun rcirc-send-string (process string)
758 "Send PROCESS a STRING plus a newline."
a2524d26 759 (let ((string (concat (encode-coding-string string rcirc-encode-coding-system)
bd43c990 760 "\n")))
488086f4 761 (unless (rcirc--connection-open-p process)
53f831f3 762 (error "Network connection to %s is not open"
a2524d26 763 (process-name process)))
adf794e4 764 (rcirc-debug process string)
bd43c990
RS
765 (process-send-string process string)))
766
1be1d1e9
DD
767(defun rcirc-send-privmsg (process target string)
768 (rcirc-send-string process (format "PRIVMSG %s :%s" target string)))
769
770(defun rcirc-send-ctcp (process target request &optional args)
771 (let ((args (if args (concat " " args) "")))
772 (rcirc-send-privmsg process target
5708ce5e 773 (format "\C-a%s%s\C-a" request args))))
1be1d1e9 774
a2524d26
EZ
775(defun rcirc-buffer-process (&optional buffer)
776 "Return the process associated with channel BUFFER.
777With no argument or nil as argument, use the current buffer."
8216fbaf
EZ
778 (or (get-buffer-process (if buffer
779 (with-current-buffer buffer
780 rcirc-server-buffer)
781 rcirc-server-buffer))
782 rcirc-process))
a2524d26
EZ
783
784(defun rcirc-server-name (process)
785 "Return PROCESS server name, given by the 001 response."
adf794e4 786 (with-rcirc-process-buffer process
195eca78
SM
787 (or rcirc-server-name
788 (warn "server name for process %S unknown" process))))
bd43c990
RS
789
790(defun rcirc-nick (process)
791 "Return PROCESS nick."
92c4adc1 792 (with-rcirc-process-buffer process
a2524d26
EZ
793 (or rcirc-nick rcirc-default-nick)))
794
795(defun rcirc-buffer-nick (&optional buffer)
796 "Return the nick associated with BUFFER.
797With no argument or nil as argument, use the current buffer."
798 (with-current-buffer (or buffer (current-buffer))
799 (with-current-buffer rcirc-server-buffer
800 (or rcirc-nick rcirc-default-nick))))
bd43c990 801
02f47e86 802(defvar rcirc-max-message-length 420
bd43c990
RS
803 "Messages longer than this value will be split.")
804
195eca78 805(defun rcirc-send-message (process target message &optional noticep silent)
bd43c990 806 "Send TARGET associated with PROCESS a privmsg with text MESSAGE.
195eca78
SM
807If NOTICEP is non-nil, send a notice instead of privmsg.
808If SILENT is non-nil, do not print the message in any irc buffer."
bd43c990
RS
809 ;; max message length is 512 including CRLF
810 (let* ((response (if noticep "NOTICE" "PRIVMSG"))
811 (oversize (> (length message) rcirc-max-message-length))
812 (text (if oversize
813 (substring message 0 rcirc-max-message-length)
814 message))
815 (text (if (string= text "")
816 " "
817 text))
818 (more (if oversize
819 (substring message rcirc-max-message-length))))
db58efbf 820 (rcirc-get-buffer-create process target)
bd43c990 821 (rcirc-send-string process (concat response " " target " :" text))
195eca78
SM
822 (unless silent
823 (rcirc-print process (rcirc-nick process) response target text))
db58efbf 824 (when more (rcirc-send-message process target more noticep))))
bd43c990
RS
825
826(defvar rcirc-input-ring nil)
827(defvar rcirc-input-ring-index 0)
27de4e20 828
bd43c990
RS
829(defun rcirc-prev-input-string (arg)
830 (ring-ref rcirc-input-ring (+ rcirc-input-ring-index arg)))
831
27de4e20
DD
832(defun rcirc-insert-prev-input ()
833 (interactive)
bd43c990
RS
834 (when (<= rcirc-prompt-end-marker (point))
835 (delete-region rcirc-prompt-end-marker (point-max))
836 (insert (rcirc-prev-input-string 0))
837 (setq rcirc-input-ring-index (1+ rcirc-input-ring-index))))
838
27de4e20
DD
839(defun rcirc-insert-next-input ()
840 (interactive)
bd43c990
RS
841 (when (<= rcirc-prompt-end-marker (point))
842 (delete-region rcirc-prompt-end-marker (point-max))
843 (setq rcirc-input-ring-index (1- rcirc-input-ring-index))
844 (insert (rcirc-prev-input-string -1))))
845
94c7243b
LL
846(defvar rcirc-server-commands
847 '("/admin" "/away" "/connect" "/die" "/error" "/info"
848 "/invite" "/ison" "/join" "/kick" "/kill" "/links"
849 "/list" "/lusers" "/mode" "/motd" "/names" "/nick"
850 "/notice" "/oper" "/part" "/pass" "/ping" "/pong"
851 "/privmsg" "/quit" "/rehash" "/restart" "/service" "/servlist"
852 "/server" "/squery" "/squit" "/stats" "/summon" "/time"
853 "/topic" "/trace" "/user" "/userhost" "/users" "/version"
854 "/wallops" "/who" "/whois" "/whowas")
855 "A list of user commands by IRC server.
856The value defaults to RFCs 1459 and 2812.")
857
858;; /me and /ctcp are not defined by `defun-rcirc-command'.
859(defvar rcirc-client-commands '("/me" "/ctcp")
860 "A list of user commands defined by IRC client rcirc.
861The list is updated automatically by `defun-rcirc-command'.")
862
863(defun rcirc-completion-at-point ()
864 "Function used for `completion-at-point-functions' in `rcirc-mode'."
0b4e93f1
LL
865 (and (rcirc-looking-at-input)
866 (let* ((beg (save-excursion
867 (if (re-search-backward " " rcirc-prompt-end-marker t)
868 (1+ (point))
869 rcirc-prompt-end-marker)))
870 (table (if (and (= beg rcirc-prompt-end-marker)
871 (eq (char-after beg) ?/))
872 (delete-dups
873 (nconc (sort (copy-sequence rcirc-client-commands)
874 'string-lessp)
875 (sort (copy-sequence rcirc-server-commands)
876 'string-lessp)))
877 (rcirc-channel-nicks (rcirc-buffer-process)
878 rcirc-target))))
879 (list beg (point) table))))
94c7243b
LL
880
881(defvar rcirc-completions nil)
882(defvar rcirc-completion-start nil)
883
884(defun rcirc-complete ()
885 "Cycle through completions from list of nicks in channel or IRC commands.
886IRC command completion is performed only if '/' is the first input char."
bd43c990 887 (interactive)
0b4e93f1
LL
888 (unless (rcirc-looking-at-input)
889 (error "Point not located after rcirc prompt"))
7faa3f8c 890 (if (eq last-command this-command)
94c7243b
LL
891 (setq rcirc-completions
892 (append (cdr rcirc-completions) (list (car rcirc-completions))))
893 (let ((completion-ignore-case t)
894 (table (rcirc-completion-at-point)))
895 (setq rcirc-completion-start (car table))
896 (setq rcirc-completions
0b4e93f1
LL
897 (and rcirc-completion-start
898 (all-completions (buffer-substring rcirc-completion-start
899 (cadr table))
900 (nth 2 table))))))
94c7243b 901 (let ((completion (car rcirc-completions)))
bd43c990 902 (when completion
94c7243b
LL
903 (delete-region rcirc-completion-start (point))
904 (insert
2a4466ca
DD
905 (cond
906 ((= (aref completion 0) ?/) (concat completion " "))
907 ((= rcirc-completion-start rcirc-prompt-end-marker)
908 (format rcirc-nick-completion-format completion))
909 (t completion))))))
bd43c990 910
a2524d26
EZ
911(defun set-rcirc-decode-coding-system (coding-system)
912 "Set the decode coding system used in this channel."
913 (interactive "zCoding system for incoming messages: ")
488086f4 914 (set (make-local-variable 'rcirc-decode-coding-system) coding-system))
a2524d26
EZ
915
916(defun set-rcirc-encode-coding-system (coding-system)
917 "Set the encode coding system used in this channel."
918 (interactive "zCoding system for outgoing messages: ")
488086f4 919 (set (make-local-variable 'rcirc-encode-coding-system) coding-system))
bd43c990 920
b016851c
SM
921(defvar rcirc-mode-map
922 (let ((map (make-sparse-keymap)))
923 (define-key map (kbd "RET") 'rcirc-send-input)
924 (define-key map (kbd "M-p") 'rcirc-insert-prev-input)
925 (define-key map (kbd "M-n") 'rcirc-insert-next-input)
926 (define-key map (kbd "TAB") 'rcirc-complete)
927 (define-key map (kbd "C-c C-b") 'rcirc-browse-url)
928 (define-key map (kbd "C-c C-c") 'rcirc-edit-multiline)
929 (define-key map (kbd "C-c C-j") 'rcirc-cmd-join)
930 (define-key map (kbd "C-c C-k") 'rcirc-cmd-kick)
931 (define-key map (kbd "C-c C-l") 'rcirc-toggle-low-priority)
932 (define-key map (kbd "C-c C-d") 'rcirc-cmd-mode)
933 (define-key map (kbd "C-c C-m") 'rcirc-cmd-msg)
934 (define-key map (kbd "C-c C-r") 'rcirc-cmd-nick) ; rename
935 (define-key map (kbd "C-c C-o") 'rcirc-omit-mode)
b016851c
SM
936 (define-key map (kbd "C-c C-p") 'rcirc-cmd-part)
937 (define-key map (kbd "C-c C-q") 'rcirc-cmd-query)
938 (define-key map (kbd "C-c C-t") 'rcirc-cmd-topic)
939 (define-key map (kbd "C-c C-n") 'rcirc-cmd-names)
940 (define-key map (kbd "C-c C-w") 'rcirc-cmd-whois)
941 (define-key map (kbd "C-c C-x") 'rcirc-cmd-quit)
942 (define-key map (kbd "C-c TAB") ; C-i
943 'rcirc-toggle-ignore-buffer-activity)
944 (define-key map (kbd "C-c C-s") 'rcirc-switch-to-server-buffer)
945 (define-key map (kbd "C-c C-a") 'rcirc-jump-to-first-unread-line)
946 map)
bd43c990
RS
947 "Keymap for rcirc mode.")
948
adf794e4
EZ
949(defvar rcirc-short-buffer-name nil
950 "Generated abbreviation to use to indicate buffer activity.")
951
bd43c990
RS
952(defvar rcirc-mode-hook nil
953 "Hook run when setting up rcirc buffer.")
954
a2524d26
EZ
955(defvar rcirc-last-post-time nil)
956
195eca78
SM
957(defvar rcirc-log-alist nil
958 "Alist of lines to log to disk when `rcirc-log-flag' is non-nil.
959Each element looks like (FILENAME . TEXT).")
960
a0a5c583
GM
961(defvar rcirc-current-line 0
962 "The current number of responses printed in this channel.
963This number is independent of the number of lines in the buffer.")
964
bd43c990 965(defun rcirc-mode (process target)
4d789d84 966 ;; FIXME: Use define-derived-mode.
2e398771 967 "Major mode for IRC channel buffers.
bd43c990
RS
968
969\\{rcirc-mode-map}"
970 (kill-all-local-variables)
971 (use-local-map rcirc-mode-map)
972 (setq mode-name "rcirc")
973 (setq major-mode 'rcirc-mode)
195eca78 974 (setq mode-line-process nil)
bd43c990 975
488086f4 976 (set (make-local-variable 'rcirc-input-ring)
183fc730
LL
977 ;; If rcirc-input-ring is already a ring with desired size do
978 ;; not re-initialize.
979 (if (and (ring-p rcirc-input-ring)
980 (= (ring-size rcirc-input-ring)
981 rcirc-input-ring-size))
982 rcirc-input-ring
983 (make-ring rcirc-input-ring-size)))
488086f4
SM
984 (set (make-local-variable 'rcirc-server-buffer) (process-buffer process))
985 (set (make-local-variable 'rcirc-target) target)
986 (set (make-local-variable 'rcirc-topic) nil)
987 (set (make-local-variable 'rcirc-last-post-time) (current-time))
988 (set (make-local-variable 'fill-paragraph-function) 'rcirc-fill-paragraph)
989 (set (make-local-variable 'rcirc-recent-quit-alist) nil)
990 (set (make-local-variable 'rcirc-current-line) 0)
991
1a2ce9ee 992 (use-hard-newlines t)
488086f4 993 (set (make-local-variable 'rcirc-short-buffer-name) nil)
82745640 994 (set (make-local-variable 'rcirc-urls) nil)
bd43c990 995
a0a5c583
GM
996 ;; setup for omitting responses
997 (setq buffer-invisibility-spec '())
998 (setq buffer-display-table (make-display-table))
999 (set-display-table-slot buffer-display-table 4
aebf69c8 1000 (let ((glyph (make-glyph-code
a0a5c583
GM
1001 ?. 'font-lock-keyword-face)))
1002 (make-vector 3 glyph)))
1003
a2524d26
EZ
1004 (dolist (i rcirc-coding-system-alist)
1005 (let ((chan (if (consp (car i)) (caar i) (car i)))
1006 (serv (if (consp (car i)) (cdar i) "")))
1007 (when (and (string-match chan (or target ""))
1008 (string-match serv (rcirc-server-name process)))
488086f4
SM
1009 (set (make-local-variable 'rcirc-decode-coding-system)
1010 (if (consp (cdr i)) (cadr i) (cdr i)))
1011 (set (make-local-variable 'rcirc-encode-coding-system)
1012 (if (consp (cdr i)) (cddr i) (cdr i))))))
a2524d26 1013
bd43c990 1014 ;; setup the prompt and markers
488086f4
SM
1015 (set (make-local-variable 'rcirc-prompt-start-marker) (point-max-marker))
1016 (set (make-local-variable 'rcirc-prompt-end-marker) (point-max-marker))
bd43c990
RS
1017 (rcirc-update-prompt)
1018 (goto-char rcirc-prompt-end-marker)
488086f4
SM
1019
1020 (set (make-local-variable 'overlay-arrow-position) (make-marker))
bd43c990 1021
a2524d26
EZ
1022 ;; if the user changes the major mode or kills the buffer, there is
1023 ;; cleanup work to do
f8db61b2
EZ
1024 (add-hook 'change-major-mode-hook 'rcirc-change-major-mode-hook nil t)
1025 (add-hook 'kill-buffer-hook 'rcirc-kill-buffer-hook nil t)
a2524d26 1026
adf794e4
EZ
1027 ;; add to buffer list, and update buffer abbrevs
1028 (when target ; skip server buffer
1029 (let ((buffer (current-buffer)))
1030 (with-rcirc-process-buffer process
1031 (setq rcirc-buffer-alist (cons (cons target buffer)
1032 rcirc-buffer-alist))))
1033 (rcirc-update-short-buffer-names))
1034
94c7243b
LL
1035 (add-hook 'completion-at-point-functions
1036 'rcirc-completion-at-point nil 'local)
1037
4d789d84 1038 (run-mode-hooks 'rcirc-mode-hook))
bd43c990 1039
adf794e4
EZ
1040(defun rcirc-update-prompt (&optional all)
1041 "Reset the prompt string in the current buffer.
c18a54de 1042
adf794e4
EZ
1043If ALL is non-nil, update prompts in all IRC buffers."
1044 (if all
1045 (mapc (lambda (process)
1046 (mapc (lambda (buffer)
1047 (with-current-buffer buffer
1048 (rcirc-update-prompt)))
1049 (with-rcirc-process-buffer process
1050 (mapcar 'cdr rcirc-buffer-alist))))
1051 (rcirc-process-list))
1052 (let ((inhibit-read-only t)
1053 (prompt (or rcirc-prompt "")))
1054 (mapc (lambda (rep)
1055 (setq prompt
a2524d26
EZ
1056 (replace-regexp-in-string (car rep) (cdr rep) prompt)))
1057 (list (cons "%n" (rcirc-buffer-nick))
8216fbaf 1058 (cons "%s" (with-rcirc-server-buffer rcirc-server-name))
adf794e4
EZ
1059 (cons "%t" (or rcirc-target ""))))
1060 (save-excursion
1061 (delete-region rcirc-prompt-start-marker rcirc-prompt-end-marker)
1062 (goto-char rcirc-prompt-start-marker)
1063 (let ((start (point)))
1064 (insert-before-markers prompt)
1065 (set-marker rcirc-prompt-start-marker start)
1066 (when (not (zerop (- rcirc-prompt-end-marker
1067 rcirc-prompt-start-marker)))
1068 (add-text-properties rcirc-prompt-start-marker
1069 rcirc-prompt-end-marker
1070 (list 'face 'rcirc-prompt
1071 'read-only t 'field t
1072 'front-sticky t 'rear-nonsticky t))))))))
1073
1074(defun rcirc-set-changed (option value)
1075 "Set OPTION to VALUE and do updates after a customization change."
1076 (set-default option value)
1077 (cond ((eq option 'rcirc-prompt)
1078 (rcirc-update-prompt 'all))
1079 (t
1080 (error "Bad option %s" option))))
bd43c990
RS
1081
1082(defun rcirc-channel-p (target)
1083 "Return t if TARGET is a channel name."
1084 (and target
1085 (not (zerop (length target)))
1086 (or (eq (aref target 0) ?#)
1087 (eq (aref target 0) ?&))))
1088
2d7d6439
GM
1089(defcustom rcirc-log-directory "~/.emacs.d/rcirc-log"
1090 "Directory to keep IRC logfiles."
1091 :type 'directory
1092 :group 'rcirc)
1093
1094(defcustom rcirc-log-flag nil
1095 "Non-nil means log IRC activity to disk.
1096Logfiles are kept in `rcirc-log-directory'."
1097 :type 'boolean
1098 :group 'rcirc)
1099
bd43c990 1100(defun rcirc-kill-buffer-hook ()
a63067fc
DD
1101 "Part the channel when killing an rcirc buffer.
1102
1103If `rcirc-kill-channel-buffers' is non-nil and the killed buffer
1104is a server buffer, kills all of the channel buffers associated
1105with it."
bd43c990 1106 (when (eq major-mode 'rcirc-mode)
80094035
GK
1107 (when (and rcirc-log-flag
1108 rcirc-log-directory)
1109 (rcirc-log-write))
a63067fc
DD
1110 (rcirc-clean-up-buffer "Killed buffer")
1111 (when (and rcirc-buffer-alist ;; it's a server buffer
1112 rcirc-kill-channel-buffers)
1113 (dolist (channel rcirc-buffer-alist)
1114 (kill-buffer (cdr channel))))))
a2524d26
EZ
1115
1116(defun rcirc-change-major-mode-hook ()
1117 "Part the channel when changing the major-mode."
1118 (rcirc-clean-up-buffer "Changed major mode"))
1119
1120(defun rcirc-clean-up-buffer (reason)
adf794e4
EZ
1121 (let ((buffer (current-buffer)))
1122 (rcirc-clear-activity buffer)
a2524d26 1123 (when (and (rcirc-buffer-process)
488086f4 1124 (rcirc--connection-open-p (rcirc-buffer-process)))
a2524d26
EZ
1125 (with-rcirc-server-buffer
1126 (setq rcirc-buffer-alist
1127 (rassq-delete-all buffer rcirc-buffer-alist)))
adf794e4 1128 (rcirc-update-short-buffer-names)
bd43c990 1129 (if (rcirc-channel-p rcirc-target)
a2524d26
EZ
1130 (rcirc-send-string (rcirc-buffer-process)
1131 (concat "PART " rcirc-target " :" reason))
adf794e4 1132 (when rcirc-target
a2524d26
EZ
1133 (rcirc-remove-nick-channel (rcirc-buffer-process)
1134 (rcirc-buffer-nick)
195eca78
SM
1135 rcirc-target))))
1136 (setq rcirc-target nil)))
adf794e4 1137
adf794e4
EZ
1138(defun rcirc-generate-new-buffer-name (process target)
1139 "Return a buffer name based on PROCESS and TARGET.
2e398771 1140This is used for the initial name given to IRC buffers."
195eca78
SM
1141 (substring-no-properties
1142 (if target
1143 (concat target "@" (process-name process))
1144 (concat "*" (process-name process) "*"))))
bd43c990 1145
adf794e4 1146(defun rcirc-get-buffer (process target &optional server)
bd43c990 1147 "Return the buffer associated with the PROCESS and TARGET.
adf794e4 1148
adf794e4
EZ
1149If optional argument SERVER is non-nil, return the server buffer
1150if there is no existing buffer for TARGET, otherwise return nil."
1151 (with-rcirc-process-buffer process
1152 (if (null target)
1153 (current-buffer)
1154 (let ((buffer (cdr (assoc-string target rcirc-buffer-alist t))))
1155 (or buffer (when server (current-buffer)))))))
bd43c990
RS
1156
1157(defun rcirc-get-buffer-create (process target)
adf794e4
EZ
1158 "Return the buffer associated with the PROCESS and TARGET.
1159Create the buffer if it doesn't exist."
1160 (let ((buffer (rcirc-get-buffer process target)))
a2524d26 1161 (if (and buffer (buffer-live-p buffer))
2fbed782 1162 (with-current-buffer buffer
db58efbf 1163 (when (not rcirc-target)
2fbed782 1164 (setq rcirc-target target))
db58efbf 1165 buffer)
195eca78
SM
1166 ;; create the buffer
1167 (with-rcirc-process-buffer process
1168 (let ((new-buffer (get-buffer-create
1169 (rcirc-generate-new-buffer-name process target))))
1170 (with-current-buffer new-buffer
a0a5c583 1171 (rcirc-mode process target)
aebf69c8 1172 (rcirc-put-nick-channel process (rcirc-nick process) target
a0a5c583 1173 rcirc-current-line))
195eca78 1174 new-buffer)))))
bd43c990
RS
1175
1176(defun rcirc-send-input ()
1177 "Send input to target associated with the current buffer."
1178 (interactive)
53f831f3
AS
1179 (if (< (point) rcirc-prompt-end-marker)
1180 ;; copy the line down to the input area
1181 (progn
1182 (forward-line 0)
1183 (let ((start (if (eq (point) (point-min))
1184 (point)
1185 (if (get-text-property (1- (point)) 'hard)
1186 (point)
1187 (previous-single-property-change (point) 'hard))))
1188 (end (next-single-property-change (1+ (point)) 'hard)))
1189 (goto-char (point-max))
1190 (insert (replace-regexp-in-string
1191 "\n\\s-+" " "
1192 (buffer-substring-no-properties start end)))))
1193 ;; process input
1194 (goto-char (point-max))
a2524d26
EZ
1195 (when (not (equal 0 (- (point) rcirc-prompt-end-marker)))
1196 ;; delete a trailing newline
1197 (when (eq (point) (point-at-bol))
d355a0b7 1198 (delete-char -1))
a2524d26
EZ
1199 (let ((input (buffer-substring-no-properties
1200 rcirc-prompt-end-marker (point))))
1201 (dolist (line (split-string input "\n"))
1202 (rcirc-process-input-line line))
1203 ;; add to input-ring
1204 (save-excursion
1205 (ring-insert rcirc-input-ring input)
1206 (setq rcirc-input-ring-index 0))))))
1207
195eca78
SM
1208(defun rcirc-fill-paragraph (&optional arg)
1209 (interactive "p")
1210 (when (> (point) rcirc-prompt-end-marker)
1211 (save-restriction
1212 (narrow-to-region rcirc-prompt-end-marker (point-max))
1213 (let ((fill-column rcirc-max-message-length))
1214 (fill-region (point-min) (point-max))))))
1215
a2524d26 1216(defun rcirc-process-input-line (line)
db58efbf
EZ
1217 (if (string-match "^/\\([^ ]+\\) ?\\(.*\\)$" line)
1218 (rcirc-process-command (match-string 1 line)
1219 (match-string 2 line)
1220 line)
1221 (rcirc-process-message line)))
1222
1223(defun rcirc-process-message (line)
1224 (if (not rcirc-target)
a2524d26 1225 (message "Not joined (no target)")
db58efbf 1226 (delete-region rcirc-prompt-end-marker (point))
a2524d26
EZ
1227 (rcirc-send-message (rcirc-buffer-process) rcirc-target line)
1228 (setq rcirc-last-post-time (current-time))))
db58efbf
EZ
1229
1230(defun rcirc-process-command (command args line)
1231 (if (eq (aref command 0) ?/)
1232 ;; "//text" will send "/text" as a message
1233 (rcirc-process-message (substring line 1))
a2524d26
EZ
1234 (let ((fun (intern-soft (concat "rcirc-cmd-" command)))
1235 (process (rcirc-buffer-process)))
db58efbf
EZ
1236 (newline)
1237 (with-current-buffer (current-buffer)
1238 (delete-region rcirc-prompt-end-marker (point))
1239 (if (string= command "me")
a2524d26 1240 (rcirc-print process (rcirc-buffer-nick)
db58efbf 1241 "ACTION" rcirc-target args)
a2524d26 1242 (rcirc-print process (rcirc-buffer-nick)
db58efbf
EZ
1243 "COMMAND" rcirc-target line))
1244 (set-marker rcirc-prompt-end-marker (point))
1245 (if (fboundp fun)
a2524d26
EZ
1246 (funcall fun args process rcirc-target)
1247 (rcirc-send-string process
f8db61b2 1248 (concat command " :" args)))))))
db58efbf 1249
bd43c990 1250(defvar rcirc-parent-buffer nil)
488086f4
SM
1251(make-variable-buffer-local 'rcirc-parent-buffer)
1252(put 'rcirc-parent-buffer 'permanent-local t)
bd43c990
RS
1253(defvar rcirc-window-configuration nil)
1254(defun rcirc-edit-multiline ()
1255 "Move current edit to a dedicated buffer."
1256 (interactive)
1257 (let ((pos (1+ (- (point) rcirc-prompt-end-marker))))
1258 (goto-char (point-max))
aebf69c8 1259 (let ((text (buffer-substring-no-properties rcirc-prompt-end-marker
a0a5c583 1260 (point)))
a2524d26 1261 (parent (buffer-name)))
bd43c990
RS
1262 (delete-region rcirc-prompt-end-marker (point))
1263 (setq rcirc-window-configuration (current-window-configuration))
1264 (pop-to-buffer (concat "*multiline " parent "*"))
a2524d26
EZ
1265 (funcall rcirc-multiline-major-mode)
1266 (rcirc-multiline-minor-mode 1)
bd43c990 1267 (setq rcirc-parent-buffer parent)
bd43c990 1268 (insert text)
db58efbf
EZ
1269 (and (> pos 0) (goto-char pos))
1270 (message "Type C-c C-c to return text to %s, or C-c C-k to cancel" parent))))
bd43c990 1271
b016851c
SM
1272(defvar rcirc-multiline-minor-mode-map
1273 (let ((map (make-sparse-keymap)))
1274 (define-key map (kbd "C-c C-c") 'rcirc-multiline-minor-submit)
1275 (define-key map (kbd "C-x C-s") 'rcirc-multiline-minor-submit)
1276 (define-key map (kbd "C-c C-k") 'rcirc-multiline-minor-cancel)
1277 (define-key map (kbd "ESC ESC ESC") 'rcirc-multiline-minor-cancel)
1278 map)
a2524d26 1279 "Keymap for multiline mode in rcirc.")
a2524d26
EZ
1280
1281(define-minor-mode rcirc-multiline-minor-mode
e1ac4066
GM
1282 "Minor mode for editing multiple lines in rcirc.
1283With a prefix argument ARG, enable the mode if ARG is positive,
1284and disable it otherwise. If called from Lisp, enable the mode
1285if ARG is omitted or nil."
a2524d26
EZ
1286 :init-value nil
1287 :lighter " rcirc-mline"
1288 :keymap rcirc-multiline-minor-mode-map
1289 :global nil
1290 :group 'rcirc
02f47e86 1291 (setq fill-column rcirc-max-message-length))
a2524d26
EZ
1292
1293(defun rcirc-multiline-minor-submit ()
bd43c990
RS
1294 "Send the text in buffer back to parent buffer."
1295 (interactive)
adf794e4 1296 (untabify (point-min) (point-max))
bd43c990
RS
1297 (let ((text (buffer-substring (point-min) (point-max)))
1298 (buffer (current-buffer))
1299 (pos (point)))
1300 (set-buffer rcirc-parent-buffer)
1301 (goto-char (point-max))
1302 (insert text)
bd43c990 1303 (kill-buffer buffer)
adf794e4
EZ
1304 (set-window-configuration rcirc-window-configuration)
1305 (goto-char (+ rcirc-prompt-end-marker (1- pos)))))
bd43c990 1306
a2524d26 1307(defun rcirc-multiline-minor-cancel ()
bd43c990
RS
1308 "Cancel the multiline edit."
1309 (interactive)
bd43c990
RS
1310 (kill-buffer (current-buffer))
1311 (set-window-configuration rcirc-window-configuration))
1312
2fbed782 1313(defun rcirc-any-buffer (process)
adf794e4 1314 "Return a buffer for PROCESS, either the one selected or the process buffer."
2fbed782
EZ
1315 (if rcirc-always-use-server-buffer-flag
1316 (process-buffer process)
1317 (let ((buffer (window-buffer (selected-window))))
1318 (if (and buffer
1319 (with-current-buffer buffer
1320 (and (eq major-mode 'rcirc-mode)
a2524d26 1321 (eq (rcirc-buffer-process) process))))
2fbed782
EZ
1322 buffer
1323 (process-buffer process)))))
bd43c990 1324
324e4da7 1325(defcustom rcirc-response-formats
195eca78
SM
1326 '(("PRIVMSG" . "<%N> %m")
1327 ("NOTICE" . "-%N- %m")
1328 ("ACTION" . "[%N %m]")
1329 ("COMMAND" . "%m")
1330 ("ERROR" . "%fw!!! %m")
1331 (t . "%fp*** %fs%n %r %m"))
324e4da7
MB
1332 "An alist of formats used for printing responses.
1333The format is looked up using the response-type as a key;
1334if no match is found, the default entry (with a key of `t') is used.
1335
1336The entry's value part should be a string, which is inserted with
1337the of the following escape sequences replaced by the described values:
1338
1339 %m The message text
2fbed782
EZ
1340 %n The sender's nick
1341 %N The sender's nick (with face `rcirc-my-nick' or `rcirc-other-nick')
324e4da7 1342 %r The response-type
324e4da7
MB
1343 %t The target
1344 %fw Following text uses the face `font-lock-warning-face'
1345 %fp Following text uses the face `rcirc-server-prefix'
1346 %fs Following text uses the face `rcirc-server'
1347 %f[FACE] Following text uses the face FACE
3715419e 1348 %f- Following text uses the default face
a2524d26 1349 %% A literal `%' character"
324e4da7
MB
1350 :type '(alist :key-type (choice (string :tag "Type")
1351 (const :tag "Default" t))
1352 :value-type string)
1353 :group 'rcirc)
1354
0ffab1eb 1355(defcustom rcirc-omit-responses
a0a5c583 1356 '("JOIN" "PART" "QUIT" "NICK")
195eca78
SM
1357 "Responses which will be hidden when `rcirc-omit-mode' is enabled."
1358 :type '(repeat string)
1359 :group 'rcirc)
1360
bd43c990 1361(defun rcirc-format-response-string (process sender response target text)
324e4da7
MB
1362 "Return a nicely-formatted response string, incorporating TEXT
1363\(and perhaps other arguments). The specific formatting used
1364is found by looking up RESPONSE in `rcirc-response-formats'."
195eca78
SM
1365 (with-temp-buffer
1366 (insert (or (cdr (assoc response rcirc-response-formats))
1367 (cdr (assq t rcirc-response-formats))))
1368 (goto-char (point-min))
1369 (let ((start (point-min))
1370 (sender (if (or (not sender)
1371 (string= (rcirc-server-name process) sender))
1372 ""
1373 sender))
1374 face)
1375 (while (re-search-forward "%\\(\\(f\\(.\\)\\)\\|\\(.\\)\\)" nil t)
1376 (rcirc-add-face start (match-beginning 0) face)
1377 (setq start (match-beginning 0))
1378 (replace-match
1379 (case (aref (match-string 1) 0)
1380 (?f (setq face
1381 (case (string-to-char (match-string 3))
1382 (?w 'font-lock-warning-face)
1383 (?p 'rcirc-server-prefix)
1384 (?s 'rcirc-server)
1385 (t nil)))
1386 "")
1387 (?n sender)
1388 (?N (let ((my-nick (rcirc-nick process)))
1389 (save-match-data
1390 (with-syntax-table rcirc-nick-syntax-table
1391 (rcirc-facify sender
1392 (cond ((string= sender my-nick)
1393 'rcirc-my-nick)
1394 ((and rcirc-bright-nicks
0ffab1eb 1395 (string-match
195eca78
SM
1396 (regexp-opt rcirc-bright-nicks
1397 'words)
1398 sender))
1399 'rcirc-bright-nick)
1400 ((and rcirc-dim-nicks
1401 (string-match
1402 (regexp-opt rcirc-dim-nicks
1403 'words)
1404 sender))
1405 'rcirc-dim-nick)
1406 (t
1407 'rcirc-other-nick)))))))
1408 (?m (propertize text 'rcirc-text text))
1409 (?r response)
1410 (?t (or target ""))
1411 (t (concat "UNKNOWN CODE:" (match-string 0))))
1412 t t nil 0)
1413 (rcirc-add-face (match-beginning 0) (match-end 0) face))
1414 (rcirc-add-face start (match-beginning 0) face))
1415 (buffer-substring (point-min) (point-max))))
bd43c990 1416
db58efbf
EZ
1417(defun rcirc-target-buffer (process sender response target text)
1418 "Return a buffer to print the server response."
1419 (assert (not (bufferp target)))
1420 (with-rcirc-process-buffer process
1421 (cond ((not target)
2fbed782 1422 (rcirc-any-buffer process))
db58efbf
EZ
1423 ((not (rcirc-channel-p target))
1424 ;; message from another user
195eca78
SM
1425 (if (or (string= response "PRIVMSG")
1426 (string= response "ACTION"))
db58efbf
EZ
1427 (rcirc-get-buffer-create process (if (string= sender rcirc-nick)
1428 target
1429 sender))
1430 (rcirc-get-buffer process target t)))
1431 ((or (rcirc-get-buffer process target)
2fbed782 1432 (rcirc-any-buffer process))))))
db58efbf 1433
f8db61b2
EZ
1434(defvar rcirc-activity-types nil)
1435(make-variable-buffer-local 'rcirc-activity-types)
a2524d26
EZ
1436(defvar rcirc-last-sender nil)
1437(make-variable-buffer-local 'rcirc-last-sender)
7faa3f8c 1438
a0a5c583
GM
1439(defcustom rcirc-omit-threshold 100
1440 "Number of lines since last activity from a nick before `rcirc-omit-responses' are omitted."
1441 :type 'integer
1442 :group 'rcirc)
1443
8f10c937
DD
1444(defcustom rcirc-log-process-buffers nil
1445 "Non-nil if rcirc process buffers should be logged to disk."
1446 :group 'rcirc
1447 :type 'boolean
1448 :version "24.1")
1449
683b7dc6 1450(defun rcirc-last-quit-line (process nick target)
a0a5c583
GM
1451 "Return the line number where NICK left TARGET.
1452Returns nil if the information is not recorded."
683b7dc6 1453 (let ((chanbuf (rcirc-get-buffer process target)))
a0a5c583
GM
1454 (when chanbuf
1455 (cdr (assoc-string nick (with-current-buffer chanbuf
1456 rcirc-recent-quit-alist))))))
1457
683b7dc6 1458(defun rcirc-last-line (process nick target)
a0a5c583 1459 "Return the line from the last activity from NICK in TARGET."
683b7dc6 1460 (let* ((chanbuf (rcirc-get-buffer process target))
a0a5c583
GM
1461 (line (or (cdr (assoc-string target
1462 (gethash nick (with-rcirc-server-buffer
1463 rcirc-nick-table)) t))
683b7dc6 1464 (rcirc-last-quit-line process nick target))))
a0a5c583
GM
1465 (if line
1466 line
1467 ;;(message "line is nil for %s in %s" nick target)
1468 nil)))
1469
683b7dc6 1470(defun rcirc-elapsed-lines (process nick target)
a0a5c583 1471 "Return the number of lines since activity from NICK in TARGET."
683b7dc6 1472 (let ((last-activity-line (rcirc-last-line process nick target)))
a0a5c583
GM
1473 (when (and last-activity-line
1474 (> last-activity-line 0))
1475 (- rcirc-current-line last-activity-line))))
1476
729f1525
DN
1477(defvar rcirc-markup-text-functions
1478 '(rcirc-markup-attributes
1479 rcirc-markup-my-nick
1480 rcirc-markup-urls
1481 rcirc-markup-keywords
683b7dc6 1482 rcirc-markup-bright-nicks)
729f1525
DN
1483
1484 "List of functions used to manipulate text before it is printed.
1485
683b7dc6
GM
1486Each function takes two arguments, SENDER, and RESPONSE. The
1487buffer is narrowed with the text to be printed and the point is
1488at the beginning of the `rcirc-text' propertized text.")
729f1525 1489
bd43c990
RS
1490(defun rcirc-print (process sender response target text &optional activity)
1491 "Print TEXT in the buffer associated with TARGET.
1492Format based on SENDER and RESPONSE. If ACTIVITY is non-nil,
1493record activity."
a2524d26 1494 (or text (setq text ""))
0ffab1eb
TTN
1495 (unless (and (or (member sender rcirc-ignore-list)
1496 (member (with-syntax-table rcirc-nick-syntax-table
1497 (when (string-match "^\\([^/]\\w*\\)[:,]" text)
1498 (match-string 1 text)))
1499 rcirc-ignore-list))
a0a5c583 1500 ;; do not ignore if we sent the message
aebf69c8 1501 (not (string= sender (rcirc-nick process))))
db58efbf 1502 (let* ((buffer (rcirc-target-buffer process sender response target text))
2c8abe90
AS
1503 (inhibit-read-only t))
1504 (with-current-buffer buffer
1505 (let ((moving (= (point) rcirc-prompt-end-marker))
1506 (old-point (point-marker))
1507 (fill-start (marker-position rcirc-prompt-start-marker)))
1508
108bf785 1509 (setq text (decode-coding-string text rcirc-decode-coding-system))
2c8abe90 1510 (unless (string= sender (rcirc-nick process))
2c8abe90
AS
1511 ;; mark the line with overlay arrow
1512 (unless (or (marker-position overlay-arrow-position)
195eca78
SM
1513 (get-buffer-window (current-buffer))
1514 (member response rcirc-omit-responses))
2c8abe90
AS
1515 (set-marker overlay-arrow-position
1516 (marker-position rcirc-prompt-start-marker))))
1517
1518 ;; temporarily set the marker insertion-type because
1519 ;; insert-before-markers results in hidden text in new buffers
1520 (goto-char rcirc-prompt-start-marker)
1521 (set-marker-insertion-type rcirc-prompt-start-marker t)
1522 (set-marker-insertion-type rcirc-prompt-end-marker t)
324e4da7 1523
195eca78 1524 (let ((start (point)))
0ffab1eb 1525 (insert (rcirc-format-response-string process sender response nil
195eca78
SM
1526 text)
1527 (propertize "\n" 'hard t))
1528
1529 ;; squeeze spaces out of text before rcirc-text
0ffab1eb 1530 (fill-region fill-start
195eca78
SM
1531 (1- (or (next-single-property-change fill-start
1532 'rcirc-text)
1533 rcirc-prompt-end-marker)))
1534
1535 ;; run markup functions
1536 (save-excursion
1537 (save-restriction
1538 (narrow-to-region start rcirc-prompt-start-marker)
1539 (goto-char (or (next-single-property-change start 'rcirc-text)
1540 (point)))
1541 (when (rcirc-buffer-process)
1542 (save-excursion (rcirc-markup-timestamp sender response))
1543 (dolist (fn rcirc-markup-text-functions)
1544 (save-excursion (funcall fn sender response)))
a0a5c583
GM
1545 (when rcirc-fill-flag
1546 (save-excursion (rcirc-markup-fill sender response))))
195eca78
SM
1547
1548 (when rcirc-read-only-flag
1549 (add-text-properties (point-min) (point-max)
1550 '(read-only t front-sticky t))))
1551 ;; make text omittable
683b7dc6
GM
1552 (let ((last-activity-lines (rcirc-elapsed-lines process sender target)))
1553 (if (and (not (string= (rcirc-nick process) sender))
1554 (member response rcirc-omit-responses)
1555 (or (not last-activity-lines)
1556 (< rcirc-omit-threshold last-activity-lines)))
1557 (put-text-property (1- start) (1- rcirc-prompt-start-marker)
1558 'invisible 'rcirc-omit)
1559 ;; otherwise increment the line count
1560 (setq rcirc-current-line (1+ rcirc-current-line))))))
195eca78
SM
1561
1562 (set-marker-insertion-type rcirc-prompt-start-marker nil)
1563 (set-marker-insertion-type rcirc-prompt-end-marker nil)
2c8abe90
AS
1564
1565 ;; truncate buffer if it is very long
1566 (save-excursion
1567 (when (and rcirc-buffer-maximum-lines
1568 (> rcirc-buffer-maximum-lines 0)
1569 (= (forward-line (- rcirc-buffer-maximum-lines)) 0))
1570 (delete-region (point-min) (point))))
1571
1572 ;; set the window point for buffers show in windows
1573 (walk-windows (lambda (w)
d40ac716
CY
1574 (when (and (not (eq (selected-window) w))
1575 (eq (current-buffer)
1576 (window-buffer w))
1577 (>= (window-point w)
1578 rcirc-prompt-end-marker))
195eca78 1579 (set-window-point w (point-max))))
2c8abe90
AS
1580 nil t)
1581
1582 ;; restore the point
1583 (goto-char (if moving rcirc-prompt-end-marker old-point))
1584
195eca78 1585 ;; keep window on bottom line if it was already there
d40ac716 1586 (when rcirc-scroll-show-maximum-output
d37e5c87
DD
1587 (let ((window (get-buffer-window)))
1588 (when window
1589 (with-selected-window window
1590 (when (eq major-mode 'rcirc-mode)
1591 (when (<= (- (window-height)
1592 (count-screen-lines (window-point)
1593 (window-start))
1594 1)
1595 0)
1596 (recenter -1)))))))
d40ac716 1597
2c8abe90
AS
1598 ;; flush undo (can we do something smarter here?)
1599 (buffer-disable-undo)
1600 (buffer-enable-undo))
1601
37269466 1602 ;; record mode line activity
f8db61b2
EZ
1603 (when (and activity
1604 (not rcirc-ignore-buffer-activity-flag)
1605 (not (and rcirc-dim-nicks sender
195eca78
SM
1606 (string-match (regexp-opt rcirc-dim-nicks) sender)
1607 (rcirc-channel-p target))))
f8db61b2
EZ
1608 (rcirc-record-activity (current-buffer)
1609 (when (not (rcirc-channel-p rcirc-target))
1610 'nick)))
2c8abe90 1611
8f10c937
DD
1612 (when (and rcirc-log-flag
1613 (or target
1614 rcirc-log-process-buffers))
195eca78
SM
1615 (rcirc-log process sender response target text))
1616
2c8abe90
AS
1617 (sit-for 0) ; displayed text before hook
1618 (run-hook-with-args 'rcirc-print-hooks
1619 process sender response target text)))))
bd43c990 1620
8f10c937
DD
1621(defun rcirc-generate-log-filename (process target)
1622 (if target
1623 (rcirc-generate-new-buffer-name process target)
1624 (process-name process)))
1625
1626(defcustom rcirc-log-filename-function 'rcirc-generate-log-filename
aacde24f
MB
1627 "A function to generate the filename used by rcirc's logging facility.
1628
1629It is called with two arguments, PROCESS and TARGET (see
1630`rcirc-generate-new-buffer-name' for their meaning), and should
1631return the filename, or nil if no logging is desired for this
1632session.
1633
1634If the returned filename is absolute (`file-name-absolute-p'
186ecaf1
DD
1635returns t), then it is used as-is, otherwise the resulting file
1636is put into `rcirc-log-directory'.
1637
1638The filename is then cleaned using `convert-standard-filename' to
1639guarantee valid filenames for the current OS."
aacde24f
MB
1640 :group 'rcirc
1641 :type 'function)
1642
195eca78
SM
1643(defun rcirc-log (process sender response target text)
1644 "Record line in `rcirc-log', to be later written to disk."
aacde24f
MB
1645 (let ((filename (funcall rcirc-log-filename-function process target)))
1646 (unless (null filename)
1647 (let ((cell (assoc-string filename rcirc-log-alist))
1648 (line (concat (format-time-string rcirc-time-format)
1649 (substring-no-properties
1650 (rcirc-format-response-string process sender
1651 response target text))
1652 "\n")))
1653 (if cell
1654 (setcdr cell (concat (cdr cell) line))
1655 (setq rcirc-log-alist
1656 (cons (cons filename line) rcirc-log-alist)))))))
195eca78
SM
1657
1658(defun rcirc-log-write ()
1659 "Flush `rcirc-log-alist' data to disk.
1660
aacde24f
MB
1661Log data is written to `rcirc-log-directory', except for
1662log-files with absolute names (see `rcirc-log-filename-function')."
195eca78 1663 (dolist (cell rcirc-log-alist)
186ecaf1
DD
1664 (let ((filename (convert-standard-filename
1665 (expand-file-name (car cell)
1666 rcirc-log-directory)))
aacde24f
MB
1667 (coding-system-for-write 'utf-8))
1668 (make-directory (file-name-directory filename) t)
1669 (with-temp-buffer
1670 (insert (cdr cell))
1671 (write-region (point-min) (point-max) filename t 'quiet))))
195eca78 1672 (setq rcirc-log-alist nil))
bd43c990 1673
d7a0fd6f
GM
1674(defun rcirc-view-log-file ()
1675 "View logfile corresponding to the current buffer."
1676 (interactive)
aebf69c8
DD
1677 (find-file-other-window
1678 (expand-file-name (funcall rcirc-log-filename-function
d7a0fd6f
GM
1679 (rcirc-buffer-process) rcirc-target)
1680 rcirc-log-directory)))
1681
bd43c990
RS
1682(defun rcirc-join-channels (process channels)
1683 "Join CHANNELS."
1684 (save-window-excursion
db58efbf
EZ
1685 (dolist (channel channels)
1686 (with-rcirc-process-buffer process
1687 (rcirc-cmd-join channel process)))))
bd43c990
RS
1688\f
1689;;; nick management
8216fbaf 1690(defvar rcirc-nick-prefix-chars "~&@%+")
bd43c990
RS
1691(defun rcirc-user-nick (user)
1692 "Return the nick from USER. Remove any non-nick junk."
db58efbf 1693 (save-match-data
8216fbaf
EZ
1694 (if (string-match (concat "^[" rcirc-nick-prefix-chars
1695 "]?\\([^! ]+\\)!?") (or user ""))
db58efbf
EZ
1696 (match-string 1 user)
1697 user)))
bd43c990 1698
bd43c990
RS
1699(defun rcirc-nick-channels (process nick)
1700 "Return list of channels for NICK."
db58efbf
EZ
1701 (with-rcirc-process-buffer process
1702 (mapcar (lambda (x) (car x))
1703 (gethash nick rcirc-nick-table))))
bd43c990 1704
a0a5c583
GM
1705(defun rcirc-put-nick-channel (process nick channel &optional line)
1706 "Add CHANNEL to list associated with NICK.
1707Update the associated linestamp if LINE is non-nil.
1708
1709If the record doesn't exist, and LINE is nil, set the linestamp
1710to zero."
2fbed782
EZ
1711 (let ((nick (rcirc-user-nick nick)))
1712 (with-rcirc-process-buffer process
1713 (let* ((chans (gethash nick rcirc-nick-table))
1714 (record (assoc-string channel chans t)))
1715 (if record
a0a5c583
GM
1716 (when line (setcdr record line))
1717 (puthash nick (cons (cons channel (or line 0))
2fbed782
EZ
1718 chans)
1719 rcirc-nick-table))))))
bd43c990
RS
1720
1721(defun rcirc-nick-remove (process nick)
1722 "Remove NICK from table."
adf794e4 1723 (with-rcirc-process-buffer process
bd43c990
RS
1724 (remhash nick rcirc-nick-table)))
1725
1726(defun rcirc-remove-nick-channel (process nick channel)
1727 "Remove the CHANNEL from list associated with NICK."
adf794e4 1728 (with-rcirc-process-buffer process
db58efbf 1729 (let* ((chans (gethash nick rcirc-nick-table))
adf794e4
EZ
1730 (newchans
1731 ;; instead of assoc-string-delete-all:
1732 (let ((record (assoc-string channel chans t)))
1733 (when record
1734 (setcar record 'delete)
1735 (assq-delete-all 'delete chans)))))
bd43c990
RS
1736 (if newchans
1737 (puthash nick newchans rcirc-nick-table)
1738 (remhash nick rcirc-nick-table)))))
1739
a2524d26
EZ
1740(defun rcirc-channel-nicks (process target)
1741 "Return the list of nicks associated with TARGET sorted by last activity."
1742 (when target
1743 (if (rcirc-channel-p target)
1744 (with-rcirc-process-buffer process
1745 (let (nicks)
1746 (maphash
1747 (lambda (k v)
1748 (let ((record (assoc-string target v t)))
1749 (if record
1750 (setq nicks (cons (cons k (cdr record)) nicks)))))
1751 rcirc-nick-table)
1752 (mapcar (lambda (x) (car x))
a0a5c583
GM
1753 (sort nicks (lambda (x y)
1754 (let ((lx (or (cdr x) 0))
1755 (ly (or (cdr y) 0)))
1756 (< ly lx)))))))
a2524d26 1757 (list target))))
2c8abe90
AS
1758
1759(defun rcirc-ignore-update-automatic (nick)
2e398771
JB
1760 "Remove NICK from `rcirc-ignore-list'
1761if NICK is also on `rcirc-ignore-list-automatic'."
2c8abe90
AS
1762 (when (member nick rcirc-ignore-list-automatic)
1763 (setq rcirc-ignore-list-automatic
1764 (delete nick rcirc-ignore-list-automatic)
1765 rcirc-ignore-list
1766 (delete nick rcirc-ignore-list))))
bd43c990 1767\f
c62bf05a 1768(defun rcirc-nickname< (s1 s2)
44ea155d
CY
1769 "Return t if IRC nickname S1 is less than S2, and nil otherwise.
1770Operator nicknames (@) are considered less than voiced
1771nicknames (+). Any other nicknames are greater than voiced
1772nicknames. The comparison is case-insensitive."
c62bf05a
DD
1773 (setq s1 (downcase s1)
1774 s2 (downcase s2))
1775 (let* ((s1-op (eq ?@ (string-to-char s1)))
1776 (s2-op (eq ?@ (string-to-char s2))))
1777 (if s1-op
1778 (if s2-op
1779 (string< (substring s1 1) (substring s2 1))
1780 t)
1781 (if s2-op
1782 nil
1783 (string< s1 s2)))))
1784
1785(defun rcirc-sort-nicknames-join (input sep)
44ea155d 1786 "Return a string of sorted nicknames.
c62bf05a 1787INPUT is a string containing nicknames separated by SEP.
44ea155d 1788This function does not alter the INPUT string."
a91dedc4
SM
1789 (let* ((parts (split-string input sep t))
1790 (sorted (sort parts 'rcirc-nickname<)))
1791 (mapconcat 'identity sorted sep)))
c62bf05a 1792\f
bd43c990 1793;;; activity tracking
b016851c
SM
1794(defvar rcirc-track-minor-mode-map
1795 (let ((map (make-sparse-keymap)))
1796 (define-key map (kbd "C-c C-@") 'rcirc-next-active-buffer)
1797 (define-key map (kbd "C-c C-SPC") 'rcirc-next-active-buffer)
1798 map)
db58efbf
EZ
1799 "Keymap for rcirc track minor mode.")
1800
e8f10ddb 1801;;;###autoload
db58efbf 1802(define-minor-mode rcirc-track-minor-mode
e1ac4066
GM
1803 "Global minor mode for tracking activity in rcirc buffers.
1804With a prefix argument ARG, enable the mode if ARG is positive,
1805and disable it otherwise. If called from Lisp, enable the mode
1806if ARG is omitted or nil."
db58efbf
EZ
1807 :init-value nil
1808 :lighter ""
1809 :keymap rcirc-track-minor-mode-map
1810 :global t
1811 :group 'rcirc
1812 (or global-mode-string (setq global-mode-string '("")))
1813 ;; toggle the mode-line channel indicator
1814 (if rcirc-track-minor-mode
a2524d26
EZ
1815 (progn
1816 (and (not (memq 'rcirc-activity-string global-mode-string))
1817 (setq global-mode-string
1818 (append global-mode-string '(rcirc-activity-string))))
1819 (add-hook 'window-configuration-change-hook
1820 'rcirc-window-configuration-change))
92c4adc1 1821 (setq global-mode-string
a2524d26
EZ
1822 (delete 'rcirc-activity-string global-mode-string))
1823 (remove-hook 'window-configuration-change-hook
1824 'rcirc-window-configuration-change)))
db58efbf 1825
adf794e4 1826(or (assq 'rcirc-ignore-buffer-activity-flag minor-mode-alist)
bd43c990 1827 (setq minor-mode-alist
adf794e4 1828 (cons '(rcirc-ignore-buffer-activity-flag " Ignore") minor-mode-alist)))
a2524d26
EZ
1829(or (assq 'rcirc-low-priority-flag minor-mode-alist)
1830 (setq minor-mode-alist
1831 (cons '(rcirc-low-priority-flag " LowPri") minor-mode-alist)))
195eca78
SM
1832(or (assq 'rcirc-omit-mode minor-mode-alist)
1833 (setq minor-mode-alist
1834 (cons '(rcirc-omit-mode " Omit") minor-mode-alist)))
bd43c990 1835
db58efbf
EZ
1836(defun rcirc-toggle-ignore-buffer-activity ()
1837 "Toggle the value of `rcirc-ignore-buffer-activity-flag'."
1838 (interactive)
1839 (setq rcirc-ignore-buffer-activity-flag
1840 (not rcirc-ignore-buffer-activity-flag))
1841 (message (if rcirc-ignore-buffer-activity-flag
1842 "Ignore activity in this buffer"
1843 "Notice activity in this buffer"))
bd43c990
RS
1844 (force-mode-line-update))
1845
a2524d26 1846(defun rcirc-toggle-low-priority ()
02f47e86 1847 "Toggle the value of `rcirc-low-priority-flag'."
a2524d26
EZ
1848 (interactive)
1849 (setq rcirc-low-priority-flag
1850 (not rcirc-low-priority-flag))
1851 (message (if rcirc-low-priority-flag
1852 "Activity in this buffer is low priority"
1853 "Activity in this buffer is normal priority"))
1854 (force-mode-line-update))
1855
195eca78
SM
1856(defun rcirc-omit-mode ()
1857 "Toggle the Rcirc-Omit mode.
0ffab1eb 1858If enabled, \"uninteresting\" lines are not shown.
195eca78
SM
1859Uninteresting lines are those whose responses are listed in
1860`rcirc-omit-responses'."
1861 (interactive)
1862 (setq rcirc-omit-mode (not rcirc-omit-mode))
a0a5c583
GM
1863 (if rcirc-omit-mode
1864 (progn
1230c3cb 1865 (add-to-invisibility-spec '(rcirc-omit . nil))
a0a5c583 1866 (message "Rcirc-Omit mode enabled"))
1230c3cb 1867 (remove-from-invisibility-spec '(rcirc-omit . nil))
a0a5c583
GM
1868 (message "Rcirc-Omit mode disabled"))
1869 (recenter (when (> (point) rcirc-prompt-start-marker) -1)))
bd43c990
RS
1870
1871(defun rcirc-switch-to-server-buffer ()
1872 "Switch to the server buffer associated with current channel buffer."
1873 (interactive)
488086f4
SM
1874 (unless (buffer-live-p rcirc-server-buffer)
1875 (error "No such buffer"))
195eca78 1876 (switch-to-buffer rcirc-server-buffer))
bd43c990
RS
1877
1878(defun rcirc-jump-to-first-unread-line ()
1879 "Move the point to the first unread line in this buffer."
1880 (interactive)
195eca78
SM
1881 (if (marker-position overlay-arrow-position)
1882 (goto-char overlay-arrow-position)
1883 (message "No unread messages")))
1884
1885(defun rcirc-non-irc-buffer ()
1886 (let ((buflist (buffer-list))
1887 buffer)
1888 (while (and buflist (not buffer))
1889 (with-current-buffer (car buflist)
1890 (unless (or (eq major-mode 'rcirc-mode)
1891 (= ?\s (aref (buffer-name) 0)) ; internal buffers
1892 (get-buffer-window (current-buffer)))
1893 (setq buffer (current-buffer))))
1894 (setq buflist (cdr buflist)))
1895 buffer))
bd43c990
RS
1896
1897(defun rcirc-next-active-buffer (arg)
195eca78
SM
1898 "Switch to the next rcirc buffer with activity.
1899With prefix ARG, go to the next low priority buffer with activity."
a2524d26
EZ
1900 (interactive "P")
1901 (let* ((pair (rcirc-split-activity rcirc-activity))
1902 (lopri (car pair))
92c4adc1 1903 (hipri (cdr pair)))
a2524d26
EZ
1904 (if (or (and (not arg) hipri)
1905 (and arg lopri))
a0a5c583
GM
1906 (progn
1907 (switch-to-buffer (car (if arg lopri hipri)))
1908 (when (> (point) rcirc-prompt-start-marker)
1909 (recenter -1)))
a2524d26 1910 (if (eq major-mode 'rcirc-mode)
195eca78 1911 (switch-to-buffer (rcirc-non-irc-buffer))
274f1353
DK
1912 (message "%s" (concat
1913 "No IRC activity."
1914 (when lopri
1915 (concat
1916 " Type C-u "
1917 (key-description (this-command-keys))
1918 " for low priority activity."))))))))
bd43c990
RS
1919
1920(defvar rcirc-activity-hooks nil
1921 "Hook to be run when there is channel activity.
1922
1923Functions are called with a single argument, the buffer with the
1924activity. Only run if the buffer is not visible and
adf794e4 1925`rcirc-ignore-buffer-activity-flag' is non-nil.")
bd43c990 1926
a2524d26 1927(defun rcirc-record-activity (buffer &optional type)
bd43c990
RS
1928 "Record BUFFER activity with TYPE."
1929 (with-current-buffer buffer
195eca78
SM
1930 (let ((old-activity rcirc-activity)
1931 (old-types rcirc-activity-types))
1932 (when (not (get-buffer-window (current-buffer) t))
1933 (setq rcirc-activity
1934 (sort (add-to-list 'rcirc-activity (current-buffer))
1935 (lambda (b1 b2)
1936 (let ((t1 (with-current-buffer b1 rcirc-last-post-time))
1937 (t2 (with-current-buffer b2 rcirc-last-post-time)))
1938 (time-less-p t2 t1)))))
1939 (pushnew type rcirc-activity-types)
1940 (unless (and (equal rcirc-activity old-activity)
1941 (member type old-types))
1942 (rcirc-update-activity-string)))))
bd43c990
RS
1943 (run-hook-with-args 'rcirc-activity-hooks buffer))
1944
1945(defun rcirc-clear-activity (buffer)
1946 "Clear the BUFFER activity."
0ffab1eb 1947 (setq rcirc-activity (remove buffer rcirc-activity))
bd43c990 1948 (with-current-buffer buffer
f8db61b2 1949 (setq rcirc-activity-types nil)))
bd43c990 1950
195eca78
SM
1951(defun rcirc-clear-unread (buffer)
1952 "Erase the last read message arrow from BUFFER."
1953 (when (buffer-live-p buffer)
1954 (with-current-buffer buffer
1955 (set-marker overlay-arrow-position nil))))
1956
a2524d26
EZ
1957(defun rcirc-split-activity (activity)
1958 "Return a cons cell with ACTIVITY split into (lopri . hipri)."
1959 (let (lopri hipri)
1960 (dolist (buf rcirc-activity)
1961 (with-current-buffer buf
1962 (if (and rcirc-low-priority-flag
f8db61b2 1963 (not (member 'nick rcirc-activity-types)))
a2524d26
EZ
1964 (add-to-list 'lopri buf t)
1965 (add-to-list 'hipri buf t))))
1966 (cons lopri hipri)))
1967
195eca78
SM
1968(defvar rcirc-update-activity-string-hook nil
1969 "Hook run whenever the activity string is updated.")
1970
adf794e4 1971;; TODO: add mouse properties
bd43c990
RS
1972(defun rcirc-update-activity-string ()
1973 "Update mode-line string."
a2524d26
EZ
1974 (let* ((pair (rcirc-split-activity rcirc-activity))
1975 (lopri (car pair))
1976 (hipri (cdr pair)))
1977 (setq rcirc-activity-string
7faa3f8c 1978 (cond ((or hipri lopri)
195eca78 1979 (concat (and hipri "[")
7faa3f8c
MB
1980 (rcirc-activity-string hipri)
1981 (and hipri lopri ",")
1982 (and lopri
1983 (concat "("
1984 (rcirc-activity-string lopri)
1985 ")"))
195eca78 1986 (and hipri "]")))
7faa3f8c 1987 ((not (null (rcirc-process-list)))
195eca78
SM
1988 "[]")
1989 (t "[]")))
1990 (run-hooks 'rcirc-update-activity-string-hook)))
a2524d26
EZ
1991
1992(defun rcirc-activity-string (buffers)
1993 (mapconcat (lambda (b)
f8db61b2 1994 (let ((s (substring-no-properties (rcirc-short-buffer-name b))))
a2524d26 1995 (with-current-buffer b
f8db61b2
EZ
1996 (dolist (type rcirc-activity-types)
1997 (rcirc-add-face 0 (length s)
1998 (case type
82741a5e
CY
1999 (nick 'rcirc-track-nick)
2000 (keyword 'rcirc-track-keyword))
f8db61b2
EZ
2001 s)))
2002 s))
a2524d26 2003 buffers ","))
bd43c990
RS
2004
2005(defun rcirc-short-buffer-name (buffer)
37269466 2006 "Return a short name for BUFFER to use in the mode line indicator."
bd43c990 2007 (with-current-buffer buffer
adf794e4
EZ
2008 (or rcirc-short-buffer-name (buffer-name))))
2009
195eca78
SM
2010(defun rcirc-visible-buffers ()
2011 "Return a list of the visible buffers that are in rcirc-mode."
2012 (let (acc)
adf794e4 2013 (walk-windows (lambda (w)
195eca78
SM
2014 (with-current-buffer (window-buffer w)
2015 (when (eq major-mode 'rcirc-mode)
2016 (push (current-buffer) acc)))))
2017 acc))
2018
2019(defvar rcirc-visible-buffers nil)
2020(defun rcirc-window-configuration-change ()
2021 (unless (minibuffer-window-active-p (minibuffer-window))
2022 ;; delay this until command has finished to make sure window is
2023 ;; actually visible before clearing activity
2024 (add-hook 'post-command-hook 'rcirc-window-configuration-change-1)))
2025
2026(defun rcirc-window-configuration-change-1 ()
2027 ;; clear activity and overlay arrows
2028 (let* ((old-activity rcirc-activity)
2029 (hidden-buffers rcirc-visible-buffers))
2030
2031 (setq rcirc-visible-buffers (rcirc-visible-buffers))
2032
2033 (dolist (vbuf rcirc-visible-buffers)
2034 (setq hidden-buffers (delq vbuf hidden-buffers))
2035 ;; clear activity for all visible buffers
2036 (rcirc-clear-activity vbuf))
2037
2038 ;; clear unread arrow from recently hidden buffers
2039 (dolist (hbuf hidden-buffers)
2040 (rcirc-clear-unread hbuf))
2041
2042 ;; remove any killed buffers from list
2043 (setq rcirc-activity
2044 (delq nil (mapcar (lambda (buf) (when (buffer-live-p buf) buf))
2045 rcirc-activity)))
2046 ;; update the mode-line string
2047 (unless (equal old-activity rcirc-activity)
2048 (rcirc-update-activity-string)))
2049
2050 (remove-hook 'post-command-hook 'rcirc-window-configuration-change-1))
bd43c990
RS
2051
2052\f
adf794e4
EZ
2053;;; buffer name abbreviation
2054(defun rcirc-update-short-buffer-names ()
2055 (let ((bufalist
2056 (apply 'append (mapcar (lambda (process)
2057 (with-rcirc-process-buffer process
2058 rcirc-buffer-alist))
2059 (rcirc-process-list)))))
2060 (dolist (i (rcirc-abbreviate bufalist))
a2524d26
EZ
2061 (when (buffer-live-p (cdr i))
2062 (with-current-buffer (cdr i)
2063 (setq rcirc-short-buffer-name (car i)))))))
adf794e4
EZ
2064
2065(defun rcirc-abbreviate (pairs)
2066 (apply 'append (mapcar 'rcirc-rebuild-tree (rcirc-make-trees pairs))))
2067
2068(defun rcirc-rebuild-tree (tree &optional acc)
2069 (let ((ch (char-to-string (car tree))))
2070 (dolist (x (cdr tree))
2071 (if (listp x)
2072 (setq acc (append acc
2073 (mapcar (lambda (y)
2074 (cons (concat ch (car y))
2075 (cdr y)))
2076 (rcirc-rebuild-tree x))))
2077 (setq acc (cons (cons ch x) acc))))
2078 acc))
2079
2080(defun rcirc-make-trees (pairs)
2081 (let (alist)
2082 (mapc (lambda (pair)
2083 (if (consp pair)
2084 (let* ((str (car pair))
2085 (data (cdr pair))
2086 (char (unless (zerop (length str))
2087 (aref str 0)))
2088 (rest (unless (zerop (length str))
2089 (substring str 1)))
2090 (part (if char (assq char alist))))
2091 (if part
2092 ;; existing partition
2093 (setcdr part (cons (cons rest data) (cdr part)))
2094 ;; new partition
2095 (setq alist (cons (if char
2096 (list char (cons rest data))
2097 data)
2098 alist))))
2099 (setq alist (cons pair alist))))
2100 pairs)
2101 ;; recurse into cdrs of alist
2102 (mapc (lambda (x)
2103 (when (and (listp x) (listp (cadr x)))
2104 (setcdr x (if (> (length (cdr x)) 1)
2105 (rcirc-make-trees (cdr x))
2106 (setcdr x (list (cdadr x)))))))
2107 alist)))
2108\f
bd43c990
RS
2109;;; /commands these are called with 3 args: PROCESS, TARGET, which is
2110;; the current buffer/channel/user, and ARGS, which is a string
2111;; containing the text following the /cmd.
2112
adf794e4 2113(defmacro defun-rcirc-command (command argument docstring interactive-form
94c7243b 2114 &rest body)
bd43c990 2115 "Define a command."
94c7243b
LL
2116 `(progn
2117 (add-to-list 'rcirc-client-commands ,(concat "/" (symbol-name command)))
2118 (defun ,(intern (concat "rcirc-cmd-" (symbol-name command)))
2119 (,@argument &optional process target)
2120 ,(concat docstring "\n\nNote: If PROCESS or TARGET are nil, the values given"
2121 "\nby `rcirc-buffer-process' and `rcirc-target' will be used.")
2122 ,interactive-form
2123 (let ((process (or process (rcirc-buffer-process)))
2124 (target (or target rcirc-target)))
2125 ,@body))))
bd43c990
RS
2126
2127(defun-rcirc-command msg (message)
2128 "Send private MESSAGE to TARGET."
2129 (interactive "i")
2130 (if (null message)
2131 (progn
2132 (setq target (completing-read "Message nick: "
92c4adc1 2133 (with-rcirc-server-buffer
a2524d26 2134 rcirc-nick-table)))
bd43c990
RS
2135 (when (> (length target) 0)
2136 (setq message (read-string (format "Message %s: " target)))
2137 (when (> (length message) 0)
2138 (rcirc-send-message process target message))))
2139 (if (not (string-match "\\([^ ]+\\) \\(.+\\)" message))
2140 (message "Not enough args, or something.")
2141 (setq target (match-string 1 message)
2142 message (match-string 2 message))
2143 (rcirc-send-message process target message))))
2144
2145(defun-rcirc-command query (nick)
2146 "Open a private chat buffer to NICK."
2147 (interactive (list (completing-read "Query nick: "
a2524d26 2148 (with-rcirc-server-buffer rcirc-nick-table))))
adf794e4
EZ
2149 (let ((existing-buffer (rcirc-get-buffer process nick)))
2150 (switch-to-buffer (or existing-buffer
2151 (rcirc-get-buffer-create process nick)))
2152 (when (not existing-buffer)
bd43c990
RS
2153 (rcirc-cmd-whois nick))))
2154
e0e36cac
DD
2155(defun-rcirc-command join (channels)
2156 "Join CHANNELS.
2157CHANNELS is a comma- or space-separated string of channel names."
2158 (interactive "sJoin channels: ")
2159 (let* ((split-channels (split-string channels "[ ,]" t))
2160 (buffers (mapcar (lambda (ch)
2161 (rcirc-get-buffer-create process ch))
fcd8ed1d
DD
2162 split-channels))
2163 (channels (mapconcat 'identity split-channels ",")))
e0e36cac 2164 (rcirc-send-string process (concat "JOIN " channels))
bd43c990 2165 (when (not (eq (selected-window) (minibuffer-window)))
e0e36cac
DD
2166 (dolist (b buffers) ;; order the new channel buffers in the buffer list
2167 (switch-to-buffer b)))))
bd43c990 2168
567457e3
LL
2169(defun-rcirc-command invite (nick-channel)
2170 "Invite NICK to CHANNEL."
2171 (interactive (list
2172 (concat
2173 (completing-read "Invite nick: "
2174 (with-rcirc-server-buffer rcirc-nick-table))
2175 " "
2176 (read-string "Channel: "))))
2177 (rcirc-send-string process (concat "INVITE " nick-channel)))
2178
195eca78 2179;; TODO: /part #channel reason, or consider removing #channel altogether
bd43c990
RS
2180(defun-rcirc-command part (channel)
2181 "Part CHANNEL."
2182 (interactive "sPart channel: ")
2183 (let ((channel (if (> (length channel) 0) channel target)))
adf794e4 2184 (rcirc-send-string process (concat "PART " channel " :" rcirc-id-string))))
bd43c990 2185
5c14e333
CY
2186(defun-rcirc-command quit (reason)
2187 "Send a quit message to server with REASON."
2188 (interactive "sQuit reason: ")
2189 (rcirc-send-string process (concat "QUIT :"
2190 (if (not (zerop (length reason)))
2191 reason
2192 rcirc-id-string))))
bd43c990
RS
2193
2194(defun-rcirc-command nick (nick)
2195 "Change nick to NICK."
2196 (interactive "i")
2197 (when (null nick)
2198 (setq nick (read-string "New nick: " (rcirc-nick process))))
2199 (rcirc-send-string process (concat "NICK " nick)))
2200
2201(defun-rcirc-command names (channel)
2202 "Display list of names in CHANNEL or in current channel if CHANNEL is nil.
2203If called interactively, prompt for a channel when prefix arg is supplied."
2204 (interactive "P")
32226619 2205 (if (called-interactively-p 'interactive)
bd43c990
RS
2206 (if channel
2207 (setq channel (read-string "List names in channel: " target))))
2208 (let ((channel (if (> (length channel) 0)
2209 channel
2210 target)))
2211 (rcirc-send-string process (concat "NAMES " channel))))
2212
2213(defun-rcirc-command topic (topic)
2214 "List TOPIC for the TARGET channel.
2215With a prefix arg, prompt for new topic."
2216 (interactive "P")
32226619 2217 (if (and (called-interactively-p 'interactive) topic)
bd43c990
RS
2218 (setq topic (read-string "New Topic: " rcirc-topic)))
2219 (rcirc-send-string process (concat "TOPIC " target
2220 (when (> (length topic) 0)
2221 (concat " :" topic)))))
2222
2223(defun-rcirc-command whois (nick)
2224 "Request information from server about NICK."
2225 (interactive (list
2226 (completing-read "Whois: "
a2524d26 2227 (with-rcirc-server-buffer rcirc-nick-table))))
bd43c990
RS
2228 (rcirc-send-string process (concat "WHOIS " nick)))
2229
2230(defun-rcirc-command mode (args)
2231 "Set mode with ARGS."
2232 (interactive (list (concat (read-string "Mode nick or channel: ")
2233 " " (read-string "Mode: "))))
2234 (rcirc-send-string process (concat "MODE " args)))
2235
2236(defun-rcirc-command list (channels)
2237 "Request information on CHANNELS from server."
2238 (interactive "sList Channels: ")
2239 (rcirc-send-string process (concat "LIST " channels)))
2240
2241(defun-rcirc-command oper (args)
2242 "Send operator command to server."
2243 (interactive "sOper args: ")
2244 (rcirc-send-string process (concat "OPER " args)))
2245
2246(defun-rcirc-command quote (message)
2247 "Send MESSAGE literally to server."
2248 (interactive "sServer message: ")
2249 (rcirc-send-string process message))
2250
2251(defun-rcirc-command kick (arg)
2252 "Kick NICK from current channel."
2253 (interactive (list
2254 (concat (completing-read "Kick nick: "
92c4adc1 2255 (rcirc-channel-nicks
a2524d26
EZ
2256 (rcirc-buffer-process)
2257 rcirc-target))
bd43c990
RS
2258 (read-from-minibuffer "Kick reason: "))))
2259 (let* ((arglist (split-string arg))
adf794e4 2260 (argstring (concat (car arglist) " :"
bd43c990
RS
2261 (mapconcat 'identity (cdr arglist) " "))))
2262 (rcirc-send-string process (concat "KICK " target " " argstring))))
2263
2264(defun rcirc-cmd-ctcp (args &optional process target)
2265 (if (string-match "^\\([^ ]+\\)\\s-+\\(.+\\)$" args)
ee6a57ab
DD
2266 (let* ((target (match-string 1 args))
2267 (request (upcase (match-string 2 args)))
2268 (function (intern-soft (concat "rcirc-ctcp-sender-" request))))
2269 (if (fboundp function) ;; use special function if available
2270 (funcall function process target request)
1be1d1e9 2271 (rcirc-send-ctcp process target request)))
adf794e4 2272 (rcirc-print process (rcirc-nick process) "ERROR" nil
bd43c990
RS
2273 "usage: /ctcp NICK REQUEST")))
2274
ee6a57ab
DD
2275(defun rcirc-ctcp-sender-PING (process target request)
2276 "Send a CTCP PING message to TARGET."
73057ba9 2277 (let ((timestamp (format "%.0f" (rcirc-float-time))))
1be1d1e9 2278 (rcirc-send-ctcp process target "PING" timestamp)))
ee6a57ab 2279
bd43c990 2280(defun rcirc-cmd-me (args &optional process target)
1be1d1e9 2281 (rcirc-send-ctcp process target "ACTION" args))
2c8abe90 2282
c00725d7
LL
2283(defun rcirc-add-or-remove (set &rest elements)
2284 (dolist (elt elements)
2285 (if (and elt (not (string= "" elt)))
2286 (setq set (if (member-ignore-case elt set)
2287 (delete elt set)
2288 (cons elt set)))))
2289 set)
f8db61b2 2290
2c8abe90
AS
2291(defun-rcirc-command ignore (nick)
2292 "Manage the ignore list.
2293Ignore NICK, unignore NICK if already ignored, or list ignored
2294nicks when no NICK is given. When listing ignored nicks, the
2e398771 2295ones added to the list automatically are marked with an asterisk."
2c8abe90 2296 (interactive "sToggle ignoring of nick: ")
c00725d7
LL
2297 (setq rcirc-ignore-list
2298 (apply #'rcirc-add-or-remove rcirc-ignore-list
2299 (split-string nick nil t)))
92c4adc1 2300 (rcirc-print process nil "IGNORE" target
db58efbf
EZ
2301 (mapconcat
2302 (lambda (nick)
2303 (concat nick
2304 (if (member nick rcirc-ignore-list-automatic)
2305 "*" "")))
2306 rcirc-ignore-list " ")))
2c8abe90 2307
f8db61b2
EZ
2308(defun-rcirc-command bright (nick)
2309 "Manage the bright nick list."
2310 (interactive "sToggle emphasis of nick: ")
c00725d7
LL
2311 (setq rcirc-bright-nicks
2312 (apply #'rcirc-add-or-remove rcirc-bright-nicks
2313 (split-string nick nil t)))
92c4adc1 2314 (rcirc-print process nil "BRIGHT" target
f8db61b2
EZ
2315 (mapconcat 'identity rcirc-bright-nicks " ")))
2316
2317(defun-rcirc-command dim (nick)
2318 "Manage the dim nick list."
2319 (interactive "sToggle deemphasis of nick: ")
c00725d7
LL
2320 (setq rcirc-dim-nicks
2321 (apply #'rcirc-add-or-remove rcirc-dim-nicks
2322 (split-string nick nil t)))
92c4adc1 2323 (rcirc-print process nil "DIM" target
f8db61b2
EZ
2324 (mapconcat 'identity rcirc-dim-nicks " ")))
2325
2326(defun-rcirc-command keyword (keyword)
2327 "Manage the keyword list.
2328Mark KEYWORD, unmark KEYWORD if already marked, or list marked
2329keywords when no KEYWORD is given."
2330 (interactive "sToggle highlighting of keyword: ")
c00725d7
LL
2331 (setq rcirc-keywords
2332 (apply #'rcirc-add-or-remove rcirc-keywords
2333 (split-string keyword nil t)))
92c4adc1 2334 (rcirc-print process nil "KEYWORD" target
f8db61b2
EZ
2335 (mapconcat 'identity rcirc-keywords " ")))
2336
bd43c990 2337\f
f8db61b2
EZ
2338(defun rcirc-add-face (start end name &optional object)
2339 "Add face NAME to the face text property of the text from START to END."
2340 (when name
2341 (let ((pos start)
2342 next prop)
2343 (while (< pos end)
2344 (setq prop (get-text-property pos 'face object)
2345 next (next-single-property-change pos 'face object end))
2346 (unless (member name (get-text-property pos 'face object))
2347 (add-text-properties pos next (list 'face (cons name prop)) object))
2348 (setq pos next)))))
adf794e4 2349
bd43c990
RS
2350(defun rcirc-facify (string face)
2351 "Return a copy of STRING with FACE property added."
f8db61b2
EZ
2352 (let ((string (or string "")))
2353 (rcirc-add-face 0 (length string) face string)
2354 string))
bd43c990 2355
bd43c990 2356(defvar rcirc-url-regexp
73dd622f
RS
2357 (concat
2358 "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
2359 "nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
2360 "\\(//[-a-z0-9_.]+:[0-9]*\\)?"
2361 (if (string-match "[[:digit:]]" "1") ;; Support POSIX?
2362 (let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
2363 (punct "!?:;.,"))
2364 (concat
2365 "\\(?:"
2366 ;; Match paired parentheses, e.g. in Wikipedia URLs:
2367 "[" chars punct "]+" "(" "[" chars punct "]+" "[" chars "]*)" "[" chars "]"
2368 "\\|"
2369 "[" chars punct "]+" "[" chars "]"
2370 "\\)"))
2371 (concat ;; XEmacs 21.4 doesn't support POSIX.
2372 "\\([-a-z0-9_=!?#$@~%&*+\\/:;.,]\\|\\w\\)+"
2373 "\\([-a-z0-9_=#$@~%&*+\\/]\\|\\w\\)"))
2374 "\\)")
2e398771 2375 "Regexp matching URLs. Set to nil to disable URL features in rcirc.")
bd43c990
RS
2376
2377(defun rcirc-browse-url (&optional arg)
2e398771 2378 "Prompt for URL to browse based on URLs in buffer."
02f47e86 2379 (interactive "P")
bd43c990
RS
2380 (let ((completions (mapcar (lambda (x) (cons x nil)) rcirc-urls))
2381 (initial-input (car rcirc-urls))
2382 (history (cdr rcirc-urls)))
2383 (browse-url (completing-read "rcirc browse-url: "
2384 completions nil nil initial-input 'history)
2385 arg)))
f8db61b2 2386\f
195eca78
SM
2387(defun rcirc-markup-timestamp (sender response)
2388 (goto-char (point-min))
0ffab1eb 2389 (insert (rcirc-facify (format-time-string rcirc-time-format)
195eca78 2390 'rcirc-timestamp)))
f8db61b2 2391
195eca78 2392(defun rcirc-markup-attributes (sender response)
f8db61b2
EZ
2393 (while (re-search-forward "\\([\C-b\C-_\C-v]\\).*?\\(\\1\\|\C-o\\)" nil t)
2394 (rcirc-add-face (match-beginning 0) (match-end 0)
2395 (case (char-after (match-beginning 1))
2396 (?\C-b 'bold)
2397 (?\C-v 'italic)
2398 (?\C-_ 'underline)))
2399 ;; keep the ^O since it could terminate other attributes
2400 (when (not (eq ?\C-o (char-before (match-end 2))))
2401 (delete-region (match-beginning 2) (match-end 2)))
2402 (delete-region (match-beginning 1) (match-end 1))
08fc78fe 2403 (goto-char (match-beginning 1)))
f8db61b2 2404 ;; remove the ^O characters now
142b4d90 2405 (goto-char (point-min))
f8db61b2
EZ
2406 (while (re-search-forward "\C-o+" nil t)
2407 (delete-region (match-beginning 0) (match-end 0))))
2408
195eca78 2409(defun rcirc-markup-my-nick (sender response)
f8db61b2 2410 (with-syntax-table rcirc-nick-syntax-table
0ffab1eb
TTN
2411 (while (re-search-forward (concat "\\b"
2412 (regexp-quote (rcirc-nick
195eca78 2413 (rcirc-buffer-process)))
f8db61b2
EZ
2414 "\\b")
2415 nil t)
92c4adc1 2416 (rcirc-add-face (match-beginning 0) (match-end 0)
f8db61b2
EZ
2417 'rcirc-nick-in-message)
2418 (when (string= response "PRIVMSG")
0ffab1eb 2419 (rcirc-add-face (point-min) (point-max)
195eca78
SM
2420 'rcirc-nick-in-message-full-line)
2421 (rcirc-record-activity (current-buffer) 'nick)))))
f8db61b2 2422
195eca78 2423(defun rcirc-markup-urls (sender response)
9ff90d99
DD
2424 (while (and rcirc-url-regexp ;; nil means disable URL catching
2425 (re-search-forward rcirc-url-regexp nil t))
f8db61b2 2426 (let ((start (match-beginning 0))
1ddd96f5
LL
2427 (end (match-end 0))
2428 (url (match-string-no-properties 0)))
2429 (make-button start end
2430 'face 'rcirc-url
2431 'follow-link t
2432 'rcirc-url url
2433 'action (lambda (button)
2434 (browse-url (button-get button 'rcirc-url))))
f8db61b2 2435 ;; record the url
1ddd96f5 2436 (push url rcirc-urls))))
195eca78
SM
2437
2438(defun rcirc-markup-keywords (sender response)
2439 (when (and (string= response "PRIVMSG")
2440 (not (string= sender (rcirc-nick (rcirc-buffer-process)))))
2441 (let* ((target (or rcirc-target ""))
2442 (keywords (delq nil (mapcar (lambda (keyword)
2443 (when (not (string-match keyword
2444 target))
2445 keyword))
2446 rcirc-keywords))))
2447 (when keywords
2448 (while (re-search-forward (regexp-opt keywords 'words) nil t)
2449 (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-keyword)
2450 (rcirc-record-activity (current-buffer) 'keyword))))))
2451
2452(defun rcirc-markup-bright-nicks (sender response)
f8db61b2
EZ
2453 (when (and rcirc-bright-nicks
2454 (string= response "NAMES"))
2455 (with-syntax-table rcirc-nick-syntax-table
2456 (while (re-search-forward (regexp-opt rcirc-bright-nicks 'words) nil t)
2457 (rcirc-add-face (match-beginning 0) (match-end 0)
2458 'rcirc-bright-nick)))))
195eca78
SM
2459
2460(defun rcirc-markup-fill (sender response)
2461 (when (not (string= response "372")) ; /motd
2462 (let ((fill-prefix
2463 (or rcirc-fill-prefix
2464 (make-string (- (point) (line-beginning-position)) ?\s)))
683b7dc6
GM
2465 (fill-column (- (cond ((eq rcirc-fill-column 'frame-width)
2466 (1- (frame-width)))
2467 (rcirc-fill-column
2468 rcirc-fill-column)
2469 (t fill-column))
2470 ;; make sure ... doesn't cause line wrapping
aebf69c8 2471 3)))
195eca78 2472 (fill-region (point) (point-max) nil t))))
bd43c990
RS
2473\f
2474;;; handlers
2475;; these are called with the server PROCESS, the SENDER, which is a
2476;; server or a user, depending on the command, the ARGS, which is a
2477;; list of strings, and the TEXT, which is the original server text,
2478;; verbatim
2479(defun rcirc-handler-001 (process sender args text)
2480 (rcirc-handler-generic process "001" sender args text)
adf794e4 2481 (with-rcirc-process-buffer process
8216fbaf
EZ
2482 (setq rcirc-connecting nil)
2483 (rcirc-reschedule-timeout process)
2484 (setq rcirc-server-name sender)
bd43c990
RS
2485 (setq rcirc-nick (car args))
2486 (rcirc-update-prompt)
72d2c2e3 2487 (if rcirc-auto-authenticate-flag
221ddf68
TH
2488 (if (and rcirc-authenticate-before-join
2489 ;; We have to ensure that there's an authentication
2490 ;; entry for that server. Else,
2491 ;; rcirc-authenticated-hook won't be triggered, and
2492 ;; autojoin won't happen at all.
2493 (let (auth-required)
2494 (dolist (s rcirc-authinfo auth-required)
2495 (when (string-match (car s) rcirc-server-name)
2496 (setq auth-required t)))))
72d2c2e3 2497 (progn
c47971d7 2498 (add-hook 'rcirc-authenticated-hook 'rcirc-join-channels-post-auth t t)
72d2c2e3
DD
2499 (rcirc-authenticate))
2500 (rcirc-authenticate)
2501 (rcirc-join-channels process rcirc-startup-channels))
2502 (rcirc-join-channels process rcirc-startup-channels))))
2503
2504(defun rcirc-join-channels-post-auth (process)
2505 "Join `rcirc-startup-channels' after authenticating."
2506 (with-rcirc-process-buffer process
adf794e4 2507 (rcirc-join-channels process rcirc-startup-channels)))
bd43c990
RS
2508
2509(defun rcirc-handler-PRIVMSG (process sender args text)
72d2c2e3 2510 (rcirc-check-auth-status process sender args text)
bd43c990
RS
2511 (let ((target (if (rcirc-channel-p (car args))
2512 (car args)
db58efbf 2513 sender))
bd43c990
RS
2514 (message (or (cadr args) "")))
2515 (if (string-match "^\C-a\\(.*\\)\C-a$" message)
2516 (rcirc-handler-CTCP process target sender (match-string 1 message))
2517 (rcirc-print process sender "PRIVMSG" target message t))
a0a5c583
GM
2518 ;; update nick linestamp
2519 (with-current-buffer (rcirc-get-buffer process target t)
2520 (rcirc-put-nick-channel process sender target rcirc-current-line))))
bd43c990
RS
2521
2522(defun rcirc-handler-NOTICE (process sender args text)
72d2c2e3 2523 (rcirc-check-auth-status process sender args text)
bd43c990
RS
2524 (let ((target (car args))
2525 (message (cadr args)))
adf794e4
EZ
2526 (if (string-match "^\C-a\\(.*\\)\C-a$" message)
2527 (rcirc-handler-CTCP-response process target sender
2528 (match-string 1 message))
2529 (rcirc-print process sender "NOTICE"
2530 (cond ((rcirc-channel-p target)
2531 target)
2532 ;;; -ChanServ- [#gnu] Welcome...
02f47e86 2533 ((string-match "\\[\\(#[^\] ]+\\)\\]" message)
adf794e4
EZ
2534 (match-string 1 message))
2535 (sender
a2524d26 2536 (if (string= sender (rcirc-server-name process))
db58efbf
EZ
2537 nil ; server notice
2538 sender)))
adf794e4 2539 message t))))
bd43c990 2540
72d2c2e3
DD
2541(defun rcirc-check-auth-status (process sender args text)
2542 "Check if the user just authenticated.
2543If authenticated, runs `rcirc-authenticated-hook' with PROCESS as
2544the only argument."
2545 (with-rcirc-process-buffer process
2546 (when (and (not rcirc-user-authenticated)
2547 rcirc-authenticate-before-join
2548 rcirc-auto-authenticate-flag)
2549 (let ((target (car args))
2550 (message (cadr args)))
2551 (when (or
2552 (and ;; nickserv
2553 (string= sender "NickServ")
2554 (string= target rcirc-nick)
2555 (member message
2556 (list
2557 (format "You are now identified for \C-b%s\C-b." rcirc-nick)
35b1c40c 2558 (format "You are successfully identified as \C-b%s\C-b." rcirc-nick)
72d2c2e3
DD
2559 "Password accepted - you are now recognized."
2560 )))
77f63d30
DD
2561 (and ;; quakenet
2562 (string= sender "Q")
2563 (string= target rcirc-nick)
c47971d7 2564 (string-match "\\`You are now logged in as .+\\.\\'" message)))
72d2c2e3
DD
2565 (setq rcirc-user-authenticated t)
2566 (run-hook-with-args 'rcirc-authenticated-hook process)
2567 (remove-hook 'rcirc-authenticated-hook 'rcirc-join-channels-post-auth t))))))
2568
bd43c990 2569(defun rcirc-handler-WALLOPS (process sender args text)
db58efbf 2570 (rcirc-print process sender "WALLOPS" sender (car args) t))
bd43c990
RS
2571
2572(defun rcirc-handler-JOIN (process sender args text)
db58efbf 2573 (let ((channel (car args)))
a0a5c583
GM
2574 (with-current-buffer (rcirc-get-buffer-create process channel)
2575 ;; when recently rejoining, restore the linestamp
2576 (rcirc-put-nick-channel process sender channel
2577 (let ((last-activity-lines
683b7dc6 2578 (rcirc-elapsed-lines process sender channel)))
a0a5c583
GM
2579 (when (and last-activity-lines
2580 (< last-activity-lines rcirc-omit-threshold))
827b77e9
DD
2581 (rcirc-last-line process sender channel))))
2582 ;; reset mode-line-process in case joining a channel with an
2583 ;; already open buffer (after getting kicked e.g.)
2584 (setq mode-line-process nil))
a0a5c583 2585
bd43c990
RS
2586 (rcirc-print process sender "JOIN" channel "")
2587
2588 ;; print in private chat buffer if it exists
a2524d26 2589 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
a0a5c583 2590 (rcirc-print process sender "JOIN" sender channel))))
bd43c990
RS
2591
2592;; PART and KICK are handled the same way
2593(defun rcirc-handler-PART-or-KICK (process response channel sender nick args)
a2524d26 2594 (rcirc-ignore-update-automatic nick)
bd43c990
RS
2595 (if (not (string= nick (rcirc-nick process)))
2596 ;; this is someone else leaving
a0a5c583
GM
2597 (progn
2598 (rcirc-maybe-remember-nick-quit process nick channel)
2599 (rcirc-remove-nick-channel process nick channel))
adf794e4
EZ
2600 ;; this is us leaving
2601 (mapc (lambda (n)
2602 (rcirc-remove-nick-channel process n channel))
2603 (rcirc-channel-nicks process channel))
2604
2605 ;; if the buffer is still around, make it inactive
2606 (let ((buffer (rcirc-get-buffer process channel)))
2607 (when buffer
195eca78 2608 (rcirc-disconnect-buffer buffer)))))
bd43c990
RS
2609
2610(defun rcirc-handler-PART (process sender args text)
a2524d26
EZ
2611 (let* ((channel (car args))
2612 (reason (cadr args))
2613 (message (concat channel " " reason)))
2614 (rcirc-print process sender "PART" channel message)
2615 ;; print in private chat buffer if it exists
2616 (when (rcirc-get-buffer (rcirc-buffer-process) sender)
2617 (rcirc-print process sender "PART" sender message))
2618
2619 (rcirc-handler-PART-or-KICK process "PART" channel sender sender reason)))
bd43c990
RS
2620
2621(defun rcirc-handler-KICK (process sender args text)
a2524d26
EZ
2622 (let* ((channel (car args))
2623 (nick (cadr args))
2624 (reason (caddr args))
2625 (message (concat nick " " channel " " reason)))
2626 (rcirc-print process sender "KICK" channel message t)
2627 ;; print in private chat buffer if it exists
2628 (when (rcirc-get-buffer (rcirc-buffer-process) nick)
2629 (rcirc-print process sender "KICK" nick message))
2630
2631 (rcirc-handler-PART-or-KICK process "KICK" channel sender nick reason)))
bd43c990 2632
a0a5c583
GM
2633(defun rcirc-maybe-remember-nick-quit (process nick channel)
2634 "Remember NICK as leaving CHANNEL if they recently spoke."
683b7dc6 2635 (let ((elapsed-lines (rcirc-elapsed-lines process nick channel)))
a0a5c583
GM
2636 (when (and elapsed-lines
2637 (< elapsed-lines rcirc-omit-threshold))
2638 (let ((buffer (rcirc-get-buffer process channel)))
2639 (when buffer
2640 (with-current-buffer buffer
683b7dc6
GM
2641 (let ((record (assoc-string nick rcirc-recent-quit-alist t))
2642 (line (rcirc-last-line process nick channel)))
a0a5c583
GM
2643 (if record
2644 (setcdr record line)
2645 (setq rcirc-recent-quit-alist
2646 (cons (cons nick line)
2647 rcirc-recent-quit-alist))))))))))
2648
bd43c990 2649(defun rcirc-handler-QUIT (process sender args text)
db58efbf
EZ
2650 (rcirc-ignore-update-automatic sender)
2651 (mapc (lambda (channel)
a0a5c583
GM
2652 ;; broadcast quit message each channel
2653 (rcirc-print process sender "QUIT" channel (apply 'concat args))
2654 ;; record nick in quit table if they recently spoke
2655 (rcirc-maybe-remember-nick-quit process sender channel))
db58efbf 2656 (rcirc-nick-channels process sender))
db58efbf 2657 (rcirc-nick-remove process sender))
bd43c990
RS
2658
2659(defun rcirc-handler-NICK (process sender args text)
db58efbf 2660 (let* ((old-nick sender)
bd43c990
RS
2661 (new-nick (car args))
2662 (channels (rcirc-nick-channels process old-nick)))
2c8abe90
AS
2663 ;; update list of ignored nicks
2664 (rcirc-ignore-update-automatic old-nick)
2665 (when (member old-nick rcirc-ignore-list)
2666 (add-to-list 'rcirc-ignore-list new-nick)
2667 (add-to-list 'rcirc-ignore-list-automatic new-nick))
bd43c990
RS
2668 ;; print message to nick's channels
2669 (dolist (target channels)
2670 (rcirc-print process sender "NICK" target new-nick))
2671 ;; update private chat buffer, if it exists
adf794e4
EZ
2672 (let ((chat-buffer (rcirc-get-buffer process old-nick)))
2673 (when chat-buffer
2674 (with-current-buffer chat-buffer
2675 (rcirc-print process sender "NICK" old-nick new-nick)
2676 (setq rcirc-target new-nick)
2677 (rename-buffer (rcirc-generate-new-buffer-name process new-nick)))))
bd43c990 2678 ;; remove old nick and add new one
adf794e4 2679 (with-rcirc-process-buffer process
bd43c990
RS
2680 (let ((v (gethash old-nick rcirc-nick-table)))
2681 (remhash old-nick rcirc-nick-table)
2682 (puthash new-nick v rcirc-nick-table))
2683 ;; if this is our nick...
2684 (when (string= old-nick rcirc-nick)
2685 (setq rcirc-nick new-nick)
adf794e4 2686 (rcirc-update-prompt t)
bd43c990
RS
2687 ;; reauthenticate
2688 (when rcirc-auto-authenticate-flag (rcirc-authenticate))))))
2689
2690(defun rcirc-handler-PING (process sender args text)
195eca78 2691 (rcirc-send-string process (concat "PONG :" (car args))))
bd43c990
RS
2692
2693(defun rcirc-handler-PONG (process sender args text)
2694 ;; do nothing
2695 )
2696
2697(defun rcirc-handler-TOPIC (process sender args text)
2698 (let ((topic (cadr args)))
2699 (rcirc-print process sender "TOPIC" (car args) topic)
2700 (with-current-buffer (rcirc-get-buffer process (car args))
2701 (setq rcirc-topic topic))))
2702
a2524d26
EZ
2703(defvar rcirc-nick-away-alist nil)
2704(defun rcirc-handler-301 (process sender args text)
2705 "RPL_AWAY"
2706 (let* ((nick (cadr args))
2707 (rec (assoc-string nick rcirc-nick-away-alist))
2708 (away-message (caddr args)))
2709 (when (or (not rec)
2710 (not (string= (cdr rec) away-message)))
2711 ;; away message has changed
2712 (rcirc-handler-generic process "AWAY" nick (cdr args) text)
2713 (if rec
2714 (setcdr rec away-message)
2715 (setq rcirc-nick-away-alist (cons (cons nick away-message)
2716 rcirc-nick-away-alist))))))
2717
c5aff743
DD
2718(defun rcirc-handler-317 (process sender args text)
2719 "RPL_WHOISIDLE"
2720 (let* ((nick (nth 1 args))
2721 (idle-secs (string-to-number (nth 2 args)))
2722 (idle-string
2723 (if (< idle-secs most-positive-fixnum)
2724 (format-seconds "%yy %dd %hh %mm %z%ss" idle-secs)
2725 "a very long time"))
2726 (signon-time (seconds-to-time (string-to-number (nth 3 args))))
2727 (signon-string (format-time-string "%c" signon-time))
2728 (message (format "%s idle for %s, signed on %s"
2729 nick idle-string signon-string)))
2730 (rcirc-print process sender "317" nil message t)))
2731
bd43c990
RS
2732(defun rcirc-handler-332 (process sender args text)
2733 "RPL_TOPIC"
adf794e4
EZ
2734 (let ((buffer (or (rcirc-get-buffer process (cadr args))
2735 (rcirc-get-temp-buffer-create process (cadr args)))))
2736 (with-current-buffer buffer
2737 (setq rcirc-topic (caddr args)))))
bd43c990
RS
2738
2739(defun rcirc-handler-333 (process sender args text)
027b979c
DD
2740 "333 says who set the topic and when.
2741Not in rfc1459.txt"
adf794e4
EZ
2742 (let ((buffer (or (rcirc-get-buffer process (cadr args))
2743 (rcirc-get-temp-buffer-create process (cadr args)))))
2744 (with-current-buffer buffer
2745 (let ((setter (caddr args))
2746 (time (current-time-string
2747 (seconds-to-time
2748 (string-to-number (cadddr args))))))
2749 (rcirc-print process sender "TOPIC" (cadr args)
2750 (format "%s (%s on %s)" rcirc-topic setter time))))))
bd43c990
RS
2751
2752(defun rcirc-handler-477 (process sender args text)
2753 "ERR_NOCHANMODES"
2754 (rcirc-print process sender "477" (cadr args) (caddr args)))
2755
2756(defun rcirc-handler-MODE (process sender args text)
2757 (let ((target (car args))
2758 (msg (mapconcat 'identity (cdr args) " ")))
2759 (rcirc-print process sender "MODE"
2760 (if (string= target (rcirc-nick process))
2761 nil
2762 target)
2763 msg)
2764
2765 ;; print in private chat buffers if they exist
2766 (mapc (lambda (nick)
db58efbf
EZ
2767 (when (rcirc-get-buffer process nick)
2768 (rcirc-print process sender "MODE" nick msg)))
adf794e4 2769 (cddr args))))
bd43c990
RS
2770
2771(defun rcirc-get-temp-buffer-create (process channel)
2772 "Return a buffer based on PROCESS and CHANNEL."
2773 (let ((tmpnam (concat " " (downcase channel) "TMP" (process-name process))))
2774 (get-buffer-create tmpnam)))
2775
2776(defun rcirc-handler-353 (process sender args text)
2777 "RPL_NAMREPLY"
0ba690bd
DD
2778 (let ((channel (nth 2 args))
2779 (names (or (nth 3 args) "")))
bd43c990
RS
2780 (mapc (lambda (nick)
2781 (rcirc-put-nick-channel process nick channel))
0ba690bd
DD
2782 (split-string names " " t))
2783 ;; create a temporary buffer to insert the names into
2784 ;; rcirc-handler-366 (RPL_ENDOFNAMES) will handle it
bd43c990
RS
2785 (with-current-buffer (rcirc-get-temp-buffer-create process channel)
2786 (goto-char (point-max))
2787 (insert (car (last args)) " "))))
2788
2789(defun rcirc-handler-366 (process sender args text)
2790 "RPL_ENDOFNAMES"
2791 (let* ((channel (cadr args))
2792 (buffer (rcirc-get-temp-buffer-create process channel)))
2793 (with-current-buffer buffer
2794 (rcirc-print process sender "NAMES" channel
c62bf05a 2795 (let ((content (buffer-substring (point-min) (point-max))))
aa1bc616 2796 (rcirc-sort-nicknames-join content " "))))
bd43c990
RS
2797 (kill-buffer buffer)))
2798
2799(defun rcirc-handler-433 (process sender args text)
2800 "ERR_NICKNAMEINUSE"
2801 (rcirc-handler-generic process "433" sender args text)
2802 (let* ((new-nick (concat (cadr args) "`")))
adf794e4 2803 (with-rcirc-process-buffer process
bd43c990
RS
2804 (rcirc-cmd-nick new-nick nil process))))
2805
2806(defun rcirc-authenticate ()
2807 "Send authentication to process associated with current buffer.
db58efbf 2808Passwords are stored in `rcirc-authinfo' (which see)."
bd43c990 2809 (interactive)
a2524d26 2810 (with-rcirc-server-buffer
db58efbf 2811 (dolist (i rcirc-authinfo)
a2524d26
EZ
2812 (let ((process (rcirc-buffer-process))
2813 (server (car i))
db58efbf
EZ
2814 (nick (caddr i))
2815 (method (cadr i))
2816 (args (cdddr i)))
77f63d30
DD
2817 (when (and (string-match server rcirc-server))
2818 (if (and (memq method '(nickserv chanserv bitlbee))
2819 (string-match nick rcirc-nick))
2820 ;; the following methods rely on the user's nickname.
2821 (case method
2822 (nickserv
2823 (rcirc-send-privmsg
2824 process
1be1d1e9 2825 (or (cadr args) "NickServ")
77f63d30
DD
2826 (concat "IDENTIFY " (car args))))
2827 (chanserv
2828 (rcirc-send-privmsg
2829 process
1be1d1e9 2830 "ChanServ"
77f63d30
DD
2831 (format "IDENTIFY %s %s" (car args) (cadr args))))
2832 (bitlbee
2833 (rcirc-send-privmsg
2834 process
1be1d1e9 2835 "&bitlbee"
77f63d30
DD
2836 (concat "IDENTIFY " (car args)))))
2837 ;; quakenet authentication doesn't rely on the user's nickname.
2838 ;; the variable `nick' here represents the Q account name.
2839 (when (eq method 'quakenet)
aebf69c8 2840 (rcirc-send-privmsg
77f63d30
DD
2841 process
2842 "Q@CServe.quakenet.org"
2843 (format "AUTH %s %s" nick (car args))))))))))
adf794e4 2844
bd43c990
RS
2845(defun rcirc-handler-INVITE (process sender args text)
2846 (rcirc-print process sender "INVITE" nil (mapconcat 'identity args " ") t))
2847
2848(defun rcirc-handler-ERROR (process sender args text)
2849 (rcirc-print process sender "ERROR" nil (mapconcat 'identity args " ")))
2850
2851(defun rcirc-handler-CTCP (process target sender text)
2852 (if (string-match "^\\([^ ]+\\) *\\(.*\\)$" text)
2853 (let* ((request (upcase (match-string 1 text)))
2854 (args (match-string 2 text))
bd43c990
RS
2855 (handler (intern-soft (concat "rcirc-handler-ctcp-" request))))
2856 (if (not (fboundp handler))
db58efbf
EZ
2857 (rcirc-print process sender "ERROR" target
2858 (format "%s sent unsupported ctcp: %s" sender text)
adf794e4 2859 t)
bd43c990 2860 (funcall handler process target sender args)
195eca78
SM
2861 (unless (or (string= request "ACTION")
2862 (string= request "KEEPALIVE"))
db58efbf 2863 (rcirc-print process sender "CTCP" target
adf794e4 2864 (format "%s" text) t))))))
bd43c990
RS
2865
2866(defun rcirc-handler-ctcp-VERSION (process target sender args)
2867 (rcirc-send-string process
db58efbf 2868 (concat "NOTICE " sender
adf794e4 2869 " :\C-aVERSION " rcirc-id-string
bd43c990
RS
2870 "\C-a")))
2871
2872(defun rcirc-handler-ctcp-ACTION (process target sender args)
2873 (rcirc-print process sender "ACTION" target args t))
2874
2875(defun rcirc-handler-ctcp-TIME (process target sender args)
2876 (rcirc-send-string process
db58efbf 2877 (concat "NOTICE " sender
bd43c990 2878 " :\C-aTIME " (current-time-string) "\C-a")))
adf794e4
EZ
2879
2880(defun rcirc-handler-CTCP-response (process target sender message)
2881 (rcirc-print process sender "CTCP" nil message t))
bd43c990 2882\f
adf794e4
EZ
2883(defgroup rcirc-faces nil
2884 "Faces for rcirc."
2885 :group 'rcirc
2886 :group 'faces)
2887
ad8121fe 2888(defface rcirc-my-nick ; font-lock-function-name-face
4b56d0fe
CY
2889 '((((class color) (min-colors 88) (background light)) :foreground "Blue1")
2890 (((class color) (min-colors 88) (background dark)) :foreground "LightSkyBlue")
2891 (((class color) (min-colors 16) (background light)) :foreground "Blue")
2892 (((class color) (min-colors 16) (background dark)) :foreground "LightSkyBlue")
2893 (((class color) (min-colors 8)) :foreground "blue" :weight bold)
2894 (t :inverse-video t :weight bold))
2895 "Rcirc face for my messages."
adf794e4 2896 :group 'rcirc-faces)
bd43c990 2897
ad8121fe
EZ
2898(defface rcirc-other-nick ; font-lock-variable-name-face
2899 '((((class grayscale) (background light))
4b56d0fe 2900 :foreground "Gray90" :weight bold :slant italic)
bd43c990 2901 (((class grayscale) (background dark))
4b56d0fe
CY
2902 :foreground "DimGray" :weight bold :slant italic)
2903 (((class color) (min-colors 88) (background light)) :foreground "DarkGoldenrod")
2904 (((class color) (min-colors 88) (background dark)) :foreground "LightGoldenrod")
2905 (((class color) (min-colors 16) (background light)) :foreground "DarkGoldenrod")
2906 (((class color) (min-colors 16) (background dark)) :foreground "LightGoldenrod")
2907 (((class color) (min-colors 8)) :foreground "yellow" :weight light)
2908 (t :weight bold :slant italic))
2909 "Rcirc face for other users' messages."
adf794e4 2910 :group 'rcirc-faces)
bd43c990 2911
02f47e86
MB
2912(defface rcirc-bright-nick
2913 '((((class grayscale) (background light))
4b56d0fe 2914 :foreground "LightGray" :weight bold :underline t)
02f47e86 2915 (((class grayscale) (background dark))
4b56d0fe
CY
2916 :foreground "Gray50" :weight bold :underline t)
2917 (((class color) (min-colors 88) (background light)) :foreground "CadetBlue")
2918 (((class color) (min-colors 88) (background dark)) :foreground "Aquamarine")
2919 (((class color) (min-colors 16) (background light)) :foreground "CadetBlue")
2920 (((class color) (min-colors 16) (background dark)) :foreground "Aquamarine")
2921 (((class color) (min-colors 8)) :foreground "magenta")
2922 (t :weight bold :underline t))
2923 "Rcirc face for nicks matched by `rcirc-bright-nicks'."
02f47e86
MB
2924 :group 'rcirc-faces)
2925
2926(defface rcirc-dim-nick
2927 '((t :inherit default))
4b56d0fe 2928 "Rcirc face for nicks in `rcirc-dim-nicks'."
02f47e86
MB
2929 :group 'rcirc-faces)
2930
ad8121fe
EZ
2931(defface rcirc-server ; font-lock-comment-face
2932 '((((class grayscale) (background light))
4b56d0fe 2933 :foreground "DimGray" :weight bold :slant italic)
bd43c990 2934 (((class grayscale) (background dark))
4b56d0fe 2935 :foreground "LightGray" :weight bold :slant italic)
ad8121fe 2936 (((class color) (min-colors 88) (background light))
4b56d0fe 2937 :foreground "Firebrick")
ad8121fe 2938 (((class color) (min-colors 88) (background dark))
4b56d0fe 2939 :foreground "chocolate1")
ad8121fe 2940 (((class color) (min-colors 16) (background light))
4b56d0fe 2941 :foreground "red")
ad8121fe 2942 (((class color) (min-colors 16) (background dark))
4b56d0fe
CY
2943 :foreground "red1")
2944 (((class color) (min-colors 8) (background light)))
2945 (((class color) (min-colors 8) (background dark)))
2946 (t :weight bold :slant italic))
2947 "Rcirc face for server messages."
adf794e4 2948 :group 'rcirc-faces)
bd43c990 2949
ad8121fe 2950(defface rcirc-server-prefix ; font-lock-comment-delimiter-face
db58efbf 2951 '((default :inherit rcirc-server)
ad8121fe
EZ
2952 (((class grayscale)))
2953 (((class color) (min-colors 16)))
2954 (((class color) (min-colors 8) (background light))
2955 :foreground "red")
2956 (((class color) (min-colors 8) (background dark))
2957 :foreground "red1"))
4b56d0fe 2958 "Rcirc face for server prefixes."
ad8121fe
EZ
2959 :group 'rcirc-faces)
2960
2961(defface rcirc-timestamp
4b56d0fe
CY
2962 '((t :inherit default))
2963 "Rcirc face for timestamps."
ad8121fe
EZ
2964 :group 'rcirc-faces)
2965
2966(defface rcirc-nick-in-message ; font-lock-keyword-face
4b56d0fe
CY
2967 '((((class grayscale) (background light)) :foreground "LightGray" :weight bold)
2968 (((class grayscale) (background dark)) :foreground "DimGray" :weight bold)
2969 (((class color) (min-colors 88) (background light)) :foreground "Purple")
2970 (((class color) (min-colors 88) (background dark)) :foreground "Cyan1")
2971 (((class color) (min-colors 16) (background light)) :foreground "Purple")
2972 (((class color) (min-colors 16) (background dark)) :foreground "Cyan")
2973 (((class color) (min-colors 8)) :foreground "cyan" :weight bold)
2974 (t :weight bold))
2975 "Rcirc face for instances of your nick within messages."
adf794e4 2976 :group 'rcirc-faces)
bd43c990 2977
4b56d0fe
CY
2978(defface rcirc-nick-in-message-full-line '((t :weight bold))
2979 "Rcirc face for emphasizing the entire message when your nick is mentioned."
92c4adc1 2980 :group 'rcirc-faces)
f8db61b2 2981
ad8121fe 2982(defface rcirc-prompt ; comint-highlight-prompt
4b56d0fe
CY
2983 '((((min-colors 88) (background dark)) :foreground "cyan1")
2984 (((background dark)) :foreground "cyan")
2985 (t :foreground "dark blue"))
2986 "Rcirc face for prompts."
adf794e4 2987 :group 'rcirc-faces)
bd43c990 2988
f8db61b2 2989(defface rcirc-track-nick
4b56d0fe
CY
2990 '((((type tty)) :inherit default)
2991 (t :inverse-video t))
2992 "Rcirc face used in the mode-line when your nick is mentioned."
f8db61b2
EZ
2993 :group 'rcirc-faces)
2994
4b56d0fe
CY
2995(defface rcirc-track-keyword '((t :weight bold))
2996 "Rcirc face used in the mode-line when keywords are mentioned."
f8db61b2
EZ
2997 :group 'rcirc-faces)
2998
4b56d0fe
CY
2999(defface rcirc-url '((t :weight bold))
3000 "Rcirc face used to highlight urls."
f8db61b2
EZ
3001 :group 'rcirc-faces)
3002
4b56d0fe
CY
3003(defface rcirc-keyword '((t :inherit highlight))
3004 "Rcirc face used to highlight keywords."
adf794e4 3005 :group 'rcirc-faces)
a2524d26 3006
bd43c990 3007\f
adf794e4 3008;; When using M-x flyspell-mode, only check words after the prompt
bd43c990
RS
3009(put 'rcirc-mode 'flyspell-mode-predicate 'rcirc-looking-at-input)
3010(defun rcirc-looking-at-input ()
3011 "Returns true if point is past the input marker."
3012 (>= (point) rcirc-prompt-end-marker))
3013\f
3014
3015(provide 'rcirc)
e636ae15 3016
bd43c990 3017;;; rcirc.el ends here