;;; list-mode.el --- Major mode for buffers containing lists of items
;; Copyright (C) 1992-4, 1997 Free Software Foundation, Inc.
;; Copyright (C) 1996 Ben Wing.
;; Maintainer: XEmacs Development Team
;; Keywords: extensions, dumped
;; This file is part of XEmacs.
;; XEmacs is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; XEmacs is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Synched up with: Not synched
;; This file is dumped with XEmacs.
;; Cleanup, merging with FSF by Ben Wing, January 1996
(defvar list-mode-extent nil)
(defvar list-mode-map nil
"Local map for buffers containing lists of items.")
(let ((map (setq list-mode-map (make-sparse-keymap 'list-mode-map))))
(define-key map 'button2up 'list-mode-item-mouse-selected)
(define-key map 'button2 'undefined)
(define-key map "\C-m" 'list-mode-item-keyboard-selected)
;; The following calls to `substitute-key-definition' losed because
;; they were based on an incorrect assumption that `forward-char' and
;; `backward-char' are bound to keys in the global map. This might not
;; be the case if a user binds motion keys to different functions,
;; and was not actually the case since 20.5 beta 28 or around.
;; (substitute-key-definition 'forward-char 'next-list-mode-item map
;; (substitute-key-definition 'backward-char 'previous-list-mode-item map
;; We bind standard keys to motion commands instead.
(dolist (key '(kp-right right (control ?f)))
(define-key map key 'next-list-mode-item))
(dolist (key '(kp-left left (control ?b)))
(define-key map key 'previous-list-mode-item))))
(defun list-mode ()
"Major mode for buffer containing lists of items."
(setq mode-name "List")
(setq major-mode 'list-mode)
(add-hook 'post-command-hook 'set-list-mode-extent nil t)
(add-hook 'pre-command-hook 'list-mode-extent-pre-hook nil t)
(setq next-line-add-newlines nil)
(setq list-mode-extent nil)
(set-specifier text-cursor-visible-p nil (current-buffer))
(setq buffer-read-only t)
;; List mode is suitable only for specially formatted data.
(put 'list-mode 'mode-class 'special)
(defvar list-mode-extent-old-point nil
"The value of point when pre-command-hook is called.
Used to determine the direction of motion.")
(defun list-mode-extent-pre-hook ()
(setq list-mode-extent-old-point (point))
;(setq atomic-extent-goto-char-p nil)
(defun set-list-mode-extent ()
"Move to the closest list item and set up the extent for it.
This is called from `post-command-hook'."
(cond ((get-char-property (point) 'list-mode-item))
((and (> (point) (point-min))
(get-char-property (1- (point)) 'list-mode-item))
(goto-char (1- (point))))
(let ((pos (point))
;this fucks things up more than it helps.
;atomic-extent-goto-char-p as currently defined is all broken,
;since it will be triggered if the command *ever* runs goto-char!
; (setq dirflag 1)
(if (and list-mode-extent-old-point
(> pos list-mode-extent-old-point))
(setq dirflag 1)
(setq dirflag -1))
(or (get-char-property (point) 'list-mode-item)
(next-list-mode-item (- dirflag))))))
(or (and list-mode-extent
(eq (current-buffer) (extent-object list-mode-extent)))
(setq list-mode-extent (make-extent nil nil (current-buffer)))
(set-extent-face list-mode-extent 'list-mode-item-selected)))
(let ((ex (extent-at (point) nil 'list-mode-item nil 'at)))
(auto-show-make-region-visible (extent-start-position ex)
(defun previous-list-mode-item (n)
"Move to the previous item in list-mode."
(next-list-mode-item (- n)))
(defun next-list-mode-item (n)
"Move to the next item in list-mode.
With prefix argument N, move N items (negative N means move backward)."
(while (and (> n 0) (not (eobp)))
(let ((prop (get-char-property (point) 'list-mode-item))
;; If in a completion, move to the end of it.
(goto-char (next-single-property-change (point) 'list-mode-item
;; Move to start of next one.
(goto-char (next-single-property-change (point)
'list-mode-item nil end)))
(setq n (1- n)))
(while (and (< n 0) (not (bobp)))
(let ((prop (get-char-property (1- (point)) 'list-mode-item))
;; If in a completion, move to the start of it.
(point) 'list-mode-item nil end)))
;; Move to end of the previous completion.
(goto-char (previous-single-property-change (point) 'list-mode-item
;; Move to the start of that one.
(goto-char (previous-single-property-change (point) 'list-mode-item nil
(setq n (1+ n))))
(defun list-mode-item-selected-1 (extent event)
(let ((func (extent-property extent 'list-mode-item-activate-callback))
(user-data (extent-property extent 'list-mode-item-user-data)))
(funcall func event extent user-data))))
;; we could make these two be just one function, but we want to be
;; able to refer to them in DOC strings.
(defun list-mode-item-keyboard-selected ()
(list-mode-item-selected-1 (extent-at (point) (current-buffer)
'list-mode-item nil 'at)
(defun list-mode-item-mouse-selected (event)
;; Sometimes event-closest-point returns nil.
;; So beep instead of bombing.
(let ((point (event-closest-point event)))
(list-mode-item-selected-1 (extent-at point
'list-mode-item nil 'at)
(defun add-list-mode-item (start end &optional buffer activate-callback
"Add a new list item in list-mode, from START to END in BUFFER.
BUFFER defaults to the current buffer.
This works by creating an extent for the span of text in question.
If ACTIVATE-CALLBACK is non-nil, it should be a function of three
arguments (EVENT EXTENT USER-DATA) that will be called when button2
is pressed on the extent. USER-DATA comes from the optional
(let ((extent (make-extent start end buffer)))
(set-extent-property extent 'list-mode-item t)
(set-extent-property extent 'start-open t)
(set-extent-property extent 'mouse-face 'highlight)
(set-extent-property extent 'list-mode-item-activate-callback
(set-extent-property extent 'list-mode-item-user-data user-data)))
;; Define the major mode for lists of completions.
(defvar completion-highlight-first-word-only nil
"*Completion will only highlight the first blank delimited word if t.
If the variable in not t or nil, the string is taken as a regexp to match for end
(defvar completion-setup-hook nil
"Normal hook run at the end of setting up the text of a completion buffer.")
; Unnecessary FSFmacs crock. We frob the extents directly in
; display-completion-list, so no "heuristics" like this are necessary.
;(defvar completion-fixup-function nil
; "A function to customize how completions are identified in completion lists.
;`completion-setup-function' calls this function with no arguments
;each time it has found what it thinks is one completion.
;Point is at the end of the completion in the completion list buffer.
;If this function moves point, it can alter the end of that completion.")
"Click \\<list-mode-map>\\[list-mode-item-mouse-selected] on a completion to select it.\n") "")
"Type \\<minibuffer-local-completion-map>\\[advertised-switch-to-completions] or \\[switch-to-completions] to move to this buffer, for keyboard selection.\n\n"))
"Form the evaluate to get a help string for completion lists.
This string is inserted at the beginning of the buffer.
(defun display-completion-list (completions &rest cl-keys)
"Display the list of completions, COMPLETIONS, using `standard-output'.
Each element may be just a symbol or string or may be a list of two
strings to be printed as if concatenated.
Frob a mousable extent onto each completion. This extent has properties
'mouse-face (so it highlights when the mouse passes over it) and
'list-mode-item (so it can be located).
:activate-callback (default is `default-choose-completion')
Value passed to activation callback.
If non-nil, width to use in displaying the list, instead of the
actual window's width.
:help-string (default is the value of `completion-default-help-string')
Form to evaluate to get a string to insert at the beginning of
the completion list buffer. This is evaluated when that buffer
is the current buffer and after it has been put into
:reference-buffer (default is the current buffer)
This specifies the value of `completion-reference-buffer' in
the completion buffer. This specifies the buffer (normally a
minibuffer) that `default-choose-completion' will insert the
At the end, run the normal hook `completion-setup-hook'.
It can find the completion buffer in `standard-output'.
If `completion-highlight-first-word-only' is non-nil, then only the start
of the string is highlighted."
;; #### I18N3 should set standard-output to be (temporarily)
(let ((old-buffer (current-buffer))
(bufferp (bufferp standard-output)))
(if (null completions)
"There are no possible completions of what you have typed."))
;; This needs fixing for the case of windows
;; that aren't the same width's the frame.
;; Sadly, the window it will appear in is not known
;; until after the text has been made.
;; We have to use last-nonminibuf-frame here
;; and not selected-frame because if a
;; minibuffer-only frame is being used it will
;; be the selected-frame at the point this is
;; run. We keep the selected-frame call around
;; just in case.
(frame-width (or (last-nonminibuf-frame)
(let ((count 0)
;; Find longest completion
(let ((tail completions))
(let* ((elt (car tail))
(len (cond ((stringp elt)
((and (consp elt)
(stringp (car elt))
(stringp (car (cdr elt))))
(+ (length (car elt))
(length (car (cdr elt)))))
(list 'stringp elt))))))
(if (> len max-width)
(setq max-width len))
(setq count (1+ count)
tail (cdr tail)))))
(setq max-width (+ 2 max-width)) ; at least two chars between cols
(let ((rows (let ((cols (min (/ win-width max-width) count)))
(if (<= cols 1)
;; re-space the columns
(setq max-width (/ win-width cols))
(if (/= (% count cols) 0) ; want ceiling...
(1+ (/ count cols))
(/ count cols)))))))
(princ (gettext "Possible completions are:"))
(let ((tail completions)
(if (eq t
(while (< r rows)
(let ((indent 0)
(let ((elt (car tail2)))
(if (/= indent 0)
(indent-to indent 2)
(while (progn (write-char ?\ )
(setq column (1+ column))
(< column indent)))))
(setq indent (+ indent max-width))
(let ((start (point))
;; Frob some mousable extents in there too!
(if (consp elt)
(princ (car elt))
(princ (car (cdr elt)))
(length (car elt))
(length (car (cdr elt)))))))
(setq column (+ column (length
(setq end (point))
(re-search-forward regexp-string end t)
nil cl-activate-callback cl-user-data)
(setq tail2 (nthcdr rows tail2)))
(setq tail (cdr tail)
r (1+ r)))))))))
(let ((mainbuf (or cl-reference-buffer (current-buffer))))
(setq completion-reference-buffer mainbuf)
;;; The value 0 is right in most cases, but not for file name completion.
;;; so this has to be turned off.
;;; (setq completion-base-size 0)
(let ((buffer-read-only nil))
(insert (eval cl-help-string)))
;; unnecessary FSFmacs crock
;;(while (re-search-forward "[^ \t\n]+\\( [^ \t\n]+\\)*" nil t)
;; (let ((beg (match-beginning 0))
;; (end (point)))
;; (if completion-fixup-function
;; (funcall completion-fixup-function))
;; (put-text-property beg (point) 'mouse-face 'highlight)
;; (put-text-property beg (point) 'list-mode-item t)
;; (goto-char end)))))
(defvar completion-display-completion-list-function 'display-completion-list
"Function to set up the list of completions in the completion buffer.
The function is called with one argument, the sorted list of completions.
Particular minibuffer interface functions (e.g. `read-file-name') may
want to change this. To do that, set a local value for this variable
in the minibuffer; that ensures that other minibuffer invocations will
not be affected.")
(defun minibuffer-completion-help ()
"Display a list of possible completions of the current minibuffer contents.
The list of completions is determined by calling `all-completions',
passing it the current minibuffer contents, the value of
`minibuffer-completion-table', and the value of
`minibuffer-completion-predicate'. The list is displayed by calling
the value of `completion-display-completion-list-function' on the sorted
list of completions, with the standard output set to the completion
(message "Making completion list...")
(let ((completions (all-completions (buffer-string)
(if (null completions)
(ding nil 'no-completion)
(temp-minibuffer-message " [No completions]"))
(sort completions #'string-lessp))))))
(define-derived-mode completion-list-mode list-mode
"Major mode for buffers showing lists of possible completions.
Type \\<completion-list-mode-map>\\[choose-completion] in the completion list\
to select the completion near point.
Use \\<completion-list-mode-map>\\[mouse-choose-completion] to select one\
with the mouse."
(setq completion-base-size nil))
(let ((map completion-list-mode-map))
(define-key map "\e\e\e" 'delete-completion-window)
(define-key map "\C-g" 'minibuffer-keyboard-quit)
(define-key map "q" 'abort-recursive-edit)
(define-key map " " (lambda () (interactive)
(define-key map "\t" (lambda () (interactive)
(defvar completion-reference-buffer nil
"Record the buffer that was current when the completion list was requested.
This is a local variable in the completion list buffer.
Initial value is nil to avoid some compiler warnings.")
(defvar completion-base-size nil
"Number of chars at beginning of minibuffer not involved in completion.
This is a local variable in the completion list buffer
but it talks about the buffer in `completion-reference-buffer'.
If this is nil, it means to compare text to determine which part
of the tail end of the buffer's text is involved in completion.")
(defun delete-completion-window ()
"Delete the completion list window.
Go to the window from which completion was requested."
(let ((buf completion-reference-buffer))
(if (get-buffer-window buf)
(select-window (get-buffer-window buf)))))
(defun completion-do-in-minibuffer ()
(set-buffer (window-buffer (minibuffer-window)))
(call-interactively (key-binding (this-command-keys)))))
(defun default-choose-completion (event extent buffer)
"Click on an alternative in the `*Completions*' buffer to choose it."
(and (button-event-p event)
;; Give temporary modes such as isearch a chance to turn off.
(or buffer (setq buffer (symbol-value-in-buffer
(or (and (button-event-p event)
(and (button-event-p event)
(select-window (event-window event)))
(if (and (one-window-p t 'selected-frame)
;; This is a special buffer's frame
(or (window-dedicated-p (selected-window))
(choose-completion-string (extent-string extent)
;; Delete the longest partial match for STRING
;; that can be found before POINT.
(defun choose-completion-delete-max-match (string)
(let ((len (min (length string)
(- (point) (point-min)))))
(goto-char (- (point) (length string)))
(setq string (downcase string)))
(while (and (> len 0)
(let ((tail (buffer-substring (point)
(+ (point) len))))
(setq tail (downcase tail)))
(not (string= tail (substring string 0 len)))))
(setq len (1- len))
;; Switch to BUFFER and insert the completion choice CHOICE.
;; BASE-SIZE, if non-nil, says how many characters of BUFFER's text
;; to keep. If it is nil, use choose-completion-delete-max-match instead.
(defun choose-completion-string (choice &optional buffer base-size)
(let ((buffer (or buffer completion-reference-buffer)))
;; If BUFFER is a minibuffer, barf unless it's the currently
;; active minibuffer.
(if (and (string-match "\\` \\*Minibuf-[0-9]+\\*\\'" (buffer-name buffer))
(or (not (active-minibuffer-window))
(not (equal buffer
(error "Minibuffer is not active for completion")
;; Insert the completion into the buffer where completion was requested.
(delete-region (+ base-size (point-min)) (point))
(remove-text-properties (- (point) (length choice)) (point)
;; Update point in the window that BUFFER is showing in.
(let ((window (get-buffer-window buffer t)))
(set-window-point window (point)))
;; If completing for the minibuffer, exit it with this choice.
(and (equal buffer (window-buffer (minibuffer-window)))
(define-key minibuffer-local-completion-map [prior]
(define-key minibuffer-local-must-match-map [prior]
(define-key minibuffer-local-completion-map "\M-v"
(define-key minibuffer-local-must-match-map "\M-v"
(defalias 'advertised-switch-to-completions 'switch-to-completions)
(defun switch-to-completions ()
"Select the completion list window."
;; Make sure we have a completions window.
(or (get-buffer-window "*Completions*")
(if (not (get-buffer-window "*Completions*"))
(select-window (get-buffer-window "*Completions*"))
(goto-char (next-single-property-change (point-min) 'list-mode-item nil
;;; list-mode.el ends here