diff --git a/babi/list_spy.py b/babi/list_spy.py new file mode 100644 index 0000000..aaf4215 --- /dev/null +++ b/babi/list_spy.py @@ -0,0 +1,81 @@ +import functools +from typing import Callable +from typing import Iterator +from typing import List +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Protocol # python3.8+ +else: + Protocol = object + + +class MutableSequenceNoSlice(Protocol): + def __len__(self) -> int: ... + def __getitem__(self, idx: int) -> str: ... + def __setitem__(self, idx: int, val: str) -> None: ... + def __delitem__(self, idx: int) -> None: ... + def insert(self, idx: int, val: str) -> None: ... + + def __iter__(self) -> Iterator[str]: + for i in range(len(self)): + yield self[i] + + def append(self, val: str) -> None: + self.insert(len(self), val) + + def pop(self, idx: int = -1) -> str: + victim = self[idx] + del self[idx] + return victim + + +def _del(lst: MutableSequenceNoSlice, *, idx: int) -> None: + del lst[idx] + + +def _set(lst: MutableSequenceNoSlice, *, idx: int, val: str) -> None: + lst[idx] = val + + +def _ins(lst: MutableSequenceNoSlice, *, idx: int, val: str) -> None: + lst.insert(idx, val) + + +class ListSpy(MutableSequenceNoSlice): + def __init__(self, lst: MutableSequenceNoSlice) -> None: + self._lst = lst + self._undo: List[Callable[[MutableSequenceNoSlice], None]] = [] + + def __repr__(self) -> str: + return f'{type(self).__name__}({self._lst})' + + def __len__(self) -> int: + return len(self._lst) + + def __getitem__(self, idx: int) -> str: + return self._lst[idx] + + def __setitem__(self, idx: int, val: str) -> None: + self._undo.append(functools.partial(_set, idx=idx, val=self._lst[idx])) + self._lst[idx] = val + + def __delitem__(self, idx: int) -> None: + if idx < 0: + idx %= len(self) + self._undo.append(functools.partial(_ins, idx=idx, val=self._lst[idx])) + del self._lst[idx] + + def insert(self, idx: int, val: str) -> None: + if idx < 0: + idx %= len(self) + self._undo.append(functools.partial(_del, idx=idx)) + self._lst.insert(idx, val) + + def undo(self, lst: MutableSequenceNoSlice) -> None: + for fn in reversed(self._undo): + fn(lst) + + @property + def has_modifications(self) -> bool: + return bool(self._undo) diff --git a/babi/main.py b/babi/main.py index c03bb51..055e68c 100644 --- a/babi/main.py +++ b/babi/main.py @@ -19,7 +19,6 @@ from typing import cast from typing import Dict from typing import Generator from typing import IO -from typing import Iterator from typing import List from typing import Match from typing import NamedTuple @@ -27,14 +26,11 @@ from typing import Optional from typing import Pattern from typing import Sequence from typing import Tuple -from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -if TYPE_CHECKING: - from typing import Protocol # python3.8+ -else: - Protocol = object +from babi.list_spy import ListSpy +from babi.list_spy import MutableSequenceNoSlice VERSION_STR = 'babi v0' TCallable = TypeVar('TCallable', bound=Callable[..., Any]) @@ -100,77 +96,6 @@ def _scrolled_line(s: str, x: int, width: int) -> str: return s.ljust(width) -class MutableSequenceNoSlice(Protocol): - def __len__(self) -> int: ... - def __getitem__(self, idx: int) -> str: ... - def __setitem__(self, idx: int, val: str) -> None: ... - def __delitem__(self, idx: int) -> None: ... - def insert(self, idx: int, val: str) -> None: ... - - def __iter__(self) -> Iterator[str]: - for i in range(len(self)): - yield self[i] - - def append(self, val: str) -> None: - self.insert(len(self), val) - - def pop(self, idx: int = -1) -> str: - victim = self[idx] - del self[idx] - return victim - - -def _del(lst: MutableSequenceNoSlice, *, idx: int) -> None: - del lst[idx] - - -def _set(lst: MutableSequenceNoSlice, *, idx: int, val: str) -> None: - lst[idx] = val - - -def _ins(lst: MutableSequenceNoSlice, *, idx: int, val: str) -> None: - lst.insert(idx, val) - - -class ListSpy(MutableSequenceNoSlice): - def __init__(self, lst: MutableSequenceNoSlice) -> None: - self._lst = lst - self._undo: List[Callable[[MutableSequenceNoSlice], None]] = [] - - def __repr__(self) -> str: - return f'{type(self).__name__}({self._lst})' - - def __len__(self) -> int: - return len(self._lst) - - def __getitem__(self, idx: int) -> str: - return self._lst[idx] - - def __setitem__(self, idx: int, val: str) -> None: - self._undo.append(functools.partial(_set, idx=idx, val=self._lst[idx])) - self._lst[idx] = val - - def __delitem__(self, idx: int) -> None: - if idx < 0: - idx %= len(self) - self._undo.append(functools.partial(_ins, idx=idx, val=self._lst[idx])) - del self._lst[idx] - - def insert(self, idx: int, val: str) -> None: - if idx < 0: - idx %= len(self) - self._undo.append(functools.partial(_del, idx=idx)) - self._lst.insert(idx, val) - - def undo(self, lst: MutableSequenceNoSlice) -> None: - for fn in reversed(self._undo): - fn(lst) - - @property - def has_modifications(self) -> bool: - return bool(self._undo) - - class Key(NamedTuple): wch: Union[int, str] keyname: bytes diff --git a/tests/list_spy_test.py b/tests/list_spy_test.py index 2f9d135..51c985f 100644 --- a/tests/list_spy_test.py +++ b/tests/list_spy_test.py @@ -1,6 +1,6 @@ import pytest -from babi.main import ListSpy +from babi.list_spy import ListSpy def test_list_spy_repr():