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