cdfc0c7520cc73558cb9d3fdaac7c63c53b001a1
[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 (defconst initz-list-modeline-string
103   "Initz")
104
105 ;; Initz list mode is suitable only for specially formatted data.
106 (put 'initz-list-mode 'mode-class 'special)
107
108 (defadvice sort-build-lists (after sort-including-newline activate)
109   (when (or (eq major-mode 'initz-list-mode)
110             (eq major-mode 'initz-error-mode))
111     (setq ad-return-value
112           (mapcar #'(lambda (list)
113                       (cons (cons (caar list) (1+ (cdar list)))
114                             (cons (cadr list) (1+ (cddr list)))))
115                   ad-return-value))))
116
117 (defun initz-list-delete-whole-line ()
118   "Delete whole line at point."
119   (setq buffer-read-only nil)
120   (delete-region (progn (beginning-of-line) (point))
121                  (progn (forward-line 1) (point)))
122   (set-buffer-modified-p nil)
123   (setq buffer-read-only t))
124
125 (defun initz-list-get-dir ()
126   "Return the dir at point."
127   (save-excursion
128     (end-of-line)
129     (when (re-search-backward "^\\[[-+]\\] \\([^ :]+\\)" nil t)
130         (match-string 1))))
131
132 (defun initz-list-input-dir (&optional default)
133   "Input the dir."
134   (let* ((completing-list (mapcar #'(lambda (list)
135                                       (symbol-name (car list)))
136                                   initz-init-alist))
137          (default (if (stringp default) default "misc")))
138     (completing-read
139      (format initz-list-input-dir-message-format
140              initz-directory default)
141      (mapcar #'(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 #'(lambda (feature)
156                      (let ((name (symbol-name feature)))
157                        (cons name name)))
158                  features)
159          nil nil init)
160       (read-string
161        (format initz-list-input-module-message-format dir)
162        init))))
163
164 (defun initz-list-insert-file (dir startup-file)
165   "Insert the STARTUP-FILE at DIR section."
166   ;; FIXME: Delete `save-excursion' and fix the next `FIXME'.
167   (save-excursion
168     (goto-char (point-min))
169     (when (re-search-forward (concat "^\\[[-+]\\] " dir "[ :]") nil t)
170       (beginning-of-line)
171       (let ((status (get-text-property (point) :status)))
172         (when (eq status 'expand)
173           (let (sort-start)
174             (forward-line 1)
175             (setq sort-start (point))
176             (if (re-search-forward "^\\[[-+]\\] " nil t)
177                 (beginning-of-line)
178               (re-search-forward "\\'" nil t))
179             (setq buffer-read-only nil)
180             (insert-char ?\  4)
181             (let ((start (point)))
182               (insert (initz-get-module-name startup-file) "\n")
183               (add-text-properties start (1- (point))
184                                    `(face initz-list-module-face
185                                           mouse-face highlight
186                                           start-open t rear-nonsticky t
187                                           help-echo ,startup-file))
188               (put-text-property start (point) :file startup-file))
189             (sort-lines nil sort-start (point)))
190           (set-buffer-modified-p nil)
191           (setq buffer-read-only t)
192           ;; FIXME: Move to the line inserted now
193           )))))
194
195 (defun initz-list-node-insert (node status)
196   (let ((prefix (initz-get-init-value node 'prefix))
197         (start (point)))
198     (setq prefix (initz-trim-separator prefix))
199     ;; `prefix' will be broken.
200     (if (string= prefix initz-null-string)
201         (setq prefix initz-null-string)
202       (setq prefix (concat " (" prefix ")")))
203     (insert "[" (if (eq status 'expand) "-" "+") "] "
204             (symbol-name node) prefix ":\n")
205     (add-text-properties start (+ start 3)
206                          `(face initz-list-node-face
207                            mouse-face highlight
208                            local-map ,initz-list-node-map
209                            keymap ,initz-list-node-map
210                            start-open t rear-nonsticky t
211                            :node ,node
212                            :status ,status))))
213
214 (defun initz-list-node-collapse (node)
215   (save-excursion
216     (setq buffer-read-only nil)
217     (goto-char (point-min))
218     (if (re-search-forward (concat "^\\[-\\] "
219                                    (symbol-name node) "[ :]") nil t)
220         (let ((start (progn (beginning-of-line) (point)))
221               end)
222           (forward-line 1)
223           (if (re-search-forward "^\\[[-+]\\] " nil t)
224               (progn
225                 (beginning-of-line)
226                 (setq end (point)))
227             (setq end (point-max)))
228           (delete-region start end))
229       (goto-char (point-max)))
230     (initz-list-node-insert node 'collapse)
231     (set-buffer-modified-p nil)
232     (setq buffer-read-only t)))
233
234 (defun initz-list-node-expand (node)
235   (save-excursion
236     (setq buffer-read-only nil)
237     (goto-char (point-min))
238     (if (re-search-forward (concat "^\\[\\+\\] "
239                                    (symbol-name node) "[ :]") nil t)
240         (delete-region (progn (beginning-of-line) (point))
241                        (progn (forward-line 1) (point)))
242       (goto-char (point-max)))
243     (initz-list-node-insert node 'expand)
244     (let ((sort-start (point))
245           (initz-features (initz-features)))
246       (mapc
247        #'(lambda (file)
248            (let* ((module (initz-get-module-name file))
249                   (loaded (memq (intern module) initz-features))
250                   start)
251              (insert-char ?\  4)
252              (setq start (point))
253              (insert module)
254              (when loaded (insert initz-list-loaded-mark))
255              (insert "\n")
256              (add-text-properties start (1- (point))
257                                   `(face ,(if loaded
258                                               'initz-list-module-face 
259                                             'initz-list-unloaded-module-face)
260                                     mouse-face highlight
261                                     start-open t rear-nonsticky t
262                                     help-echo ,file))
263              (put-text-property start (point) :file file)))
264        (initz-get-files 'startup node))
265       (sort-lines nil sort-start (point)))
266     (set-buffer-modified-p nil)
267     (setq buffer-read-only t)))
268
269 (defun initz-list-node-enter ()
270   (interactive)
271   (let ((node (get-text-property (point) :node))
272         (status (get-text-property (point) :status)))
273     (when (and node status)
274       (if (eq status 'expand)
275           (initz-list-node-collapse node)
276         (initz-list-node-expand node))
277       (forward-char 1))))
278
279 (defun initz-list-node-click (e)
280   (interactive "e")
281   (mouse-set-point e)
282   (initz-list-node-enter))
283
284 (defun initz-list-next-line (&optional arg)
285   (interactive)
286   (if (integerp arg)
287       (next-line arg)
288     (next-line 1))
289   (beginning-of-line)
290   (let ((start (re-search-forward "^\\(    \\|\\[\\|\\)" nil t)))
291     (when (integer-or-marker-p start)
292         (goto-char start))))
293
294 (defun initz-list-previous-line ()
295   (interactive)
296   (initz-list-next-line -1))
297
298 (defun initz-list-print-file ()
299   "Print the file name under point."
300   (interactive)
301   (let ((file (get-text-property (point) :file)))
302     (and file
303          (initz-message-no-log file))))
304
305 (defun initz-list-find-file ()
306   "View the file under point."
307   (interactive)
308   (let ((file (get-text-property (point) :file)))
309     (and file
310          (find-file-other-window file))))
311
312 (defun initz-list-find-file-mouse (e)
313   "View the file under clicked point."
314   (interactive "e")
315   (mouse-set-point e)
316   (unless (eolp)
317     (initz-list-find-file)))
318
319 (defun initz-list-byte-compile-file ()
320   "Byte-compile the file under point."
321   (interactive)
322   (let ((file (get-text-property (point) :file)))
323     (when file
324       (condition-case nil
325           (when (save-window-excursion
326                   (byte-compile-file file))
327             (let* ((compile-file (initz-get-correspondence-file file))
328                    (startup-directory (file-name-directory file))
329                    (flavor-directory (file-name-directory compile-file)))
330               (install-file (file-name-nondirectory compile-file)
331                             startup-directory flavor-directory t t))
332             (setq initz-compile-error-files
333                   (delete file initz-compile-error-files)))
334         (error)))))
335
336 (defun initz-list-delete-file ()
337   "Delete the file under point."
338   (interactive)
339   (let ((file (get-text-property (point) :file)))
340     (when (and file
341                (y-or-n-p
342                 (format initz-list-delete-file-ask-message-format
343                         (initz-get-module-name file))))
344       (delete-file file)
345       (setq initz-compile-error-files
346             (delete file initz-compile-error-files))
347       (setq initz-load-error-files
348             (delete file initz-load-error-files))
349       (initz-list-delete-whole-line)
350       (initz-list-previous-line)
351       (initz-list-next-line))))
352
353 (defun initz-list-load-file ()
354   "Load the file under point."
355   (interactive)
356   (let* ((file (get-text-property (point) :file)))
357     (initz-list-byte-compile-file)
358     (when (initz-load-file (initz-get-correspondence-file file))
359       (setq initz-load-error-files
360             (delete file initz-load-error-files)))))
361
362 ;;;###autoload
363 (defun initz-list-new-file ()
364   "Make new init file."
365   (interactive)
366   (let* ((default (initz-list-get-dir))
367          (dir (initz-list-input-dir default))
368          (module (initz-list-input-module dir)))
369     (if (not (or (and (string= dir "misc") (string= module initz-null-string))
370                  (string-match (concat "^" initz-module-regexp "$") module)))
371         (message initz-list-new-file-illegal-message)
372       (setq module (initz-trim-separator module))
373       (let* ((startup-file (expand-file-name
374                             (concat initz-prefix
375                                     (if (string= module initz-null-string)
376                                         initz-null-string
377                                       initz-separator-string)
378                                     module ".el")
379                             (initz-startup-directory (intern dir)))))
380         (if (file-exists-p startup-file)
381             (message initz-list-new-file-exists-message)
382           (let ((base-name (initz-get-base-name startup-file)))
383             (if (memq (intern base-name) features)
384                 (message initz-list-new-file-provided-message)
385               (initz-list-insert-file dir startup-file)
386               (find-file-other-window startup-file)
387               (insert (format initz-list-new-file-comment-message-format
388                               (file-name-nondirectory startup-file)
389                               (if (string= module initz-null-string)
390                                   initz-prefix
391                                 module)))
392               (insert (format initz-list-new-file-provide-message-format
393                               base-name))
394               (save-buffer)
395               (goto-char (point-min))
396               (search-forward "\n\n"))))))))
397
398 (defun initz-list-quit ()
399   "Quit the initz list mode."
400   (interactive)
401   (when (or (eq major-mode 'initz-list-mode)
402             (eq major-mode 'initz-error-mode))
403     (remove-hook 'post-command-hook
404                  (intern (concat
405                           (substring (symbol-name major-mode) 0
406                                      (string-match "mode"
407                                                    (symbol-name major-mode)))
408                           "print-file")))
409     (let ((buf (current-buffer)))
410       (unless (one-window-p)
411         (delete-window))
412       (kill-buffer buf))))
413
414 (defun initz-list-mode ()
415   "\\<initz-list-mode-map>
416    Major mode for browsing initz list buffer.
417
418 \\[initz-list-next-line]        Next line.
419 \\[initz-list-previous-line]    Previous line.
420 \\[forward-char]        Forward char.
421 \\[backward-char]       Backward char.
422
423 \\[initz-list-find-file]        View the file under point.
424 \\[initz-list-byte-compile-file]        Byte-compile the file under point.
425 \\[initz-list-delete-file]      Delete the file under point.
426 \\[initz-list-load-file]        Load the file under point.
427 \\[initz-list-new-file] Make new init file.
428 \\[initz-startup]       Initz startup.
429 \\[initz-list-quit]     Quit the initz list mode."
430   (interactive)
431   (kill-all-local-variables)
432   (use-local-map initz-list-mode-map)
433   (setq mode-name initz-list-mode-name)
434   (setq major-mode 'initz-list-mode)
435   (when (or (featurep 'xemacs) (< emacs-major-version 21))
436     (make-local-hook 'post-command-hook))
437   (add-hook 'post-command-hook 'initz-list-print-file)
438   (setq mode-line-buffer-identification initz-list-modeline-string)
439   (run-hooks 'initz-list-mode-hook))
440
441 ;;;###autoload
442 (defun initz-list ()
443   "Show initz list buffer."
444   (interactive)
445   ;; FIXME: ad-hoc
446   (let ((buf (get-buffer initz-list-buffer-name)))
447     (when buf
448       (unless (one-window-p)
449         (delete-window))
450       (kill-buffer buf)))
451   (switch-to-buffer-other-window initz-list-buffer-name)
452   (initz-list-mode)
453   (goto-char (point-min))
454   (insert
455    (format initz-list-click-message-format
456            (substitute-command-keys "\\[initz-list-find-file-mouse]")))
457   (insert
458    (format initz-list-enter-message-format
459            (substitute-command-keys "\\[initz-list-find-file]")))
460   (insert "\n")
461   (mapc #'(lambda (alist)
462             (let ((sym (car alist)))
463               (funcall (intern (concat "initz-list-node-"
464                                        (symbol-name
465                                         initz-list-default-node-status)))
466                        sym)))
467         initz-init-alist)
468   (set-buffer-modified-p nil)
469   (setq buffer-read-only t)
470   (goto-char (point-min))
471   (search-forward "\n\n")
472   (forward-char 1))
473
474 (provide 'initz-list)
475
476 ;;; initz-list.el ends here