diff lisp/pcomplete.el @ 105762:5f2c736569a0

(pcomplete-unquote-argument-function): New var. (pcomplete-unquote-argument): New function. (pcomplete--common-suffix): Always pay attention to case. (pcomplete--table-subvert): Quote and unquote the text. (pcomplete--common-quoted-suffix): New function. (pcomplete-std-complete): Use it and pcomplete-begin.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Sun, 25 Oct 2009 20:38:06 +0000
parents 65c5d19965b2
children a5db863758a8
line wrap: on
line diff
--- a/lisp/pcomplete.el	Sun Oct 25 18:09:57 2009 +0000
+++ b/lisp/pcomplete.el	Sun Oct 25 20:38:06 2009 +0000
@@ -351,6 +351,173 @@
 
 ;;; User Functions:
 
+;;; Alternative front-end using the standard completion facilities.
+
+;; The way pcomplete-parse-arguments, pcomplete-stub, and
+;; pcomplete-quote-argument work only works because of some deep
+;; hypothesis about the way the completion work.  Basically, it makes
+;; it pretty much impossible to have completion other than
+;; prefix-completion.
+;;
+;; pcomplete--common-quoted-suffix and pcomplete--table-subvert try to
+;; work around this difficulty with heuristics, but it's
+;; really a hack.
+
+(defvar pcomplete-unquote-argument-function nil)
+
+(defun pcomplete-unquote-argument (s)
+  (cond
+   (pcomplete-unquote-argument-function
+    (funcall pcomplete-unquote-argument-function s))
+   ((null pcomplete-arg-quote-list) s)
+   (t
+    (replace-regexp-in-string "\\\\\\(.\\)" "\\1" s t))))
+
+(defun pcomplete--common-suffix (s1 s2)
+  (assert (not (or (string-match "\n" s1) (string-match "\n" s2))))
+  ;; Since S2 is expected to be the "unquoted/expanded" version of S1,
+  ;; there shouldn't be any case difference, even if the completion is
+  ;; case-insensitive.
+  (let ((case-fold-search nil)) ;; pcomplete-ignore-case
+    (string-match ".*?\\(.*\\)\n.*\\1\\'" (concat s1 "\n" s2))
+    (- (match-end 1) (match-beginning 1))))
+
+(defun pcomplete--common-quoted-suffix (s1 s2)
+  "Find the common suffix between S1 and S2 where S1 is the expanded S2.
+S1 is expected to be the unquoted and expanded version of S1.
+Returns (PS1 . PS2), i.e. the shortest prefixes of S1 and S2, such that
+S1 = (concat PS1 SS1) and S2 = (concat PS2 SS2) and
+SS1 = (unquote SS2)."
+  (let* ((cs (pcomplete--common-suffix s1 s2))
+         (ss1 (substring s1 (- (length s1) cs)))
+         (qss1 (pcomplete-quote-argument ss1))
+         qc)
+    (if (and (not (equal ss1 qss1))
+             (setq qc (pcomplete-quote-argument (substring ss1 0 1)))
+             (eq t (compare-strings s2 (- (length s2) cs (length qc) -1)
+                                    (- (length s2) cs -1)
+                                    qc nil nil)))
+        ;; The difference found is just that one char is quoted in S2
+        ;; but not in S1, keep looking before this difference.
+        (pcomplete--common-quoted-suffix
+         (substring s1 0 (- (length s1) cs))
+         (substring s2 0 (- (length s2) cs (length qc) -1)))
+      (cons (substring s1 0 (- (length s1) cs))
+            (substring s2 0 (- (length s2) cs))))))
+
+(defun pcomplete--table-subvert (table s1 s2 string pred action)
+  "Completion table that replaces the prefix S1 with S2 in STRING.
+When TABLE, S1 and S2 are provided by `apply-partially', the result
+is a completion table which completes strings of the form (concat S1 S)
+in the same way as TABLE completes strings of the form (concat S2 S)."
+  (let* ((str (if (eq t (compare-strings string 0 (length s1) s1 nil nil
+                                         completion-ignore-case))
+                  (concat s2 (pcomplete-unquote-argument
+                              (substring string (length s1))))))
+         (res (if str (complete-with-action action table str pred))))
+    (when res
+      (cond
+       ((and (eq (car-safe action) 'boundaries))
+        (let ((beg (or (and (eq (car-safe res) 'boundaries) (cadr res)) 0)))
+          (list* 'boundaries
+                 (max (length s1)
+                      ;; FIXME: Adjust because of quoting/unquoting.
+                      (+ beg (- (length s1) (length s2))))
+                 (and (eq (car-safe res) 'boundaries) (cddr res)))))
+       ((stringp res)
+        (if (eq t (compare-strings res 0 (length s2) s2 nil nil
+                                   completion-ignore-case))
+            (concat s1 (pcomplete-quote-argument
+                        (substring res (length s2))))))
+       ((eq action t)
+        (let ((bounds (completion-boundaries str table pred "")))
+          (if (>= (car bounds) (length s2))
+              res
+            (let ((re (concat "\\`"
+                              (regexp-quote (substring s2 (car bounds))))))
+              (delq nil
+                    (mapcar (lambda (c)
+                              (if (string-match re c)
+                                  (substring c (match-end 0))))
+                            res))))))))))
+        
+;; I don't think such commands are usable before first setting up buffer-local
+;; variables to parse args, so there's no point autoloading it.
+;; ;;;###autoload
+(defun pcomplete-std-complete ()
+  "Provide standard completion using pcomplete's completion tables.
+Same as `pcomplete' but using the standard completion UI."
+  (interactive)
+  ;; FIXME: it doesn't implement paring.
+  (catch 'pcompleted
+    (let* ((pcomplete-stub)
+           pcomplete-seen pcomplete-norm-func
+           pcomplete-args pcomplete-last pcomplete-index
+           (pcomplete-autolist pcomplete-autolist)
+           (pcomplete-suffix-list pcomplete-suffix-list)
+           ;; Apparently the vars above are global vars modified by
+           ;; side-effects, whereas pcomplete-completions is the core
+           ;; function that finds the chunk of text to complete
+           ;; (returned indirectly in pcomplete-stub) and the set of
+           ;; possible completions.
+           (completions (pcomplete-completions))
+           ;; Usually there's some close connection between pcomplete-stub
+           ;; and the text before point.  But depending on what
+           ;; pcomplete-parse-arguments-function does, that connection
+           ;; might not be that close.  E.g. in eshell,
+           ;; pcomplete-parse-arguments-function expands envvars.
+           ;; 
+           ;; Since we use minibuffer-complete, which doesn't know
+           ;; pcomplete-stub and works from the buffer's text instead,
+           ;; we need to trick minibuffer-complete, into using
+           ;; pcomplete-stub without its knowledge.  To that end, we
+           ;; use pcomplete--table-subvert to construct a completion
+           ;; table which expects strings using a prefix from the
+           ;; buffer's text but internally uses the corresponding
+           ;; prefix from pcomplete-stub.
+           (beg (max (- (point) (length pcomplete-stub))
+                     (pcomplete-begin)))
+           (buftext (buffer-substring beg (point)))
+           (table
+            (if (not (equal pcomplete-stub buftext))
+                ;; This isn't always strictly right (e.g. if
+                ;; FOO="toto/$FOO", then completion of /$FOO/bar may
+                ;; result in something incorrect), but given the lack of
+                ;; any other info, it's about as good as it gets, and in
+                ;; practice it should work just fine (fingers crossed).
+                (let ((prefixes (pcomplete--common-quoted-suffix
+                                 pcomplete-stub buftext)))
+                  (apply-partially
+                   'pcomplete--table-subvert
+                   completions
+                   (cdr prefixes) (car prefixes)))
+              (lexical-let ((completions completions))
+                (lambda (string pred action)
+                  (let ((res (complete-with-action
+                              action completions string pred)))
+                    (if (stringp res)
+                        (pcomplete-quote-argument res)
+                      res)))))))
+
+      (let ((ol (make-overlay beg (point) nil nil t))
+            (minibuffer-completion-table
+             ;; Add a space at the end of completion.  Use a terminator-regexp
+             ;; that never matches since the terminator cannot appear
+             ;; within the completion field anyway.
+             (if (zerop (length pcomplete-termination-string))
+                 table
+               (apply-partially 'completion-table-with-terminator
+                                (cons pcomplete-termination-string
+                                      "\\`a\\`")
+                                table)))
+            (minibuffer-completion-predicate nil))
+        (overlay-put ol 'field 'pcomplete)
+        (unwind-protect
+            (call-interactively 'minibuffer-complete)
+          (delete-overlay ol))))))
+
+;;; Pcomplete's native UI.
+
 ;;;###autoload
 (defun pcomplete (&optional interactively)
   "Support extensible programmable completion.
@@ -396,115 +563,6 @@
 					   '(sole shortest))
 				     pcomplete-last-completion-raw))))))
 
-(defun pcomplete-common-suffix (s1 s2)
-  (assert (not (or (string-match "\n" s1) (string-match "\n" s2))))
-  (let ((case-fold-search pcomplete-ignore-case))
-    (string-match ".*?\\(.*\\)\n.*\\1\\'" (concat s1 "\n" s2))
-    (- (match-end 1) (match-beginning 1))))
-
-(defun pcomplete-table-subvert (table s1 s2 string pred action)
-  "Completion table that replaces the prefix S1 with S2 in STRING.
-When TABLE, S1 and S2 are provided by `apply-partially', the result
-is a completion table which completes strings of the form (concat S1 S)
-in the same way as TABLE completes strings of the form (concat S2 S)."
-  (let* ((str (if (eq t (compare-strings string 0 (length s1) s1 nil nil
-                                         completion-ignore-case))
-                  (concat s2 (substring string (length s1)))))
-         (res (if str (complete-with-action action table str pred))))
-    (when res
-      (cond
-       ((and (eq (car-safe action) 'boundaries))
-        (let ((beg (or (and (eq (car-safe res) 'boundaries) (cadr res)) 0)))
-          (list* 'boundaries
-                 (max (length s1)
-                      (+ beg (- (length s1) (length s2))))
-                 (and (eq (car-safe res) 'boundaries) (cddr res)))))
-       ((stringp res)
-        (if (eq t (compare-strings res 0 (length s2) s2 nil nil
-                                   completion-ignore-case))
-            (concat s1 (substring res (length s2)))))
-       ((eq action t)
-        (let ((bounds (completion-boundaries str table pred "")))
-          (if (>= (car bounds) (length s2))
-              res
-            (let ((re (concat "\\`"
-                              (regexp-quote (substring s2 (car bounds))))))
-              (delq nil
-                    (mapcar (lambda (c)
-                              (if (string-match re c)
-                                  (substring c (match-end 0))))
-                            res))))))))))
-        
-
-(defun pcomplete-std-complete ()
-  "Provide standard completion using pcomplete's completion tables.
-Same as `pcomplete' but using the standard completion UI."
-  (interactive)
-  ;; FIXME: it fails to unquote/requote the arguments.
-  ;; FIXME: it doesn't implement paring.
-  ;; FIXME: when we bring up *Completions* we never bring it back down.
-  (catch 'pcompleted
-    (let* ((pcomplete-stub)
-           pcomplete-seen pcomplete-norm-func
-           pcomplete-args pcomplete-last pcomplete-index
-           (pcomplete-autolist pcomplete-autolist)
-           (pcomplete-suffix-list pcomplete-suffix-list)
-           ;; Apparently the vars above are global vars modified by
-           ;; side-effects, whereas pcomplete-completions is the core
-           ;; function that finds the chunk of text to complete
-           ;; (returned indirectly in pcomplete-stub) and the set of
-           ;; possible completions.
-           (completions (pcomplete-completions))
-           ;; Usually there's some close connection between pcomplete-stub
-           ;; and the text before point.  But depending on what
-           ;; pcomplete-parse-arguments-function does, that connection
-           ;; might not be that close.  E.g. in eshell,
-           ;; pcomplete-parse-arguments-function expands envvars.
-           ;; 
-           ;; Since we use minibuffer-complete, which doesn't know
-           ;; pcomplete-stub and works from the buffer's text instead,
-           ;; we need to trick minibuffer-complete, into using
-           ;; pcomplete-stub without its knowledge.  To that end, we
-           ;; use pcomplete-table-subvert to construct a completion
-           ;; table which expects strings using a prefix from the
-           ;; buffer's text but internally uses the corresponding
-           ;; prefix from pcomplete-stub.
-           (beg (max (- (point) (length pcomplete-stub))
-                     ;; Rather than `point-min' we should use the
-                     ;; beginning position of the current arg.
-                     (point-min)))
-           (buftext (buffer-substring beg (point)))
-           ;; This isn't always strictly right (e.g. if
-           ;; FOO="toto/$FOO", then completion of /$FOO/bar may
-           ;; result in something incorrect), but given the lack of
-           ;; any other info, it's about as good as it gets, and in
-           ;; practice it should work just fine (fingers crossed).
-           (suflen (pcomplete-common-suffix pcomplete-stub buftext)))
-      (unless (= suflen (length pcomplete-stub))
-        (setq completions
-              (apply-partially
-               'pcomplete-table-subvert
-               completions
-               (substring buftext 0 (- (length buftext) suflen))
-               (substring pcomplete-stub
-                          0 (- (length pcomplete-stub) suflen)))))
-      (let ((ol (make-overlay beg (point) nil nil t))
-            (minibuffer-completion-table
-             ;; Add a space at the end of completion.  Use a terminator-regexp
-             ;; that never matches since the terminator cannot appear
-             ;; within the completion field anyway.
-             (if (zerop (length pcomplete-termination-string))
-                 completions
-               (apply-partially 'completion-table-with-terminator
-                                (cons pcomplete-termination-string
-                                      "\\`a\\`")
-                                completions)))
-            (minibuffer-completion-predicate nil))
-        (overlay-put ol 'field 'pcomplete)
-        (unwind-protect
-            (call-interactively 'minibuffer-complete)
-          (delete-overlay ol))))))
-
 ;;;###autoload
 (defun pcomplete-reverse ()
   "If cycling completion is in use, cycle backwards."
@@ -713,6 +771,7 @@
 ;;;###autoload
 (defun pcomplete-shell-setup ()
   "Setup `shell-mode' to use pcomplete."
+  ;; FIXME: insufficient
   (pcomplete-comint-setup 'comint-dynamic-complete-functions))
 
 (declare-function comint-bol "comint" (&optional arg))
@@ -789,23 +848,17 @@
 Magic characters are those in `pcomplete-arg-quote-list'."
   (if (null pcomplete-arg-quote-list)
       filename
-    (let ((len (length filename))
-	  (index 0)
-	  (result "")
-	  replacement char)
-      (while (< index len)
-	(setq replacement (run-hook-with-args-until-success
-			   'pcomplete-quote-arg-hook filename index))
-	(cond
-	 (replacement
-	  (setq result (concat result replacement)))
-	 ((memq (setq char (aref filename index))
-                pcomplete-arg-quote-list)
-	  (setq result (concat result (string "\\" char))))
-	 (t
-	  (setq result (concat result (char-to-string char)))))
-	(setq index (1+ index)))
-      result)))
+    (let ((index 0))
+      (mapconcat (lambda (c)
+                   (prog1
+                       (or (run-hook-with-args-until-success
+                            'pcomplete-quote-arg-hook filename index)
+                           (when (memq c pcomplete-arg-quote-list)
+                             (string "\\" c))
+                           (char-to-string c))
+                     (setq index (1+ index))))
+                 filename
+                 ""))))
 
 ;; file-system completion lists
 
@@ -829,65 +882,46 @@
 \(files for which the PREDICATE returns nil will be excluded).
 If no directory information can be extracted from the completed
 component, `default-directory' is used as the basis for completion."
-  (let* ((name (substitute-env-vars pcomplete-stub))
-         (completion-ignore-case pcomplete-ignore-case)
-	 (default-directory (expand-file-name
-			     (or (file-name-directory name)
-				 default-directory)))
-	 above-cutoff)
-    (setq name (file-name-nondirectory name)
-	  pcomplete-stub name)
-    (let ((completions
-	   (file-name-all-completions name default-directory)))
-      (if regexp
-	  (setq completions
-		(pcomplete-pare-list
-		 completions nil
-		 (function
-		  (lambda (file)
-		    (not (string-match regexp file)))))))
-      (if predicate
-	  (setq completions
-		(pcomplete-pare-list
-		 completions nil
-		 (function
-		  (lambda (file)
-		    (not (funcall predicate file)))))))
-      (if (or pcomplete-file-ignore pcomplete-dir-ignore)
-	  (setq completions
-		(pcomplete-pare-list
-		 completions nil
-		 (function
-		  (lambda (file)
-		    (if (eq (aref file (1- (length file)))
-			    ?/)
-			(and pcomplete-dir-ignore
-			     (string-match pcomplete-dir-ignore file))
-		      (and pcomplete-file-ignore
-			   (string-match pcomplete-file-ignore file))))))))
-      (setq above-cutoff (and pcomplete-cycle-cutoff-length
-			     (> (length completions)
-				pcomplete-cycle-cutoff-length)))
-      (sort completions
-	    (function
-	     (lambda (l r)
-	       ;; for the purposes of comparison, remove the
-	       ;; trailing slash from directory names.
-	       ;; Otherwise, "foo.old/" will come before "foo/",
-	       ;; since . is earlier in the ASCII alphabet than
-	       ;; /
-	       (let ((left (if (eq (aref l (1- (length l)))
-				   ?/)
-			       (substring l 0 (1- (length l)))
-			     l))
-		     (right (if (eq (aref r (1- (length r)))
-				    ?/)
-				(substring r 0 (1- (length r)))
-			      r)))
-		 (if above-cutoff
-		     (string-lessp left right)
-		   (funcall pcomplete-compare-entry-function
-			    left right)))))))))
+  ;; FIXME: obey pcomplete-file-ignore and pcomplete-dir-ignore.
+  ;; FIXME: obey pcomplete-compare-entry-function (tho only if there
+  ;; are less than pcomplete-cycle-cutoff-length completions).
+  ;; FIXME: expand envvars?  shouldn't this be done globally instead?
+  (let* ((reg-pred (when regexp
+                     (lexical-let ((re regexp))
+                       (lambda (f)
+                         ;; (let ((name (file-name-nondirectory f)))
+                         ;;   (if (zerop (length name))
+                         ;;       (setq name (file-name-as-directory
+                         ;;                   (file-name-nondirectory
+                         ;;                    (directory-file-name f)))))
+                         ;;   (string-match re name))
+                         (string-match re f)))))
+         (pred (cond
+                ((null predicate) reg-pred)
+                ((null reg-pred) predicate)
+                (t (lexical-let ((predicate predicate)
+                                 (reg-pred reg-pred))
+                     (lambda (f)
+                       (and (funcall predicate f)
+                            (funcall reg-pred f)))))))
+         (fun
+          (lexical-let ((pred pred)
+                        (dir default-directory))
+            (lambda (s p a)
+              ;; Remember the default-directory that was active when we built
+              ;; the completion table.
+              (let ((default-directory dir)
+                    ;; The old code used only file-name-all-completions
+                    ;; which ignores completion-ignored-extensions.
+                    (completion-ignored-extensions nil))
+                (completion-table-with-predicate
+                 'completion-file-name-table pred 'strict s p a)))))
+         ;; Indirect through a symbol rather than returning a lambda
+         ;; expression, so as to help catch bugs where the caller
+         ;; might treat the lambda expression as a list of completions.
+         (sym (make-symbol "pcomplete-read-file-name-internal")))
+    (fset sym fun)
+    sym))
 
 (defsubst pcomplete-all-entries (&optional regexp predicate)
   "Like `pcomplete-entries', but doesn't ignore any entries."