8789a14db19b69dd4a89e3ebf4ca9237d0881c05
[chise/xemacs-chise.git.1] / lisp / list-mode.el
1 ;;; list-mode.el --- Major mode for buffers containing lists of items
2
3 ;; Copyright (C) 1992-4, 1997 Free Software Foundation, Inc.
4 ;; Copyright (C) 1996 Ben Wing.
5  
6 ;; Maintainer: XEmacs Development Team
7 ;; Keywords: extensions, dumped
8
9 ;; This file is part of XEmacs.
10
11 ;; XEmacs is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; XEmacs is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 ;; General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with XEmacs; see the file COPYING.  If not, write to the 
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Synched up with: Not synched
27
28 ;;; Commentary:
29
30 ;; This file is dumped with XEmacs.
31
32 ;; Cleanup, merging with FSF by Ben Wing, January 1996
33
34 ;;; Code:
35
36 (defvar list-mode-extent nil)
37 (make-variable-buffer-local 'list-mode-extent)
38
39 (defvar list-mode-map nil
40   "Local map for buffers containing lists of items.")
41 (or list-mode-map
42     (let ((map (setq list-mode-map (make-sparse-keymap 'list-mode-map))))
43       (suppress-keymap map)
44       (define-key map 'button2up 'list-mode-item-mouse-selected)
45       (define-key map 'button2 'undefined)
46       (define-key map "\C-m" 'list-mode-item-keyboard-selected)
47 ;;
48 ;; The following calls to `substitute-key-definition' losed because
49 ;; they were based on an incorrect assumption that `forward-char' and
50 ;; `backward-char' are bound to keys in the global map. This might not
51 ;; be the case if a user binds motion keys to different functions,
52 ;; and was not actually the case since 20.5 beta 28 or around.
53 ;;
54 ;;    (substitute-key-definition 'forward-char 'next-list-mode-item map
55 ;;                               global-map)
56 ;;    (substitute-key-definition 'backward-char 'previous-list-mode-item map
57 ;;                               global-map)
58 ;;
59 ;; We bind standard keys to motion commands instead.
60 ;;
61       (dolist (key '(kp-right right (control ?f)))
62         (define-key map key 'next-list-mode-item))
63       (dolist (key '(kp-left left (control ?b)))
64         (define-key map key 'previous-list-mode-item))))
65
66 (defun list-mode ()
67   "Major mode for buffer containing lists of items."
68   (interactive)
69   (kill-all-local-variables)
70   (use-local-map list-mode-map)
71   (setq mode-name "List")
72   (setq major-mode 'list-mode)
73   (make-local-hook 'post-command-hook)
74   (add-hook 'post-command-hook 'set-list-mode-extent nil t)
75   (make-local-hook 'pre-command-hook)
76   (add-hook 'pre-command-hook 'list-mode-extent-pre-hook nil t)
77   (make-local-variable 'next-line-add-newlines)
78   (setq next-line-add-newlines nil)
79   (setq list-mode-extent nil)
80   (set-specifier text-cursor-visible-p nil (current-buffer))
81   (setq buffer-read-only t)
82   (goto-char (point-min))
83   (run-hooks 'list-mode-hook))
84
85 ;; List mode is suitable only for specially formatted data.
86 (put 'list-mode 'mode-class 'special)
87
88 (defvar list-mode-extent-old-point nil
89   "The value of point when pre-command-hook is called.
90 Used to determine the direction of motion.")
91 (make-variable-buffer-local 'list-mode-extent-old-point)
92
93 (defun list-mode-extent-pre-hook ()
94   (setq list-mode-extent-old-point (point))
95   ;(setq atomic-extent-goto-char-p nil)
96 )
97
98 (defun set-list-mode-extent ()
99   "Move to the closest list item and set up the extent for it.
100 This is called from `post-command-hook'."
101   (cond ((get-char-property (point) 'list-mode-item))
102         ((and (> (point) (point-min))
103               (get-char-property (1- (point)) 'list-mode-item))
104          (goto-char (1- (point))))
105         (t
106          (let ((pos (point))
107                dirflag)
108            ;this fucks things up more than it helps.
109            ;atomic-extent-goto-char-p as currently defined is all broken,
110            ;since it will be triggered if the command *ever* runs goto-char!
111            ;(if atomic-extent-goto-char-p
112            ;    (setq dirflag 1)
113            (if (and list-mode-extent-old-point
114                     (> pos list-mode-extent-old-point))
115                (setq dirflag 1)
116              (setq dirflag -1))
117            (next-list-mode-item dirflag)
118            (or (get-char-property (point) 'list-mode-item)
119                (next-list-mode-item (- dirflag))))))
120   (or (and list-mode-extent
121            (eq (current-buffer) (extent-object list-mode-extent)))
122       (progn
123         (setq list-mode-extent (make-extent nil nil (current-buffer)))
124         (set-extent-face list-mode-extent 'list-mode-item-selected)))
125   (let ((ex (extent-at (point) nil 'list-mode-item nil 'at)))
126     (if ex
127         (progn
128           (set-extent-endpoints list-mode-extent
129                                 (extent-start-position ex)
130                                 (extent-end-position ex))
131           (auto-show-make-region-visible (extent-start-position ex)
132                                          (extent-end-position ex)))
133       (detach-extent list-mode-extent))))
134
135 (defun previous-list-mode-item (n)
136   "Move to the previous item in list-mode."
137   (interactive "p")
138   (next-list-mode-item (- n)))
139
140 (defun next-list-mode-item (n)
141   "Move to the next item in list-mode.
142 With prefix argument N, move N items (negative N means move backward)."
143   (interactive "p")
144   (while (and (> n 0) (not (eobp)))
145     (let ((extent (extent-at (point) (current-buffer) 'list-mode-item))
146           (end (point-max)))
147       ;; If in a completion, move to the end of it.
148       (if extent (goto-char (extent-end-position extent)))
149       ;; Move to start of next one.
150       (or (extent-at (point) (current-buffer) 'list-mode-item)
151           (goto-char (next-single-property-change (point) 'list-mode-item
152                                                   nil end))))
153     (setq n (1- n)))
154   (while (and (< n 0) (not (bobp)))
155     (let ((extent (extent-at (point) (current-buffer) 'list-mode-item))
156           (end (point-min)))
157       ;; If in a completion, move to the start of it.
158       (if extent (goto-char (extent-start-position extent)))
159       ;; Move to the start of that one.
160       (if (setq extent (extent-at (point) (current-buffer) 'list-mode-item
161                                   nil 'before))
162           (goto-char (extent-start-position extent))
163         (goto-char (previous-single-property-change
164                     (point) 'list-mode-item nil end))
165         (if (setq extent (extent-at (point) (current-buffer) 'list-mode-item
166                                     nil 'before))
167             (goto-char (extent-start-position extent)))))
168     (setq n (1+ n))))
169
170 (defun list-mode-item-selected-1 (extent event)
171   (let ((func (extent-property extent 'list-mode-item-activate-callback))
172         (user-data (extent-property extent 'list-mode-item-user-data)))
173     (if func
174         (funcall func event extent user-data))))
175
176 ;; we could make these two be just one function, but we want to be
177 ;; able to refer to them in DOC strings.
178
179 (defun list-mode-item-keyboard-selected ()
180   (interactive)
181   (list-mode-item-selected-1 (extent-at (point) (current-buffer)
182                                         'list-mode-item nil 'at)
183                              nil))
184
185 (defun list-mode-item-mouse-selected (event)
186   (interactive "e")
187   ;; Sometimes event-closest-point returns nil.
188   ;; So beep instead of bombing.
189   (let ((point (event-closest-point event)))
190     (if point
191         (list-mode-item-selected-1 (extent-at point
192                                               (event-buffer event)
193                                               'list-mode-item nil 'at)
194                                    event)
195       (ding))))
196
197 (defun add-list-mode-item (start end &optional buffer activate-callback
198                                  user-data)
199   "Add a new list item in list-mode, from START to END in BUFFER.
200 BUFFER defaults to the current buffer.
201 This works by creating an extent for the span of text in question.
202 If ACTIVATE-CALLBACK is non-nil, it should be a function of three
203   arguments (EVENT EXTENT USER-DATA) that will be called when button2
204   is pressed on the extent.  USER-DATA comes from the optional
205   USER-DATA argument."
206   (let ((extent (make-extent start end buffer)))
207     (set-extent-property extent 'list-mode-item t)
208     (set-extent-property extent 'start-open t)
209     (if activate-callback
210         (progn
211           (set-extent-property extent 'mouse-face 'highlight)
212           (set-extent-property extent 'list-mode-item-activate-callback
213                                activate-callback)
214           (set-extent-property extent 'list-mode-item-user-data user-data)))
215     extent))
216
217 \f
218 ;; Define the major mode for lists of completions.
219
220
221 (defvar completion-highlight-first-word-only nil
222   "*Completion will only highlight the first blank delimited word if t.
223 If the variable in not t or nil, the string is taken as a regexp to match for end
224 of highlight")
225
226 (defvar completion-setup-hook nil
227   "Normal hook run at the end of setting up the text of a completion buffer.")
228
229 ; Unnecessary FSFmacs crock.  We frob the extents directly in
230 ; display-completion-list, so no "heuristics" like this are necessary.
231 ;(defvar completion-fixup-function nil
232 ;  "A function to customize how completions are identified in completion lists.
233 ;`completion-setup-function' calls this function with no arguments
234 ;each time it has found what it thinks is one completion.
235 ;Point is at the end of the completion in the completion list buffer.
236 ;If this function moves point, it can alter the end of that completion.")
237
238 (defvar completion-default-help-string
239   '(concat
240     (if (device-on-window-system-p)
241         (substitute-command-keys
242          "Click \\<list-mode-map>\\[list-mode-item-mouse-selected] on a completion to select it.\n") "")
243     (substitute-command-keys
244      "Type \\<minibuffer-local-completion-map>\\[advertised-switch-to-completions] or \\[switch-to-completions] to move to this buffer, for keyboard selection.\n\n"))
245   "Form the evaluate to get a help string for completion lists.
246 This string is inserted at the beginning of the buffer.
247 See `display-completion-list'.")
248
249 (defun display-completion-list (completions &rest cl-keys)
250   "Display the list of completions, COMPLETIONS, using `standard-output'.
251 Each element may be just a symbol or string or may be a list of two
252  strings to be printed as if concatenated.
253 Frob a mousable extent onto each completion.  This extent has properties
254  'mouse-face (so it highlights when the mouse passes over it) and
255  'list-mode-item (so it can be located).
256
257 Keywords:
258   :activate-callback (default is `default-choose-completion')
259     See `add-list-mode-item'.
260   :user-data
261     Value passed to activation callback.
262   :window-width
263     If non-nil, width to use in displaying the list, instead of the
264     actual window's width.
265   :help-string (default is the value of `completion-default-help-string')
266     Form to evaluate to get a string to insert at the beginning of
267     the completion list buffer.  This is evaluated when that buffer
268     is the current buffer and after it has been put into
269     completion-list-mode.
270   :reference-buffer (default is the current buffer)
271     This specifies the value of `completion-reference-buffer' in
272     the completion buffer.  This specifies the buffer (normally a
273     minibuffer) that `default-choose-completion' will insert the
274     completion into.
275
276 At the end, run the normal hook `completion-setup-hook'.
277 It can find the completion buffer in `standard-output'.
278 If `completion-highlight-first-word-only' is non-nil, then only the start
279  of the string is highlighted."
280    ;; #### I18N3 should set standard-output to be (temporarily)
281    ;; output-translating.
282   (cl-parsing-keywords
283       ((:activate-callback 'default-choose-completion)
284        :user-data
285        :reference-buffer
286        (:help-string completion-default-help-string)
287        :window-width)
288       ()
289     (let ((old-buffer (current-buffer))
290           (bufferp (bufferp standard-output)))
291       (if bufferp
292           (set-buffer standard-output))
293       (if (null completions)
294           (princ (gettext
295                   "There are no possible completions of what you have typed."))
296         (let ((win-width
297                (or cl-window-width
298                    (if bufferp
299                        ;; This needs fixing for the case of windows 
300                        ;; that aren't the same width's the frame.
301                        ;; Sadly, the window it will appear in is not known
302                        ;; until after the text has been made.
303
304                        ;; We have to use last-nonminibuf-frame here
305                        ;; and not selected-frame because if a
306                        ;; minibuffer-only frame is being used it will
307                        ;; be the selected-frame at the point this is
308                        ;; run.  We keep the selected-frame call around
309                        ;; just in case.
310                        (frame-width (or (last-nonminibuf-frame)
311                                         (selected-frame)))
312                      80))))
313           (let ((count 0)
314                 (max-width 0))
315             ;; Find longest completion
316             (let ((tail completions))
317               (while tail
318                 (let* ((elt (car tail))
319                        (len (cond ((stringp elt)
320                                    (length elt))
321                                   ((and (consp elt)
322                                         (stringp (car elt))
323                                         (stringp (car (cdr elt))))
324                                    (+ (length (car elt))
325                                       (length (car (cdr elt)))))
326                                   (t
327                                    (signal 'wrong-type-argument
328                                            (list 'stringp elt))))))
329                   (if (> len max-width)
330                       (setq max-width len))
331                   (setq count (1+ count)
332                         tail (cdr tail)))))
333         
334             (setq max-width (+ 2 max-width)) ; at least two chars between cols
335             (let ((rows (let ((cols (min (/ win-width max-width) count)))
336                           (if (<= cols 1)
337                               count
338                             (progn
339                               ;; re-space the columns
340                               (setq max-width (/ win-width cols))
341                               (if (/= (% count cols) 0) ; want ceiling...
342                                   (1+ (/ count cols))
343                                 (/ count cols)))))))
344               (princ (gettext "Possible completions are:"))
345               (let ((tail completions)
346                     (r 0)
347                     (regexp-string
348                      (if (eq t
349                              completion-highlight-first-word-only)
350                          "[ \t]"
351                        completion-highlight-first-word-only)))
352                 (while (< r rows)
353                   (terpri)
354                   (let ((indent 0)
355                         (column 0)
356                         (tail2 tail))
357                     (while tail2
358                       (let ((elt (car tail2)))
359                         (if (/= indent 0)
360                             (if bufferp
361                                 (indent-to indent 2)
362                               (while (progn (write-char ?\ )
363                                             (setq column (1+ column))
364                                             (< column indent)))))
365                         (setq indent (+ indent max-width))
366                         (let ((start (point))
367                               end)
368                           ;; Frob some mousable extents in there too!
369                           (if (consp elt)
370                               (progn
371                                 (princ (car elt))
372                                 (princ (car (cdr elt)))
373                                 (or bufferp
374                                     (setq column
375                                           (+ column
376                                              (length (car elt))
377                                              (length (car (cdr elt)))))))
378                             (progn
379                               (princ elt)
380                               (or bufferp
381                                   (setq column (+ column (length
382                                                           elt))))))
383                           (add-list-mode-item
384                            start
385                            (progn
386                              (setq end (point))
387                              (or
388                               (and completion-highlight-first-word-only
389                                    (goto-char start)
390                                    (re-search-forward regexp-string end t)
391                                    (match-beginning 0))
392                               end))
393                            nil cl-activate-callback cl-user-data)
394                           (goto-char end)))
395                       (setq tail2 (nthcdr rows tail2)))
396                     (setq tail (cdr tail)
397                           r (1+ r)))))))))
398       (if bufferp
399           (set-buffer old-buffer)))
400     (save-excursion
401       (let ((mainbuf (or cl-reference-buffer (current-buffer))))
402         (set-buffer standard-output)
403         (completion-list-mode)
404         (make-local-variable 'completion-reference-buffer)
405         (setq completion-reference-buffer mainbuf)
406 ;;; The value 0 is right in most cases, but not for file name completion.
407 ;;; so this has to be turned off.
408 ;;;      (setq completion-base-size 0)
409         (goto-char (point-min))
410         (let ((buffer-read-only nil))
411           (insert (eval cl-help-string)))
412           ;; unnecessary FSFmacs crock
413           ;;(forward-line 1)
414           ;;(while (re-search-forward "[^ \t\n]+\\( [^ \t\n]+\\)*" nil t)
415           ;;      (let ((beg (match-beginning 0))
416           ;;            (end (point)))
417           ;;        (if completion-fixup-function
418           ;;            (funcall completion-fixup-function))
419           ;;        (put-text-property beg (point) 'mouse-face 'highlight)
420           ;;        (put-text-property beg (point) 'list-mode-item t)
421           ;;        (goto-char end)))))
422         ))
423     (run-hooks 'completion-setup-hook)))
424
425 (defvar completion-display-completion-list-function 'display-completion-list
426   "Function to set up the list of completions in the completion buffer.
427 The function is called with one argument, the sorted list of completions.
428 Particular minibuffer interface functions (e.g. `read-file-name') may
429 want to change this.  To do that, set a local value for this variable
430 in the minibuffer; that ensures that other minibuffer invocations will
431 not be affected.")
432
433 (defun minibuffer-completion-help ()
434   "Display a list of possible completions of the current minibuffer contents.
435 The list of completions is determined by calling `all-completions',
436 passing it the current minibuffer contents, the value of
437 `minibuffer-completion-table', and the value of
438 `minibuffer-completion-predicate'.  The list is displayed by calling
439 the value of `completion-display-completion-list-function' on the sorted
440 list of completions, with the standard output set to the completion
441 buffer."
442   (interactive)
443   (message "Making completion list...")
444   (let ((completions (all-completions (buffer-string)
445                                       minibuffer-completion-table
446                                       minibuffer-completion-predicate)))
447     (message nil)
448     (if (null completions)
449         (progn
450           (ding nil 'no-completion)
451           (temp-minibuffer-message " [No completions]"))
452         (with-output-to-temp-buffer "*Completions*"
453           (funcall completion-display-completion-list-function
454                    (sort completions #'string-lessp))))))
455
456 (define-derived-mode completion-list-mode list-mode 
457   "Completion List"
458   "Major mode for buffers showing lists of possible completions.
459 Type \\<completion-list-mode-map>\\[choose-completion] in the completion list\
460  to select the completion near point.
461 Use \\<completion-list-mode-map>\\[mouse-choose-completion] to select one\
462  with the mouse."
463   (make-local-variable 'completion-base-size)
464   (setq completion-base-size nil))
465
466 (let ((map completion-list-mode-map))
467   (define-key map "\e\e\e" 'delete-completion-window)
468   (define-key map "\C-g" 'minibuffer-keyboard-quit)
469   (define-key map "q" 'abort-recursive-edit)
470   (define-key map " " (lambda () (interactive)
471                         (select-window (minibuffer-window))))
472   (define-key map "\t" (lambda () (interactive)
473                          (select-window (minibuffer-window)))))
474
475 (defvar completion-reference-buffer nil
476   "Record the buffer that was current when the completion list was requested.
477 This is a local variable in the completion list buffer.
478 Initial value is nil to avoid some compiler warnings.")
479
480 (defvar completion-base-size nil
481   "Number of chars at beginning of minibuffer not involved in completion.
482 This is a local variable in the completion list buffer
483 but it talks about the buffer in `completion-reference-buffer'.
484 If this is nil, it means to compare text to determine which part
485 of the tail end of the buffer's text is involved in completion.")
486
487 (defun delete-completion-window ()
488   "Delete the completion list window.
489 Go to the window from which completion was requested."
490   (interactive)
491   (let ((buf completion-reference-buffer))
492     (delete-window (selected-window))
493     (if (get-buffer-window buf)
494          (select-window (get-buffer-window buf)))))
495
496 (defun completion-do-in-minibuffer ()
497   (interactive "_")
498   (save-excursion
499     (set-buffer (window-buffer (minibuffer-window)))
500     (call-interactively (key-binding (this-command-keys)))))
501
502 (defun default-choose-completion (event extent buffer)
503   "Click on an alternative in the `*Completions*' buffer to choose it."
504   (and (button-event-p event)
505        ;; Give temporary modes such as isearch a chance to turn off.
506        (run-hooks 'mouse-leave-buffer-hook))
507   (or buffer (setq buffer (symbol-value-in-buffer
508                            'completion-reference-buffer
509                            (or (and (button-event-p event)
510                                     (event-buffer event))
511                                (current-buffer)))))
512   (save-selected-window
513    (and (button-event-p event)
514         (select-window (event-window event)))
515    (if (and (one-window-p t 'selected-frame)
516             (window-dedicated-p (selected-window)))
517        ;; This is a special buffer's frame
518        (iconify-frame (selected-frame))
519      (or (window-dedicated-p (selected-window))
520          (bury-buffer))))
521   (choose-completion-string (extent-string extent)
522                             buffer
523                             completion-base-size))
524
525 ;; Delete the longest partial match for STRING
526 ;; that can be found before POINT.
527 (defun choose-completion-delete-max-match (string)
528   (let ((len (min (length string)
529                   (- (point) (point-min)))))
530     (goto-char (- (point) (length string)))
531     (if completion-ignore-case
532          (setq string (downcase string)))
533     (while (and (> len 0)
534                  (let ((tail (buffer-substring (point)
535                                                (+ (point) len))))
536                    (if completion-ignore-case
537                        (setq tail (downcase tail)))
538                    (not (string= tail (substring string 0 len)))))
539       (setq len (1- len))
540       (forward-char 1))
541     (delete-char len)))
542
543 ;; Switch to BUFFER and insert the completion choice CHOICE.
544 ;; BASE-SIZE, if non-nil, says how many characters of BUFFER's text
545 ;; to keep.  If it is nil, use choose-completion-delete-max-match instead.
546 (defun choose-completion-string (choice &optional buffer base-size)
547   (let ((buffer (or buffer completion-reference-buffer)))
548     ;; If BUFFER is a minibuffer, barf unless it's the currently
549     ;; active minibuffer.
550     (if (and (string-match "\\` \\*Minibuf-[0-9]+\\*\\'" (buffer-name buffer))
551               (or (not (active-minibuffer-window))
552                   (not (equal buffer
553                               (window-buffer (active-minibuffer-window))))))
554          (error "Minibuffer is not active for completion")
555       ;; Insert the completion into the buffer where completion was requested.
556       (set-buffer buffer)
557       (if base-size
558            (delete-region (+ base-size (point-min)) (point))
559          (choose-completion-delete-max-match choice))
560       (insert choice)
561       (remove-text-properties (- (point) (length choice)) (point)
562                                '(highlight nil))
563       ;; Update point in the window that BUFFER is showing in.
564       (let ((window (get-buffer-window buffer t)))
565          (set-window-point window (point)))
566       ;; If completing for the minibuffer, exit it with this choice.
567       (and (equal buffer (window-buffer (minibuffer-window)))
568             minibuffer-completion-table
569             (exit-minibuffer)))))
570
571 (define-key minibuffer-local-completion-map [prior]
572   'switch-to-completions)
573 (define-key minibuffer-local-must-match-map [prior]
574   'switch-to-completions)
575 (define-key minibuffer-local-completion-map "\M-v"
576   'advertised-switch-to-completions)
577 (define-key minibuffer-local-must-match-map "\M-v"
578   'advertised-switch-to-completions)
579
580 (defalias 'advertised-switch-to-completions 'switch-to-completions)
581 (defun switch-to-completions ()
582   "Select the completion list window."
583   (interactive)
584   ;; Make sure we have a completions window.
585   (or (get-buffer-window "*Completions*")
586       (minibuffer-completion-help))
587   (if (not (get-buffer-window "*Completions*"))
588       nil
589     (select-window (get-buffer-window "*Completions*"))
590     (goto-char (next-single-property-change (point-min) 'list-mode-item nil
591                                             (point-max)))))
592
593 ;;; list-mode.el ends here