Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sublime_lib/_util/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 14 additions & 9 deletions sublime_lib/_util/guard.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
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:
return wrapped(self, *args, **kwargs)
else:
return wrapped(self, *args, **kwargs)

return wrapper_guards
return wrapper

return decorator
9 changes: 5 additions & 4 deletions sublime_lib/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.

Expand Down
88 changes: 46 additions & 42 deletions sublime_lib/view_stream.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,58 @@
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

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
Expand All @@ -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
):
Expand Down Expand Up @@ -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.)"""
Expand Down
Loading