bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#12638: 24.2.50; FR: Some suggestions for icomplete-mode


From: Jambunathan K
Subject: bug#12638: 24.2.50; FR: Some suggestions for icomplete-mode
Date: Fri, 02 Nov 2012 17:19:14 +0530
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.50 (gnu/linux)

I am attaching a second revision of the earlier patch.  

It is not that patch is final.  It is worthy of further discussion and
experimentation.

Changes are in minibuffer.el and icomplete.el.

1. `minibuffer-summarize-completions' is meant as a replacement for
   `icomplete-exhibit'.  As the name suggests, it is meant to go in to
    minibuffer.el.  It's presence in minibuffer.el proved problematic
   (details in followup mail) and I had to move it to icomplete.el.

   TODO: Handle key binding hints for sole command matches?  Is it
   really necessary.  It seems so `old school'.  May be it made sense
   when Emacs /did not/ provide key binding hints.

2. There is a display overlay that is used in minibuffer (see
   `minibuffer-message').  There is a counterpart in icomplete.el named
   `icomplete-overlay'. 

   I am wondering whether `icomplete-overlay' could be thrown away and
   have it use, yet to be introduced `minibuffer-overlay'.  Should this
   be buffer-local etc etc. I am not sure of.

   I can exchange notes if there is some interest around this area.

3. Apropos `minibuffer-force-complete-and-exit' and `confirm' etc.

    >> +(defun icomplete-this-match ()
    >> +  "Input first of the displayed matches to minibuffer prompt.
    >> +See `icomplete-matches'."
    >> +  (interactive)
    >> +  (delete-region (minibuffer-prompt-end) (point))
    >> +  (when icomplete-matches
    >> +    (insert (car icomplete-matches)))
    >> +  (exit-minibuffer))
    >
    > I think it should still call test-completion and obey
    > minibuffer-completion-confirm if that test fails.

  Can `test-completion' fail if the prompt is filled from valid
  candidates? Remember, `minibuffer-force-and-complete-and-exit' doesn't
  rely on manual-typing at the prompt.


--8<---------------cut here---------------start------------->8---

=== modified file 'lisp/icomplete.el'
--- lisp/icomplete.el   2012-06-22 17:37:28 +0000
+++ lisp/icomplete.el   2012-11-02 11:12:09 +0000
@@ -71,6 +71,9 @@
 (make-obsolete-variable
  'icomplete-prospects-length 'icomplete-prospects-height "23.1")
 
+(defvar icomplete-separator " | "
+  "String used by icomplete to separate alternatives in the minibuffer.")
+
 ;;;_* User Customization variables
 (defcustom icomplete-prospects-height
   ;; 20 is an estimated common size for the prompt + minibuffer content, to
@@ -102,7 +105,14 @@
   :type 'boolean
   :group 'icomplete)
 
-(defcustom icomplete-minibuffer-setup-hook nil
+(defcustom icomplete-minibuffer-setup-hook
+  '((lambda ()
+      (define-key icomplete-minibuffer-map
+       "\C-j" 'minibuffer-force-complete-and-exit)
+      (define-key icomplete-minibuffer-map
+       "\C-s" 'minibuffer-forward-sorted-completions)
+      (define-key icomplete-minibuffer-map
+       "\C-r" 'minibuffer-backward-sorted-completions)))
   "Icomplete-specific customization of minibuffer setup.
 
 This hook is run during minibuffer setup if icomplete is active.
@@ -118,6 +128,21 @@
 will constrain Emacs to a maximum minibuffer height of 3 lines when
 icompletion is occurring."
   :type 'hook
+  :version "24.3"
+  :group 'icomplete)
+
+(defcustom icomplete-hide-common-prefix t
+  "When non-nil, hide common prefix (if any) from completion candidates.
+Default setting (typically) allows more candidates to be shown
+for given `icomplete-prospects-height'.  When nil, show
+candidates in full."
+  :type 'boolean
+  :version "24.3"
+  :group 'icomplete)
+
+(defface icomplete-first-match  '((t :weight bold))
+  "Face used by icomplete for highlighting first match."
+  :version "24.3"
   :group 'icomplete)
 
 
@@ -169,6 +194,11 @@
 Icomplete does not operate with any specialized completion tables
 except those on this list.")
 
+(defvar icomplete-minibuffer-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map [?\M-\t] 'minibuffer-force-complete)
+    map))
+
 ;;;_ > icomplete-mode (&optional prefix)
 ;;;###autoload
 (define-minor-mode icomplete-mode
@@ -207,7 +237,9 @@
   "Run in minibuffer on activation to establish incremental completion.
 Usually run by inclusion in `minibuffer-setup-hook'."
   (when (and icomplete-mode (icomplete-simple-completing-p))
-    (set (make-local-variable 'completion-show-inline-help) nil)
+    ;; (set (make-local-variable 'completion-show-inline-help) nil)
+    (use-local-map (make-composed-keymap icomplete-minibuffer-map
+                                        (current-local-map)))
     (add-hook 'pre-command-hook
              (lambda () (let ((non-essential t))
                       (run-hooks 'icomplete-pre-command-hook)))
@@ -227,155 +259,192 @@
   "Remove completions display \(if any) prior to new user input.
 Should be run in on the minibuffer `pre-command-hook'.  See `icomplete-mode'
 and `minibuffer-setup-hook'."
-  (delete-overlay icomplete-overlay))
+  (unless (memq this-command '(minibuffer-force-complete-and-exit 
minibuffer-forward-sorted-completions
+                                                   
minibuffer-backward-sorted-completions))
+    ;; Current command does not belong to icomplete-mode.
+    ;; Delete the overlay.
+    (delete-overlay icomplete-overlay)))
+
+(defun minibuffer-summarize-completions (&optional separator max-width
+                                                  first-match-face
+                                                  strip-common-substring)
+  "Summarize minibuffer completion and return it as a string.
+
+Return one of \"[No match]\", \"[Sole completion]\" or a string
+of the form:
+
+    \"\(TAB-COMPL\){CAND-1 SEPARATOR CAND-2 ... CAND-N SEPARATOR CONTD}\"
+
+CAND-1, CAND-2 etc are completion candidates.
+
+TAB-COMPL, when present, indicates the string to which next
+`minibuffer-complete' will complete to.
+
+CONTD (string \"...\"), when present, indicates that not all
+completions are shown.  To disclose completions that are hidden,
+use `minibuffer-forward-sorted-completions' and
+`minibuffer-backward-sorted-completions'.
+
+SEPARATOR is a string to separate completions.  If nil, use \" | \".
+
+MAX-WIDTH is width in characters, as a multiple of window width,
+to which summary string is capped.  If MAX-WIDTH is not a
+multiple of `\(window-width\)', it is increased suitably.  If it
+is nil (or in any case), it is forcibly increased to just
+accommodate minibuffer contents.
+
+FIRST-MATCH-FACE is a face to be applied to first of the matches,
+which can be input with `minibuffer-force-complete-and-exit'.  If
+nil, use 'bold face.
+
+STRIP-COMMON-SUBSTRING, a boolean, controls whether how
+completion candidates are displayed.  If nil, show completions in
+full.  Otherwise, strip common prefix of all
+completions (obtained with `try-completion') from completions."
+  
+  ;; Set defaults for optional params.
+  (setq separator (or separator " | "))
+  ;; Ensure max-width is big enough to hold current minibuffer
+  ;; content.  Stretch max-width to a multiple of window width.
+  (let* ((content-width (string-width (buffer-string))))
+    (setq max-width (max content-width (or max-width 0)))
+    (setq max-width
+         (let* ((normalized-height (/ max-width (window-width)))
+                (normalized-width (* normalized-height (window-width))))
+           (if (< normalized-height max-width)
+               (+ normalized-width (window-width))
+             normalized-width))))
+  (setq first-match-face (or first-match-face 'bold))
+  (let* ((beg (field-beginning))
+         (end (field-end))
+         (string (buffer-substring beg end))
+         (md (completion--field-metadata beg))
+         (try-comp (completion-try-completion
+                   string
+                   minibuffer-completion-table
+                   minibuffer-completion-predicate
+                   (- (point) beg)
+                   md)))
+    (cond
+     ;; No match.
+     ((null try-comp)
+      "[No match]")
+     ;; Sole completion.  Exact and unique match.
+     ((eq t try-comp)
+      "[Sole completion]")
+     (t
+      ;; `completed' should be t if some completion was done, which doesn't
+      ;; include simply changing the case of the entered string.  However,
+      ;; for appearance, the string is rewritten if the case changes.
+      (let* ((comp-pos (cdr try-comp))
+             (completion (car try-comp))
+            (compare (compare-strings completion nil nil
+                                      string nil nil
+                                      completion-ignore-case))
+             (completed (not (eq t compare)))
+            (exact (test-completion completion
+                                    minibuffer-completion-table
+                                    minibuffer-completion-predicate))
+            (comps (completion-all-sorted-completions))
+            ;; Compute common substring, if user wants it stripped from
+            ;; completion candidates.
+            (common-substring (when strip-common-substring
+                                (let ((common (try-completion "" comps)))
+                                  (when (stringp common) common))))
+            (common-substring-len (when common-substring
+                                    (length common-substring)))
+            width)
+       ;; Trim completion, when possible.
+       (if (not completed)
+           (setq completion nil)
+         (setq compare (1- (abs compare)))
+         (setq completion (if (= compare (length string))
+                              (substring completion compare)
+                            (concat "..." completion))))
+
+       ;; Account for various filler characters.
+       (setq width (string-width (concat (buffer-string)
+                                         "(...)" "{" separator "..." "}")))
+       ;; Add completion.
+       (when completion
+         (setq width (+ width (string-width completion)))
+         (unless (< width max-width)
+           (setq completion nil)))
+
+       ;; Add completion candidates.
+       (let* ((last (last comps))
+              (base-size (cdr last))
+              first limit prospects)
+         ;; Remove the base-size tail to get a properly
+         ;; nil-terminated list that is amenable for easy iteration.
+         (when last (setcdr last nil))
+
+         ;; Decorate the first of completion with requested face.
+         (setq first (copy-sequence (pop comps)))
+         (put-text-property 0 (length first) 'face first-match-face first)
+         (setq first (if (not common-substring-len) first
+                       (substring first common-substring-len)))
+         (setq width (+ width (string-width first)))
+         (when (< width max-width)
+           (setq prospects first))
+
+         ;; Rest of the completions.
+         (while (and comps (not limit))
+           (let* ((comp (pop comps)))
+             (setq comp (concat separator
+                                (if (not common-substring-len) comp
+                                  (substring comp common-substring-len))))
+             (setq width (+ width (string-width comp)))
+             (if (< width max-width)
+                 (setq prospects (concat prospects comp))
+               (setq prospects (concat prospects separator "..."))
+               (setq limit t))))
+
+         ;; Restore the base-size info, since
+         ;; `completion-all-sorted-completions' is cached.
+         (when last (setcdr last base-size))
+
+         ;; Return summary output.
+         (concat (and completion (concat "(" completion ")"))
+                 (and prospects (concat "{" prospects "}")))))))))
 
 ;;;_ > icomplete-exhibit ()
 (defun icomplete-exhibit ()
   "Insert icomplete completions display.
 Should be run via minibuffer `post-command-hook'.  See `icomplete-mode'
 and `minibuffer-setup-hook'."
-  (when (and icomplete-mode (icomplete-simple-completing-p))
-    (save-excursion
-      (goto-char (point-max))
+  (if (and icomplete-mode (icomplete-simple-completing-p))
+      (save-excursion
+       (goto-char (point-max))
                                         ; Insert the match-status information:
-      (if (and (> (point-max) (minibuffer-prompt-end))
-              buffer-undo-list         ; Wait for some user input.
-              (or
-               ;; Don't bother with delay after certain number of chars:
-               (> (- (point) (field-beginning)) icomplete-max-delay-chars)
-               ;; Don't delay if alternatives number is small enough:
-               (and (sequencep minibuffer-completion-table)
-                    (< (length minibuffer-completion-table)
-                       icomplete-delay-completions-threshold))
-               ;; Delay - give some grace time for next keystroke, before
-               ;; embarking on computing completions:
-               (sit-for icomplete-compute-delay)))
-         (let ((text (while-no-input
-                        (icomplete-completions
-                         (field-string)
-                         minibuffer-completion-table
-                         minibuffer-completion-predicate
-                         (not minibuffer-completion-confirm))))
-               (buffer-undo-list t)
-               deactivate-mark)
-           ;; Do nothing if while-no-input was aborted.
-           (when (stringp text)
-              (move-overlay icomplete-overlay (point) (point) (current-buffer))
-              ;; The current C cursor code doesn't know to use the overlay's
-              ;; marker's stickiness to figure out whether to place the cursor
-              ;; before or after the string, so let's spoon-feed it the pos.
-              (put-text-property 0 1 'cursor t text)
-              (overlay-put icomplete-overlay 'after-string text)))))))
-
-;;;_ > icomplete-completions (name candidates predicate require-match)
-(defun icomplete-completions (name candidates predicate require-match)
-  "Identify prospective candidates for minibuffer completion.
-
-The display is updated with each minibuffer keystroke during
-minibuffer completion.
-
-Prospective completion suffixes (if any) are displayed, bracketed by
-one of \(), \[], or \{} pairs.  The choice of brackets is as follows:
-
-  \(...) - a single prospect is identified and matching is enforced,
-  \[...] - a single prospect is identified but matching is optional, or
-  \{...} - multiple prospects, separated by commas, are indicated, and
-          further input is required to distinguish a single one.
-
-The displays for unambiguous matches have ` [Matched]' appended
-\(whether complete or not), or ` \[No matches]', if no eligible
-matches exist.  \(Keybindings for uniquely matched commands
-are exhibited within the square braces.)"
-
-  (let* ((md (completion--field-metadata (field-beginning)))
-        (comps (completion-all-sorted-completions))
-         (last (if (consp comps) (last comps)))
-         (base-size (cdr last))
-         (open-bracket (if require-match "(" "["))
-         (close-bracket (if require-match ")" "]")))
-    ;; `concat'/`mapconcat' is the slow part.
-    (if (not (consp comps))
-        (format " %sNo matches%s" open-bracket close-bracket)
-      (if last (setcdr last nil))
-      (let* ((most-try
-              (if (and base-size (> base-size 0))
-                  (completion-try-completion
-                   name candidates predicate (length name) md)
-                ;; If the `comps' are 0-based, the result should be
-                ;; the same with `comps'.
-                (completion-try-completion
-                 name comps nil (length name) md)))
-            (most (if (consp most-try) (car most-try)
-                     (if most-try (car comps) "")))
-             ;; Compare name and most, so we can determine if name is
-             ;; a prefix of most, or something else.
-            (compare (compare-strings name nil nil
-                                      most nil nil completion-ignore-case))
-            (determ (unless (or (eq t compare) (eq t most-try)
-                                (= (setq compare (1- (abs compare)))
-                                   (length most)))
-                      (concat open-bracket
-                              (cond
-                               ((= compare (length name))
-                                 ;; Typical case: name is a prefix.
-                                (substring most compare))
-                               ((< compare 5) most)
-                               (t (concat "..." (substring most compare))))
-                              close-bracket)))
-            ;;"-prospects" - more than one candidate
-            (prospects-len (+ (length determ) 6 ;; take {,...} into account
-                               (string-width (buffer-string))))
-             (prospects-max
-              ;; Max total length to use, including the minibuffer content.
-              (* (+ icomplete-prospects-height
-                    ;; If the minibuffer content already uses up more than
-                    ;; one line, increase the allowable space accordingly.
-                    (/ prospects-len (window-width)))
-                 (window-width)))
-             (prefix-len
-              ;; Find the common prefix among `comps'.
-             ;; We can't use the optimization below because its assumptions
-             ;; aren't always true, e.g. when completion-cycling (bug#10850):
-             ;; (if (eq t (compare-strings (car comps) nil (length most)
-             ;;                         most nil nil completion-ignore-case))
-             ;;     ;; Common case.
-             ;;     (length most)
-             ;; Else, use try-completion.
-             (let ((comps-prefix (try-completion "" comps)))
-               (and (stringp comps-prefix)
-                    (length comps-prefix)))) ;;)
-
-            prospects most-is-exact comp limit)
-       (if (eq most-try t) ;; (or (null (cdr comps))
-           (setq prospects nil)
-         (while (and comps (not limit))
-           (setq comp
-                 (if prefix-len (substring (car comps) prefix-len) (car comps))
-                 comps (cdr comps))
-           (cond ((string-equal comp "") (setq most-is-exact t))
-                 ((member comp prospects))
-                 (t (setq prospects-len
-                           (+ (string-width comp) 1 prospects-len))
-                    (if (< prospects-len prospects-max)
-                        (push comp prospects)
-                      (setq limit t))))))
-        ;; Restore the base-size info, since completion-all-sorted-completions
-        ;; is cached.
-        (if last (setcdr last base-size))
-       (if prospects
-           (concat determ
-                   "{"
-                   (and most-is-exact ",")
-                   (mapconcat 'identity (nreverse prospects) ",")
-                   (and limit ",...")
-                   "}")
-         (concat determ
-                 " [Matched"
-                 (let ((keys (and icomplete-show-key-bindings
-                                  (commandp (intern-soft most))
-                                  (icomplete-get-keys most))))
-                   (if keys (concat "; " keys) ""))
-                 "]"))))))
+       (if (and (> (point-max) (minibuffer-prompt-end))
+                buffer-undo-list       ; Wait for some user input.
+                (or
+                 ;; Don't bother with delay after certain number of chars:
+                 (> (- (point) (field-beginning)) icomplete-max-delay-chars)
+                 ;; Don't delay if alternatives number is small enough:
+                 (and (sequencep minibuffer-completion-table)
+                      (< (length minibuffer-completion-table)
+                         icomplete-delay-completions-threshold))
+                 ;; Delay - give some grace time for next keystroke, before
+                 ;; embarking on computing completions:
+                 (sit-for icomplete-compute-delay)))
+           (let ((text (while-no-input
+                         (minibuffer-summarize-completions
+                          icomplete-separator
+                          (* icomplete-prospects-height (window-width))
+                          'icomplete-first-match
+                          icomplete-hide-common-prefix)))
+                 (buffer-undo-list t)
+                 deactivate-mark)
+             ;; Do nothing if while-no-input was aborted.
+             (when (stringp text)
+               (move-overlay icomplete-overlay (point) (point) 
(current-buffer))
+               ;; The current C cursor code doesn't know to use the overlay's
+               ;; marker's stickiness to figure out whether to place the cursor
+               ;; before or after the string, so let's spoon-feed it the pos.
+               (put-text-property 0 1 'cursor t text)
+               (overlay-put icomplete-overlay 'after-string text)))))))
 
 ;;_* Local emacs vars.
 ;;Local variables:

=== modified file 'lisp/minibuffer.el'
--- lisp/minibuffer.el  2012-10-28 19:07:52 +0000
+++ lisp/minibuffer.el  2012-11-02 07:57:15 +0000
@@ -1108,12 +1108,68 @@
             (let ((hist (symbol-value minibuffer-history-variable)))
               (setq all (sort all (lambda (c1 c2)
                                     (> (length (member c1 hist))
-                                       (length (member c2 hist))))))))
+                                       (length (member c2 hist)))))))
+           ;; Bring exact (but not unique) match to the front.
+           (when (member string all)
+             (push string all))
+
+           ;; Delete duplicates.
+           (delete-dups all))
           ;; Cache the result.  This is not just for speed, but also so that
           ;; repeated calls to minibuffer-force-complete can cycle through
           ;; all possibilities.
           (completion--cache-all-sorted-completions (nconc all base-size))))))
 
+(defun minibuffer-force-complete-and-exit ()
+  "Complete the minibuffer with first of the matches and exit."
+  (interactive)
+  ;; FIXME: Need to deal with the extra-size issue here as well.
+  ;; FIXME: ~/src/emacs/t<M-TAB>/lisp/minibuffer.el completes to
+  ;; ~/src/emacs/trunk/ and throws away lisp/minibuffer.el.
+  (let* ((start (copy-marker (field-beginning)))
+         (end (field-end))
+         ;; (md (completion--field-metadata start))
+         (all (completion-all-sorted-completions))
+         (base (+ start (or (cdr (last all)) 0))))
+    (cond
+     ((not (consp all))
+      (completion--message
+       (if all "No more completions" "No completions")))
+     ((not (consp (cdr all)))
+      (let ((done (equal (car all) (buffer-substring-no-properties base end))))
+        (unless done (completion--replace base end (car all)))
+        (completion--done (buffer-substring-no-properties start (point))
+                          'finished (when done "Sole completion"))
+       (exit-minibuffer)))
+     (t
+      (completion--replace base end (car all))
+      (completion--done (buffer-substring-no-properties start (point))
+                       'sole)
+      (exit-minibuffer)))))
+
+(defun minibuffer-forward-sorted-completions ()
+  "Step forward completions by one entry.
+Second entry becomes the first and can be selected with
+`minibuffer-force-complete-and-exit'."
+  (interactive)
+  (let* ((comps (completion-all-sorted-completions))
+        (last (last comps)))
+    (setcdr last (cons (car comps) (cdr last)))
+    (completion--cache-all-sorted-completions (cdr comps))))
+
+(defun minibuffer-backward-sorted-completions ()
+  "Step backward completions by one entry.
+Last entry becomes the first and can be selected with
+`minibuffer-force-complete-and-exit'."
+  (interactive)
+  (let* ((comps (completion-all-sorted-completions))
+        (last-but-one (last comps 2))
+        (last (cdr last-but-one)))
+    (when last
+      (setcdr last-but-one (cdr last))
+      (push (car last) comps)
+      (completion--cache-all-sorted-completions comps))))
+
 (defun minibuffer-force-complete ()
   "Complete the minibuffer to an exact match.
 Repeated uses step through the possible completions."

--8<---------------cut here---------------end--------------->8---

reply via email to

[Prev in Thread] Current Thread [Next in Thread]