* riece-addon.el (riece-command-list-addons): Adjust width of 2nd
[riece] / lisp / riece-addon.el
1 ;;; riece-addon.el --- add-on management
2 ;; Copyright (C) 1998-2004 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 'font-lock)
28 (require 'riece-options)
29 (require 'riece-compat)
30 (require 'riece-misc)
31 (require 'riece-addon-modules)
32
33 (defgroup riece-addon-list nil
34   "Add-on listing."
35   :tag "Addon list"
36   :prefix "riece-addon-list-"
37   :group 'riece)
38
39 (defgroup riece-addon-list-faces nil
40   "Faces for riece-addon-list-mode."
41   :tag "Faces"
42   :prefix "riece-addon-list-"
43   :group 'riece-addon-list)
44
45 (defface riece-addon-list-enabled-face
46   '((((class color) (background dark))
47      (:foreground "PaleTurquoise" :bold t))
48     (t
49      (:bold t)))
50   "Face used for displaying the enabled addon."
51   :group 'riece-addon-list-faces)
52 (defvar riece-addon-list-enabled-face 'riece-addon-list-enabled-face)
53
54 (defface riece-addon-list-disabled-face
55   '((((class color) (background dark))
56      (:foreground "PaleTurquoise" :italic t))
57     (t
58      ()))
59   "Face used for displaying the disabled addon."
60   :group 'riece-addon-list-faces)
61 (defvar riece-addon-list-disabled-face 'riece-addon-list-disabled-face)
62
63 (defface riece-addon-list-uninstalled-face
64   '((t
65      (:italic t)))
66   "Face used for displaying the uninstalled addon."
67   :group 'riece-addon-list-faces)
68 (defvar riece-addon-list-uninstalled-face 'riece-addon-list-uninstalled-face)
69
70 (defface riece-addon-list-unloaded-face
71   '((t
72      (:italic t :inverse-video t)))
73   "Face used for displaying the unloaded addon."
74   :group 'riece-addon-list-faces)
75 (defvar riece-addon-list-unloaded-face 'riece-addon-list-unloaded-face)
76
77 (defface riece-addon-list-description-face
78   '((((class color)
79       (background dark))
80      (:foreground "lightyellow"))
81     (((class color)
82       (background light))
83      (:foreground "blue4"))
84     (t
85      ()))
86   "Face used for displaying the description addon."
87   :group 'riece-addon-list-faces)
88 (defvar riece-addon-list-description-face 'riece-addon-list-description-face)
89
90 (defcustom riece-addon-list-mark-face-alist
91   '((?+ . riece-addon-list-enabled-face)
92     (?- . riece-addon-list-disabled-face)
93     (?? . riece-addon-list-uninstalled-face)
94     (?  . riece-addon-list-unloaded-face))
95   "An alist mapping marks on riece-addon-list-buffer to faces."
96   :type 'list
97   :group 'riece-addon-list)
98
99 (defcustom riece-addon-list-font-lock-keywords
100   '(("^\\([-+? ] \\S-+\\)\\s-+\\(.*\\)"
101      (1 (cdr (assq (aref (match-string 1) 0)
102                    riece-addon-list-mark-face-alist)))
103      (2 riece-addon-list-description-face)))
104   "Default expressions to addon in riece-addon-list-mode."
105   :type '(repeat (list string))
106   :group 'riece-addon-list)
107
108 (defvar riece-addon-list-mode-map
109   (let ((keymap (make-sparse-keymap)))
110     (define-key keymap "+" 'riece-command-enable-addon)
111     (define-key keymap "-" 'riece-command-disable-addon)
112     (define-key keymap "i" 'riece-command-insinuate-addon)
113     (define-key keymap "u" 'riece-command-uninstall-addon)
114     (define-key keymap "U" 'riece-command-unload-addon)
115     (define-key keymap "n" 'next-line)
116     (define-key keymap "p" 'previous-line)
117     (define-key keymap " " 'scroll-up)
118     (define-key keymap [delete] 'scroll-down)
119     (define-key keymap "q" 'bury-buffer)
120     keymap))
121
122 (defun riece-load-and-build-addon-dependencies (addons)
123   (let ((load-path (cons riece-addon-directory load-path))
124         dependencies)
125     (while addons
126       (require (car addons))            ;error will be reported here
127       (let* ((requires-function
128               (intern-soft
129                (concat (symbol-name (car addons)) "-requires")))
130              (requires
131               (if (and requires-function
132                        (fboundp requires-function))
133                   (funcall requires-function)))
134              (pointer requires)
135              entry)
136         ;; Increment succs' pred count.
137         (if (setq entry (assq (car addons) dependencies))
138             (setcar (cdr entry) (+ (length requires) (nth 1 entry)))
139           (setq dependencies (cons (list (car addons) (length requires))
140                                    dependencies)))
141         ;; Merge pred's succs.
142         (while pointer
143           (if (setq entry (assq (car pointer) dependencies))
144               (setcdr (cdr entry) (cons (car addons) (nthcdr 2 entry)))
145             (setq dependencies (cons (list (car pointer) 0 (car addons))
146                                      dependencies)))
147           (setq pointer (cdr pointer))))
148       (setq addons (cdr addons)))
149     dependencies))
150
151 (defun riece-sort-addon-dependencies (dependencies)
152   (let ((pointer dependencies)
153         addons queue)
154     (while pointer
155       (if (zerop (nth 1 (car pointer)))
156           (setq dependencies (delq (car pointer) dependencies)
157                 queue (cons (car pointer) queue)))
158       (setq pointer (cdr pointer)))
159     (while queue
160       (setq addons (cons (cons (car (car queue)) (nthcdr 2 (car queue)))
161                          addons)
162             pointer (nthcdr 2 (car queue)))
163       (while pointer
164         (let* ((entry (assq (car pointer) dependencies))
165                (count (1- (nth 1 entry))))
166           (if (zerop count)
167               (setq dependencies (delq entry dependencies)
168                     queue (nconc queue (list entry)))
169             (setcar (cdr entry) count)))
170         (setq pointer (cdr pointer)))
171       (setq queue (cdr queue)))
172     (if dependencies
173         (error "Circular add-on dependency found: %S" dependencies))
174     (nreverse addons)))
175
176 (defun riece-resolve-addons (addons)
177   ;; Add files in riece-addon-directory to addons.
178   (if (file-directory-p riece-addon-directory)
179       (setq addons (nconc
180                     addons
181                     (mapcar
182                      (lambda (name)
183                        (unless (file-directory-p
184                                 (expand-file-name name riece-addon-directory))
185                          (intern (file-name-sans-extension name))))
186                      (directory-files riece-addon-directory nil "\\`[^.]")))))
187   ;; Sort & uniquify.
188   (setq addons (sort addons (lambda (symbol1 symbol2)
189                               (string-lessp (symbol-name symbol1)
190                                             (symbol-name symbol2)))))
191   (let ((pointer addons))
192     (while pointer
193       (if (memq (car pointer) (cdr pointer))
194           (setcar pointer nil))
195       (setq pointer (cdr pointer)))
196     (delq nil addons))
197   ;; Build & resolve dependencies.
198   (riece-sort-addon-dependencies
199    (riece-load-and-build-addon-dependencies addons)))
200
201 (defun riece-insinuate-addon-1 (addon verbose)
202   (if (get addon 'riece-addon-insinuated)
203       (if verbose
204           (message "Add-on %S is already insinuated" addon))
205     (require addon)
206     (funcall (intern (concat (symbol-name addon) "-insinuate")))
207     (put addon 'riece-addon-insinuated t)
208     (if verbose
209         (message "Add-on %S is insinuated" addon))
210     (unless (get addon 'riece-addon-default-disabled)
211       (riece-enable-addon addon t))))
212
213 (defun riece-insinuate-addon (addon &optional verbose)
214   (unless (assq addon riece-addon-dependencies)
215     (setq riece-addons (cons addon riece-addons)
216           riece-save-variables-are-dirty t
217           riece-addon-dependencies
218           (riece-resolve-addons
219            (cons addon (mapcar #'car riece-addon-dependencies)))))
220   (let ((pointer riece-addon-dependencies)
221         addons)
222     (while pointer
223       (unless (get (car (car pointer)) 'riece-addon-insinuated)
224         (setq addons (cons (car (car pointer)) addons)))
225       (if (eq (car (car pointer)) addon)
226           (setq pointer nil)
227         (setq pointer (cdr pointer))))
228     (setq addons (nreverse addons))
229     (if (and (> (length addons) 1)
230              (eq verbose 'ask)
231              (not (y-or-n-p (format "%s will be insinuated.  Continue? "
232                                     (mapconcat #'symbol-name addons ", ")))))
233         (error "Insinuate operation was cancelled"))
234     (while addons
235       (riece-insinuate-addon-1 (car addons) verbose)
236       (setq addons (cdr addons)))))
237
238 (defun riece-uninstall-addon (addon &optional verbose)
239   (if (not (get addon 'riece-addon-insinuated))
240       (if verbose
241           (message "Add-on %S is not insinuated" addon))
242     (let ((entry (assq addon riece-addon-dependencies))
243           (uninstall (intern-soft (concat (symbol-name addon) "-uninstall"))))
244       (if entry
245           (if (cdr entry)
246               (if (= (length (cdr entry)) 1)
247                   (error "%S depends on %S" (car (cdr entry)) addon)
248                 (error "%s depend on %S"
249                        (mapconcat #'symbol-name (cdr entry) ", ")
250                        addon))
251             (riece-disable-addon addon verbose)
252             (if (and uninstall
253                      (fboundp uninstall))
254                 (funcall uninstall))
255             (setq riece-addon-dependencies
256                   (delq entry riece-addon-dependencies))
257             (put addon 'riece-addon-insinuated nil)
258             (setq riece-addons (delq addon riece-addons)
259                   riece-save-variables-are-dirty t
260                   riece-addon-dependencies
261                   (riece-resolve-addons
262                    (delq addon (mapcar #'car riece-addon-dependencies))))))
263       (if verbose
264           (message "Add-on %S is uninstalled" addon)))))
265
266 (defun riece-enable-addon (addon &optional verbose)
267   (unless (get addon 'riece-addon-insinuated)
268     (error "Add-on %S is not insinuated" addon))
269   (if (get addon 'riece-addon-enabled)
270       (if verbose
271           (message "Add-on %S is already enabled" addon))
272     (let ((enable (intern-soft (concat (symbol-name addon) "-enable"))))
273       (if (and enable
274                (fboundp enable))
275           (funcall enable))
276       (put addon 'riece-addon-enabled t)
277       (if verbose
278           (message "Add-on %S enabled" addon)))))
279
280 (defun riece-disable-addon (addon &optional verbose)
281   (unless (get addon 'riece-addon-insinuated)
282     (error "Add-on %S is not insinuated" addon))
283   (if (not (get addon 'riece-addon-enabled))
284       (if verbose
285           (message "Add-on %S is already disabled" addon))
286     (let ((disable (intern-soft (concat (symbol-name addon) "-disable"))))
287       (if (and disable
288                (fboundp disable))
289           (funcall disable))
290       (put addon 'riece-addon-enabled nil)
291       (if verbose
292           (message "Add-on %S disabled" addon)))))
293
294 (put 'riece-addon-list-mode 'font-lock-defaults
295      '(riece-addon-list-font-lock-keywords t))
296
297 (defun riece-addon-list-mode ()
298   "Major mode for displaying addon list.
299 All normal editing commands are turned off."
300   (kill-all-local-variables)
301   (buffer-disable-undo)
302   (setq major-mode 'riece-addon-list-mode
303         mode-name "AddOns"
304         mode-line-buffer-identification
305         (riece-mode-line-buffer-identification '("Riece: %12b"))
306         truncate-lines t
307         buffer-read-only t)
308   (use-local-map riece-addon-list-mode-map)
309   (make-local-variable 'font-lock-defaults)
310   (setq font-lock-defaults '(riece-addon-list-font-lock-keywords t))
311   ;; In XEmacs, auto-initialization of font-lock is not effective
312   ;; if buffer-file-name is not set.
313   (font-lock-set-defaults)
314   (run-hooks 'riece-addon-list-mode-hook))
315
316 (defun riece-command-list-addons ()
317   (interactive)
318   (set-buffer (riece-get-buffer-create "*AddOn*" 'riece-addon-list-mode))
319   (riece-addon-list-mode)
320   (let ((inhibit-read-only t)
321         buffer-read-only
322         (pointer riece-addon-dependencies)
323         module-description-alist
324         description point longest)
325     (while pointer
326       (setq description (intern-soft (concat (symbol-name (car (car pointer)))
327                                              "-description"))
328             module-description-alist
329             (cons (cons (car (car pointer))
330                         (if (and description
331                                  (boundp description))
332                             (symbol-value description)
333                           "(no description)"))
334                   module-description-alist)
335             pointer (cdr pointer)))
336     (setq pointer riece-addon-modules)
337     (while pointer
338       (unless (assq (car (car pointer))
339                     module-description-alist)
340         (setq module-description-alist
341               (cons (car pointer) module-description-alist)))
342       (setq pointer (cdr pointer)))
343     (erase-buffer)
344     (riece-kill-all-overlays)
345     (setq pointer module-description-alist
346           longest "")
347     (while pointer
348       (if (> (length (symbol-name (car (car pointer))))
349              (length longest))
350           (setq longest (symbol-name (car (car pointer)))))
351       (setq pointer (cdr pointer)))
352     (setq pointer (sort module-description-alist
353                         (lambda (entry1 entry2)
354                           (string-lessp (symbol-name (car entry1))
355                                         (symbol-name (car entry2))))))
356     (while pointer
357       (setq point (point))
358       (insert (format (format "%%c %%-%dS %%s\n" (length longest))
359                       (if (not (featurep (car (car pointer))))
360                           ? 
361                         (if (not (get (car (car pointer))
362                                       'riece-addon-insinuated))
363                             ??
364                           (if (get (car (car pointer)) 'riece-addon-enabled)
365                               ?+
366                             ?-)))
367                       (car (car pointer))
368                       (cdr (car pointer))))
369       (put-text-property point (point) 'riece-addon (car (car pointer)))
370       (setq pointer (cdr pointer)))
371     (insert "
372 Symbols in the leftmost column:
373
374    +     The add-on is enabled.
375    -     The add-on is disabled.
376    ?     The add-on is not insinuated.
377          The add-on is not loaded.
378 ")
379     (insert (substitute-command-keys "
380 Useful keys:
381
382    `\\[riece-command-enable-addon]' to enable the current add-on.
383    `\\[riece-command-disable-addon]' to disable the current add-on.
384    `\\[riece-command-insinuate-addon]' to insinuate the current add-on.
385    `\\[riece-command-uninstall-addon]' to uninstall the current add-on.
386    `\\[riece-command-unload-addon]' to unload the current add-on.
387 "))
388     (goto-char (point-min))
389     (pop-to-buffer (current-buffer))
390     (delete-other-windows)))
391
392 (defun riece-addon-list-set-point (addon)
393   (let ((point (point-min)))
394     (while (and (not (eq (get-text-property point 'riece-addon) addon))
395                 (setq point (next-single-property-change point
396                                                          'riece-addon))))
397     (if point
398         (goto-char point))))
399
400 (defun riece-command-enable-addon (addon)
401   (interactive
402    (list
403     (or (if (eq major-mode 'riece-addon-list-mode)
404             (get-text-property (point) 'riece-addon))
405         (intern-soft
406          (completing-read "Add-on: "
407                           (mapcar (lambda (dependency)
408                                     (list (symbol-name (car dependency))))
409                                   riece-addon-dependencies)
410                           (lambda (pointer)
411                             (let ((enabled
412                                    (intern-soft (concat (car pointer)
413                                                         "-enabled"))))
414                               (and enabled
415                                    (null (symbol-value enabled)))))
416                           t)))))
417   (riece-enable-addon addon t)
418   (when (eq major-mode 'riece-addon-list-mode)
419     (riece-command-list-addons)
420     (riece-addon-list-set-point addon)))
421
422 (defun riece-command-disable-addon (addon)
423   (interactive
424    (list
425     (or (if (eq major-mode 'riece-addon-list-mode)
426             (get-text-property (point) 'riece-addon))
427         (intern-soft
428          (completing-read "Add-on: "
429                           (mapcar (lambda (dependency)
430                                     (list (symbol-name (car dependency))))
431                                   riece-addon-dependencies)
432                           (lambda (pointer)
433                             (let ((enabled
434                                    (intern-soft (concat (car pointer)
435                                                         "-enabled"))))
436                               (and enabled
437                                    (symbol-value enabled))))
438                           t)))))
439   (riece-disable-addon addon t)
440   (when (eq major-mode 'riece-addon-list-mode)
441     (riece-command-list-addons)
442     (riece-addon-list-set-point addon)))
443
444 (defun riece-command-insinuate-addon (addon)
445   (interactive
446    (list
447     (or (if (eq major-mode 'riece-addon-list-mode)
448             (get-text-property (point) 'riece-addon))
449         (intern-soft
450          (completing-read "Add-on: "
451                           (mapcar (lambda (dependency)
452                                     (list (symbol-name (car dependency))))
453                                   riece-addon-modules)
454                           (lambda (pointer)
455                             (not (get (car pointer) 'riece-addon-insinuated)))
456                           t)))))
457   (riece-insinuate-addon addon 'ask)
458   (when (eq major-mode 'riece-addon-list-mode)
459     (riece-command-list-addons)
460     (riece-addon-list-set-point addon)))
461
462 (defun riece-command-uninstall-addon (addon)
463   (interactive
464    (list
465     (or (if (eq major-mode 'riece-addon-list-mode)
466             (get-text-property (point) 'riece-addon))
467         (intern-soft
468          (completing-read "Add-on: "
469                           (mapcar (lambda (dependency)
470                                     (list (symbol-name (car dependency))))
471                                   riece-addon-dependencies)
472                           (lambda (pointer)
473                             (get (car pointer) 'riece-addon-insinuated))
474                           t)))))
475   (riece-uninstall-addon addon t)
476   (when (eq major-mode 'riece-addon-list-mode)
477     (riece-command-list-addons)
478     (riece-addon-list-set-point addon)))
479
480 (defun riece-command-unload-addon (addon)
481   (interactive
482    (list
483     (or (if (eq major-mode 'riece-addon-list-mode)
484             (get-text-property (point) 'riece-addon))
485         (intern-soft
486          (completing-read "Add-on: "
487                           (mapcar (lambda (dependency)
488                                     (list (symbol-name (car dependency))))
489                                   riece-addon-dependencies)
490                           (lambda (pointer)
491                             (get (car pointer) 'riece-addon-insinuated))
492                           t)))))
493   (riece-uninstall-addon addon t)
494   (if (get addon 'riece-addon-not-unloadable)
495       (message "Add-on %S is not allowed to unload" addon)
496     (unload-feature addon)
497     (message "Add-on %S is unloaded" addon))
498   (when (eq major-mode 'riece-addon-list-mode)
499     (riece-command-list-addons)
500     (riece-addon-list-set-point addon)))
501
502 (provide 'riece-addon)
503
504 ;;; riece-addon.el ends here