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