From 5f331728e2a2303d9959923f226d8ada8a9e66c4 Mon Sep 17 00:00:00 2001 From: Saad Abdullah Date: Wed, 14 Oct 2026 10:10:49 +0000 Subject: [PATCH] fix(buffer): regenerate auto-suggestion after text changes such as deletions --- src/prompt_toolkit/buffer.py | 9 +++++++ tests/test_buffer.py | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/prompt_toolkit/buffer.py b/src/prompt_toolkit/buffer.py index ae1198f11..d65c84966 100644 --- a/src/prompt_toolkit/buffer.py +++ b/src/prompt_toolkit/buffer.py @@ -535,6 +535,15 @@ def _text_changed(self) -> None: if self.validator and self.validate_while_typing(): get_app().create_background_task(self._async_validator()) + # Auto suggestion. + # (This also happens on all change events, also when deleting text. + # Suggestions have just been cleared above, so ask the auto suggester + # for a new one for the current text. When the text was changed by + # `insert_text`, this can schedule the suggester a second time, but + # that is harmless: it is guarded to run only one at a time.) + if self.auto_suggest: + get_app().create_background_task(self._async_suggester()) + def _cursor_position_changed(self) -> None: # Remove any complete state. # (Input validation should only be undone when the cursor position diff --git a/tests/test_buffer.py b/tests/test_buffer.py index e636137d5..442c0aa4f 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -1,8 +1,16 @@ from __future__ import annotations +import asyncio + import pytest +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import set_app +from prompt_toolkit.auto_suggest import AutoSuggest, Suggestion from prompt_toolkit.buffer import Buffer +from prompt_toolkit.document import Document +from prompt_toolkit.input import DummyInput +from prompt_toolkit.output import DummyOutput @pytest.fixture @@ -110,3 +118,45 @@ def test_swap_characters_before_cursor(_buffer): _buffer.swap_characters_before_cursor() assert _buffer.text == "hello wrold" + + +class _AlwaysSuggest(AutoSuggest): + """Auto suggester that records the documents it was called with.""" + + def __init__(self) -> None: + self.documents: list[Document] = [] + + def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion: + self.documents.append(document) + return Suggestion(" suggestion") + + +def test_auto_suggest_after_delete(): + """ + A suggestion should be generated after deleting text as well, not only + after inserting text. + """ + + async def run() -> None: + suggester = _AlwaysSuggest() + buff = Buffer(auto_suggest=suggester) + app = Application(input=DummyInput(), output=DummyOutput()) + + with set_app(app): + # Typing generates a suggestion. + buff.insert_text("hello") + await asyncio.sleep(0.1) + assert buff.suggestion is not None + assert buff.suggestion.text == " suggestion" + assert [d.text for d in suggester.documents] == ["hello"] + + # Deleting a character clears the suggestion, but a new one + # should be generated for the remaining text. + buff.delete_before_cursor() + await asyncio.sleep(0.1) + assert buff.text == "hell" + assert buff.suggestion is not None + assert buff.suggestion.text == " suggestion" + assert suggester.documents[-1].text == "hell" + + asyncio.run(run())