2f3b858dbe0cca265186e8bb24807e9ff4d0f75c
[riece] / lisp / riece.el
1 ;;; riece.el --- IRC client for Emacsen
2 ;; Copyright (C) 1998-2003 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Created: 1998-09-28
6 ;; Keywords: IRC, riece
7
8 ;; This file is part of Riece.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Code:
26
27 (require 'riece-filter)
28 (require 'riece-display)
29 (require 'riece-server)
30 (require 'riece-compat)
31 (require 'riece-commands)
32
33 (defvar riece-channel-list-mode-map (make-sparse-keymap))
34 (defvar riece-user-list-mode-map (make-sparse-keymap))
35
36 (defvar riece-dialogue-mode-map
37   (let ((keymap (make-keymap)))
38     (suppress-keymap keymap 'nodigit)
39     keymap))
40
41 (defvar riece-command-mode-map (make-keymap))
42 (defvar riece-command-map (make-sparse-keymap))
43
44 (defvar riece-command-mode-syntax-table nil)
45
46 (put 'riece-command-mode 'mode-class 'special)
47 (put 'riece-dialogue-mode 'mode-class 'special)
48 (put 'riece-others-mode 'derived-mode-parent 'riece-dialogue-mode)
49 (put 'riece-channel-list-mode 'mode-class 'special)
50 (put 'riece-user-list-mode 'mode-class 'special)
51 (put 'riece-channel-mode 'derived-mode-parent 'riece-dialogue-mode)
52
53 (defvar riece-buffer-alist
54   '((riece-command-buffer "*Commands*" riece-command-mode)
55     (riece-dialogue-buffer "*Dialogue*" riece-dialogue-mode)
56     (riece-others-buffer "*Others*" riece-others-mode)
57     (riece-channel-list-buffer "*Channels*" riece-channel-list-mode)
58     (riece-user-list-buffer " *Users*" riece-user-list-mode)))
59
60 (defvar riece-select-keys
61   '("1" riece-command-switch-to-channel-by-number-1
62     "2" riece-command-switch-to-channel-by-number-2
63     "3" riece-command-switch-to-channel-by-number-3
64     "4" riece-command-switch-to-channel-by-number-4
65     "5" riece-command-switch-to-channel-by-number-5
66     "6" riece-command-switch-to-channel-by-number-6
67     "7" riece-command-switch-to-channel-by-number-7
68     "8" riece-command-switch-to-channel-by-number-8
69     "9" riece-command-switch-to-channel-by-number-9
70     "0" riece-command-switch-to-channel-by-number-10
71     "\C-c1" riece-command-switch-to-channel-by-number-11
72     "\C-c2" riece-command-switch-to-channel-by-number-12
73     "\C-c3" riece-command-switch-to-channel-by-number-13
74     "\C-c4" riece-command-switch-to-channel-by-number-14
75     "\C-c5" riece-command-switch-to-channel-by-number-15
76     "\C-c6" riece-command-switch-to-channel-by-number-16
77     "\C-c7" riece-command-switch-to-channel-by-number-17
78     "\C-c8" riece-command-switch-to-channel-by-number-18
79     "\C-c9" riece-command-switch-to-channel-by-number-19
80     "\C-c0" riece-command-switch-to-channel-by-number-20))
81
82 ;;; Keymap macros. -- borrowed from `gnus-util.el'.
83 (defmacro riece-local-set-keys (&rest plist)
84   "Set the keys in PLIST in the current keymap."
85   `(riece-define-keys-1 (current-local-map) ',plist))
86
87 (defmacro riece-define-keys (keymap &rest plist)
88   "Assign KEYMAP keys from PLIST."
89   `(riece-define-keys-1 ',keymap ',plist))
90
91 (defmacro riece-define-keys-safe (keymap &rest plist)
92   "Assign KEYMAP keys from PLIST without overwriting previous definitions."
93   `(riece-define-keys-1 ',keymap ',plist t))
94
95 (put 'riece-define-keys 'lisp-indent-function 1)
96 (put 'riece-define-keys-safe 'lisp-indent-function 1)
97 (put 'riece-local-set-keys 'lisp-indent-function 1)
98
99 (defun riece-define-keys-1 (keymap plist &optional safe)
100   "Assign KEYMAP keys from PLIST.
101 If optional argument SAFE is nil, overwrite previous definitions."
102   (unless keymap
103     (error "Can't set keys in a null keymap"))
104   (cond
105    ((symbolp keymap)
106     (setq keymap (symbol-value keymap)))
107    ((keymapp keymap))
108    ((listp keymap)
109     (set (car keymap) nil)
110     (define-prefix-command (car keymap))
111     (define-key (symbol-value (nth 2 keymap)) (nth 1 keymap) (car keymap))
112     (setq keymap (symbol-value (car keymap)))))
113   (let (key)
114     (while plist
115       (if (symbolp (setq key (car plist)))
116           (setq key (symbol-value key)))
117       (setq plist (cdr plist))
118       (if (or (not safe)
119               (eq (lookup-key keymap key) 'undefined))
120           (define-key keymap key (car plist))
121         (car plist))
122       (setq plist (cdr plist)))))
123
124 (when t
125   (riece-define-keys riece-dialogue-mode-map
126     "\177" scroll-down
127     [delete] scroll-down
128     [backspace] scroll-down
129     [return] scroll-up
130     " " scroll-up
131     "$" end-of-buffer
132     "/" riece-command-raw
133     ">" end-of-buffer
134     "<" beginning-of-buffer
135     "\C-ta" riece-command-toggle-away
136     "c" riece-command-select-command-buffer
137     "f" riece-command-finger
138     "\C-tf" riece-command-toggle-freeze
139     "\C-to" riece-command-toggle-own-freeze
140     "\C-tu" riece-command-toggle-user-list-buffer-mode
141     "\C-tc" riece-command-toggle-channel-buffer-mode
142     "\C-tC" riece-command-toggle-channel-list-buffer-mode
143     "\C-tl" riece-command-change-layout
144     "i" riece-command-invite
145     "j" riece-command-join
146     "\C-k" riece-command-kick
147     "l" riece-command-list
148     "M" riece-command-change-mode
149     "n" riece-command-change-nickname
150     "N" riece-command-names
151     "o" other-window
152     "O" riece-command-open-server
153     "C" riece-command-close-server
154     "M" riece-command-universal-server-name-argument
155     "q" riece-command-quit
156     "r" riece-command-configure-windows
157     "x" riece-command-copy-region
158     "t" riece-command-topic
159     "w" riece-command-who)
160
161   (riece-define-keys riece-command-mode-map
162     "\r" riece-command-enter-message
163     [(control return)] riece-command-enter-message-as-notice)
164
165   (riece-define-keys (riece-command-map "\C-c" riece-command-mode-map)
166     "\177" riece-command-scroll-down
167     [delete] riece-command-scroll-down
168     [backspace] riece-command-scroll-down
169     " " riece-command-scroll-up
170     "$" riece-command-end-of-buffer
171     ">" riece-command-next-channel
172     "<" riece-command-previous-channel
173     "\C-j" riece-command-next-channel
174     "\C-n" riece-command-names
175     "l" riece-command-list
176     "\C-m" riece-command-change-mode
177     "o" riece-command-set-operators
178     "\C-p" riece-command-part
179     "r" riece-command-configure-windows
180     "v" riece-command-set-speakers)
181   (set-keymap-parent riece-command-map riece-dialogue-mode-map)
182
183   (riece-define-keys riece-user-list-mode-map
184     "o" riece-command-set-operators
185     "v" riece-command-set-voices
186     "f" riece-command-finger
187     " " riece-command-nick-scroll-up
188     "\177" riece-command-nick-scroll-down
189     [delete] riece-command-nick-scroll-down
190     [backspace] riece-command-nick-scroll-down
191     "c" riece-command-select-command-buffer)
192
193   (riece-define-keys riece-channel-list-mode-map
194     ">" riece-command-next-channel
195     "<" riece-command-previous-channel
196     "o" other-window
197     "c" riece-command-select-command-buffer)
198
199   (riece-define-keys-1 riece-dialogue-mode-map riece-select-keys)
200   (riece-define-keys-1 riece-channel-list-mode-map riece-select-keys))
201
202 (defun riece-read-variables-files (&optional file)
203   "Read variables FILEs."
204   (or (file-directory-p riece-directory)
205       (make-directory riece-directory))
206   (let ((files (if file
207                    (setq riece-variables-file file
208                          riece-variables-files (list file))
209                  riece-variables-files)))
210     (while files
211       (condition-case nil
212           (load (expand-file-name (car files)))
213         (file-error nil))
214       (setq files (cdr files)))))
215
216 (defvar print-quoted)
217 (defvar print-escape-multibyte)
218 (defun riece-save-variables-files ()
219   "Save current settings to `riece-variables-file'."
220   (with-temp-file riece-saved-variables-file
221     (let ((print-quoted t)
222           (print-readably t)
223           print-escape-multibyte
224           print-level
225           print-length
226           (variables riece-saved-forms))
227       (while variables
228         (prin1 `(setq ,(car variables)
229                       ',(symbol-value (car variables)))
230                (current-buffer))
231         (insert "\n")
232         (setq variables (cdr variables)))))
233   (setq riece-save-variables-are-dirty nil))
234
235 ;;;###autoload
236 (defun riece (&optional confirm)
237   "Connect to the IRC server and start chatting.
238 If optional argument CONFIRM is non-nil, ask which IRC server to connect."
239   (interactive "P")
240   (riece-read-variables-files (if noninteractive
241                                   (car command-line-args-left)))
242   (riece-insinuate-addons riece-addons)
243   (run-hooks 'riece-after-load-startup-hook)
244   (if (riece-server-opened)
245       (error "Already running"))
246   (if (or confirm (null riece-server))
247       (setq riece-server (completing-read "Server: " riece-server-alist)))
248   (if (stringp riece-server)
249       (setq riece-server (riece-server-name-to-server riece-server)))
250   (riece-create-buffers)
251   (switch-to-buffer riece-command-buffer)
252   (riece-redisplay-buffers)
253   (riece-open-server riece-server "")
254   (run-hooks 'riece-startup-hook)
255   (message "%s" (substitute-command-keys "Type \\[describe-mode] for help")))
256
257 (defun riece-exit ()
258   (if riece-save-variables-are-dirty
259       (riece-save-variables-files))
260   (while riece-buffer-list
261     (if (and (get-buffer (car riece-buffer-list))
262              (buffer-live-p (car riece-buffer-list)))
263         (funcall riece-buffer-dispose-function (car riece-buffer-list)))
264     (setq riece-buffer-list (cdr riece-buffer-list)))
265   (setq riece-server nil
266         riece-current-channels nil
267         riece-current-channel nil
268         riece-channel-buffer-alist nil
269         riece-user-indicator nil
270         riece-long-channel-indicator "None"
271         riece-channel-list-indicator "No channel"
272         riece-away-indicator "-"
273         riece-operator-indicator "-"
274         riece-freeze-indicator "-")
275   (delete-other-windows)
276   (run-hooks 'riece-exit-hook))
277
278 (defun riece-command-mode ()
279   "Major mode for Riece.  Normal edit function are available.
280 Typing Return or Linefeed enters the current line in the dialogue.
281 The following special commands are available:
282 For a list of the generic commands type \\[riece-command-generic] ? RET.
283 \\{riece-command-mode-map}"
284   (interactive)
285   (kill-all-local-variables)
286
287   (setq riece-away-indicator "-"
288         riece-operator-indicator "-"
289         major-mode 'riece-command-mode
290         mode-name "Commands"
291         mode-line-buffer-identification
292         (riece-mode-line-buffer-identification
293          '("Riece: "
294            riece-away-indicator
295            riece-operator-indicator
296            riece-freeze-indicator
297            " "
298            riece-user-indicator
299            " "
300            riece-channel-indicator)))
301   (riece-simplify-mode-line-format)
302   (use-local-map riece-command-mode-map)
303
304   (unless riece-command-mode-syntax-table
305     (setq riece-command-mode-syntax-table
306           (copy-syntax-table (syntax-table)))
307     (set-syntax-table riece-command-mode-syntax-table)
308     (mapcar
309      (lambda (c) (modify-syntax-entry c "w"))
310      "^[]{}'`"))
311
312   (run-hooks 'riece-command-mode-hook))
313   
314 (defun riece-dialogue-mode ()
315   "Major mode for displaying the IRC dialogue.
316 All normal editing commands are turned off.
317 Instead, these commands are available:
318 \\{riece-dialogue-mode-map}"
319   (kill-all-local-variables)
320   (make-local-variable 'riece-freeze)
321   (make-local-variable 'tab-stop-list)
322   (setq riece-freeze riece-default-freeze
323         riece-away-indicator "-"
324         riece-operator-indicator "-"
325         major-mode 'riece-dialogue-mode
326         mode-name "Dialogue"
327         mode-line-buffer-identification
328         (riece-mode-line-buffer-identification
329          '("Riece: "
330            riece-away-indicator
331            riece-operator-indicator
332            riece-freeze-indicator
333            " "
334            riece-channel-list-indicator " "))
335         buffer-read-only t
336         tab-stop-list riece-tab-stop-list)
337   (riece-simplify-mode-line-format)
338   (use-local-map riece-dialogue-mode-map)
339   (buffer-disable-undo)
340   (run-hooks 'riece-dialogue-mode-hook))
341
342 (define-derived-mode riece-others-mode riece-dialogue-mode
343   "Others"
344   "Major mode for displaying the IRC others message except current channel.
345 All normal editing commands are turned off.
346 Instead, these commands are available:
347 \\{riece-others-mode-map}")
348
349 (define-derived-mode riece-channel-mode riece-dialogue-mode
350   "Channel"
351   "Major mode for displaying the IRC current channel buffer.
352 All normal editing commands are turned off.
353 Instead, these commands are available:
354 \\{riece-channel-mode-map}"
355   (setq mode-line-buffer-identification
356         (riece-mode-line-buffer-identification
357          '("Riece: "
358            riece-away-indicator
359            riece-operator-indicator
360            riece-freeze-indicator
361            " "
362            riece-long-channel-indicator))))
363
364 (defun riece-channel-list-mode ()
365   "Major mode for displaying channel list.
366 All normal editing commands are turned off."
367   (kill-all-local-variables)
368   (buffer-disable-undo)
369   (setq major-mode 'riece-channel-list-mode
370         mode-name "Channels"
371         mode-line-buffer-identification
372         (riece-mode-line-buffer-identification '("Riece: "))
373         truncate-lines t
374         buffer-read-only t)
375   (make-local-hook 'riece-update-buffer-functions)
376   (add-hook 'riece-update-buffer-functions
377             'riece-update-channel-list-buffer nil t)
378   (use-local-map riece-channel-list-mode-map)
379   (run-hooks 'riece-channel-list-mode-hook))
380
381 (defun riece-user-list-mode ()
382   "Major mode for displaying members in the IRC current channel buffer.
383 All normal editing commands are turned off.
384 Instead, these commands are available:
385 \\{riece-user-list-mode-map}"
386   (kill-all-local-variables)
387   (buffer-disable-undo)
388   (setq major-mode 'riece-user-list-mode
389         mode-name "User list"
390         mode-line-buffer-identification
391         (riece-mode-line-buffer-identification
392          '("Riece: " riece-long-channel-indicator " "))
393         truncate-lines t
394         buffer-read-only t)
395   (if (boundp 'transient-mark-mode)
396       (set (make-local-variable 'transient-mark-mode) t))
397   (make-local-hook 'riece-update-buffer-functions)
398   (add-hook 'riece-update-buffer-functions
399             'riece-update-user-list-buffer nil t)
400   (use-local-map riece-user-list-mode-map)
401   (run-hooks 'riece-user-list-mode-hook))
402
403 (defun riece-create-buffers ()
404   (let ((alist riece-buffer-alist))
405     (while alist
406       (save-excursion
407         (set-buffer (apply #'riece-get-buffer-create
408                            (cdr (car alist))))
409         (set (car (car alist)) (current-buffer))
410         (unless (or (null (nth 2 (car alist)))
411                     (eq major-mode (nth 2 (car alist))))
412           (funcall (nth 2 (car alist))))
413         (setq alist (cdr alist))))))
414
415 (defun riece-load-and-build-addon-dependencies (addons)
416   (let ((load-path (cons riece-addon-directory load-path))
417         dependencies)
418     (while addons
419       (require (car addons))            ;error will be reported here
420       (let* ((requires
421               (funcall (or (intern-soft
422                             (concat (symbol-name (car addons)) "-requires"))
423                            #'ignore)))
424              (pointer requires)
425              entry)
426         ;; Increment succs' pred count.
427         (if (setq entry (assq (car addons) dependencies))
428             (setcar (cdr entry) (+ (length requires) (nth 1 entry)))
429           (setq dependencies (cons (list (car addons) (length requires))
430                                    dependencies)))
431         ;; Merge pred's succs.
432         (while pointer
433           (if (setq entry (assq (car pointer) dependencies))
434               (setcdr (cdr entry)
435                       (cons (car addons) (nthcdr 2 entry)))
436             (setq dependencies (cons (list (car pointer) 0 (car addons))
437                                      dependencies)))
438           (setq pointer (cdr pointer))))
439       (setq addons (cdr addons)))
440     dependencies))
441
442 (defun riece-insinuate-addons (addons)
443   (let ((pointer addons)
444         dependencies queue)
445     ;; Uniquify, first.
446     (while pointer
447       (if (memq (car pointer) (cdr pointer))
448           (setcar pointer nil))
449       (setq pointer (cdr pointer)))
450     (setq dependencies (riece-load-and-build-addon-dependencies
451                         (delq nil addons))
452           pointer dependencies)
453     ;; Sort them.
454     (while pointer
455       (if (zerop (nth 1 (car pointer)))
456           (setq dependencies (delq (car pointer) dependencies)
457                 queue (cons (car pointer) queue)))
458       (setq pointer (cdr pointer)))
459     (setq addons nil)
460     (while queue
461       (setq addons (cons (car (car queue)) addons)
462             pointer (nthcdr 2 (car queue)))
463       (while pointer
464         (let* ((entry (assq (car pointer) dependencies))
465                (count (1- (nth 1 entry))))
466           (if (zerop count)
467               (progn
468                 (setq dependencies (delq entry dependencies)
469                       queue (nconc queue (list entry))))
470             (setcar (cdr entry) count)))
471         (setq pointer (cdr pointer)))
472       (setq queue (cdr queue)))
473     (if dependencies
474         (error "Circular add-on dependency found"))
475     (setq addons (nreverse addons))
476     (while addons
477       (require (car addons))            ;implicit dependency
478       (funcall (intern (concat (symbol-name (car addons)) "-insinuate")))
479       (if riece-debug
480           (message "Add-on %S is loaded" (car addons)))
481       (setq addons (cdr addons)))))
482
483 (provide 'riece)
484
485 ;;; riece.el ends here