initial import into CVS
[elisp/initz.git] / lisp / initz-list.el
1 ;;; initz-list.el --- File list mode.
2
3 ;; Copyright (C) 2002 OHASHI Akira <bg66@koka-in.org>
4
5 ;; Author: OHASHI Akira <bg66@koka-in.org>
6 ;; Keywords: startup, init
7
8 ;; This file is part of Initz.
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
26 ;;; Commentary:
27 ;;
28
29 ;;; Code:
30
31 (require 'emu)
32 (require 'initz-globals)
33 (require 'initz)
34
35 (defvar initz-list-mode-map nil
36   "Local map for initz list buffers.")
37 (unless initz-list-mode-map
38     (let ((map (make-sparse-keymap)))
39       (define-key map mouse-button-2 'initz-list-find-file-mouse)
40       (define-key map "n" 'initz-list-next-line)
41       (define-key map "p" 'initz-list-previous-line)
42       (define-key map "h" 'backward-char)
43       (define-key map "j" 'initz-list-next-line)
44       (define-key map "k" 'initz-list-previous-line)
45       (define-key map "l" 'forward-char)
46       (define-key map " " 'initz-list-find-file)
47       (define-key map "\C-m" 'initz-list-find-file)
48       (define-key map "B" 'initz-list-byte-compile-file)
49       (define-key map "D" 'initz-list-delete-file)
50       (define-key map "L" 'initz-list-load-file)
51       (define-key map "N" 'initz-list-new-file)
52       (define-key map "S" 'initz-startup)
53       (define-key map "q" 'initz-list-quit)
54       (setq initz-list-mode-map map)))
55
56 (defvar initz-list-node-map nil)
57 (unless initz-list-node-map
58   (let ((map (make-sparse-keymap)))
59     (set-keymap-parent map initz-list-mode-map)
60     (define-key map mouse-button-2 'initz-list-node-click)
61     (define-key map " " 'initz-list-node-enter)
62     (define-key map "\C-m" 'initz-list-node-enter)
63     (setq initz-list-node-map map)))
64
65 (defvar initz-list-mode-hook nil
66   "Normal hook run when entering initz-list-mode.")
67
68 (defconst initz-list-mode-name "Initz List")
69
70 (defconst initz-list-buffer-name "*Initz List*")
71
72 (defconst initz-list-delete-file-ask-message-format
73   "Delete %s? ")
74
75 (defconst initz-list-input-dir-message-format
76   "Dir[%s] (%s): ")
77
78 (defconst initz-list-input-module-message-format
79   "Module[%s]: ")
80
81 (defconst initz-list-new-file-illegal-message
82   "Module name is illegal.")
83
84 (defconst initz-list-new-file-exists-message
85   "File already exists.")
86
87 (defconst initz-list-new-file-provided-message
88   "Module is already provided.")
89
90 (defconst initz-list-new-file-comment-message-format
91   ";;; %s --- init file for %s.\n\n\n\n")
92
93 (defconst initz-list-new-file-provide-message-format
94   "(provide '%s)\n")
95
96 (defconst initz-list-click-message-format
97   "Click %s on the module name to select it.\n")
98
99 (defconst initz-list-enter-message-format
100   "In this buffer, type %s to select the module name under point.\n")
101
102 ;; Initz list mode is suitable only for specially formatted data.
103 (put 'initz-list-mode 'mode-class 'special)
104
105 (defadvice sort-build-lists (after sort-including-newline activate)
106   (when (or (eq major-mode 'initz-list-mode)
107             (eq major-mode 'initz-error-mode))
108     (setq ad-return-value
109           (mapcar
110            (function (lambda (list)
111                        (cons (cons (caar list) (1+ (cdar list)))
112                              (cons (cadr list) (1+ (cddr list))))))
113            ad-return-value))))
114
115 (defun initz-list-delete-whole-line ()
116   "Delete whole line at point."
117   (setq buffer-read-only nil)
118   (delete-region (progn (beginning-of-line) (point))
119                  (progn (forward-line 1) (point)))
120   (set-buffer-modified-p nil)
121   (setq buffer-read-only t))
122
123 (defun initz-list-get-dir ()
124   "Return the dir at point."
125   (save-excursion
126     (end-of-line)
127     (when (re-search-backward "^\\[[-+]\\] \\([^ :]+\\)" nil t)
128         (match-string 1))))
129
130 (defun initz-list-input-dir (&optional default)
131   "Input the dir."
132   (let* ((completing-list (mapcar
133                            (function (lambda (list)
134                                        (symbol-name (car list))))
135                            initz-init-alist))
136          (default (if (stringp default) default "misc")))
137     (completing-read
138      (format initz-list-input-dir-message-format
139              initz-directory default)
140      (mapcar
141       (function (lambda (name)
142                   (cons name name)))
143       completing-list)
144      nil t nil nil default)))
145
146 (defun initz-list-input-module (dir)
147   "Input the module."
148   (let ((init (initz-get-init-value (intern dir) 'prefix)))
149     (setq init (initz-trim-separator init))
150     (unless (string= init initz-null-string)
151       (setq init (concat init initz-separator-string)))
152     (if initz-list-input-module-completing
153         (completing-read
154          (format initz-list-input-module-message-format dir)
155          (mapcar
156           (function (lambda (feature)
157                       (let ((name (symbol-name feature)))
158                         (cons name name))))
159           features)
160          nil nil init)
161       (read-string
162          (format initz-list-input-module-message-format dir)
163          init))))
164
165 (defun initz-list-insert-file (dir startup-file)
166   "Insert the STARTUP-FILE at DIR section."
167   ;; FIXME: Delete `save-excursion' and fix the next `FIXME'.
168   (save-excursion
169     (goto-char (point-min))
170     (when (re-search-forward (concat "^\\[[-+]\\] " dir "[ :]") nil t)
171       (beginning-of-line)
172       (let ((status (get-text-property (point) :status)))
173         (when (eq status 'expand)
174           (let (sort-start)
175             (forward-line 1)
176             (setq sort-start (point))
177             (if (re-search-forward "^\\[[-+]\\] " nil t)
178                 (beginning-of-line)
179               (re-search-forward "\\'" nil t))
180             (setq buffer-read-only nil)
181             (insert-char ?\  4)
182             (let ((start (point)))
183               (insert (initz-get-module-name startup-file) "\n")
184               (add-text-properties start (1- (point))
185                                    `(face initz-list-module-face
186                                           mouse-face highlight
187                                           start-open t rear-nonsticky t
188                                           help-echo ,startup-file))
189               (put-text-property start (point) :file startup-file))
190             (sort-lines nil sort-start (point)))
191           (set-buffer-modified-p nil)
192           (setq buffer-read-only t)
193           ;; FIXME: Move to the line inserted now
194           )))))
195
196 (defun initz-list-node-insert (node status)
197   (let ((prefix (initz-get-init-value node 'prefix))
198         (start (point)))
199     (setq prefix (initz-trim-separator prefix))
200     ;; `prefix' will be broken.
201     (if (string= prefix initz-null-string)
202         (setq prefix initz-null-string)
203       (setq prefix (concat " (" prefix ")")))
204     (insert "[" (if (eq status 'expand) "-" "+") "] "
205             (symbol-name node) prefix ":\n")
206     (add-text-properties start (+ start 3)
207                          `(face initz-list-node-face
208                            mouse-face highlight
209                            local-map ,initz-list-node-map
210                            keymap ,initz-list-node-map
211                            start-open t rear-nonsticky t
212                            :node ,node
213                            :status ,status))))
214
215 (defun initz-list-node-collapse (node)
216   (save-excursion
217     (setq buffer-read-only nil)
218     (goto-char (point-min))
219     (if (re-search-forward (concat "^\\[-\\] "
220                                    (symbol-name node) "[ :]") nil t)
221         (let ((start (progn (beginning-of-line) (point)))
222               end)
223           (forward-line 1)
224           (if (re-search-forward "^\\[[-+]\\] " nil t)
225               (progn
226                 (beginning-of-line)
227                 (setq end (point)))
228             (setq end (point-max)))
229           (delete-region start end))
230       (goto-char (point-max)))
231     (initz-list-node-insert node 'collapse)
232     (set-buffer-modified-p nil)
233     (setq buffer-read-only t)))
234
235 (defun initz-list-node-expand (node)
236   (save-excursion
237     (setq buffer-read-only nil)
238     (goto-char (point-min))
239     (if (re-search-forward (concat "^\\[\\+\\] "
240                                    (symbol-name node) "[ :]") nil t)
241         (delete-region (progn (beginning-of-line) (point))
242                        (progn (forward-line 1) (point)))
243       (goto-char (point-max)))
244     (initz-list-node-insert node 'expand)
245     (let ((sort-start (point))
246           (initz-features (initz-features)))
247       (mapc
248        (function (lambda (file)
249                    (let* ((module (initz-get-module-name file))
250                           (loaded (memq (intern module) initz-features))
251                           start)
252                      (insert-char ?\  4)
253                      (setq start (point))
254                      (insert module)
255                      (when loaded (insert initz-list-loaded-mark))
256                      (insert "\n")
257                      (add-text-properties
258                       start (1- (point))
259                       `(face ,(if loaded
260                                   'initz-list-module-face 
261                                 'initz-list-unloaded-module-face)
262                         mouse-face highlight
263                         start-open t rear-nonsticky t
264                         help-echo ,file))
265                      (put-text-property start (point) :file file))))
266        (initz-get-files 'startup node))
267       (sort-lines nil sort-start (point)))
268     (set-buffer-modified-p nil)
269     (setq buffer-read-only t)))
270
271 (defun initz-list-node-enter ()
272   (interactive)
273   (let ((node (get-text-property (point) :node))
274         (status (get-text-property (point) :status)))
275     (when (and node status)
276       (if (eq status 'expand)
277           (initz-list-node-collapse node)
278         (initz-list-node-expand node))
279       (forward-char 1))))
280
281 (defun initz-list-node-click (e)
282   (interactive "e")
283   (mouse-set-point e)
284   (initz-list-node-enter))
285
286 (defun initz-list-next-line (&optional arg)
287   (interactive)
288   (if (integerp arg)
289       (next-line arg)
290     (next-line 1))
291   (beginning-of-line)
292   (let ((start (re-search-forward "^\\(    \\|\\[\\|\\)" nil t)))
293     (when (integer-or-marker-p start)
294         (goto-char start))))
295
296 (defun initz-list-previous-line ()
297   (interactive)
298   (initz-list-next-line -1))
299
300 (defun initz-list-print-file ()
301   "Print the file name under point."
302   (interactive)
303   (let ((file (get-text-property (point) :file)))
304     (and file
305          (initz-message-no-log file))))
306
307 (defun initz-list-find-file ()
308   "View the file under point."
309   (interactive)
310   (let ((file (get-text-property (point) :file)))
311     (and file
312          (find-file-other-window file))))
313
314 (defun initz-list-find-file-mouse (e)
315   "View the file under clicked point."
316   (interactive "e")
317   (mouse-set-point e)
318   (unless (eolp)
319     (initz-list-find-file)))
320
321 (defun initz-list-byte-compile-file ()
322   "Byte-compile the file under point."
323   (interactive)
324   (let ((file (get-text-property (point) :file)))
325     (when file
326       (condition-case nil
327           (when (save-window-excursion
328                   (byte-compile-file file))
329             (let* ((compile-file (initz-get-correspondence-file file))
330                    (startup-directory (file-name-directory file))
331                    (flavor-directory (file-name-directory compile-file)))
332               (install-file (file-name-nondirectory compile-file)
333                             startup-directory flavor-directory t t))
334             (setq initz-compile-error-files
335                   (delete file initz-compile-error-files)))
336         (error)))))
337
338 (defun initz-list-delete-file ()
339   "Delete the file under point."
340   (interactive)
341   (let ((file (get-text-property (point) :file)))
342     (when (and file
343                (y-or-n-p
344                 (format initz-list-delete-file-ask-message-format
345                         (initz-get-module-name file))))
346       (delete-file file)
347       (setq initz-compile-error-files
348             (delete file initz-compile-error-files))
349       (setq initz-load-error-files
350             (delete file initz-load-error-files))
351       (initz-list-delete-whole-line)
352       (initz-list-previous-line)
353       (initz-list-next-line))))
354
355 (defun initz-list-load-file ()
356   "Load the file under point."
357   (interactive)
358   (let* ((file (get-text-property (point) :file)))
359     (initz-list-byte-compile-file)
360     (when (initz-load-file (initz-get-correspondence-file file))
361       (setq initz-load-error-files
362             (delete file initz-load-error-files)))))
363
364 ;;;###autoload
365 (defun initz-list-new-file ()
366   "Make new init file."
367   (interactive)
368   (let* ((default (initz-list-get-dir))
369          (dir (initz-list-input-dir default))
370          (module (initz-list-input-module dir)))
371     (if (not (or (and (string= dir "misc") (string= module initz-null-string))
372                  (string-match (concat "^" initz-module-regexp "$") module)))
373         (message initz-list-new-file-illegal-message)
374       (setq module (initz-trim-separator module))
375       (let* ((startup-file (expand-file-name
376                             (concat initz-prefix
377                                     (if (string= module initz-null-string)
378                                         initz-null-string
379                                       initz-separator-string)
380                                     module ".el")
381                             (initz-startup-directory (intern dir)))))
382         (if (file-exists-p startup-file)
383             (message initz-list-new-file-exists-message)
384           (let ((base-name (initz-get-base-name startup-file)))
385             (if (memq (intern base-name) features)
386                 (message initz-list-new-file-provided-message)
387               (initz-list-insert-file dir startup-file)
388               (find-file-other-window startup-file)
389               (insert (format initz-list-new-file-comment-message-format
390                               (file-name-nondirectory startup-file)
391                               (if (string= module initz-null-string)
392                                   initz-prefix
393                                 module)))
394               (insert (format initz-list-new-file-provide-message-format
395                               base-name))
396               (save-buffer)
397               (goto-char (point-min))
398               (search-forward "\n\n"))))))))
399
400 (defun initz-list-quit ()
401   "Quit the initz list mode."
402   (interactive)
403   (when (or (eq major-mode 'initz-list-mode)
404             (eq major-mode 'initz-error-mode))
405     (remove-hook 'post-command-hook
406                  (intern (concat
407                           (substring (symbol-name major-mode) 0
408                                      (string-match "mode"
409                                                    (symbol-name major-mode)))
410                           "print-file")))
411     (let ((buf (current-buffer)))
412       (unless (one-window-p)
413         (delete-window))
414       (kill-buffer buf))))
415
416 (defun initz-list-mode ()
417   "\\<initz-list-mode-map>
418    Major mode for browsing initz list buffer.
419
420 \\[initz-list-next-line]        Next line.
421 \\[initz-list-previous-line]    Previous line.
422 \\[forward-char]        Forward char.
423 \\[backward-char]       Backward char.
424
425 \\[initz-list-find-file]        View the file under point.
426 \\[initz-list-byte-compile-file]        Byte-compile the file under point.
427 \\[initz-list-delete-file]      Delete the file under point.
428 \\[initz-list-load-file]        Load the file under point.
429 \\[initz-list-new-file] Make new init file.
430 \\[initz-startup]       Initz startup.
431 \\[initz-list-quit]     Quit the initz list mode."
432   (interactive)
433   (kill-all-local-variables)
434   (use-local-map initz-list-mode-map)
435   (setq mode-name initz-list-mode-name)
436   (setq major-mode 'initz-list-mode)
437   (when (or (featurep 'xemacs) (< emacs-major-version 21))
438     (make-local-hook 'post-command-hook))
439   (add-hook 'post-command-hook 'initz-list-print-file)
440   (run-hooks 'initz-list-mode-hook))
441
442 ;;;###autoload
443 (defun initz-list ()
444   "Show initz list buffer."
445   (interactive)
446   ;; FIXME: ad-hoc
447   (let ((buf (get-buffer initz-list-buffer-name)))
448     (when buf
449       (unless (one-window-p)
450         (delete-window))
451       (kill-buffer buf)))
452   (switch-to-buffer-other-window initz-list-buffer-name)
453   (initz-list-mode)
454   (goto-char (point-min))
455   (insert
456    (format initz-list-click-message-format
457            (substitute-command-keys "\\[initz-list-find-file-mouse]")))
458   (insert
459    (format initz-list-enter-message-format
460            (substitute-command-keys "\\[initz-list-find-file]")))
461   (insert "\n")
462   (mapc
463    (function (lambda (alist)
464                (let ((sym (car alist)))
465                  (funcall
466                   (intern (concat "initz-list-node-"
467                                   (symbol-name
468                                    initz-list-default-node-status)))
469                   sym))))
470    initz-init-alist)
471   (set-buffer-modified-p nil)
472   (setq buffer-read-only t)
473   (goto-char (point-min))
474   (search-forward "\n\n")
475   (forward-char 1))
476
477 (provide 'initz-list)
478
479 ;;; initz-list.el ends here