1
Fork 0
mirror of https://https.git.savannah.gnu.org/git/guix.git/ synced 2025-07-14 19:10:49 +02:00

gnu: emacs-bash-completion: Preserve PS1 prompt, exit code.

* gnu/packages/patches/emacs-bash-completion-preserve-exit-code.patch: New patch.
* gnu/packages/patches/emacs-bash-completion-preserve-ps1.patch: Likewise.
* gnu/local.mk (dist_patch_DATA): Register them.
* gnu/packages/emacs-xyz.scm (emacs-bash-completion) [source]: Apply them.

Change-Id: Id179f4ee976efdbe52f05dcb0919ad586dc8bcad
This commit is contained in:
Maxim Cournoyer 2025-04-26 11:00:51 +09:00
parent e018fb6f61
commit 2e25cd62cb
No known key found for this signature in database
GPG key ID: 1260E46482E63562
4 changed files with 271 additions and 1 deletions

View file

@ -1216,6 +1216,8 @@ dist_patch_DATA = \
%D%/packages/patches/elm-offline-package-registry.patch \
%D%/packages/patches/elm-reactor-static-files.patch \
%D%/packages/patches/emacs-all-the-icons-remove-duplicate-rs.patch \
%D%/packages/patches/emacs-bash-completion-preserve-exit-code.patch \
%D%/packages/patches/emacs-bash-completion-preserve-ps1.patch \
%D%/packages/patches/emacs-deferred-fix-number-of-arguments.patch \
%D%/packages/patches/emacs-elpy-dup-test-name.patch \
%D%/packages/patches/emacs-disable-jit-compilation.patch \

View file

