瀏覽代碼

Alternate handling of nil default

The new behavior is based on the principle that "empty string is the
default default" for completing-read, as explained here:
https://debbugs.gnu.org/cgi/bugreport.cgi?bug=27158#8

This should hopefully give somewhat reasonable behavior in all cases
without the need for caller-dependent behavior. However, there is
still a facility for overriding this behavior in order to improve the
situation for callers that don't want/expect completing-read to return
an empty string.
Ryan C. Thompson 8 年之前
父節點
當前提交
175dc8f71f
共有 1 個文件被更改,包括 98 次插入39 次删除
  1. 98 39
      ido-completing-read+.el

+ 98 - 39
ido-completing-read+.el

@@ -85,6 +85,31 @@ This is not meant to be set permanently, but rather let-bound
 before calling `ido-completing-read+' under controlled
 circumstances.")
 
+(defvar ido-cr+-no-default-action 'prepend-empty-string
+  "Controls the behavior of ido-cr+ when DEF is nil and REQUIRE-MATCH is non-nil.
+
+Possible values:
+
+- `prepend-empty-string': The empty string will be added to the
+  front of COLLECTION, making it the default. This is the
+  standard behavior since it mimics the semantics of
+  `completing-read-default'.
+
+- `append-empty-string': The empty string will be added to the
+  end of COLLECTION, thus keeping the original default while
+  making the empty string available as a completion.
+
+- `nil': No action will be taken.
+
+- Any other value: The value will be interpreted as a 1-argument
+  function, which will receive the current collection as its
+  argument and return the collection with any necessary
+  modifications applied.
+
+This is not meant to be set permanently, but rather let-bound
+before calling `ido-completing-read+' under controlled
+circumstances.")
+
 (defvar ido-cr+-orig-completing-read-args nil
   "Original arguments passed to `ido-completing-read+'.
 
@@ -171,6 +196,10 @@ https://github.com/DarwinAwardWinner/ido-ubiquitous/issues"
   "Returns non-nil if ido-cr+ is currently using the minibuffer."
   (>= ido-cr+-minibuffer-depth (minibuffer-depth)))
 
