diff --git a/sublime_lib/_util/enum.py b/sublime_lib/_util/enum.py index 629dbb5..335b34a 100644 --- a/sublime_lib/_util/enum.py +++ b/sublime_lib/_util/enum.py @@ -21,7 +21,7 @@ def decorator(cls: Any) -> EnumMeta: def __new__(cls: EnumMeta, *args: Any, **kwargs: Any) -> Enum: return constructor(next_constructor, cls, *args, **kwargs) - cls.__new__ = __new__ # type: ignore + cls.__new__ = __new__ return cls return decorator diff --git a/sublime_lib/_util/guard.py b/sublime_lib/_util/guard.py index 33608f8..08b97c4 100644 --- a/sublime_lib/_util/guard.py +++ b/sublime_lib/_util/guard.py @@ -1,18 +1,23 @@ from __future__ import annotations from functools import wraps -from typing import Any, Callable, ContextManager, TypeVar +from typing import TYPE_CHECKING - -_Self = TypeVar('_Self') -_WrappedType = Callable[..., Any] +if TYPE_CHECKING: + from typing import Any, Callable, ContextManager, TypeVar + from typing_extensions import ParamSpec, Concatenate + _Self = TypeVar('_Self') + _T = TypeVar('_T') + _P = ParamSpec('_P') def define_guard( - guard_fn: Callable[[_Self], ContextManager | None] -) -> Callable[[_WrappedType], _WrappedType]: - def decorator(wrapped: _WrappedType) -> _WrappedType: + guard_fn: Callable[[_Self], ContextManager[Any] | None] +) -> Callable[[Callable[Concatenate[_Self, _P], _T]], Callable[Concatenate[_Self, _P], _T]]: + def decorator( + wrapped: Callable[Concatenate[_Self, _P], _T] + ) -> Callable[Concatenate[_Self, _P], _T]: @wraps(wrapped) - def wrapper_guards(self: _Self, *args: Any, **kwargs: Any) -> Any: + def wrapper(self: _Self, /, *args: _P.args, **kwargs: _P.kwargs) -> _T: ret_val = guard_fn(self) if ret_val is not None: with ret_val: @@ -20,6 +25,6 @@ def wrapper_guards(self: _Self, *args: Any, **kwargs: Any) -> Any: else: return wrapped(self, *args, **kwargs) - return wrapper_guards + return wrapper return decorator diff --git a/sublime_lib/panel.py b/sublime_lib/panel.py index a9650a2..e60fbed 100644 --- a/sublime_lib/panel.py +++ b/sublime_lib/panel.py @@ -10,6 +10,11 @@ __all__ = ['Panel', 'OutputPanel'] +@define_guard +def guard_exists(panel: Panel) -> None: + panel._checkExists() + + class Panel(): """An abstraction of a panel, such as the console or an output panel. @@ -36,10 +41,6 @@ def _checkExists(self) -> None: if not self.exists(): raise ValueError(f"Panel {self.panel_name} does not exist.") - @define_guard - def guard_exists(self) -> None: - self._checkExists() - def exists(self) -> bool: """Return ``True`` if the panel exists, or ``False`` otherwise. diff --git a/sublime_lib/view_stream.py b/sublime_lib/view_stream.py index c943597..1d61c10 100644 --- a/sublime_lib/view_stream.py +++ b/sublime_lib/view_stream.py @@ -1,8 +1,8 @@ from __future__ import annotations from collections.abc import Generator from contextlib import contextmanager -from io import SEEK_SET, SEEK_CUR, SEEK_END, TextIOBase -from typing import Any +from io import SEEK_SET, SEEK_CUR, SEEK_END +from typing import Any, TextIO import sublime from sublime import Region @@ -10,7 +10,49 @@ from ._util.guard import define_guard -class ViewStream(TextIOBase): +@define_guard +@contextmanager +def guard_read_only(vs: ViewStream) -> Generator[Any, None, None]: + if vs.view.is_read_only(): + if vs.force_writes: + vs.view.set_read_only(False) + yield + vs.view.set_read_only(True) + else: + raise ValueError("The underlying view is read-only.") + else: + yield + + +@define_guard +@contextmanager +def guard_auto_indent(vs: ViewStream) -> Generator[Any, None, None]: + settings = vs.view.settings() + if settings.get('auto_indent'): + settings.set('auto_indent', False) + yield + settings.set('auto_indent', True) + else: + yield + + +@define_guard +def guard_validity(vs: ViewStream) -> None: + if not vs.view.is_valid(): + raise ValueError("The underlying view is invalid.") + + +@define_guard +def guard_selection(vs: ViewStream) -> None: + if len(vs.view.sel()) == 0: + raise ValueError("The underlying view has no selection.") + elif len(vs.view.sel()) > 1: + raise ValueError("The underlying view has multiple selections.") + elif not vs.view.sel()[0].empty(): + raise ValueError("The underlying view's selection is not empty.") + + +class ViewStream(TextIO): """A :class:`~io.TextIOBase` encapsulating a :class:`~sublime.View` object. All public methods (except :meth:`flush`) require @@ -35,44 +77,6 @@ class ViewStream(TextIOBase): Added the `follow_cursor` option. """ - @define_guard - @contextmanager - def guard_read_only(self) -> Generator[Any, None, None]: - if self.view.is_read_only(): - if self.force_writes: - self.view.set_read_only(False) - yield - self.view.set_read_only(True) - else: - raise ValueError("The underlying view is read-only.") - else: - yield - - @define_guard - @contextmanager - def guard_auto_indent(self) -> Generator[Any, None, None]: - settings = self.view.settings() - if settings.get('auto_indent'): - settings.set('auto_indent', False) - yield - settings.set('auto_indent', True) - else: - yield - - @define_guard - def guard_validity(self) -> None: - if not self.view.is_valid(): - raise ValueError("The underlying view is invalid.") - - @define_guard - def guard_selection(self) -> None: - if len(self.view.sel()) == 0: - raise ValueError("The underlying view has no selection.") - elif len(self.view.sel()) > 1: - raise ValueError("The underlying view has multiple selections.") - elif not self.view.sel()[0].empty(): - raise ValueError("The underlying view's selection is not empty.") - def __init__( self, view: sublime.View, *, force_writes: bool = False, follow_cursor: bool = False ): @@ -136,7 +140,7 @@ def write(self, s: str) -> int: def print(self, *objects: object, sep: str = ' ', end: str = '\n') -> None: """Shorthand for :func:`print()` passing this ViewStream as the `file` argument.""" - print(*objects, file=self, sep=sep, end=end) # type: ignore + print(*objects, file=self, sep=sep, end=end) def flush(self) -> None: """Do nothing. (The stream is not buffered.)"""