From 672379785683d434415cbf0fb81417fe219f0593 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sun, 23 Aug 2026 22:25:27 -0700 Subject: [PATCH] Don't add trailing space when completing options that take arguments The space is unhelpful, since the user likely wants to add a value for that option immediately after the "=" (bug#81689). * lisp/pcomplete.el (pcomplete-long-option-completion-table): New function... (pcomplete-here-using-help): * lisp/pcmpl-gnu.el (pcomplete/tar): * lisp/pcmpl-linux.el (pcomplete/systemctl): ... use it. * test/lisp/pcomplete-tests.el (pcomplete-shell-completion) (pcomplete/pcmpl-option-test): New functions. (pcomplete-test-long-options/with-value) (pcomplete-test-long-options/without-value): New tests. --- lisp/pcmpl-gnu.el | 3 ++- lisp/pcmpl-linux.el | 7 ++++--- lisp/pcomplete.el | 23 ++++++++++++++++++++++- test/lisp/pcomplete-tests.el | 28 ++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lisp/pcmpl-gnu.el b/lisp/pcmpl-gnu.el index e25d686b9f0..20a625ff499 100644 --- a/lisp/pcmpl-gnu.el +++ b/lisp/pcmpl-gnu.el @@ -274,7 +274,8 @@ Return the new list." (if (pcomplete-match "^--" 0) (cond ((pcomplete-match "^--\\([^= \t\n\f]*\\)\\'" 0) - (pcomplete-here* pcmpl-gnu--tar-long-options)) + (pcomplete-here* (pcomplete-long-option-completion-table + pcmpl-gnu--tar-long-options))) ((pcomplete-match "\\`--directory=\\(.*\\)" 0) (pcomplete-here* (pcomplete-dirs) (pcomplete-match-string 1 0))) diff --git a/lisp/pcmpl-linux.el b/lisp/pcmpl-linux.el index abf56260915..f3cf8147ad0 100644 --- a/lisp/pcmpl-linux.el +++ b/lisp/pcmpl-linux.el @@ -148,9 +148,10 @@ Test is done using `equal'." :metavar (rx (group (+ " " (>= 2 (any upper "[]|.")))))))) (while (not (member (pcomplete-arg 1) subcmds)) (if (string-prefix-p "-" (pcomplete-arg 0)) - (pcomplete-here (pcomplete-from-help "systemctl --help" - :metavar "[^ ]+" - :separator " \\(\\)-")) + (pcomplete-here (pcomplete-long-option-completion-table + (pcomplete-from-help "systemctl --help" + :metavar "[^ ]+" + :separator " \\(\\)-"))) (pcomplete-here subcmds))) (let ((subcmd (pcomplete-arg 1)) (context (if (member "--user" pcomplete-args) "--user" "--system"))) diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el index 7e97141eb4c..d5ffee6ce42 100644 --- a/lisp/pcomplete.el +++ b/lisp/pcomplete.el @@ -1370,6 +1370,26 @@ Sequence should be a vector or list of strings." (pcomplete-read-hosts pcomplete-hosts-file 'pcomplete--host-name-cache 'pcomplete--host-name-cache-timestamp))) +(defun pcomplete-long-option-completion-table (options) + "Make a completion table for a list of OPTIONS. +OPTIONS should be a list of option strings; if a string ends in +\"=\", it's an option that accepts a value." + (lambda (string pred action) + (pcase action + ('nil ; try-completion + (let ((result (try-completion string options pred))) + ;; If the completion ends in "=", then it's not really + ;; complete yet: we expect a value after the "=". Return the + ;; original STRING in this case instead of t to indicate + ;; that. + (if (and (eq result t) (string-suffix-p "=" string)) + string + result))) + ('t ; all-completions + (all-completions string options pred)) + ('lambda ; test-completion + (test-completion string options pred))))) + ;;; Parsing help messages (defvar pcomplete-from-help (make-hash-table :test #'equal) @@ -1494,7 +1514,8 @@ COMMAND and ARGS as arguments." (pcomplete-here (pcomplete-entries) (pcomplete-match-string 1 0))) ((string-prefix-p "-" (pcomplete-arg 0)) - (pcomplete-here (apply #'pcomplete-from-help command args))) + (pcomplete-here (pcomplete-long-option-completion-table + (apply #'pcomplete-from-help command args)))) (t (pcomplete-here* (pcomplete-entries)))))) (provide 'pcomplete) diff --git a/test/lisp/pcomplete-tests.el b/test/lisp/pcomplete-tests.el index ab08c295fbc..a5357f18835 100644 --- a/test/lisp/pcomplete-tests.el +++ b/test/lisp/pcomplete-tests.el @@ -96,5 +96,33 @@ usage: git [-v | --version] [-h | --help] [-C ] [-c =] #("--super-prefix=" 0 1 (pcomplete-annotation "")) #("--config-env=" 0 1 (pcomplete-annotation ""))))))) +(defun pcomplete-shell-completion (input) + "Insert INPUT into shell mode, run completion, and return the result." + (with-temp-buffer + (shell (current-buffer)) + (unwind-protect + (let ((start (point))) + (insert input) + (completion-at-point) + (buffer-substring start (point))) + (let (kill-buffer-query-functions) + (kill-buffer (current-buffer)))))) + +(defun pcomplete/pcmpl-option-test () + "Completion for a test command taking long options." + (while (pcomplete-match "^--" 0) + (pcomplete-here* (pcomplete-long-option-completion-table + '("--version" "--owner="))))) + +(ert-deftest pcomplete-test-long-options/with-value () + "Test that completing long options taking values doesn't insert a space." + (should (string= (pcomplete-shell-completion "pcmpl-option-test --own") + "pcmpl-option-test --owner="))) + +(ert-deftest pcomplete-test-long-options/without-value () + "Test that completing long options without values inserts a space." + (should (string= (pcomplete-shell-completion "pcmpl-option-test --ver") + "pcmpl-option-test --version "))) + (provide 'pcomplete-tests) ;;; pcomplete-tests.el ends here