+(defsubst ido-cr+-default-was-provided ()
+  "Returns non-nil if ido-cr+ was passed a non-nil default argument."
+  (and (nth 6 ido-cr+-orig-completing-read-args)))
+
 ;;;###autoload
 (defun ido-completing-read+ (prompt collection &optional predicate
                                     require-match initial-input
@@ -207,12 +236,6 @@ completion for them."
           ;; who worked so hard to make lazy collections work; ido
           ;; doesn't know how to handle those.)
           (setq collection (all-completions "" collection predicate))
-          ;; Check for a specific bug
-          (when (and (version< emacs-version "26.1")
-                     ido-enable-dot-prefix
-                     (member "" collection))
-            (signal 'ido-cr+-fallback
-                    '("ido cannot handle the empty string as an option when `ido-enable-dot-prefix' is non-nil; see https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997")))
           ;; No point in using ido unless there's a collection
           (when (= (length collection) 0)
             (signal 'ido-cr+-fallback '("ido is not needed for an empty collection")))
@@ -224,33 +247,56 @@ completion for them."
                      (format
                       "there are more than %i items in COLLECTION (see `ido-cr+-max-items')"
                       ido-cr+-max-items))))
-          ;; ido doesn't natively handle DEF being a list. If DEF is a
-          ;; list, prepend it to COLLECTION and set DEF to just the
-          ;; car of the default list.
-          (when (and def (listp def))
-            (setq collection
-                  (append def
-                          (nreverse (cl-set-difference collection def)))
-                  def (car def)))
-          ;; Work around a bug in ido when both INITIAL-INPUT and
-          ;; DEF are provided.
-          (let ((initial
-                 (or (if (consp initial-input)
-                         (car initial-input)
-                       initial-input)
-                     "")))
-            (when (and def initial
-                       (stringp initial)
-                       (not (string= initial "")))
-              ;; Both default and initial input were provided. So keep
-              ;; the initial input and preprocess the collection list
-              ;; to put the default at the head, then proceed with
-              ;; default = nil.
-              (setq collection (cons def (remove def collection))
-                    def nil)))
-          ;; Ready to do actual ido completion
+
+          ;; In ido, the semantics of "default" are simply "put it at
+          ;; the front of the list". Furthermore, ido has certain
+          ;; issues with a non-nil DEF arg. Specifically, it can't
+          ;; handle list defaults or providing both DEF and
+          ;; INITIAL-INPUT. So, just pre-process the collection to put
+          ;; the default(s) at the front and then set DEF to nil in
+          ;; the call to ido to avoid these issues.
+          (unless (listp def)
+            ;; Ensure DEF is a list
+            (setq def (list def)))
+          (when def
+            (setq collection (append def (cl-set-difference collection def
+                                                            :test #'equal))
+                  def nil))
+
+          ;; If DEF was nil and REQUIRE-MATCH was non-nil, then we need to
+          ;; add the empty string as the first option, because RET on
+          ;; an empty input needs to return "". (Or possibly we need
+          ;; to take some other action based on the value of
+          ;; `ido-cr+-no-default-action'.)
+          (when (and require-match
+                     ido-cr+-no-default-action
+                     (not (ido-cr+-default-was-provided)))
+            (cl-case ido-cr+-no-default-action
+              (nil
+               ;; Take no action
+               t)
+              (prepend-empty-string
+               (ido-cr+--debug-message "Adding \"\" as the default completion since no default was provided.")
+               (setq collection (cons "" collection)))
+              (append-empty-string
+               (ido-cr+--debug-message "Adding \"\" as a completion option since no default was provided.")
+               (setq collection (append collection '(""))))
+              (otherwise
+               (ido-cr+--debug-message "Running custom action function since no default was provided.")
+               (setq collection (funcall ido-cr+-no-default-action collection)))))
+
+          ;; Check for a specific bug
+          (when (and ido-enable-dot-prefix
+                     (version< emacs-version "26.1")
+                     (member "" collection))
+            (signal 'ido-cr+-fallback
+                    '("ido cannot handle the empty string as an option when `ido-enable-dot-prefix' is non-nil; see https://debbugs.gnu.org/cgi/bugreport.cgi?bug=26997")))
+
+          ;; Finally ready to do actual ido completion
           (prog1
-              (let ((ido-cr+-minibuffer-depth (1+ (minibuffer-depth))))
+              (let ((ido-cr+-minibuffer-depth (1+ (minibuffer-depth)))
+                    ;; Reset this for the next call to ido-cr+
+                    (ido-cr+-no-default-action 'prepend-empty-string))
                 (ido-completing-read
                  prompt collection
                  predicate require-match initial-input hist def
@@ -262,9 +308,11 @@ completion for them."
 
       ;; Handler for ido-cr+-fallback signal
       (ido-cr+-fallback
-       (ido-cr+--explain-fallback sig)
-       (run-hooks 'ido-cr+-before-fallback-hook)
-       (apply ido-cr+-fallback-function ido-cr+-orig-completing-read-args)))))
+       (let ( ;; Reset this for the next call to ido-cr+
+             (ido-cr+-no-default-action 'prepend-empty-string))
+         (ido-cr+--explain-fallback sig)
+         (run-hooks 'ido-cr+-before-fallback-hook)
+         (apply ido-cr+-fallback-function ido-cr+-orig-completing-read-args))))))
 
 ;;;###autoload
 (defadvice ido-completing-read (around ido-cr+ activate)
@@ -326,10 +374,7 @@ sets up C-j to be equivalent to TAB in the same situation."
        (ido-cr+-active)
        ;; Require-match is non-nil
        (with-no-warnings ido-require-match)
-       ;; A default was provided, or ido-text is non-empty
-       (or (with-no-warnings ido-default-item)
-           (not (string= ido-text "")))
-       ;; Only if current text is not a complete choice
+       ;; Current text is not a complete choice
        (not (member ido-text (with-no-warnings ido-cur-list))))
       (progn
         (ido-cr+--debug-message
@@ -337,6 +382,20 @@ sets up C-j to be equivalent to TAB in the same situation."
         (ido-complete))
     ad-do-it))
 
+;; Interoperation with minibuffer-electric-default-mode: only show the
+;; default when the input is empty and the empty string is the selected
+(defadvice minibuf-eldef-update-minibuffer (around ido-cr+-compat activate)
+  (if (ido-active)
+      (unless (eq minibuf-eldef-showing-default-in-prompt
+                  (and (string= (car ido-cur-list) "")
+                       (string= ido-text "")))
+        ;; Swap state.
+        (setq minibuf-eldef-showing-default-in-prompt
+              (not minibuf-eldef-showing-default-in-prompt))
+        (overlay-put minibuf-eldef-overlay 'invisible
+                     (not minibuf-eldef-showing-default-in-prompt)))
+    ad-do-it))
+
 (provide 'ido-completing-read+)
 
 ;;; ido-completing-read+.el ends here