diff --git a/src/prompt_toolkit/filters/app.py b/src/prompt_toolkit/filters/app.py index 303a078c4..9619f8619 100644 --- a/src/prompt_toolkit/filters/app.py +++ b/src/prompt_toolkit/filters/app.py @@ -24,6 +24,7 @@ "has_selection", "has_suggestion", "has_validation_error", + "has_any_validation_error", "is_done", "is_read_only", "is_multiline", @@ -170,6 +171,32 @@ def has_validation_error() -> bool: return get_app().current_buffer.validation_error is not None +@Condition +def has_any_validation_error() -> bool: + "Any buffer in the layout has validation error." + from prompt_toolkit.layout.containers import Window + from prompt_toolkit.layout.controls import ( + BufferControl, + SearchBufferControl, + UIControl, + ) + + # Extract buffer validation errors for buffer UIControl children classes + def get_buffer_from_content(content: UIControl) -> bool: + if isinstance(content, (BufferControl, SearchBufferControl)): + return content.buffer.validation_error is not None + else: + return False + # Get all windows for the current layout + windows = get_app().layout.find_all_windows() + # Iterate over each window in the layout and return true if any of the buffers have validation errors + for window in windows: + if isinstance(window, Window): + if get_buffer_from_content(window.content): + return True + return False + + @Condition def has_arg() -> bool: "Enable when the input processor has an 'arg'." diff --git a/src/prompt_toolkit/shortcuts/dialogs.py b/src/prompt_toolkit/shortcuts/dialogs.py index d78e7dbdf..362dda6b5 100644 --- a/src/prompt_toolkit/shortcuts/dialogs.py +++ b/src/prompt_toolkit/shortcuts/dialogs.py @@ -110,6 +110,7 @@ def input_dialog( cancel_text: str = "Cancel", completer: Completer | None = None, validator: Validator | None = None, + validate_while_typing: FilterOrBool = False, password: FilterOrBool = False, style: BaseStyle | None = None, default: str = "", @@ -124,7 +125,9 @@ def accept(buf: Buffer) -> bool: return True # Keep text. def ok_handler() -> None: - get_app().exit(result=textfield.text) + textfield.buffer.validate() # validate one final time before exiting + if textfield.buffer.validation_error is None: + get_app().exit(result=textfield.text) ok_button = Button(text=ok_text, handler=ok_handler) cancel_button = Button(text=cancel_text, handler=_return_none) @@ -135,6 +138,7 @@ def ok_handler() -> None: password=password, completer=completer, validator=validator, + validate_while_typing=validate_while_typing, accept_handler=accept, ) @@ -144,7 +148,7 @@ def ok_handler() -> None: [ Label(text=text, dont_extend_height=True), textfield, - ValidationToolbar(), + ValidationToolbar(buffer=textfield.buffer), ], padding=D(preferred=1, max=1), ), diff --git a/src/prompt_toolkit/widgets/base.py b/src/prompt_toolkit/widgets/base.py index cddb91596..323b7c734 100644 --- a/src/prompt_toolkit/widgets/base.py +++ b/src/prompt_toolkit/widgets/base.py @@ -179,6 +179,7 @@ def __init__( completer: Completer | None = None, complete_while_typing: FilterOrBool = True, validator: Validator | None = None, + validate_while_typing: FilterOrBool = False, accept_handler: BufferAcceptHandler | None = None, history: History | None = None, focusable: FilterOrBool = True, @@ -215,6 +216,7 @@ def __init__( self.read_only = read_only self.wrap_lines = wrap_lines self.validator = validator + self.validate_while_typing = validate_while_typing self.buffer = Buffer( document=Document(text, 0), @@ -225,6 +227,9 @@ def __init__( lambda: is_true(self.complete_while_typing) ), validator=DynamicValidator(lambda: self.validator), + validate_while_typing=Condition( + lambda: is_true(self.validate_while_typing) + ), auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), accept_handler=accept_handler, history=history, diff --git a/src/prompt_toolkit/widgets/toolbars.py b/src/prompt_toolkit/widgets/toolbars.py index deddf1542..a45141dae 100644 --- a/src/prompt_toolkit/widgets/toolbars.py +++ b/src/prompt_toolkit/widgets/toolbars.py @@ -9,10 +9,10 @@ Condition, FilterOrBool, emacs_mode, + has_any_validation_error, has_arg, has_completions, has_focus, - has_validation_error, to_filter, vi_mode, vi_navigation_mode, @@ -342,16 +342,19 @@ def __pt_container__(self) -> Container: class ValidationToolbar: - def __init__(self, show_position: bool = False) -> None: + def __init__(self, show_position: bool = False, buffer: Buffer | None = None) -> None: def get_formatted_text() -> StyleAndTextTuples: - buff = get_app().current_buffer - + # If buffer not specified, use the currently focused buffer + if buffer is None: + buff = get_app().current_buffer + else: + buff = buffer if buff.validation_error: - row, column = buff.document.translate_index_to_position( - buff.validation_error.cursor_position - ) - if show_position: + row, column = buff.document.translate_index_to_position( + buff.validation_error.cursor_position + ) + text = "{} (line={} column={})".format( buff.validation_error.message, row + 1, @@ -367,7 +370,7 @@ def get_formatted_text() -> StyleAndTextTuples: self.control = FormattedTextControl(get_formatted_text) self.container = ConditionalContainer( - content=Window(self.control, height=1), filter=has_validation_error + content=Window(self.control, height=1), filter=has_any_validation_error ) def __pt_container__(self) -> Container: