]> code.delx.au - gnu-emacs/blobdiff - lisp/pcomplete.el
(tab-always-indent): Fix custom-type.
[gnu-emacs] / lisp / pcomplete.el
index f23b219e1e1de3a43c9abe315296d3141c2d13e6..80f09492fee4c0c9b8211e015cc3f0a8cdaad933 100644 (file)
@@ -1,7 +1,7 @@
 ;;; pcomplete.el --- programmable completion
 
 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004
-;;   2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+;;   2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
 
 ;; Author: John Wiegley <johnw@gnu.org>
 ;; Keywords: processes abbrev
@@ -351,65 +351,69 @@ modified to be an empty string, or the desired separation string."
 
 ;;; User Functions:
 
-;;;###autoload
-(defun pcomplete (&optional interactively)
-  "Support extensible programmable completion.
-To use this function, just bind the TAB key to it, or add it to your
-completion functions list (it should occur fairly early in the list)."
-  (interactive "p")
-  (if (and interactively
-          pcomplete-cycle-completions
-          pcomplete-current-completions
-          (memq last-command '(pcomplete
-                               pcomplete-expand-and-complete
-                               pcomplete-reverse)))
-      (progn
-       (delete-backward-char pcomplete-last-completion-length)
-       (if (eq this-command 'pcomplete-reverse)
-           (progn
-             (setq pcomplete-current-completions
-                   (cons (car (last pcomplete-current-completions))
-                         pcomplete-current-completions))
-             (setcdr (last pcomplete-current-completions 2) nil))
-         (nconc pcomplete-current-completions
-                (list (car pcomplete-current-completions)))
-         (setq pcomplete-current-completions
-               (cdr pcomplete-current-completions)))
-       (pcomplete-insert-entry pcomplete-last-completion-stub
-                                (car pcomplete-current-completions)
-                               nil pcomplete-last-completion-raw))
-    (setq pcomplete-current-completions nil
-         pcomplete-last-completion-raw nil)
-    (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)
-            (completions (pcomplete-completions))
-            (result (pcomplete-do-complete pcomplete-stub completions)))
-       (and result
-            (not (eq (car result) 'listed))
-            (cdr result)
-            (pcomplete-insert-entry pcomplete-stub (cdr result)
-                                    (memq (car result)
-                                          '(sole shortest))
-                                    pcomplete-last-completion-raw))))))
+;;; Alternative front-end using the standard completion facilities.
 
-(defun pcomplete-common-suffix (s1 s2)
+;; 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))))
-  (let ((case-fold-search pcomplete-ignore-case))
+  ;; 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-table-subvert (table s1 s2 string pred action)
+(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 (substring string (length s1)))))
+                  (concat s2 (pcomplete-unquote-argument
+                              (substring string (length s1))))))
          (res (if str (complete-with-action action table str pred))))
     (when res
       (cond
@@ -417,12 +421,14 @@ in the same way as TABLE completes strings of the form (concat S2 S)."
         (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 (substring res (length s2)))))
