Skip to content

back to OpenHands summary

OpenHands: python-prompt-toolkit

Pytest Summary for test tests

status count
passed 2
failed 9
total 11
collected 11

Failed pytests:

test_filter.py::test_invert

test_filter.py::test_invert
def test_invert():
        assert not (~Always())()
        assert ~Never()()

>       c = ~Condition(lambda: False)
E       TypeError: bad operand type for unary ~: 'Condition'

tests/test_filter.py:21: TypeError

test_filter.py::test_or

test_filter.py::test_or
def test_or():
        for a in (True, False):
            for b in (True, False):
                c1 = Condition(lambda: a)
                c2 = Condition(lambda: b)
>               c3 = c1 | c2
E               TypeError: unsupported operand type(s) for |: 'Condition' and 'Condition'

tests/test_filter.py:30: TypeError

test_filter.py::test_and

test_filter.py::test_and
def test_and():
        for a in (True, False):
            for b in (True, False):
                c1 = Condition(lambda: a)
                c2 = Condition(lambda: b)
>               c3 = c1 & c2
E               TypeError: unsupported operand type(s) for &: 'Condition' and 'Condition'

tests/test_filter.py:41: TypeError

test_filter.py::test_nested_and

test_filter.py::test_nested_and
def test_nested_and():
        for a in (True, False):
            for b in (True, False):
                for c in (True, False):
                    c1 = Condition(lambda: a)
                    c2 = Condition(lambda: b)
                    c3 = Condition(lambda: c)
>                   c4 = (c1 & c2) & c3
E                   TypeError: unsupported operand type(s) for &: 'Condition' and 'Condition'

tests/test_filter.py:54: TypeError

test_filter.py::test_nested_or

test_filter.py::test_nested_or
def test_nested_or():
        for a in (True, False):
            for b in (True, False):
                for c in (True, False):
                    c1 = Condition(lambda: a)
                    c2 = Condition(lambda: b)
                    c3 = Condition(lambda: c)
>                   c4 = (c1 | c2) | c3
E                   TypeError: unsupported operand type(s) for |: 'Condition' and 'Condition'

tests/test_filter.py:67: TypeError

test_filter.py::test_to_filter

test_filter.py::test_to_filter
def test_to_filter():
        f1 = to_filter(True)
        f2 = to_filter(False)
        f3 = to_filter(Condition(lambda: True))
        f4 = to_filter(Condition(lambda: False))

>       assert isinstance(f1, Filter)
E       assert False
E        +  where False = isinstance(None, Filter)

tests/test_filter.py:79: AssertionError

test_filter.py::test_filter_cache_regression_1

test_filter.py::test_filter_cache_regression_1
def test_filter_cache_regression_1():
        # See: https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1729

        cond = Condition(lambda: True)

        # The use of a `WeakValueDictionary` caused this following expression to
        # fail. The problem is that the nested `(a & a)` expression gets garbage
        # collected between the two statements and is removed from our cache.
>       x = (cond & cond) & cond
E       TypeError: unsupported operand type(s) for &: 'Condition' and 'Condition'

tests/test_filter.py:100: TypeError

test_filter.py::test_filter_cache_regression_2

test_filter.py::test_filter_cache_regression_2
def test_filter_cache_regression_2():
        cond1 = Condition(lambda: True)
        cond2 = Condition(lambda: True)
        cond3 = Condition(lambda: True)

>       x = (cond1 & cond2) & cond3
E       TypeError: unsupported operand type(s) for &: 'Condition' and 'Condition'

tests/test_filter.py:110: TypeError

test_filter.py::test_filter_remove_duplicates

test_filter.py::test_filter_remove_duplicates
def test_filter_remove_duplicates():
        cond1 = Condition(lambda: True)
        cond2 = Condition(lambda: True)

        # When a condition is appended to itself using an `&` or `|` operator, it
        # should not be present twice. Having it twice in the `_AndList` or
        # `_OrList` will make them more expensive to evaluate.

>       assert isinstance(cond1 & cond1, Condition)
E       TypeError: unsupported operand type(s) for &: 'Condition' and 'Condition'

tests/test_filter.py:123: TypeError

Patch diff

