emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/bash-completion fbdc78b877 198/313: Support compopt when u


From: ELPA Syncer
Subject: [nongnu] elpa/bash-completion fbdc78b877 198/313: Support compopt when using Bash 4.
Date: Sat, 3 Dec 2022 10:59:30 -0500 (EST)

branch: elpa/bash-completion
commit fbdc78b8770833752ac4bda28e2c3ea764bf8e76
Author: Stephane Zermatten <szermatt@gmx.net>
Commit: Stephane Zermatten <szermatt@gmx.net>

    Support compopt when using Bash 4.
    
    This commit allows bash 4 completion functions to manipulate -o
    nospace option using compopt. This is done by generalizing the
    approach taken for supporting 124 status: introducing extra "side
    channel" information into the output, captured later by
    emacs-bash-completion.
    
    This seems the only compot option that's useful to intercept for
    bash-completion, so this commit fixes #32
---
 bash-completion.el                       | 126 +++++++++++++++++++++++--------
 test/bash-completion-integration-test.el |  45 +++++++++++
 test/bash-completion-test.el             |  61 ++++++++++++++-
 3 files changed, 198 insertions(+), 34 deletions(-)

diff --git a/bash-completion.el b/bash-completion.el
index ba4895a5b4..eb93f99876 100644
--- a/bash-completion.el
+++ b/bash-completion.el
@@ -226,13 +226,6 @@ Bash processes")
 (defconst bash-completion-special-chars "[^-0-9a-zA-Z_./\n=]"
   "Regexp of characters that must be escaped or quoted.")
 
-(defconst bash-completion-wrapped-status
-  "\e\ebash-completion-wrapped-status=124\e\e"
-  "String output by __bash_complete_wrapper when the wrapped
-function returns status code 124, meaning that the completion
-should be retried. This should be a string that's unlikely
-to be included into a completion output.")
-
 (eval-when-compile
   (unless (or (and (= emacs-major-version 24) (>= emacs-minor-version 1))
               (>= emacs-major-version 25))
@@ -261,6 +254,7 @@ to be included into a completion output.")
   open-quote     ; quote open at stub end: nil, ?' or ?\""
   compgen-args   ; compgen arguments for this command (list of strings)
   wordbreaks     ; value of COMP_WORDBREAKS active for this completion
+  compopt        ; options forced with compopt nil or `(nospace . ,bool) 
 )
 
 (defun bash-completion--type (comp)
@@ -283,10 +277,13 @@ The option can be:
  - set globally, by setting `bash-completion-nospace' to t
  - set for a customized completion, in bash, with
    '-o' 'nospace'."
-  (if bash-completion-nospace
-      t ; set globally
-    (bash-completion--has-compgen-option
-     (bash-completion--compgen-args comp) "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")))))
 
 (defun bash-completion--command (comp)
   "Return the current command for the completion, if there is one."
@@ -726,15 +723,23 @@ 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 ((candidates) (result (list)))
-    (setq candidates (delete-dups
-                      (with-current-buffer buffer
-                        (split-string (buffer-string) "\n" t))))
+  (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)))
     (if (eq 1 (length candidates))
         (list (bash-completion-fix (car candidates) comp t))
-      (dolist (completion candidates)
-        (push (bash-completion-fix completion comp nil) result))
-      (delete-dups (nreverse result)))))
+      ;; multiple candidates
+      (let ((result (list)))
+        (dolist (completion candidates)
+          (push (bash-completion-fix completion comp nil) result))
+        (delete-dups (nreverse result))))))
 
 (defun bash-completion-fix (str comp single)
   "Fix completion candidate in STR for COMP
@@ -989,15 +994,43 @@ is set to t."
                     (with-current-buffer (process-buffer process)
                       (string-to-number (buffer-substring-no-properties
                                          (point-min) (point-max)))))
