branch: elpa/bash-completion commit b2c976cd04969f28f2c44d2913d489052781a951 Author: Stephane Zermatten <szerm...@gmx.net> Commit: Stephane Zermatten <szerm...@gmx.net>
feat: Support functions calling compopt -o default and others. Before this change, calling compopt from a running completion function only worked for setting the nospace option. However, Bash supports setting option for fallbacks, such as -o default -o bashdefault and others. This change introduces supports for such use of the compopt command from inside completion functions. This fixes issue #75, where bash-completion scripts 2.13 _comp_complete_minimal started relying on this Bash feature. --- bash-completion.el | 104 ++++++++++++++++++++----------- test/bash-completion-integration-test.el | 34 +++++++++- test/bash-completion-test.el | 24 ++++--- 3 files changed, 115 insertions(+), 47 deletions(-) diff --git a/bash-completion.el b/bash-completion.el index 6092d2e946..10d0841356 100644 --- a/bash-completion.el +++ b/bash-completion.el @@ -329,8 +329,8 @@ Created by `bash-completion-send' and printed by stub ; parsed version of the stub open-quote ; quote open at stub end: nil, ?' or ?\"" compgen-args ; compgen arguments for this command (list of strings) + compopt-args ; additional compgen arguments forced with compopt wordbreaks ; value of COMP_WORDBREAKS active for this completion - compopt ; options forced with compopt nil or `(nospace . ,bool) ) (defun bash-completion--type (comp) @@ -351,14 +351,15 @@ customized, usually by `bash-completion--customize'." The option can be: - set globally, by setting `bash-completion-nospace' to t - set for a customized completion, in bash, with - \"-o nospace\"." - (let ((cell)) - (cond - (bash-completion-nospace t) ; set globally - ((setq cell (assq 'nospace (bash-completion--compopt comp))) - (cdr cell)) - (t (bash-completion--has-compgen-option - (bash-completion--compgen-args comp) "nospace"))))) + \"-o nospace\". + - set during completion, using compopt" + (or + bash-completion-nospace ; set globally + (eq 'set + (or (bash-completion--get-compgen-option + (bash-completion--compopt-args comp) "nospace") + (bash-completion--get-compgen-option + (bash-completion--compgen-args comp) "nospace"))))) (defun bash-completion--command (comp) "Return the current command for the completion, if there is one. @@ -424,13 +425,9 @@ returned." (concat "function compopt {" " command compopt \"$@\" 2>/dev/null;" " local ret=$?; " - " if [[ $ret == 1 && \"$*\" = *\"-o nospace\"* ]]; then" - " _EMACS_COMPOPT='-o nospace';" - " return 0;" - " fi;" - " if [[ $ret == 1 && \"$*\" = *\"+o nospace\"* ]]; then" - " _EMACS_COMPOPT='+o nospace';" - " return 0;" + " if [[ $ret == 1 ]]; then" + " _EMACS_COMPOPT=\"$EMACS_COMPOPT $*\";" + " return 0;" " fi;" " return $ret; " "}") @@ -934,8 +931,27 @@ The result is a list of candidates, which might be empty." (setq completion-status (bash-completion-send (bash-completion-generate-line comp) process cmd-timeout comp))) - (when (eq 0 completion-status) - (bash-completion-extract-candidates comp buffer)))) + (let ((candidates (when (eq 0 completion-status) + (bash-completion-extract-candidates comp buffer))) + (compopt (bash-completion--compopt-args comp))) + + ;; Possibly run compgen as instructed by a call to compopt + ;; inside of a function. + (when (or (member "plusdir" compopt) + (and (null candidates) + (or (member "default" compopt) + (member "bashdefault" compopt) + (member "dirnames" compopt) + (member "filenames" compopt)))) + (setf (bash-completion--compgen-args comp) + (bash-completion--compopt-args comp)) + (when (eq 0 (bash-completion-send + (bash-completion-generate-line comp) + process cmd-timeout comp)) + (setq candidates (append candidates + (bash-completion-extract-candidates + comp buffer))))) + candidates))) (defun bash-completion-extract-candidates (comp buffer) "Extract the completion candidates for COMP form BUFFER. @@ -950,16 +966,11 @@ for directory name detection to work. Post-processing includes escaping special characters, adding a / to directory names, replacing STUB with UNPARSED-STUB in the result. See `bash-completion-fix' for more details." - (let ((output) (candidates)) - (with-current-buffer buffer - (let ((compopt (bash-completion--parse-side-channel-data "compopt"))) - (cond - ((string= "-o nospace" compopt) - (setf (bash-completion--compopt comp) '((nospace . t)))) - ((string= "+o nospace" compopt) - (setf (bash-completion--compopt comp) '((nospace . nil)))))) - (setq output (buffer-string))) - (setq candidates (delete-dups (split-string output "\n" t))) + (with-current-buffer buffer + (setf (bash-completion--compopt-args comp) + (bash-completion--parse-side-channel-data "compopt" 'tokenize))) + (let* ((output (with-current-buffer buffer (buffer-string))) + (candidates (delete-dups (split-string output "\n" t)))) (if (eq 1 (length candidates)) (list (bash-completion-fix (car candidates) comp t)) ;; multiple candidates @@ -1689,14 +1700,23 @@ characters. These are output as-is." (file-remote-p expanded 'localname) expanded))) -(defun bash-completion--has-compgen-option (compgen-args option-name) - "Check whether COMPGEN-ARGS contains -o OPTION-NAME." - (let ((rest compgen-args) (found)) - (while (and (not found) - (setq rest (cdr (member "-o" rest)))) - (when (string= option-name (car rest)) - (setq found t)) +(defun bash-completion--get-compgen-option (compgen-args option-name) + "Check whether COMPGEN-ARGS contains -o or +o OPTION-NAME. + +Returns nil if the option was unspecified, \\='set if it was +specified with -o and \\='unset if it was specified with +o." + (let ((rest compgen-args) + found) + (while rest + (cond + ((and (string= "-o" (car rest)) + (equal option-name (car (cdr rest)))) + (setq found 'set)) + ((and (string= "+o" (car rest)) + (equal option-name (car (cdr rest)))) + (setq found 'unset))) (setq rest (cdr rest))) + found)) (defun bash-completion--side-channel-data (name value) @@ -1706,7 +1726,7 @@ Parse that data from the buffer output using `bash-completion--side-channel-data'." (format " echo \"\e\e%s=%s\e\e\";" name value)) -(defun bash-completion--parse-side-channel-data (name) +(defun bash-completion--parse-side-channel-data (name &optional tokenize) "Parse side-channel data NAME from the current buffer. This parses data added by `bash-completion--side-channel-data' @@ -1720,8 +1740,16 @@ Return the parsed value, as a string or nil." (format "\e\e%s=\\([^\e]*\\)\e\e" (regexp-quote name)) nil 'noerror) - (prog1 (match-string 1) - (delete-region (match-beginning 0) (1+ (match-end 0)))))))) + (let ((result (match-string 1)) + (start (match-beginning 0)) + (end (1+ (match-end 0)))) + (when tokenize + (setq result + (bash-completion-strings-from-tokens + (bash-completion-tokenize (match-beginning 1) (match-end 1))))) + (delete-region start end) + + result))))) (defun bash-completion--completion-table-with-cache (comp process) "Build a dynamic completion table for COMP using PROCESS. diff --git a/test/bash-completion-integration-test.el b/test/bash-completion-integration-test.el index b85f57fd58..e2271652c5 100644 --- a/test/bash-completion-integration-test.el +++ b/test/bash-completion-integration-test.el @@ -853,7 +853,6 @@ $ "))))) (should (equal nil (funcall compfunc-nonprefix "babeetai" (lambda (c) (equal "babeedai" c)) 'lambda)))))) - (ert-deftest bash-completion_test-issue-74 () (bash-completion_test-with-shell-harness (concat ; .bashrc @@ -867,5 +866,38 @@ $ "))))) (should (equal "mycmd bar " (bash-completion_test-complete "mycmd b"))))) +(ert-deftest bash-completion-integration-filenames-option () + (bash-completion_test-with-shell-harness + (concat ; .bashrc + "function _dummy {\n" + "}\n" + "complete -F _dummy -o plusdir dummy\n") + nil + (should (equal + "dummy some/other/" + (bash-completion_test-complete "dummy some/ot"))))) + +(ert-deftest bash-completion-integration-plusdir-option () + (bash-completion_test-with-shell-harness + (concat ; .bashrc + "function _dummy {\n" + "}\n" + "complete -F _dummy -o filenames dummy\n") + nil + (should (equal + "dummy moretestfile " + (bash-completion_test-complete "dummy moret"))))) + +(ert-deftest bash-completion-integration-new-compopt-options () + (bash-completion_test-with-shell-harness + (concat ; .bashrc + "function _dummy {\n" + " compopt -o default -o bashdefault\n" + "}\n" + "complete -F _dummy dummy\n") + nil + (should (equal + "dummy moretestfile " + (bash-completion_test-complete "dummy moret"))))) ;;; bash-completion-integration-test.el ends here diff --git a/test/bash-completion-test.el b/test/bash-completion-test.el index e121a0c4c1..01e7a9b2d2 100644 --- a/test/bash-completion-test.el +++ b/test/bash-completion-test.el @@ -1140,22 +1140,30 @@ before calling `bash-completion-dynamic-complete-nocomint'. '("somedir/") (nth 2 (bash-completion-dynamic-complete-nocomint 3 (point)))))))) -(ert-deftest bash-completion--has-compgen-option () +(ert-deftest bash-completion--get-compgen-option () (should (equal nil - (bash-completion--has-compgen-option + (bash-completion--get-compgen-option '("-F" "boo" "-o" "filenames" "-a" "-o" "default") "nospace"))) - (should (equal t - (bash-completion--has-compgen-option + (should (equal 'set + (bash-completion--get-compgen-option '("-F" "boo" "-o" "filenames" "-a" "-o" "default") "filenames"))) - (should (equal t - (bash-completion--has-compgen-option + (should (equal 'set + (bash-completion--get-compgen-option '("-F" "boo" "-o" "filenames" "-a" "-o" "default") "default"))) - (should (equal nil (bash-completion--has-compgen-option + (should (equal 'unset + (bash-completion--get-compgen-option + '("-F" "boo" "+o" "nospace" "-o" "default") + "nospace"))) + (should (equal 'set + (bash-completion--get-compgen-option + '("+o" "nospace" "-o" "nospace") + "nospace"))) + (should (equal nil (bash-completion--get-compgen-option '("-o") "any"))) - (should (equal nil (bash-completion--has-compgen-option '() "any")))) + (should (equal nil (bash-completion--get-compgen-option '() "any")))) (ert-deftest bash-completion--parse-side-channel-data () (bash-completion-test-with-buffer