update.
[elisp/egg.git] / menudiag.el
1 ;;; menudiag.el --- Minibuffer Menu System
2
3 ;; Copyright (C) 1997 Mule Project, Powered by Electrotechnical
4 ;; Laboratory, JAPAN.
5 ;; Project Leader: Satoru Tomura <tomura@etl.go.jp>
6
7 ;; Author: NIIBE Yutaka <gniibe@mri.co.jp>
8 ;; Maintainer: NIIBE Yutaka <gniibe@mri.co.jp>
9
10 ;; This file will be part of GNU Emacs (in future).
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28 ;; Inspired by the menu subsystem of EGG V3.0
29 ;;
30 ;; Completely different implementation, using keymap and recursive edit.
31
32 ;;; Code:
33
34 ;;
35 ;; Data structure of MENU
36 ;;
37 ;; <menu> ::= ( menu <prompt> <item-list> )
38 ;; <prompt> ::= STRING
39 ;; <item-list> ::= ( <item> ... )
40 ;; <item> ::= <string> | ( <string> . <value> )
41 ;;
42 ;; <value> ::=  <menu> | INTEGER | STRING  (Must *NOT* cons cell)
43 ;;
44 ;;
45 ;
46 ;;
47 ;; <selection-list> ::= ( <line>... )
48 ;; <line>  ::= ( <item>... )
49 ;;
50
51 (defvar menudiag-mode-map
52   (let ((map (make-keymap))
53         (ch 0))
54     (while (< ch 127)
55       (define-key map (char-to-string ch) 'undefined)
56       (setq ch (1+ ch)))
57     (setq ch ?0)
58     (while (< ch ?9)
59       (define-key map (char-to-string ch) 'menudiag-goto-item)
60       (setq ch (1+ ch)))
61     (setq ch ?a)
62     (while (< ch ?z)
63       (define-key map (char-to-string ch) 'menudiag-goto-item)
64       (setq ch (1+ ch)))
65     (setq ch ?A)
66     (while (< ch ?Z)
67       (define-key map (char-to-string ch) 'menudiag-goto-item)
68       (setq ch (1+ ch)))
69     (define-key map "\C-a" 'menudiag-beginning-of-line)
70     (define-key map "\C-e" 'menudiag-end-of-line)
71     (define-key map "\C-f" 'menudiag-forward-item)
72     (define-key map "\C-b" 'menudiag-backward-item)
73     (define-key map "\C-n" 'menudiag-next-line)
74     (define-key map "\C-p" 'menudiag-previous-line)
75     (define-key map "\C-]" 'menudiag-exit)
76     (define-key map "\C-g" 'menudiag-exit-one-level)
77     (define-key map "\C-l" 'menudiag-redraw)
78     (define-key map "\C-m" 'menudiag-select-this-item)
79     (define-key map [return] 'menudiag-select-this-item)
80     (define-key map [left] 'menudiag-backward-item)
81     (define-key map [right] 'menudiag-forward-item)
82     (define-key map [up] 'menudiag-previous-line)
83     (define-key map [down] 'menudiag-next-line)
84     (define-key map [menudiag-continuation] 'menudiag-follow-continuation)
85     map)
86   "Keymap for MENU.")
87
88 (defun menudiag-menu-p (item)
89   (and (consp item) (eq 'menu (car item))))
90
91 (defun menudiag-item-string (item)
92   (if (stringp item)
93       item
94     (format "%s" (car item))))
95
96 (defun menudiag-item-value (item)
97   (if (stringp item)
98       item
99     (cdr item)))
100
101 (defsubst menudiag-item-width (item)
102   (+ 4 (string-width (menudiag-item-string item))))
103
104 (defun menudiag-make-selection-list (item-list line-width)
105   (let ((l nil)
106         (line nil)
107         (width 0))
108     (while item-list
109       (let* ((item (car item-list))
110              (item-width (menudiag-item-width item)))
111         (if (and line (>= (+ width item-width) line-width))
112             (setq l (cons (reverse line) l)
113                   line nil
114                   width 0))
115         (setq line (cons item line)
116               width (+ width (menudiag-item-width item))
117               item-list (cdr item-list))))
118     (if line
119         (reverse (cons (reverse line) l))
120       (reverse l))))
121
122 ;; Entry function
123 (defun menudiag-select (menu &optional menudiag-continuation return-contin)
124   (let (value)
125     (if menudiag-continuation
126         (setq unread-command-events (cons 'menudiag-continuation
127                                           unread-command-events)))
128     (if (not return-contin)
129         (setq value t))
130     (if (catch 'menudiag-exit
131           (menudiag-select-internal menu))
132         (signal 'quit "")
133       value)))
134
135 ;; Entry function
136 (defun menudiag-get-value (continuation)
137   (menudiag-item-value (nth (1- (length continuation)) continuation)))
138
139 (defun menudiag-follow-continuation ()
140   (interactive)
141   (let ((item (car menudiag-continuation)))
142     (setq menudiag-continuation (cdr menudiag-continuation))
143     (if menudiag-continuation
144         (setq unread-command-events (cons 'menudiag-continuation
145                                           unread-command-events)))
146     (let ((in-loop t))
147       (while in-loop
148         (if (eq item (nth pos-in-line line))
149             (setq in-loop nil)
150           (menudiag-forward-item))))
151     (let ((v (menudiag-item-value item)))
152       (if (menudiag-menu-p v)
153           (unwind-protect
154               (progn
155                 (menudiag-select-internal v)
156                 (menudiag-redraw))
157             (if (consp value)
158                 (setq value (cons item value))))))))
159
160 (defun menudiag-select-internal (menu)
161   (let* ((minibuf-prompt (nth 1 menu))
162          (selection-list
163           (menudiag-make-selection-list (nth 2 menu)
164                                     (- (window-width (minibuffer-window))
165                                        (string-width minibuf-prompt))))
166          (line (car selection-list))
167          (minibuf-contents
168           (menudiag-make-menu-formatted-string line)))
169     (let ((linepos 0)
170           (pos-in-line 0))
171       (read-from-minibuffer minibuf-prompt
172                             (cons minibuf-contents 3)
173                             menudiag-mode-map))))
174
175 (defun menudiag-make-menu-formatted-string (item-list)
176   (let ((i -1))
177     (mapconcat
178      (function (lambda (item)
179                  (setq i (1+ i))
180                  (format "  %x.%s" i (menudiag-item-string item))))
181      item-list "")))
182
183 (defun menudiag-goto-item ()
184   (interactive)
185   (let ((ch last-command-char)
186         (n 0))
187     (cond ((and (<= ?0 ch) (<= ch ?9))
188            (setq n (- ch ?0)))
189           ((and (<= ?a ch) (<= ch ?z))
190            (setq n (+ 10 (- ch ?a))))
191           ((and (<= ?A ch) (<= ch ?Z))
192            (setq n (+ 10 (- ch ?A)))))
193     (if (>= n (length line))
194         (error "No such item")
195       (menudiag-goto-item-internal n))))
196
197 (defun menudiag-goto-item-internal (n)
198   (let ((old-pos-in-line pos-in-line)
199         (p 3)
200         (i 0))
201     (setq pos-in-line n)
202     (while (< i pos-in-line)
203       (setq p (+ p (length (menudiag-item-string (nth i line))) 4))
204       (setq i (1+ i)))
205     (goto-char p)))
206
207 (defun menudiag-beginning-of-line ()
208   (interactive)
209   (menudiag-goto-item-internal 0))
210
211 (defun menudiag-end-of-line ()
212   (interactive)
213   (menudiag-goto-item-internal (1- (length line))))
214
215 (defun menudiag-forward-item ()
216   (interactive)
217   (if (< pos-in-line (1- (length line)))
218       (menudiag-goto-item-internal (1+ pos-in-line))
219     (if (>= linepos (1- (length selection-list)))
220         (signal 'end-of-buffer "")
221       (menudiag-goto-line (1+ linepos))
222       (menudiag-beginning-of-line))))
223
224 (defun menudiag-backward-item ()
225   (interactive)
226   (if (< 0 pos-in-line)
227       (menudiag-goto-item-internal (1- pos-in-line))
228     (if (< linepos 1)
229         (signal 'beginning-of-buffer "")
230       (menudiag-goto-line (1- linepos))
231       (menudiag-end-of-line))))
232
233 (defun menudiag-goto-line (n)
234   (if (or (>= n (length selection-list)) (< n 0))
235       (ding)
236     (setq line (nth n selection-list)
237           linepos n)
238     (delete-region (point-min) (point-max))
239     (insert (menudiag-make-menu-formatted-string line))))
240
241 (defun menudiag-next-line ()
242   (interactive)
243   (menudiag-goto-line (1+ linepos))
244   (if (< pos-in-line (length line))
245       (menudiag-goto-item-internal pos-in-line)
246     (menudiag-end-of-line)))
247
248 (defun menudiag-previous-line ()
249   (interactive)
250   (menudiag-goto-line (1- linepos))
251   (if (< pos-in-line (length line))
252       (menudiag-goto-item-internal pos-in-line)
253     (menudiag-end-of-line)))
254
255 (defun menudiag-redraw ()
256   (interactive)
257   (menudiag-goto-line linepos)
258   (menudiag-goto-item-internal pos-in-line))
259
260 (defun menudiag-exit-one-level ()
261   (interactive)
262   (exit-minibuffer))
263
264 (defun menudiag-exit ()
265   (interactive)
266   (throw 'menudiag-exit t))
267
268 (defun menudiag-select-this-item ()
269   (interactive)
270   (let* ((item (nth pos-in-line line))
271          (v (menudiag-item-value item)))
272     (if (menudiag-menu-p v)
273         (unwind-protect
274             (progn
275               (menudiag-select-internal v)
276               (menudiag-redraw))
277           (if (consp value)
278               (setq value (cons item value))))
279       (if (eq value t)
280           (setq value (menudiag-item-value item))
281         (setq value (cons item nil)))
282       (throw 'menudiag-exit nil))))
283
284 (provide 'menudiag)
285 ;;; menudiag.el ends here.