-              (bash-completion-send (concat "function __bash_complete_wrapper 
{"
-                                            (if (>= bash-major-version 4)
-                                                " COMP_TYPE=9; COMP_KEY=9;" "")
-                                            " eval $__BASH_COMPLETE_WRAPPER;"
-                                            " n=$?; if [[ $n = 124 ]]; then"
-                                            "  echo -n \""
-                                            bash-completion-wrapped-status
-                                            "\"; return 1; "
-                                            " fi; }") process)
+              (bash-completion-send
+               (concat "function __bash_complete_wrapper {"
+                       (if (>= bash-major-version 4)
+                           " COMP_TYPE=9; COMP_KEY=9; _EMACS_COMPOPT=\"\";"
+                         "")
+                       " eval $__BASH_COMPLETE_WRAPPER;"
+                       " n=$?;"
+                       " if [[ $n = 124 ]]; then"
+                       (bash-completion--side-channel-data
+                        "wrapped-status" "124")
+                       "  return 1; "
+                       " fi; "
+                       (when (>= bash-major-version 4)
+                         (concat " if [[ -n \"${_EMACS_COMPOPT}\" ]]; then"
+                                 (bash-completion--side-channel-data
+                                  "compopt" "${_EMACS_COMPOPT}")
+                                 " fi;"))
+                       " return $n;"
+                       "}")
+               process)
+              (if (>= bash-major-version 4)
+                  (bash-completion-send
+                   (concat
+                    "function compopt {"
+                    " command compopt \"$@\" 2>/dev/null;"
+                    " 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;"
+                    " fi;"
+                    " return $ret; "
+                    "}")
+                   process))
               (bash-completion-send "echo -n ${COMP_WORDBREAKS}" process)
               (process-put process 'wordbreaks
                            (with-current-buffer (process-buffer process)
@@ -1212,16 +1245,19 @@ Return the status code of the command, as a number."
                           (buffer-substring-no-properties
                            (1+ control-t-position) (1- control-v-position)))))
        (delete-region control-t-position (point-max))
-       (goto-char (point-min))
-       (let ((case-fold-search nil))
-         (when (search-forward bash-completion-wrapped-status nil t)
-           (setq status-code 124)
-           (delete-region (match-beginning 0) (match-end 0))))
        ;; (message "status: %d content: \"%s\""
        ;;       status-code
        ;;       (buffer-substring-no-properties
        ;;        (point-min) (point-max)))
-       status-code))))
+        (if (string=
+               "124"
+               (bash-completion--parse-side-channel-data "wrapped-status"))
+            124
+          status-code)))))
+
+(defun bash-completion--get-output (process)
+  "Return the output of the last command sent through `bash-completion-send'."
+  (with-current-buffer (process-buffer process) (buffer-string)))
 
 (defun bash-completion--expand-file-name (name &optional local-part-only)
   (let* ((remote (file-remote-p default-directory))
@@ -1244,5 +1280,29 @@ Return the status code of the command, as a number."
       (setq rest (cdr rest)))
     found))
 
+(defun bash-completion--side-channel-data (name value)
+  "Return an echo command that outputs NAME=VALUE as side-channel data.
+
+Parse that data from the buffer output using
+`bash-completion--side-channel-data'."
+  (format " echo -n \"\e\e%s=%s\e\e\";" name value))
+
+(defun bash-completion--parse-side-channel-data (name)
+  "Parse side-channel data NAME from the current buffer.
+
+This parses data added by `bash-completion--side-channel-data'
+being run by the shell and removes it from the buffer.
+
+Return the parsed value, as a string or nil."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((case-fold-search nil))
+      (when (search-forward-regexp
+             (format "\e\e%s=\\([^\e]*\\)\e\e"
+                     (regexp-quote name))
+             nil 'noerror)
+        (prog1 (match-string 1)
+          (delete-region (match-beginning 0) (match-end 0)))))))
+
 (provide 'bash-completion)
 ;;; bash-completion.el ends here
diff --git a/test/bash-completion-integration-test.el 
b/test/bash-completion-integration-test.el
index 6f36ec1b1d..ae3751d415 100644
--- a/test/bash-completion-integration-test.el
+++ b/test/bash-completion-integration-test.el
@@ -166,4 +166,49 @@ for testing completion."
      (should (equal "dosomethingelse du"
                       (bash-completion_test-complete "dosomethingelse du"))))))
 
+(ert-deftest bash-completion-integration-bash-4-compopt ()
+  (bash-completion_test-with-shell-harness
+   (concat ; .bashrc
+    "function _sometimes_nospace {\n"
+    "  if [[ ${COMP_WORDS[COMP_CWORD]} == du ]]; then\n"
+    "    COMPREPLY=(dummy)\n"
+    "  fi\n"
+    "  if [[ ${COMP_WORDS[COMP_CWORD]} == dum ]]; then\n"
+    "    COMPREPLY=(dummyo)\n"
+    "    compopt -o nospace\n"
+    "  fi\n"
+    "}\n"
+    "function _sometimes_not_nospace {\n"
+    "  if [[ ${COMP_WORDS[COMP_CWORD]} == du ]]; then\n"
+    "    COMPREPLY=(dummy)\n"
+    "  fi\n"
+    "  if [[ ${COMP_WORDS[COMP_CWORD]} == dum ]]; then \n"
+    "    COMPREPLY=(dummyo)\n"
+    "    compopt +o nospace\n"
+    "  fi\n"
+    "}\n"
+    "complete -F _sometimes_nospace sometimes_nospace\n"
+    "complete -F _sometimes_not_nospace -o nospace sometimes_not_nospace\n")
+   (when (>= bash-major-version 4)
+     (should (equal
+              "sometimes_nospace dummy "
+              (bash-completion_test-complete "sometimes_nospace du")))
+     (should (equal
+              "sometimes_nospace dummyo"
+              (bash-completion_test-complete "sometimes_nospace dum")))
+     (should (equal
+              "sometimes_not_nospace dummy"
+              (bash-completion_test-complete "sometimes_not_nospace du")))
+     (should (equal
+              "sometimes_not_nospace dummyo "
+              (bash-completion_test-complete "sometimes_not_nospace dum")))
+     (let ((bash-completion-nospace t)) ;; never nospace
+       (should (equal
+                "sometimes_nospace dummy"
+                (bash-completion_test-complete "sometimes_nospace du")))
+       (should (equal
+                "sometimes_not_nospace dummyo"
+                (bash-completion_test-complete "sometimes_not_nospace 
dum")))))))
+
+
 ;;; bash-completion-integration-test.el ends here
diff --git a/test/bash-completion-test.el b/test/bash-completion-test.el
index c3f3b78974..b0a1cbaeee 100644
--- a/test/bash-completion-test.el
+++ b/test/bash-completion-test.el
@@ -541,7 +541,7 @@ Return (const return-value new-buffer-content)"
   (should (equal
           (cons 124 "line1\nline2\n")
           (bash-completion-test-send
-           (concat "line1\nli" bash-completion-wrapped-status "ne2\n\t0\v")))))
+           (concat "line1\nli\e\ewrapped-status=124\e\ene2\n\t0\v")))))
 
 (ert-deftest bash-completion-cd-command-prefix-test ()
   ;; no current dir
@@ -793,6 +793,47 @@ Return (const return-value new-buffer-content)"
                                :cword 1)
         (current-buffer)))))))
 
+(ert-deftest bash-completion-extract-candidates-compopt-test ()
+  (let ((bash-completion-nospace nil))
+    (should
+     (equal
+      '("hello")
+      (bash-completion-test-with-buffer
+       "\e\ecompopt=-o nospace\e\ehello\n"
+       (bash-completion-extract-candidates
+        (bash-completion--make :stub "hell"
+                               :unparsed-stub "hell"
+                               :wordbreaks ""
+                               :cword 1)
+        (current-buffer)))))
+
+    (should
+     (equal
+      '("hello ")
+      (bash-completion-test-with-buffer
+       "\e\ecompopt=+o nospace\e\ehello\n"
+       (bash-completion-extract-candidates
+        (bash-completion--make :stub "hell"
+                               :unparsed-stub "hell"
+                               :wordbreaks ""
+                               :cword 1
+                               :compgen-args '("-o" "nospace"))
+        (current-buffer)))))))
+
+(ert-deftest bash-completion-extract-candidates-ignore-compopt-test ()
+  (let ((bash-completion-nospace t))
+    (should
+     (equal
+      '("hello")
+      (bash-completion-test-with-buffer
+       "hello\n\e\ecompopt=+o nospace\e\e"
+       (bash-completion-extract-candidates
+        (bash-completion--make :stub "hell"
+                               :unparsed-stub "hell"
+                               :wordbreaks ""
+                               :cword 1)
+        (current-buffer)))))))
+
 (ert-deftest bash-completion-nonsep-test ()
   (should (equal "^ \t\n\r;&|'\""
                 (bash-completion-nonsep nil "")))
@@ -1198,4 +1239,22 @@ before calling 
`bash-completion-dynamic-complete-nocomint'.
                       '("-o") "any")))
   (should (equal nil (bash-completion--has-compgen-option '() "any"))))
 
+(ert-deftest bash-completion--parse-side-channel-data ()
+  (bash-completion-test-with-buffer
+   "test\ntest\e\ename=value\e\e\ntest"
+   (should (equal
+            "value"
+            (bash-completion--parse-side-channel-data "name")))
+   (should (equal "test\ntest\ntest" (buffer-string))))
+  ;; leave other data alone
+  (bash-completion-test-with-buffer
+   "test\ntest\e\eothername=value\e\e\ntest"
+   (should (null (bash-completion--parse-side-channel-data "name")))
+   (should (equal "test\ntest\e\eothername=value\e\e\ntest" (buffer-string))))
+  ;; name can contain chars special for regexps
+  (bash-completion-test-with-buffer 
+   "\e\ename*=value\e\etest"
+   (should (equal "value" (bash-completion--parse-side-channel-data "name*")))
+   (should (equal "test" (buffer-string)))))
+
 ;;; bash-completion_test.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]