@ -24356,7 +24356,10 @@ Slack client.")
(commit version)))
(file-name (git-file-name name version))
(sha256
(base32 "0664dihdfvrbxqxy00fw0skdg454njm673ip54qrgkh38vyv5432"))))
(base32 "0664dihdfvrbxqxy00fw0skdg454njm673ip54qrgkh38vyv5432"))
(patches
(search-patches "emacs-bash-completion-preserve-ps1.patch"
"emacs-bash-completion-preserve-exit-code.patch"))))
(build-system emacs-build-system)
(arguments
(list

View file

@ -0,0 +1,131 @@
From a96525afd9077c06d781c59e78bfc6620e41be8f Mon Sep 17 00:00:00 2001
From: Stephane Zermatten <szermatt@gmx.net>
Date: Fri, 25 Apr 2025 18:08:01 +0300
Subject: [PATCH] fix: Recover $? after completion.
Before this change, the value of $? was lost when doing a completion as
it required running a command, so $? became the status code of the
completion command.
So if you typed:
> false
> ech<TAB> $?
You would get 0 instead of 1, set by false.
This change stores the value of $? first thing before executing any
command, then have __ebcret restore it. The status code that bash
completion, the one that's embedded in the next prompt, remains the
status code of the completion command, but $? is the status code of the
last user command, before completion was run.
issue #77
---
bash-completion.el | 22 +++++++--------
test/bash-completion-integration-test.el | 34 ++++++++++++++++++++++++
2 files changed, 45 insertions(+), 11 deletions(-)
diff --git a/bash-completion.el b/bash-completion.el
index 130152f..5a7d9ff 100644
--- a/bash-completion.el
+++ b/bash-completion.el
@@ -294,7 +294,7 @@ Bash processes.")
(defconst bash-completion-special-chars "[ -$&-*,:-<>?[-^`{-}]"
"Regexp of characters that must be escaped or quoted.")
-(defconst bash-completion--ps1 "'==emacs==ret=$?==.'"
+(defconst bash-completion--ps1 "'==emacs==ret=${__ebcret:-$?}==.'"
"Value for the special PS1 prompt set for completions, quoted.")
(eval-when-compile
@@ -1532,12 +1532,12 @@ Return the status code of the command, as a number."
;; single process, assume __ebcpre is already defined
((not define-functions)
(concat
- "if type __ebcpre &>/dev/null; then "
+ "__ebcor=$?; if type __ebcpre &>/dev/null; then "
" __ebcpre; %s; __ebcret $?; "
"else "
" echo ==emacs==nopre=${BASH_VERSION}==.; "
- " __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\");"
- " unset PS1 PROMPT_COMMAND;"
+ " __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\" $__ebcor);"
+ " unset PS1 PROMPT_COMMAND __ebcor;"
"fi;\n"))
;; single process, define __ebcpre
(t
@@ -1549,23 +1549,23 @@ Return the status code of the command, as a number."
" fi;"
" history -d $c &>/dev/null || true;"
"} ; function __ebcret {"
- " __ebcret=t;"
- " return $1;"
+ " __ebcret=$1;"
+ " return ${__ebcp[2]};"
"} ; function __ebctrap {"
- " if [[ \"$__ebcret\" = \"t\" && ${#__ebcp[@]} -gt 0 ]]; then"
+ " if [[ -n \"$__ebcret\" && ${#__ebcp[@]} -gt 0 ]]; then"
" PS1=\"${__ebcp[0]}\";"
" PROMPT_COMMAND=\"${__ebcp[1]}\";"
- " unset __ebcp;"
- " unset __ebcret;"
+ " unset __ebcp __ebcret;"
" fi;"
"} ; trap __ebctrap DEBUG ; function __ebcpre {"
+ " __ebcor=${__ebcor:-$?}; "
" set +x; set +o emacs; set +o vi;"
" echo \"==emacs==bash=${BASH_VERSION}==.\";"
" if [[ ${#__ebcp[@]} = 0 ]]; then "
- " __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\");"
+ " __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\" $__ebcor);"
" fi;"
" PS1=" bash-completion--ps1 ";"
- " unset PROMPT_COMMAND;"
+ " unset PROMPT_COMMAND __ebcor;"
" __ebcnohistory 1;"
"} ; { __ebcpre; %s; __ebcret $?; }\n")))
commandline)))
diff --git a/test/bash-completion-integration-test.el b/test/bash-completion-integration-test.el
index f57ddc7..02c24ec 100644
--- a/test/bash-completion-integration-test.el
+++ b/test/bash-completion-integration-test.el
@@ -926,4 +926,38 @@ $ ")))))
(should (equal (bash-completion_test-buffer-string)
"$ dummy dummy\n--$ --\n$ dummy dummy\n--$ --\n$ "))))
+(ert-deftest bash-completion-integration-recover-status-code ()
+ (bash-completion_test-with-shell-harness
+ (concat ; .bashrc
+ "function failwith { return $1; }\n"
+ "function dummy { echo $?; }\n"
+ "function _dummy {\n"
+ " COMPREPLY=( dummy )\n"
+ "}\n"
+ "complete -F _dummy dummy\n"
+ "PS1='\$ '")
+ nil
+ ;; The first time initializes completion, the second time executes
+ ;; an already initialized completion. The two cases behave very
+ ;; differently, so we test both.
+ (dotimes (i 2)
+ (bash-completion_test-send (format "failwith %s" (+ 100 i)))
+ (should (equal
+ "dummy dummy "
+ (bash-completion_test-complete "dummy dum")))
+ (let ((start (line-beginning-position)))
+ (comint-send-input)
+ (bash-completion_test-wait-for-prompt start)))
+ ;; The status code printed by the dummy function should be the one
+ ;; from testfail, so 123, and not the one from the completion
+ ;; command executed to do completion for the dummy function.
+ (should (equal (bash-completion_test-buffer-string)
+ (concat "$ failwith 100\n"
+ "$ dummy dummy\n"
+ "100\n"
+ "$ failwith 101\n"
+ "$ dummy dummy\n"
+ "101\n"
+ "$ ")))))
+
;;; bash-completion-integration-test.el ends here

View file