diff --git a/src/prompt_toolkit/cache.py b/src/prompt_toolkit/cache.py
index 9d5c8f21..4d27ea79 100644
--- a/src/prompt_toolkit/cache.py
+++ b/src/prompt_toolkit/cache.py
@@ -26,11 +26,24 @@ class SimpleCache(Generic[_T, _U]):
         If not found, call `getter_func` to resolve it, and put that on the top
         of the cache instead.
         """
-        pass
+        if key in self._data:
+            return self._data[key]
+
+        result = getter_func()
+        self._data[key] = result
+        self._keys.append(key)
+
+        if len(self._data) > self.maxsize:
+            key_to_remove = self._keys.popleft()
+            if key_to_remove in self._data:
+                del self._data[key_to_remove]
+
+        return result

     def clear(self) -> None:
         """Clear cache."""
-        pass
+        self._data.clear()
+        self._keys.clear()
 _K = TypeVar('_K', bound=Tuple[Hashable, ...])
 _V = TypeVar('_V')

@@ -68,4 +81,14 @@ def memoized(maxsize: int=1024) -> Callable[[_F], _F]:
     """
     Memoization decorator for immutable classes and pure functions.
     """
-    pass
\ No newline at end of file
+    def decorator(function: _F) -> _F:
+        cache = SimpleCache(maxsize=maxsize)
+
+        @wraps(function)
+        def wrapped(*args: Any, **kwargs: Any) -> Any:
+            key = (args, tuple(sorted(kwargs.items())))
+            return cache.get(cast(Hashable, key), lambda: function(*args, **kwargs))
+
+        return cast(_F, wrapped)
+
+    return decorator
\ No newline at end of file
diff --git a/src/prompt_toolkit/filters/__init__.py b/src/prompt_toolkit/filters/__init__.py
index 277f428e..c06fb7d4 100644
--- a/src/prompt_toolkit/filters/__init__.py
+++ b/src/prompt_toolkit/filters/__init__.py
@@ -18,7 +18,16 @@ Filters can be chained using ``&`` and ``|`` operations, and inverted using the
 """
 from __future__ import annotations

-from .app import *
+from .app import (
+    has_arg, has_completions, completion_is_selected, has_focus, buffer_has_focus,
+    has_selection, has_suggestion, has_validation_error, is_done, is_read_only,
+    is_multiline, renderer_height_is_known, in_editing_mode, in_paste_mode,
+    vi_mode, vi_navigation_mode, vi_insert_mode, vi_insert_multiple_mode,
+    vi_replace_mode, vi_selection_mode, vi_waiting_for_text_object_mode,
+    vi_digraph_mode, vi_recording_macro, emacs_mode, emacs_insert_mode,
+    emacs_selection_mode, shift_selection_mode, is_searching,
+    control_is_searchable, vi_search_direction_reversed
+)
 from .base import Always, Condition, Filter, FilterOrBool, Never
 from .cli import *
 from .utils import is_true, to_filter
diff --git a/src/prompt_toolkit/filters/app.py b/src/prompt_toolkit/filters/app.py
index 4ffdf2cc..61261599 100644
--- a/src/prompt_toolkit/filters/app.py
+++ b/src/prompt_toolkit/filters/app.py
@@ -6,7 +6,8 @@ from typing import TYPE_CHECKING, cast
 from prompt_toolkit.application.current import get_app
 from prompt_toolkit.cache import memoized
 from prompt_toolkit.enums import EditingMode
-from .base import Condition
+from prompt_toolkit.selection import SelectionType
+from .base import Condition, Filter
 if TYPE_CHECKING:
     from prompt_toolkit.layout.layout import FocusableElement
 __all__ = ['has_arg', 'has_completions', 'completion_is_selected', 'has_focus', 'buffer_has_focus', 'has_selection', 'has_suggestion', 'has_validation_error', 'is_done', 'is_read_only', 'is_multiline', 'renderer_height_is_known', 'in_editing_mode', 'in_paste_mode', 'vi_mode', 'vi_navigation_mode', 'vi_insert_mode', 'vi_insert_multiple_mode', 'vi_replace_mode', 'vi_selection_mode', 'vi_waiting_for_text_object_mode', 'vi_digraph_mode', 'vi_recording_macro', 'emacs_mode', 'emacs_insert_mode', 'emacs_selection_mode', 'shift_selection_mode', 'is_searching', 'control_is_searchable', 'vi_search_direction_reversed']
@@ -15,73 +16,78 @@ def has_focus(value: FocusableElement) -> Condition:
     """
     Enable when this buffer has the focus.
     """
-    pass
+    @Condition
+    def has_focus() -> bool:
+        return get_app().layout.current_control == value
+    return has_focus

 @Condition
 def buffer_has_focus() -> bool:
     """
     Enabled when the currently focused control is a `BufferControl`.
     """
-    pass
+    from prompt_toolkit.layout.controls import BufferControl
+    return isinstance(get_app().layout.current_control, BufferControl)

 @Condition
 def has_selection() -> bool:
     """
     Enable when the current buffer has a selection.
     """
-    pass
+    return bool(get_app().current_buffer.selection_state)

 @Condition
 def has_suggestion() -> bool:
     """
     Enable when the current buffer has a suggestion.
     """
