diff --git a/src/prompt_toolkit/shortcuts/prompt.py b/src/prompt_toolkit/shortcuts/prompt.py index f14ecd80c..74466d4c3 100644 --- a/src/prompt_toolkit/shortcuts/prompt.py +++ b/src/prompt_toolkit/shortcuts/prompt.py @@ -314,7 +314,9 @@ class PromptSession(Generic[_T]): and returns formatted text. When this is `None` (the default), then `prompt_width` spaces will be used. :param complete_style: ``CompleteStyle.COLUMN``, - ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``. + ``CompleteStyle.MULTI_COLUMN``, ``CompleteStyle.READLINE_LIKE`` or a + callable that returns a ``CompleteStyle``. The callable is evaluated + dynamically. :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter` to enable mouse support. :param placeholder: Text to be displayed when no input has been given @@ -397,7 +399,9 @@ def __init__( completer: Completer | None = None, complete_in_thread: bool = False, reserve_space_for_menu: int = 8, - complete_style: CompleteStyle = CompleteStyle.COLUMN, + complete_style: CompleteStyle | Callable[[], CompleteStyle] = ( + CompleteStyle.COLUMN + ), auto_suggest: AutoSuggest | None = None, style: BaseStyle | None = None, style_transformation: StyleTransformation | None = None, @@ -524,7 +528,7 @@ def accept(buff: Buffer) -> bool: lambda: ( is_true(self.complete_while_typing) and not is_true(self.enable_history_search) - and not self.complete_style == CompleteStyle.READLINE_LIKE + and not self._get_complete_style() == CompleteStyle.READLINE_LIKE ) ), validate_while_typing=dyncond("validate_while_typing"), @@ -641,7 +645,7 @@ def get_search_buffer_control() -> SearchBufferControl: @Condition def multi_column_complete_style() -> bool: - return self.complete_style == CompleteStyle.MULTI_COLUMN + return self._get_complete_style() == CompleteStyle.MULTI_COLUMN # Build the layout. @@ -834,7 +838,7 @@ def _accept_input(event: E) -> None: @Condition def readline_complete_style() -> bool: - return self.complete_style == CompleteStyle.READLINE_LIKE + return self._get_complete_style() == CompleteStyle.READLINE_LIKE @handle("tab", filter=readline_complete_style & default_focused) def _complete_like_readline(event: E) -> None: @@ -908,7 +912,7 @@ def prompt( search_ignore_case: FilterOrBool | None = None, complete_while_typing: FilterOrBool | None = None, validate_while_typing: FilterOrBool | None = None, - complete_style: CompleteStyle | None = None, + complete_style: CompleteStyle | Callable[[], CompleteStyle] | None = None, auto_suggest: AutoSuggest | None = None, validator: Validator | None = None, clipboard: Clipboard | None = None, @@ -1148,7 +1152,7 @@ async def prompt_async( search_ignore_case: FilterOrBool | None = None, complete_while_typing: FilterOrBool | None = None, validate_while_typing: FilterOrBool | None = None, - complete_style: CompleteStyle | None = None, + complete_style: CompleteStyle | Callable[[], CompleteStyle] | None = None, auto_suggest: AutoSuggest | None = None, validator: Validator | None = None, clipboard: Clipboard | None = None, @@ -1291,7 +1295,7 @@ def _get_default_buffer_control_height(self) -> Dimension: # layout has at least a minimal height in order to display it. if ( self.completer is not None - and self.complete_style != CompleteStyle.READLINE_LIKE + and self._get_complete_style() != CompleteStyle.READLINE_LIKE ): space = self.reserve_space_for_menu else: @@ -1308,6 +1312,16 @@ def _get_default_buffer_control_height(self) -> Dimension: return Dimension() + def _get_complete_style(self) -> CompleteStyle: + complete_style = self.complete_style + + if callable(complete_style): + style: CompleteStyle = complete_style() + else: + style = complete_style + + return style + def _get_prompt(self) -> StyleAndTextTuples: return to_formatted_text(self.message, style="class:prompt") @@ -1424,7 +1438,7 @@ def prompt( search_ignore_case: FilterOrBool | None = None, complete_while_typing: FilterOrBool | None = None, validate_while_typing: FilterOrBool | None = None, - complete_style: CompleteStyle | None = None, + complete_style: CompleteStyle | Callable[[], CompleteStyle] | None = None, auto_suggest: AutoSuggest | None = None, validator: Validator | None = None, clipboard: Clipboard | None = None, diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 287c6d33a..661635183 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,7 +1,12 @@ from __future__ import annotations +from prompt_toolkit.application.current import get_app, set_app from prompt_toolkit.shortcuts import print_container -from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt +from prompt_toolkit.shortcuts.prompt import ( + CompleteStyle, + PromptSession, + _split_multiline_prompt, +) from prompt_toolkit.widgets import Frame, TextArea @@ -55,6 +60,33 @@ def test_split_multiline_prompt(): assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] +def test_callable_complete_style_can_read_current_buffer(): + complete_style_calls: list[str] = [] + + def get_complete_style() -> CompleteStyle: + text = get_app().current_buffer.text + complete_style_calls.append(text) + if text: + return CompleteStyle.MULTI_COLUMN + return CompleteStyle.READLINE_LIKE + + session: PromptSession[str] = PromptSession( + complete_style=get_complete_style, validate_while_typing=False + ) + + # The callable requires an active application, so it should not be called + # while constructing the session. + assert complete_style_calls == [] + + with set_app(session.app): + assert session.default_buffer.complete_while_typing() is False + + session.default_buffer.text = "text" + assert session.default_buffer.complete_while_typing() is True + + assert complete_style_calls == ["", "text"] + + def test_print_container(tmpdir): # Call `print_container`, render to a dummy file. f = tmpdir.join("output")