@ -0,0 +1,134 @@
From a79863f9c2080d450aa63dbda872b8ccee8ac790 Mon Sep 17 00:00:00 2001
From: Stephane Zermatten <szermatt@gmx.net>
Date: Fri, 25 Apr 2025 18:08:54 +0300
Subject: [PATCH] fix: Recover PS1 before command execution.
Before this change, when doing completion in the same process PS1 was
modified to be able to detect the end of the completion command output
and it was recovered later on from PROMPT_COMMAND, just *after*
executing the user command, before building the next prompt.
The effect was not visible, unless the user command included $PS1, so if
you did:
> echo $PS1
and ran completion while editing that command, the PS1 that would be
output would be the one from bash-completion.
This change switches to another approach for recovering the prompt that
allows recovering it before executing the user command: a DEBUG trap is
registered which restores PS1 and PROMPT_COMMAND if the previous command
included __ebrcet. This change also adds __ebcret after the last command
issued by completion.
This way, the last command issued by completion uses the fake prompt,
but the command run just after that sees the real prompt in its
variable.
issue #77
---
bash-completion.el | 34 +++++++++++-------------
test/bash-completion-integration-test.el | 26 ++++++++++++++++++
2 files changed, 42 insertions(+), 18 deletions(-)
diff --git a/bash-completion.el b/bash-completion.el
index e3bebeb..130152f 100644
--- a/bash-completion.el
+++ b/bash-completion.el
@@ -1533,7 +1533,7 @@ Return the status code of the command, as a number."
((not define-functions)
(concat
"if type __ebcpre &>/dev/null; then "
- " __ebcpre; %s; "
+ " __ebcpre; %s; __ebcret $?; "
"else "
" echo ==emacs==nopre=${BASH_VERSION}==.; "
" __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\");"
@@ -1548,28 +1548,26 @@ Return the status code of the command, as a number."
" c=$((c+1));"
" fi;"
" history -d $c &>/dev/null || true;"
- "}; function __ebcpre {"
+ "} ; function __ebcret {"
+ " __ebcret=t;"
+ " return $1;"
+ "} ; function __ebctrap {"
+ " if [[ \"$__ebcret\" = \"t\" && ${#__ebcp[@]} -gt 0 ]]; then"
+ " PS1=\"${__ebcp[0]}\";"
+ " PROMPT_COMMAND=\"${__ebcp[1]}\";"
+ " unset __ebcp;"
+ " unset __ebcret;"
+ " fi;"
+ "} ; trap __ebctrap DEBUG ; function __ebcpre {"
" set +x; set +o emacs; set +o vi;"
" echo \"==emacs==bash=${BASH_VERSION}==.\";"
" if [[ ${#__ebcp[@]} = 0 ]]; then "
" __ebcp=(\"$PS1\" \"$PROMPT_COMMAND\");"
" fi;"
- " PROMPT_COMMAND=" ;; set a temporary prompt
- (bash-completion-quote
- (concat "PS1=" bash-completion--ps1 ";"
- "PROMPT_COMMAND=" ;; recover prompt
- (bash-completion-quote
- (concat
- "__ebcr=$?;"
- "PS1=\"${__ebcp[0]}\";"
- "PROMPT_COMMAND=\"${__ebcp[1]}\";"
- "unset __ebcp;"
- "if [[ -n \"$PROMPT_COMMAND\" ]]; then"
- " (exit $__ebcr); eval \"$PROMPT_COMMAND\";"
- "fi;"))))
- ";"
+ " PS1=" bash-completion--ps1 ";"
+ " unset PROMPT_COMMAND;"
" __ebcnohistory 1;"
- "} && { __ebcpre; %s; }\n")))
+ "} ; { __ebcpre; %s; __ebcret $?; }\n")))
commandline)))
(setq bash-completion--debug-info
(list (cons 'commandline complete-command)
@@ -1591,7 +1589,7 @@ Return the status code of the command, as a number."
;; common initialization, then retry.
(bash-completion-send "__ebcnohistory" process timeout debug-context 'define-functions)
(bash-completion--setup-bash-common process)
- (funcall send-string process (concat "__ebcpre; " commandline ";\n"))
+ (funcall send-string process (concat "__ebcpre; " commandline "; __ebcret $?\n"))
(bash-completion--wait-for-regexp
"short-timeout" process "==emacs==bash=[0-9].*?==."
bash-completion-short-command-timeout))
diff --git a/test/bash-completion-integration-test.el b/test/bash-completion-integration-test.el
index e227165..f57ddc7 100644
--- a/test/bash-completion-integration-test.el
+++ b/test/bash-completion-integration-test.el
@@ -900,4 +900,30 @@ $ ")))))
"dummy moretestfile "
(bash-completion_test-complete "dummy moret")))))
+(ert-deftest bash-completion-integration-recover-status-ps1 ()
+ (bash-completion_test-with-shell-harness
+ (concat ; .bashrc
+ "function dummy { echo --$PS1--; }\n"
+ "function _dummy {\n"
+ " COMPREPLY=( dummy )\n"
+ "}\n"
+ "complete -F _dummy dummy\n"
+ "PS1='$ '")
+ nil
+ ;; The first time initializes completion, the second time executes
+ ;; an already initialized completion. The two cases behave very
+ ;; differently, so we test both.
+ (dotimes (i 2)
+ (should (equal
+ "dummy dummy "
+ (bash-completion_test-complete "dummy dum")))
+ (let ((start (line-beginning-position)))
+ (comint-send-input)
+ (bash-completion_test-wait-for-prompt start)))
+
+ ;; The PS1 printed by the dummy function should be the one set in
+ ;; the init section, and not the one set by bash completion.
+ (should (equal (bash-completion_test-buffer-string)
+ "$ dummy dummy\n--$ --\n$ dummy dummy\n--$ --\n$ "))))
+
;;; bash-completion-integration-test.el ends here