+            (concat s1 (pcomplete-quote-argument
+                        (substring res (length s2))))))
        ((eq action t)
         (let ((bounds (completion-boundaries str table pred "")))
           (if (>= (car bounds) (length s2))
@@ -435,14 +441,15 @@ in the same way as TABLE completes strings of the form (concat S2 S)."
                                   (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 fails to unquote/requote the arguments.
-  ;; FIXME: it doesn't implement paring.
-  ;; FIXME: when we bring up *Completions* we never bring it back down.
+  ;; FIXME: it only completes the text before point, whereas the
+  ;; standard UI may also consider text after point.
   (catch 'pcompleted
     (let* ((pcomplete-stub)
            pcomplete-seen pcomplete-norm-func
@@ -465,45 +472,108 @@ Same as `pcomplete' but using the standard completion UI."
            ;; 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
+           ;; 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)))
+                     (pcomplete-begin)))
            (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))
+           (table
+            (cond
+             ((null completions) nil)
+             ((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
-               (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))))))
+                 (cdr prefixes) (car prefixes))))
+             (t
+              (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)))))))
+           (pred
+            ;; pare it down, if applicable
+            (when (and table pcomplete-use-paring pcomplete-seen)
+              (setq pcomplete-seen
+                    (mapcar (lambda (f)
+                              (funcall pcomplete-norm-func
+                                       (directory-file-name f)))
+                            pcomplete-seen))
+              (lambda (f)
+                (not (member
+                      (funcall pcomplete-norm-func
+                               (directory-file-name f))
+                      pcomplete-seen))))))
+
+      (completion-in-region
+       beg (point)
+       ;; 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))
+       pred))))
+
+;;; Pcomplete's native UI.
+
+;;;###autoload
+(defun pcomplete (&optional interactively)
+  "Support extensible programmable completion.
+To use this function, just bind the TAB key to it, or add it to your
+completion functions list (it should occur fairly early in the list)."
+  (interactive "p")
+  (if (and interactively
+          pcomplete-cycle-completions
+          pcomplete-current-completions
+          (memq last-command '(pcomplete
+                               pcomplete-expand-and-complete
+                               pcomplete-reverse)))
+      (progn
+       (delete-backward-char pcomplete-last-completion-length)
+       (if (eq this-command 'pcomplete-reverse)
+           (progn
+              (push (car (last pcomplete-current-completions))
+                    pcomplete-current-completions)
+             (setcdr (last pcomplete-current-completions 2) nil))
+         (nconc pcomplete-current-completions
+                (list (car pcomplete-current-completions)))
+         (setq pcomplete-current-completions
+               (cdr pcomplete-current-completions)))
+       (pcomplete-insert-entry pcomplete-last-completion-stub
+                                (car pcomplete-current-completions)
+                               nil pcomplete-last-completion-raw))
+    (setq pcomplete-current-completions nil
+         pcomplete-last-completion-raw nil)
+    (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)
+            (completions (pcomplete-completions))
+            (result (pcomplete-do-complete pcomplete-stub completions)))
+       (and result
+            (not (eq (car result) 'listed))
+            (cdr result)
+            (pcomplete-insert-entry pcomplete-stub (cdr result)
+                                    (memq (car result)
+                                          '(sole shortest))
+                                    pcomplete-last-completion-raw))))))
 
 ;;;###autoload
 (defun pcomplete-reverse ()
@@ -686,12 +756,12 @@ user actually typed in."
       (goto-char begin)
       (while (< (point) end)
        (skip-chars-forward " \t\n")
-       (setq begins (cons (point) begins))
+       (push (point) begins)
        (skip-chars-forward "^ \t\n")
-       (setq args (cons (buffer-substring-no-properties
-                         (car begins) (point))
-                        args)))
-      (cons (reverse args) (reverse begins)))))
+       (push (buffer-substring-no-properties
+               (car begins) (point))
+              args))
+      (cons (nreverse args) (nreverse begins)))))
 
 ;;;###autoload
 (defun pcomplete-comint-setup (completef-sym)
@@ -704,8 +774,8 @@ this is `comint-dynamic-complete-functions'."
   (set (make-local-variable completef-sym)
        (copy-sequence (symbol-value completef-sym)))
   (let* ((funs (symbol-value completef-sym))
-        (elem (or (memq 'comint-dynamic-complete-filename funs)
-                  (memq 'shell-dynamic-complete-filename funs))))
+        (elem (or (memq 'shell-dynamic-complete-filename funs)
+                  (memq 'comint-dynamic-complete-filename funs))))
     (if elem
        (setcar elem 'pcomplete)
       (add-to-list completef-sym 'pcomplete))))
@@ -713,6 +783,7 @@ this is `comint-dynamic-complete-functions'."
 ;;;###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 +860,17 @@ this is `comint-dynamic-complete-functions'."
 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
 
@@ -940,7 +1005,7 @@ behaves, for example."
              (let ((result (read-from-string options index)))
                (setq index (cdr result)))
            (unless (memq char '(?/ ?* ?? ?.))
-             (setq choices (cons (char-to-string char) choices)))
+             (push (char-to-string char) choices))
            (setq index (1+ index))))
        (throw 'pcomplete-completions
               (mapcar
@@ -988,11 +1053,10 @@ See the documentation for `pcomplete-here'."
          (unless (eq paring t)
            (let ((arg (pcomplete-arg)))
              (when (stringp arg)
-               (setq pcomplete-seen
-                     (cons (if paring
-                               (funcall paring arg)
-                             (file-truename arg))
-                           pcomplete-seen))))))
+                (push (if paring
+                          (funcall paring arg)
+                        (file-truename arg))
+                      pcomplete-seen)))))
        (pcomplete-next-arg)
        t)
     (when pcomplete-show-help