DarwinAwardWinner / ido-completing-read-plus

Fancy completion all over Emacs, not just for buffers and files.
GNU General Public License v3.0
241 stars 31 forks source link

ido-completing-read+ calls system-users even if unnecessary #159

Closed phst closed 5 years ago

phst commented 5 years ago

For example, run

cask emacs -Q -l ido-completing-read+.el --eval="(progn (ido-mode 1) (ido-everywhere 1) (ido-ubiquitous-mode 1) (with-temp-buffer (insert \"~/foobar\\n\") (goto-char 5) (debug-on-entry 'system-users) (ffap)))"

This uses find-file-at-point to visit nonexisting file ~/foobar. The file has to be in the current user's home directory. Nevertheless, this ends up calling system-users:

Debugger entered--entering a function:
* system-users()
  completion-file-name-table("~" nil t)
  all-completions("~" completion-file-name-table nil)
  completion--file-name-table("~" nil t)
  complete-with-action(t completion--file-name-table "~" nil)
  #f(compiled-function (table) #<bytecode 0xd7c709>)(completion--file-name-table)
  completion--some(#f(compiled-function (table) #<bytecode 0xd7c709>) (completion--embedded-envvar-table completion--file-name-table))
  read-file-name-internal("~" nil t)
  ffap-read-file-or-url-internal("~" nil t)
  #<subr all-completions>("~" ffap-read-file-or-url-internal nil nil)
  apply(#<subr all-completions> ("~" ffap-read-file-or-url-internal nil))
  #f(compiled-function (&rest args) #<bytecode 0xe24c25>)("~" ffap-read-file-or-url-internal nil)
  funcall(#f(compiled-function (&rest args) #<bytecode 0xe24c25>) "~" ffap-read-file-or-url-internal nil)
  (append completion-list (funcall ido-cr+-all-completions-memoized (s-left i string) collection predicate))
  (setq completion-list (append completion-list (funcall ido-cr+-all-completions-memoized (s-left i string) collection predicate)))
  (while (<= i --cl-var--) (setq completion-list (append completion-list (funcall ido-cr+-all-completions-memoized (s-left i string) collection predicate))) (setq i (+ i 1)))
  (let* ((i 0) (--cl-var-- (length string)) (completion-list nil)) (while (<= i --cl-var--) (setq completion-list (append completion-list (funcall ido-cr+-all-completions-memoized (s-left i string) collection predicate))) (setq i (+ i 1))) (delete-dups completion-list))
  (cond ((functionp collection) (let* ((i 0) (--cl-var-- (length string)) (completion-list nil)) (while (<= i --cl-var--) (setq completion-list (append completion-list (funcall ido-cr+-all-completions-memoized (s-left i string) collection predicate))) (setq i (+ i 1))) (delete-dups completion-list))) (t (all-completions "" collection predicate)))
  (closure (t) (string collection &optional predicate) "Run `all-completions' on every prefix of STRING.\n\nArguments COLLECTION and PREDICATE are as in `all-completions'.\nNote that \"all prefixes\" includes both STRING itself and the\nempty string. The return value is the union of all the returned\nlists, with elements ordered by their first occurrence.\n\nThis function is only useful if COLLECTION is a function that\nmight return additional completions for certain non-empty strings\nthat it wouldn't return for the empty string. If COLLECTION is\nnot a function, this is equivalent to\n`(all-completions \"\" COLELCTION PREDICATE)'." (cond ((functionp collection) (let* ((i 0) (--cl-var-- (length string)) (completion-list nil)) (while (<= i --cl-var--) (setq completion-list (append completion-list (funcall ido-cr+-all-completions-memoized ... collection predicate))) (setq i (+ i 1))) (delete-dups completion-list))) (t (all-completions "" collection predicate))))("~/" ffap-read-file-or-url-internal nil)
  apply((closure (t) (string collection &optional predicate) "Run `all-completions' on every prefix of STRING.\n\nArguments COLLECTION and PREDICATE are as in `all-completions'.\nNote that \"all prefixes\" includes both STRING itself and the\nempty string. The return value is the union of all the returned\nlists, with elements ordered by their first occurrence.\n\nThis function is only useful if COLLECTION is a function that\nmight return additional completions for certain non-empty strings\nthat it wouldn't return for the empty string. If COLLECTION is\nnot a function, this is equivalent to\n`(all-completions \"\" COLELCTION PREDICATE)'." (cond ((functionp collection) (let* ((i 0) (--cl-var-- (length string)) (completion-list nil)) (while (<= i --cl-var--) (setq completion-list (append completion-list ...)) (setq i (+ i 1))) (delete-dups completion-list))) (t (all-completions "" collection predicate)))) ("~/" ffap-read-file-or-url-internal nil))
  #f(compiled-function (&rest args) #<bytecode 0xe24829>)("~/" ffap-read-file-or-url-internal nil)
  funcall(#f(compiled-function (&rest args) #<bytecode 0xe24829>) "~/" ffap-read-file-or-url-internal nil)
  (if ido-cr+-dynamic-collection (funcall ido-cr+-all-prefix-completions-memoized initial-input-string collection predicate) (all-completions "" collection predicate))
  (setq collection (if ido-cr+-dynamic-collection (funcall ido-cr+-all-prefix-completions-memoized initial-input-string collection predicate) (all-completions "" collection predicate)))
  (progn (if (and inherit-input-method current-input-method) (progn (signal (quote ido-cr+-fallback) (quote ("ido cannot handle alternate input methods"))))) (if (functionp collection) (progn (if (ido-cr+-function-is-blacklisted collection) (progn (if (symbolp collection) (signal (quote ido-cr+-fallback) (list ...)) (signal (quote ido-cr+-fallback) (list "collection function is blacklisted"))))) (if (and (not whitelisted) (ido-cr+-function-is-whitelisted collection)) (progn (ido-cr+--debug-message (if (symbolp collection) (format "Collection function `%S' is whitelisted" collection) "Collection function is whitelisted")) (setq whitelisted t))) (if (and require-match (null def) (let* ((--cl-var-- ido-cr+-nil-def-alternate-behavior-list) (entry nil) (--cl-var-- t) --cl-var--) (while (and ... ...) (setq --cl-var-- ...)) (if --cl-var-- (progn ...) --cl-var--))) (progn (ido-cr+--debug-message (if (symbolp collection) (format "Using alternate nil DEF handling for collection function `%S'" collection) "Using alternate nil DEF handling for collection function")) (setq alt-nil-def t))))) (setq collection (if ido-cr+-dynamic-collection (funcall ido-cr+-all-prefix-completions-memoized initial-input-string collection predicate) (all-completions "" collection predicate))) (if (and (= (length collection) 0) (not ido-cr+-dynamic-collection)) (progn (signal (quote ido-cr+-fallback) (quote ("ido is not needed for an empty collection"))))) (if (and ido-cr+-max-items (> (length collection) ido-cr+-max-items)) (progn (signal (quote ido-cr+-fallback) (list (format "there are more than %i items in COLLECTION (see `ido-cr+-max-items')" ido-cr+-max-items))))) (if (ido-cr+--called-from-completing-read) (progn (let* ((--cl-var-- (list this-command ido-cr+-current-command)) (cmd nil)) (while (consp --cl-var--) (setq cmd (car --cl-var--)) (if (ido-cr+-function-is-blacklisted cmd) (progn (signal ... ...))) (if (and (not whitelisted) (ido-cr+-function-is-whitelisted cmd)) (progn (progn ... ...))) (if (and require-match (null def) (not alt-nil-def) (let* ... ... ...)) (progn (progn ... ...))) (setq --cl-var-- (cdr --cl-var--))) nil) (let* ((i 1) (caller nil)) (while (and (progn (setq caller ...) caller) (not (memq ... ...))) (if (ido-cr+-function-is-blacklisted caller) (progn (signal ... ...))) (if (and (not whitelisted) (ido-cr+-function-is-whitelisted caller)) (progn (progn ... ...))) (if (and require-match (null def) (not alt-nil-def) (let* ... ... ...)) (progn (progn ... ...))) (setq i (+ i 1))) nil))) (if whitelisted nil (signal (quote ido-cr+-fallback) (list "no functions or commands matched the whitelist for this call"))) (if (and require-match (null def)) (progn (if alt-nil-def (ido-cr+--debug-message "Leaving the default at nil because alternate nil DEF handling is enabled.") (ido-cr+--debug-message "Adding \"\" as the default completion since no default was provided.") (setq def (list ""))))) (if (listp def) nil (setq def (list def))) (if def (progn (setq def (mapcar (apply-partially (function format) "%s") def)) (setq collection (delete-dups (append def collection)) def nil))) (if (and ido-enable-dot-prefix (version< emacs-version "26.1") (member "" collection)) (progn (signal (quote ido-cr+-fallback) (quote ("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"))))) (if (consp initial-input) (progn (let* ((v initial-input)) (setcdr v (+ (cdr v) 1))))) (prog1 (let ((ido-cr+-minibuffer-depth (1+ (minibuffer-depth))) (ido-cr+-dynamic-update-timer nil) (ido-cr+-exhibit-pending t) (ido-cr+-assume-static-collection nil)) (unwind-protect (ido-completing-read prompt collection predicate require-match initial-input hist def inherit-input-method) (if ido-cr+-dynamic-update-timer (progn (cancel-timer ido-cr+-dynamic-update-timer) (setq ido-cr+-dynamic-update-timer nil))))) (if (eq ido-exit (quote fallback)) (progn (signal (quote ido-cr+-fallback) (quote ("user manually triggered fallback")))))))
  (condition-case sig (progn (if (and inherit-input-method current-input-method) (progn (signal (quote ido-cr+-fallback) (quote ("ido cannot handle alternate input methods"))))) (if (functionp collection) (progn (if (ido-cr+-function-is-blacklisted collection) (progn (if (symbolp collection) (signal ... ...) (signal ... ...)))) (if (and (not whitelisted) (ido-cr+-function-is-whitelisted collection)) (progn (ido-cr+--debug-message (if ... ... "Collection function is whitelisted")) (setq whitelisted t))) (if (and require-match (null def) (let* (... ... ... --cl-var--) (while ... ...) (if --cl-var-- ... --cl-var--))) (progn (ido-cr+--debug-message (if ... ... "Using alternate nil DEF handling for collection function")) (setq alt-nil-def t))))) (setq collection (if ido-cr+-dynamic-collection (funcall ido-cr+-all-prefix-completions-memoized initial-input-string collection predicate) (all-completions "" collection predicate))) (if (and (= (length collection) 0) (not ido-cr+-dynamic-collection)) (progn (signal (quote ido-cr+-fallback) (quote ("ido is not needed for an empty collection"))))) (if (and ido-cr+-max-items (> (length collection) ido-cr+-max-items)) (progn (signal (quote ido-cr+-fallback) (list (format "there are more than %i items in COLLECTION (see `ido-cr+-max-items')" ido-cr+-max-items))))) (if (ido-cr+--called-from-completing-read) (progn (let* ((--cl-var-- (list this-command ido-cr+-current-command)) (cmd nil)) (while (consp --cl-var--) (setq cmd (car --cl-var--)) (if (ido-cr+-function-is-blacklisted cmd) (progn ...)) (if (and ... ...) (progn ...)) (if (and require-match ... ... ...) (progn ...)) (setq --cl-var-- (cdr --cl-var--))) nil) (let* ((i 1) (caller nil)) (while (and (progn ... caller) (not ...)) (if (ido-cr+-function-is-blacklisted caller) (progn ...)) (if (and ... ...) (progn ...)) (if (and require-match ... ... ...) (progn ...)) (setq i (+ i 1))) nil))) (if whitelisted nil (signal (quote ido-cr+-fallback) (list "no functions or commands matched the whitelist for this call"))) (if (and require-match (null def)) (progn (if alt-nil-def (ido-cr+--debug-message "Leaving the default at nil because alternate nil DEF handling is enabled.") (ido-cr+--debug-message "Adding \"\" as the default completion since no default was provided.") (setq def (list ""))))) (if (listp def) nil (setq def (list def))) (if def (progn (setq def (mapcar (apply-partially (function format) "%s") def)) (setq collection (delete-dups (append def collection)) def nil))) (if (and ido-enable-dot-prefix (version< emacs-version "26.1") (member "" collection)) (progn (signal (quote ido-cr+-fallback) (quote ("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"))))) (if (consp initial-input) (progn (let* ((v initial-input)) (setcdr v (+ (cdr v) 1))))) (prog1 (let ((ido-cr+-minibuffer-depth (1+ (minibuffer-depth))) (ido-cr+-dynamic-update-timer nil) (ido-cr+-exhibit-pending t) (ido-cr+-assume-static-collection nil)) (unwind-protect (ido-completing-read prompt collection predicate require-match initial-input hist def inherit-input-method) (if ido-cr+-dynamic-update-timer (progn (cancel-timer ido-cr+-dynamic-update-timer) (setq ido-cr+-dynamic-update-timer nil))))) (if (eq ido-exit (quote fallback)) (progn (signal (quote ido-cr+-fallback) (quote ("user manually triggered fallback"))))))) (ido-cr+-fallback (let ((minibuffer-setup-hook orig-minibuffer-setup-hook) (ido-cr+-assume-static-collection nil)) (ido-cr+--explain-fallback sig) (apply ido-cr+-fallback-function ido-cr+-orig-completing-read-args))))
  (let* ((ido-cr+-orig-completing-read-args (list prompt collection predicate require-match initial-input hist def inherit-input-method)) (orig-minibuffer-setup-hook (cl-copy-list minibuffer-setup-hook)) (initial-input-string (cond ((consp initial-input) (car initial-input)) ((stringp initial-input) initial-input) ((null initial-input) "") (t (signal (quote wrong-type-argument) (list (quote stringp) initial-input))))) (ido-cr+-active-restrictions nil) (ido-cr+-dynamic-collection (if (and (not ido-cr+-assume-static-collection) (functionp collection)) (progn collection))) (ido-cr+-last-dynamic-update-text nil) (ido-cr+-all-prefix-completions-memoized (if (and ido-cr+-dynamic-collection (featurep (quote memoize))) (memoize (indirect-function (quote ido-cr+-all-prefix-completions))) (quote ido-cr+-all-prefix-completions))) (ido-cr+-all-completions-memoized (if (and ido-cr+-dynamic-collection (featurep (quote memoize))) (memoize (indirect-function (quote all-completions))) (quote all-completions))) (whitelisted (not ido-cr+-function-whitelist)) (alt-nil-def nil)) (condition-case sig (progn (if (and inherit-input-method current-input-method) (progn (signal (quote ido-cr+-fallback) (quote ("ido cannot handle alternate input methods"))))) (if (functionp collection) (progn (if (ido-cr+-function-is-blacklisted collection) (progn (if ... ... ...))) (if (and (not whitelisted) (ido-cr+-function-is-whitelisted collection)) (progn (ido-cr+--debug-message ...) (setq whitelisted t))) (if (and require-match (null def) (let* ... ... ...)) (progn (ido-cr+--debug-message ...) (setq alt-nil-def t))))) (setq collection (if ido-cr+-dynamic-collection (funcall ido-cr+-all-prefix-completions-memoized initial-input-string collection predicate) (all-completions "" collection predicate))) (if (and (= (length collection) 0) (not ido-cr+-dynamic-collection)) (progn (signal (quote ido-cr+-fallback) (quote ("ido is not needed for an empty collection"))))) (if (and ido-cr+-max-items (> (length collection) ido-cr+-max-items)) (progn (signal (quote ido-cr+-fallback) (list (format "there are more than %i items in COLLECTION (see `ido-cr+-max-items')" ido-cr+-max-items))))) (if (ido-cr+--called-from-completing-read) (progn (let* ((--cl-var-- ...) (cmd nil)) (while (consp --cl-var--) (setq cmd ...) (if ... ...) (if ... ...) (if ... ...) (setq --cl-var-- ...)) nil) (let* ((i 1) (caller nil)) (while (and ... ...) (if ... ...) (if ... ...) (if ... ...) (setq i ...)) nil))) (if whitelisted nil (signal (quote ido-cr+-fallback) (list "no functions or commands matched the whitelist for this call"))) (if (and require-match (null def)) (progn (if alt-nil-def (ido-cr+--debug-message "Leaving the default at nil because alternate nil DEF handling is enabled.") (ido-cr+--debug-message "Adding \"\" as the default completion since no default was provided.") (setq def (list ""))))) (if (listp def) nil (setq def (list def))) (if def (progn (setq def (mapcar (apply-partially ... "%s") def)) (setq collection (delete-dups (append def collection)) def nil))) (if (and ido-enable-dot-prefix (version< emacs-version "26.1") (member "" collection)) (progn (signal (quote ido-cr+-fallback) (quote ("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"))))) (if (consp initial-input) (progn (let* ((v initial-input)) (setcdr v (+ ... 1))))) (prog1 (let ((ido-cr+-minibuffer-depth (1+ ...)) (ido-cr+-dynamic-update-timer nil) (ido-cr+-exhibit-pending t) (ido-cr+-assume-static-collection nil)) (unwind-protect (ido-completing-read prompt collection predicate require-match initial-input hist def inherit-input-method) (if ido-cr+-dynamic-update-timer (progn ... ...)))) (if (eq ido-exit (quote fallback)) (progn (signal (quote ido-cr+-fallback) (quote ...)))))) (ido-cr+-fallback (let ((minibuffer-setup-hook orig-minibuffer-setup-hook) (ido-cr+-assume-static-collection nil)) (ido-cr+--explain-fallback sig) (apply ido-cr+-fallback-function ido-cr+-orig-completing-read-args)))))
  ido-completing-read+("Find file or URL: " ffap-read-file-or-url-internal nil nil ("~/" . 2) file-name-history nil nil)
  completing-read("Find file or URL: " ffap-read-file-or-url-internal nil nil ("~/" . 2) file-name-history nil)
  ffap-read-file-or-url("Find file or URL: " "~/")
  ffap-prompter()
  ffap()
  (progn (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap))
  (unwind-protect (progn (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap)) (and (buffer-name temp-buffer) (kill-buffer temp-buffer)))
  (save-current-buffer (set-buffer temp-buffer) (unwind-protect (progn (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap)) (and (buffer-name temp-buffer) (kill-buffer temp-buffer))))
  (with-current-buffer temp-buffer (unwind-protect (progn (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap)) (and (buffer-name temp-buffer) (kill-buffer temp-buffer))))
  (let ((temp-buffer (generate-new-buffer " *temp*"))) (with-current-buffer temp-buffer (unwind-protect (progn (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap)) (and (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
  (with-temp-buffer (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap))
  (progn (ido-mode 1) (ido-everywhere 1) (ido-ubiquitous-mode 1) (with-temp-buffer (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap)))
  eval((progn (ido-mode 1) (ido-everywhere 1) (ido-ubiquitous-mode 1) (with-temp-buffer (insert "~/foobar\n") (goto-char 5) (debug-on-entry (quote system-users)) (ffap))))
  command-line-1(("-l" "ido-completing-read+.el" "--eval=(progn (ido-mode 1) (ido-everywhere 1) (ido-ubiquitous-mode 1) (with-temp-buffer (insert \"~/foobar\\n\") (goto-char 5) (debug-on-entry 'system-users) (ffap)))"))
  command-line()
  normal-top-level()

This is unnecessary and can be very slow if there are lots of users (e.g. in large organizations). Somehow ido-completing-read+ should prevent completion of ~ here.

DarwinAwardWinner commented 5 years ago

Well, it's actually a bug that ffap is triggering ido-cr+ at all, since ido-cr+ isn't about completing files. I think I need to blacklist ffap-read-file-or-url-internal, and possibly a few other ffap subroutines. That seems to fix this particular instance of the problem.

DarwinAwardWinner commented 5 years ago

As for the general problem of calling system-users when completing file names with ~ in them, that might be a problem with Emacs itself. There's nothing ido-cr+ can do about that.