From 812b8000f79c1fbb747a2de5114fc74515558857 Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Mon, 28 Jul 2025 16:56:51 -0700 Subject: [PATCH 1/4] Fix rendering when `prompt_suffix` is empty This changes the implementation of the fixes in #1836 and #2093 to address the problem that they created when `click.prompt` or `click.confirm` has a `prompt_suffix` value which is empty. This still fixes the problems reported in #665 and #2092 but does so without modifying the prompt and adding a trailing space. Previously, the problem was solved by trimming all trailing space characters from the prompt and then adding a single trailing space to every prompt. This was only visible if the last character in the prompt wasn't a space character. In those cases a prompt like "This is my prompt" would render as "This is my prompt ". Additionally, in the previous solution, if the `err=True` argument was passed, that final trailing space character that was added would be sent to STDOUT while the rest of the prompt would go to STDERR. This also wasn't very apparent since it was a space character. This different implementation uses the last character of the prompt to work around #665 and #2092 instead of adding a space character. This fixes most of the potentially confusing behavior. It does retain one confusing behavior which is described above, where if you use `err=True` the last character of the prompt is sent to STDOUT instead of STDERR. Previously, this last character sent to STDOUT would always be a space character. Now if the last character of the prompt is not a space character, that final character will go to STDOUT. For example if you call ```python click.prompt("bar", prompt_suffix="", err=True) ``` The `ba` will go to STDERR and the `r` will go to STDOUT. Previously `bar` would go to STDERR and an added ` ` would go to STDOUT. This odd behavior, which is already present but just applies to a space character, is worth accepting so that the `prompt_suffix` correctly renders the suffix as it did in version 7.1.2 and prior. This also adds unit tests for cases where `prompt_suffix` is empty. This fixes #3019 --- CHANGES.rst | 2 ++ src/click/termui.py | 12 ++++++------ tests/test_utils.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2dcffe023..295c29fba 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ Unreleased screen. Refs :issue:`2911` :pr:`3004` - Fix completion for the Z shell (``zsh``) for completion items containing colons. :issue:`2703` :pr:`2846` +- Fix rendering when ``prompt`` and ``confirm`` parameter ``prompt_suffix`` is + empty. :issue:`3019` Version 8.2.1 ------------- diff --git a/src/click/termui.py b/src/click/termui.py index dcbb22216..e69c8367d 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -138,10 +138,10 @@ def prompt_func(text: str) -> str: try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(text.rstrip(" "), nl=False, err=err) - # Echo a space to stdout to work around an issue where + echo(text[:-1], nl=False, err=err) + # Echo the last character to stdout to work around an issue where # readline causes backspace to clear the whole line. - return f(" ") + return f(text[-1:]) except (KeyboardInterrupt, EOFError): # getpass doesn't print a newline if the user aborts input with ^C. # Allegedly this behavior is inherited from getpass(3). @@ -231,10 +231,10 @@ def confirm( try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(prompt.rstrip(" "), nl=False, err=err) - # Echo a space to stdout to work around an issue where + echo(prompt[:-1], nl=False, err=err) + # Echo the last character to stdout to work around an issue where # readline causes backspace to clear the whole line. - value = visible_prompt_func(" ").lower().strip() + value = visible_prompt_func(prompt[-1:]).lower().strip() except (KeyboardInterrupt, EOFError): raise Abort() from None if value in ("y", "yes"): diff --git a/tests/test_utils.py b/tests/test_utils.py index e941415b6..b27dd8a56 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -430,24 +430,48 @@ def emulate_input(text): assert out == "Prompt to stdin: " assert err == "" + emulate_input("asdlkj\n") + click.prompt("Prompt to stdin with no suffix", prompt_suffix="") + out, err = capfd.readouterr() + assert out == "Prompt to stdin with no suffix" + assert err == "" + emulate_input("asdlkj\n") click.prompt("Prompt to stderr", err=True) out, err = capfd.readouterr() assert out == " " assert err == "Prompt to stderr:" + emulate_input("asdlkj\n") + click.prompt("Prompt to stderr with no suffix", prompt_suffix="", err=True) + out, err = capfd.readouterr() + assert out == "x" + assert err == "Prompt to stderr with no suffi" + emulate_input("y\n") click.confirm("Prompt to stdin") out, err = capfd.readouterr() assert out == "Prompt to stdin [y/N]: " assert err == "" + emulate_input("y\n") + click.confirm("Prompt to stdin with no suffix", prompt_suffix="") + out, err = capfd.readouterr() + assert out == "Prompt to stdin with no suffix [y/N]" + assert err == "" + emulate_input("y\n") click.confirm("Prompt to stderr", err=True) out, err = capfd.readouterr() assert out == " " assert err == "Prompt to stderr [y/N]:" + emulate_input("y\n") + click.confirm("Prompt to stderr with no suffix", prompt_suffix="", err=True) + out, err = capfd.readouterr() + assert out == "]" + assert err == "Prompt to stderr with no suffix [y/N" + monkeypatch.setattr(click.termui, "isatty", lambda x: True) monkeypatch.setattr(click.termui, "getchar", lambda: " ") From 1c9fc6df7cf37407b65fecc9c7a65b29b7727cff Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Thu, 25 Sep 2025 09:32:24 -0700 Subject: [PATCH 2/4] Add details to `prompt` and `confirm` docstrings regarding 8.3.1 change This adds details of the change in #3021 --- src/click/termui.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/click/termui.py b/src/click/termui.py index e69c8367d..d3d10de9e 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -119,6 +119,13 @@ def prompt( show_choices is true and text is "Group by" then the prompt will be "Group by (day, week): ". + .. versionchanged:: 8.3.1 + The prompt displayed no longer strips trailing whitespace, adding a single + trailing space. Instead, ``text`` is now displayed followed by + ``prompt_suffix`` with no whitespace manipulation. + When ``err`` is True the prompt is sent to ``stderr`` except for the last + character which is sent to ``stdout``. + .. versionadded:: 8.0 ``confirmation_prompt`` can be a custom string. @@ -214,6 +221,13 @@ def confirm( :param err: if set to true the file defaults to ``stderr`` instead of ``stdout``, the same as with echo. + .. versionchanged:: 8.3.1 + The prompt displayed no longer strips trailing whitespace, adding a single + trailing space. Instead, ``text`` is now displayed followed by + ``prompt_suffix`` with no whitespace manipulation. + When ``err`` is True the prompt is sent to ``stderr`` except for the last + character which is sent to ``stdout``. + .. versionchanged:: 8.0 Repeat until input is given if ``default`` is ``None``. From 20df0b3882abe69454560e085fcdfd25f7aaf453 Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Thu, 25 Sep 2025 09:33:29 -0700 Subject: [PATCH 3/4] Add note to CHANGES.rst about #3021 --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 95d6556df..f53001bf9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,8 @@ Unreleased the ``Context.invoke()`` method. :issue:`3066` :issue:`3065` :pr:`3068` - Fix conversion of ``Sentinel.UNSET`` happening too early, which caused incorrect behavior for multiple parameters using the same name. :issue:`3071` :pr:`3079` +- Fix rendering when ``prompt`` and ``confirm`` parameter ``prompt_suffix`` is + empty. :issue:`3019` :pr:`3021` Version 8.3.0 -------------- From 27de74af68bfd967c639ad4beb330fa4ed0d470f Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Thu, 25 Sep 2025 11:11:19 -0700 Subject: [PATCH 4/4] Shorten the docstring annotations for confirm and prompt --- src/click/termui.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/click/termui.py b/src/click/termui.py index d3d10de9e..2e98a0771 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -120,11 +120,7 @@ def prompt( prompt will be "Group by (day, week): ". .. versionchanged:: 8.3.1 - The prompt displayed no longer strips trailing whitespace, adding a single - trailing space. Instead, ``text`` is now displayed followed by - ``prompt_suffix`` with no whitespace manipulation. - When ``err`` is True the prompt is sent to ``stderr`` except for the last - character which is sent to ``stdout``. + A space is no longer appended to the prompt. .. versionadded:: 8.0 ``confirmation_prompt`` can be a custom string. @@ -222,11 +218,7 @@ def confirm( ``stdout``, the same as with echo. .. versionchanged:: 8.3.1 - The prompt displayed no longer strips trailing whitespace, adding a single - trailing space. Instead, ``text`` is now displayed followed by - ``prompt_suffix`` with no whitespace manipulation. - When ``err`` is True the prompt is sent to ``stderr`` except for the last - character which is sent to ``stdout``. + A space is no longer appended to the prompt. .. versionchanged:: 8.0 Repeat until input is given if ``default`` is ``None``.