;;; pcomplete.el --- programmable completion
-;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004
-;; 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2011 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
;; Keywords: processes abbrev
:group 'pcomplete)
(defcustom pcomplete-ignore-case (memq system-type '(ms-dos windows-nt cygwin))
+ ;; FIXME: the doc mentions file-name completion, but the code
+ ;; seems to apply it to all completions.
"If non-nil, ignore case when doing filename completion."
:type 'boolean
:group 'pcomplete)
(defvar pcomplete-show-list nil)
(defvar pcomplete-expand-only-p nil)
+;; for the sake of the bye-compiler, when compiling other files that
+;; contain completion functions
+(defvar pcomplete-args nil)
+(defvar pcomplete-begins nil)
+(defvar pcomplete-last nil)
+(defvar pcomplete-index nil)
+(defvar pcomplete-stub nil)
+(defvar pcomplete-seen nil)
+(defvar pcomplete-norm-func nil)
+
;;; 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-completions-at-point ()
+ "Provide standard completion using pcomplete's completion tables.
+Same as `pcomplete' but using the standard completion UI."
+ ;; FIXME: it only completes the text before point, whereas the
+ ;; standard UI may also consider text after point.
+ ;; FIXME: the `pcomplete' UI may be used internally during
+ ;; pcomplete-completions and then throw to `pcompleted', thus
+ ;; imposing the pcomplete UI over the standard UI.
+ (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))))
+ (when completions
+ (let ((table
+ (cond
+ ((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))))
+ (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 pcomplete-use-paring pcomplete-seen)
+ (setq pcomplete-seen
+ (mapcar (lambda (f)
+ (funcall pcomplete-norm-func
+ (directory-file-name f)))
+ pcomplete-seen))
+ (lambda (f)
+ (not (when pcomplete-seen
+ (member
+ (funcall pcomplete-norm-func
+ (directory-file-name f))
+ pcomplete-seen)))))))
+ (unless (zerop (length pcomplete-termination-string))
+ ;; 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.
+ (setq table
+ (apply-partially #'completion-table-with-terminator
+ (cons pcomplete-termination-string
+ "\\`a\\`")
+ table)))
+ (when pcomplete-ignore-case
+ (setq table
+ (apply-partially #'completion-table-case-fold table)))
+ (list beg (point) table :predicate pred))))))
+
+ ;; 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 ()
+ (let ((data (pcomplete-completions-at-point)))
+ (completion-in-region (nth 0 data) (nth 1 data) (nth 2 data)
+ (plist-get :predicate (nthcdr 3 data)))))
+
+;;; Pcomplete's native UI.
+
;;;###autoload
(defun pcomplete (&optional interactively)
"Support extensible programmable completion.
pcomplete-expand-and-complete
pcomplete-reverse)))
(progn
- (delete-backward-char pcomplete-last-completion-length)
+ (delete-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))
+ (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)))
'(sole shortest))
pcomplete-last-completion-raw))))))
-(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))
- ;; The pcomplete code seems to presume that pcomplete-stub
- ;; is always the text before point.
- (ol (make-overlay (- (point) (length pcomplete-stub))
- (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.
- (apply-partially 'completion-table-with-terminator
- '(" " . "\\`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."
(pcomplete)
(when (and pcomplete-current-completions
(> (length pcomplete-current-completions) 0)) ;??
- (delete-backward-char pcomplete-last-completion-length)
+ (delete-char (- pcomplete-last-completion-length))
(while pcomplete-current-completions
(unless (pcomplete-insert-entry
"" (car pcomplete-current-completions) t
(when (and pcomplete-cycle-completions
pcomplete-current-completions
(eq last-command 'pcomplete-argument))
- (delete-backward-char pcomplete-last-completion-length)
+ (delete-char (- pcomplete-last-completion-length))
(setq pcomplete-current-completions nil
pcomplete-last-completion-raw nil))
(let ((pcomplete-show-list t))
;;; Internal Functions:
;; argument handling
-
-;; for the sake of the bye-compiler, when compiling other files that
-;; contain completion functions
-(defvar pcomplete-args nil)
-(defvar pcomplete-begins nil)
-(defvar pcomplete-last nil)
-(defvar pcomplete-index nil)
-(defvar pcomplete-stub nil)
-(defvar pcomplete-seen nil)
-(defvar pcomplete-norm-func nil)
-
(defun pcomplete-arg (&optional index offset)
"Return the textual content of the INDEXth argument.
INDEX is based from the current processing position. If INDEX is
(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)
this is `comint-dynamic-complete-functions'."
(set (make-local-variable 'pcomplete-parse-arguments-function)
'pcomplete-parse-comint-arguments)
- (make-local-variable completef-sym)
+ (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 'comint-filename-completion funs)
+ (memq 'shell-filename-completion funs)
+ (memq 'shell-dynamic-complete-filename funs)
+ (memq 'comint-dynamic-complete-filename funs))))
(if elem
(setcar elem 'pcomplete)
(add-to-list completef-sym 'pcomplete))))
;;;###autoload
(defun pcomplete-shell-setup ()
"Setup `shell-mode' to use pcomplete."
- (pcomplete-comint-setup 'shell-dynamic-complete-functions))
+ ;; FIXME: insufficient
+ (pcomplete-comint-setup 'comint-dynamic-complete-functions))
(declare-function comint-bol "comint" (&optional arg))
(goto-char begin)
(while (< (point) end)
(skip-chars-forward " \t\n")
- (setq begins (cons (point) begins))
+ (push (point) begins)
(let ((skip t))
(while skip
(skip-chars-forward "^ \t\n")
(if (eq (char-before) ?\\)
(skip-chars-forward " \t\n")
(setq skip nil))))
- (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)))))
(defun pcomplete-parse-arguments (&optional expand-p)
"Parse the command line arguments. Most completions need this info."
pcomplete-stub (pcomplete-arg 'last))
(let ((begin (pcomplete-begin 'last)))
(if (and pcomplete-cycle-completions
- (listp pcomplete-stub)
+ (listp pcomplete-stub) ;??
(not pcomplete-expand-only-p))
- (let* ((completions pcomplete-stub)
+ (let* ((completions pcomplete-stub) ;??
(common-stub (car completions))
(c completions)
(len (length common-stub)))
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)))
- ((and (setq char (aref filename index))
- (memq char pcomplete-arg-quote-list))
- (setq result (concat result "\\" (char-to-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
(pcomplete-next-arg)
(funcall sym)))))))
-(defun pcomplete-opt (options &optional prefix no-ganging args-follow)
+(defun pcomplete-opt (options &optional prefix _no-ganging _args-follow)
"Complete a set of OPTIONS, each beginning with PREFIX (?- by default).
PREFIX may be t, in which case no PREFIX character is necessary.
If NO-GANGING is non-nil, each option is separate (-xy is not allowed).
If ARGS-FOLLOW is non-nil, then options which take arguments may have
the argument appear after a ganged set of options. This is how tar
-behaves, for example."
+behaves, for example.
+Arguments NO-GANGING and ARGS-FOLLOW are currently ignored."
(if (and (= pcomplete-index pcomplete-last)
(string= (pcomplete-arg) "-"))
(let ((len (length options))
(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
(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
(defmacro pcomplete-here* (&optional form stub form-only)
"An alternate form which does not participate in argument paring."
(declare (debug t))
- `(pcomplete-here (lambda () ,form) ,stub t ,form-only))
+ `(pcomplete-here ,form ,stub t ,form-only))
;; display support
(substring entry (length stub)))))
;; the stub is not quoted at this time, so to determine the
;; length of what should be in the buffer, we must quote it
- (delete-backward-char (length (pcomplete-quote-argument stub)))
+ ;; FIXME: Here we presume that quoting `stub' gives us the exact
+ ;; text in the buffer before point, which is not guaranteed;
+ ;; e.g. it is not the case in eshell when completing ${FOO}tm[TAB].
+ (delete-char (- (length (pcomplete-quote-argument stub))))
;; if there is already a backslash present to handle the first
;; character, don't bother quoting it
(when (eq (char-before) ?\\)
(setq completions
(apply-partially 'completion-table-with-predicate
completions
- (lambda (f)
- (not (member
- (funcall pcomplete-norm-func
- (directory-file-name f))
- pcomplete-seen)))
+ (when pcomplete-seen
+ (lambda (f)
+ (not (member
+ (funcall pcomplete-norm-func
+ (directory-file-name f))
+ pcomplete-seen))))
'strict)))
;; OK, we've got a list of completions.
(if pcomplete-show-list
(provide 'pcomplete)
-;; arch-tag: ae32ef2d-dbed-4244-8b0f-cf5a2a3b07a4
;;; pcomplete.el ends here