1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-15 10:30:25 -08:00

(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.
This commit is contained in:
Stefan Monnier 2009-10-25 20:38:06 +00:00
parent 955ef4309a
commit 2d0853070d
2 changed files with 226 additions and 185 deletions

View file

@ -351,6 +351,173 @@ modified to be an empty string, or the desired separation string."
;;; 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 @@ completion functions list (it should occur fairly early in the list)."
'(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 @@ 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 +848,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
@ -829,65 +882,46 @@ If PREDICATE is non-nil, it will also be used to refine the match
\(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."