Subject: Re: bug#16197: 24.3.50; no completion on commands following "sudo" in shell and eshell

From: Lars Ingebrigtsen <[email-masked]>
Date: Fri, 25 Sep 2020 12:44:29 +0200

Thierry Volpiatto <[email-masked]> writes:

>> Can you send it as a patch against pcmpl-unix.el to fix pcomplete/xargs
>> (and add pcomplete/sudo at the same time)?
>
> * lisp/pcml-unix.el (pcomplete/sudo): New.
> * lisp/pcomplete.el (pcomplete-command-name): Allow extracting
> command-name in more complex command lines.
> * lisp/shell.el (shell-command-completion-function): Fix it (was not
> working at all).

There was unfortunately no followup to this at the time.  I've now
respun the patch and tried to do some testing, but...  this doesn't seem
to work?

Here's how I tested:

1) Apply the patch
2) Build Emacs
3) emacs -Q
f) M-x shell
IV) sudo apt-g<TAB>

Result: "No match"

I'm not very familiar with the completion framework, and the code has
changed over the years...  Anybody?

diff --git a/lisp/pcmpl-unix.el b/lisp/pcmpl-unix.el
index 74f45b9523..0096020ab4 100644
--- a/lisp/pcmpl-unix.el
+++ b/lisp/pcmpl-unix.el
@@ -82,9 +82,13 @@ pcomplete/rm
 ;;;###autoload
 (defun pcomplete/xargs ()
   "Completion for `xargs'."
-  (pcomplete-here (funcall pcomplete-command-completion-function))
-  (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
-	       pcomplete-default-completion-function)))
+  (let ((pcomplete-cmd-name (pcomplete-command-name)))
+    (cond ((string= "xargs" pcomplete-cmd-name)
+           (while (pcomplete-here*
+                   (funcall pcomplete-command-completion-function)
+                   (pcomplete-arg 'last) t)))
+          (t (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
+                          pcomplete-default-completion-function))))))
 
 ;;;###autoload
 (defalias 'pcomplete/time 'pcomplete/xargs)
@@ -240,6 +244,22 @@ pcomplete/rsh
   (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
                pcomplete-default-completion-function)))
 
+(defun pcomplete/sudo ()
+  (let ((pcomplete-cmd-name (pcomplete-command-name)))
+    (while (and (string= "sudo" pcomplete-cmd-name)
+                (pcomplete-match "^-" 'last))
+      (when (< pcomplete-index pcomplete-last)
+        (pcomplete-next-arg))
+      (pcomplete-opt "AbCDEegHhiKknPpSsUuVv-"))
+    (cond ((string= "sudo" pcomplete-cmd-name)
+           (while (pcomplete-here*
+                   (funcall pcomplete-command-completion-function)
+                   (pcomplete-arg 'last) t)))
+          (t
+           (funcall (or (pcomplete-find-completion-function
+                         pcomplete-cmd-name)
+                        pcomplete-default-completion-function))))))
+
 (provide 'pcmpl-unix)
 
 ;;; pcmpl-unix.el ends here
diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el
index 014f9628b9..9ac4652126 100644
--- a/lisp/pcomplete.el
+++ b/lisp/pcomplete.el
@@ -119,6 +119,7 @@
 ;;; Code:
 
 (require 'comint)
+(eval-when-compile (require 'cl-lib))
 
 (defgroup pcomplete nil
   "Programmable completion."
@@ -674,9 +675,42 @@ pcomplete-next-arg
 	(message "No completions")
 	(throw 'pcompleted nil))))
 
+(defvar pcomplete-special-commands '("sudo" "xargs"))
 (defun pcomplete-command-name ()
   "Return the command name of the first argument."
-  (file-name-nondirectory (pcomplete-arg 'first)))
+  (let ((coms (cl-loop with lst = (reverse (pcomplete-parse-arguments))
+                       for str in (or (member "|" lst)
+                                      (member "||" lst)
+                                      (member "&" lst)
+                                      (member ";" lst)
+                                      lst)
+                       for exec = (or (executable-find str)
+                                      ;; `executable-find' or 'which'
+                                      ;; doesn't return these paths.
+                                      (car (member str '("cd" "pushd" "popd"))))
+                       when exec collect exec)))
+    (file-name-nondirectory
+     ;; we may have commands embeded in executables that looks
+     ;; like executables (e.g apt-get install).
+     ;; Assume that all executables are using only one command
+     ;; like this.
+     ;; e.g - if we have (install apt-get sudo)
+     ;;       what we want is apt-get.
+     ;;     - if we have (apt-get sudo)
+     ;;       what we want is sudo,
+     ;;       then pcomplete/sudo will check if
+     ;;       a pcomplete handler exists for apt-get.
+     (cond (;; e.g (install apt-get sudo)
+            (> (length coms) 2) (cadr coms))
+           (;; e.g (apt-get sudo)
+            (and (= (length coms) 2)
+                 (member (file-name-nondirectory (cadr coms))
+                         pcomplete-special-commands))
+            (car coms))
+           (;; e.g (sudo)
+            (= (length coms) 1) (car coms))
+           (t ;; e.g (install apt-get)
+            (cadr coms))))))
 
 (defun pcomplete-match (regexp &optional index offset start)
   "Like `string-match', but on the current completion argument."
diff --git a/lisp/shell.el b/lisp/shell.el
index 9667dab2af..48eb69f0b6 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -460,9 +460,8 @@ shell-command-completion-function
 This is the value of `pcomplete-command-completion-function' for
 Shell buffers.  It implements `shell-completion-execonly' for
 `pcomplete' completion."
-  (pcomplete-here (pcomplete-entries nil
-				     (if shell-completion-execonly
-					 'file-executable-p))))
+  (let ((data (shell--command-completion-data)))
+    (and data (pcomplete-here (all-completions "" (nth 2 data))))))
 
 (defun shell-completion-vars ()
   "Setup completion vars for `shell-mode' and `read-shell-command'."

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no