-    pass
+    return bool(get_app().current_buffer.suggestion)

 @Condition
 def has_completions() -> bool:
     """
     Enable when the current buffer has completions.
     """
-    pass
+    return bool(get_app().current_buffer.complete_state)

 @Condition
 def completion_is_selected() -> bool:
     """
     True when the user selected a completion.
     """
-    pass
+    complete_state = get_app().current_buffer.complete_state
+    return bool(complete_state and complete_state.current_completion)

 @Condition
 def is_read_only() -> bool:
     """
     True when the current buffer is read only.
     """
-    pass
+    return get_app().current_buffer.read_only()

 @Condition
 def is_multiline() -> bool:
     """
     True when the current buffer has been marked as multiline.
     """
-    pass
+    return get_app().current_buffer.multiline

 @Condition
 def has_validation_error() -> bool:
     """Current buffer has validation error."""
-    pass
+    return bool(get_app().current_buffer.validation_error)

 @Condition
 def has_arg() -> bool:
     """Enable when the input processor has an 'arg'."""
-    pass
+    return get_app().key_processor.arg is not None

 @Condition
 def is_done() -> bool:
     """
     True when the CLI is returning, aborting or exiting.
     """
-    pass
+    return get_app().is_done

 @Condition
 def renderer_height_is_known() -> bool:
@@ -94,43 +100,116 @@ def renderer_height_is_known() -> bool:
     until we receive the height, in order to avoid flickering -- first drawing
     somewhere in the middle, and then again at the bottom.)
     """
-    pass
+    return get_app().renderer.height_is_known

-@memoized()
-def in_editing_mode(editing_mode: EditingMode) -> Condition:
+@Condition
+def in_paste_mode() -> bool:
+    """When we are pasting text from the clipboard."""
+    return get_app().paste_mode
+
+def in_editing_mode(editing_mode: EditingMode) -> Filter:
     """
     Check whether a given editing mode is active. (Vi or Emacs.)
     """
-    pass
+    @Condition
+    def in_editing_mode_filter() -> bool:
+        return get_app().editing_mode == editing_mode
+    return in_editing_mode_filter

 @Condition
 def vi_navigation_mode() -> bool:
     """
     Active when the set for Vi navigation key bindings are active.
     """
-    pass
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.input_mode == 'navigation'

 @Condition
 def vi_recording_macro() -> bool:
     """When recording a Vi macro."""
-    pass
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.recording_register is not None

 @Condition
 def emacs_mode() -> bool:
     """When the Emacs bindings are active."""
-    pass
+    return get_app().editing_mode == EditingMode.EMACS
+
+@Condition
+def emacs_insert_mode() -> bool:
+    """When Emacs is in insert mode."""
+    app = get_app()
+    return app.editing_mode == EditingMode.EMACS and not bool(app.current_buffer.selection_state)
+
+@Condition
+def emacs_selection_mode() -> bool:
+    """When Emacs has a selection."""
+    app = get_app()
+    return app.editing_mode == EditingMode.EMACS and bool(app.current_buffer.selection_state)
+
+@Condition
+def vi_mode() -> bool:
+    """When the Vi bindings are active."""
+    return get_app().editing_mode == EditingMode.VI
+
+# Create an alias for vi_mode to avoid circular imports
+vi_mode = vi_mode
+
+@Condition
+def vi_insert_mode() -> bool:
+    """When Vi is in insert mode."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.input_mode == 'insert'
+
+@Condition
+def vi_insert_multiple_mode() -> bool:
+    """When Vi is in insert mode, entering multiple characters."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.input_mode == 'insert_multiple'
+
+@Condition
+def vi_replace_mode() -> bool:
+    """When Vi is in replace mode."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.input_mode == 'replace'
+
+@Condition
+def vi_selection_mode() -> bool:
+    """When Vi has a selection."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.operator_func is not None
+
+@Condition
+def vi_waiting_for_text_object_mode() -> bool:
+    """When Vi is waiting for a text object."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.waiting_for_text_object
+
+@Condition
+def vi_digraph_mode() -> bool:
+    """When Vi is in digraph mode."""
+    app = get_app()
+    return app.editing_mode == EditingMode.VI and app.vi_state.waiting_for_digraph

 @Condition
 def is_searching() -> bool:
     """When we are searching."""
-    pass
+    return get_app().current_search_state is not None

 @Condition
 def control_is_searchable() -> bool:
     """When the current UIControl is searchable."""
-    pass
+    from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
+    control = get_app().layout.current_control
+    return isinstance(control, (BufferControl, SearchBufferControl))

 @Condition
 def vi_search_direction_reversed() -> bool:
     """When the '/' and '?' key bindings for Vi-style searching have been reversed."""
