comparison lisp/modes/list-mode.el @ 0:376386a54a3c r19-14

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