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