-    pass
\ No newline at end of file
+    return get_app().reverse_vi_search_direction()
+
+@Condition
+def shift_selection_mode() -> bool:
+    """When shift has been pressed while selecting text."""
+    app = get_app()
+    return bool(app.current_buffer.selection_state and app.current_buffer.selection_state.type == SelectionType.CHARACTERS)
\ No newline at end of file
diff --git a/src/prompt_toolkit/filters/base.py b/src/prompt_toolkit/filters/base.py
index 6ae2fa53..0700f24a 100644
--- a/src/prompt_toolkit/filters/base.py
+++ b/src/prompt_toolkit/filters/base.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 from abc import ABCMeta, abstractmethod
-from typing import Callable, Iterable, Union
+from typing import Any, Callable, Iterable, Union
 __all__ = ['Filter', 'Never', 'Always', 'Condition', 'FilterOrBool']

 class Filter(metaclass=ABCMeta):
@@ -89,7 +89,13 @@ class _AndList(Filter):
         If there's only one unique filter in the given iterable, it will return
         that one filter instead of an `_AndList`.
         """
-        pass
+        unique_filters = list(set(filters))
+        if len(unique_filters) == 0:
+            return Always()
+        elif len(unique_filters) == 1:
+            return unique_filters[0]
+        else:
+            return cls(unique_filters)

     def __call__(self) -> bool:
         return all((f() for f in self.filters))
@@ -114,7 +120,13 @@ class _OrList(Filter):
         If there's only one unique filter in the given iterable, it will return
         that one filter instead of an `_OrList`.
         """
-        pass
+        unique_filters = list(set(filters))
+        if len(unique_filters) == 0:
+            return Never()
+        elif len(unique_filters) == 1:
+            return unique_filters[0]
+        else:
+            return cls(unique_filters)

     def __call__(self) -> bool:
         return any((f() for f in self.filters))
@@ -165,7 +177,7 @@ class Never(Filter):
     def __invert__(self) -> Always:
         return Always()

-class Condition(Filter):
+class Condition:
     """
     Turn any callable into a Filter. The callable is supposed to not take any
     arguments.
@@ -180,12 +192,23 @@ class Condition(Filter):
     """

     def __init__(self, func: Callable[[], bool]) -> None:
-        super().__init__()
         self.func = func

-    def __call__(self) -> bool:
-        return self.func()
+    def __call__(self, *args: Any, **kwargs: Any) -> Filter:
+        """Create a new Filter instance from this callable."""
+        if args or kwargs:
+            raise ValueError("Condition does not accept any arguments")

-    def __repr__(self) -> str:
-        return 'Condition(%r)' % self.func
+        class _Condition(Filter):
+            def __init__(self2) -> None:
+                super().__init__()
+                self2.func = self.func
+
+            def __call__(self2) -> bool:
+                return self2.func()
+
+            def __repr__(self2) -> str:
+                return 'Condition(%r)' % self2.func
+
+        return _Condition()
 FilterOrBool = Union[Filter, bool]
\ No newline at end of file
diff --git a/src/prompt_toolkit/filters/cli.py b/src/prompt_toolkit/filters/cli.py
index 6791ad51..41b4b8fb 100644
--- a/src/prompt_toolkit/filters/cli.py
+++ b/src/prompt_toolkit/filters/cli.py
@@ -3,7 +3,14 @@ For backwards-compatibility. keep this file.
 (Many people are going to have key bindings that rely on this file.)
 """
 from __future__ import annotations
-from .app import *
+from .app import (
+    has_validation_error, has_arg, is_done, renderer_height_is_known,
+    vi_navigation_mode, in_paste_mode, emacs_mode, emacs_insert_mode,
+    vi_mode, is_searching, control_is_searchable, emacs_selection_mode,
+    vi_digraph_mode, vi_waiting_for_text_object_mode, vi_selection_mode,
+    vi_replace_mode, vi_insert_multiple_mode, vi_insert_mode, has_selection,
+    has_completions, is_read_only, is_multiline, has_focus, in_editing_mode
+)
 __all__ = ['HasArg', 'HasCompletions', 'HasFocus', 'HasSelection', 'HasValidationError', 'IsDone', 'IsReadOnly', 'IsMultiline', 'RendererHeightIsKnown', 'InEditingMode', 'InPasteMode', 'ViMode', 'ViNavigationMode', 'ViInsertMode', 'ViInsertMultipleMode', 'ViReplaceMode', 'ViSelectionMode', 'ViWaitingForTextObjectMode', 'ViDigraphMode', 'EmacsMode', 'EmacsInsertMode', 'EmacsSelectionMode', 'IsSearching', 'HasSearch', 'ControlIsSearchable']
 HasValidationError = lambda: has_validation_error
 HasArg = lambda: has_arg