@@ -23,7 +23,7 @@ repos:
|
||||
rev: v2.6.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py3-plus]
|
||||
args: [--py3-plus, --add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
@@ -33,7 +33,7 @@ repos:
|
||||
rev: v2.25.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py36-plus]
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v1.17.0
|
||||
hooks:
|
||||
|
||||
@@ -15,5 +15,5 @@ resources:
|
||||
jobs:
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [pypy3, py36, py37, py38, py39]
|
||||
toxenvs: [py37, py38, py39]
|
||||
os: linux
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from babi.main import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
47
babi/buf.py
47
babi/buf.py
@@ -1,12 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bisect
|
||||
import contextlib
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi._types import Protocol
|
||||
from babi.horizontal_scrolling import line_x
|
||||
@@ -19,7 +18,7 @@ DelCallback = Callable[['Buf', int, str], None]
|
||||
InsCallback = Callable[['Buf', int], None]
|
||||
|
||||
|
||||
def _offsets(s: str, tab_size: int) -> Tuple[int, ...]:
|
||||
def _offsets(s: str, tab_size: int) -> tuple[int, ...]:
|
||||
ret = [0]
|
||||
for c in s:
|
||||
if c == '\t':
|
||||
@@ -30,14 +29,14 @@ def _offsets(s: str, tab_size: int) -> Tuple[int, ...]:
|
||||
|
||||
|
||||
class Modification(Protocol):
|
||||
def __call__(self, buf: 'Buf') -> None: ...
|
||||
def __call__(self, buf: Buf) -> None: ...
|
||||
|
||||
|
||||
class SetModification(NamedTuple):
|
||||
idx: int
|
||||
s: str
|
||||
|
||||
def __call__(self, buf: 'Buf') -> None:
|
||||
def __call__(self, buf: Buf) -> None:
|
||||
buf[self.idx] = self.s
|
||||
|
||||
|
||||
@@ -45,29 +44,29 @@ class InsModification(NamedTuple):
|
||||
idx: int
|
||||
s: str
|
||||
|
||||
def __call__(self, buf: 'Buf') -> None:
|
||||
def __call__(self, buf: Buf) -> None:
|
||||
buf.insert(self.idx, self.s)
|
||||
|
||||
|
||||
class DelModification(NamedTuple):
|
||||
idx: int
|
||||
|
||||
def __call__(self, buf: 'Buf') -> None:
|
||||
def __call__(self, buf: Buf) -> None:
|
||||
del buf[self.idx]
|
||||
|
||||
|
||||
class Buf:
|
||||
def __init__(self, lines: List[str], tab_size: int = 4) -> None:
|
||||
def __init__(self, lines: list[str], tab_size: int = 4) -> None:
|
||||
self._lines = lines
|
||||
self.expandtabs = True
|
||||
self.tab_size = tab_size
|
||||
self.file_y = self.y = self._x = self._x_hint = 0
|
||||
|
||||
self._set_callbacks: List[SetCallback] = [self._set_cb]
|
||||
self._del_callbacks: List[DelCallback] = [self._del_cb]
|
||||
self._ins_callbacks: List[InsCallback] = [self._ins_cb]
|
||||
self._set_callbacks: list[SetCallback] = [self._set_cb]
|
||||
self._del_callbacks: list[DelCallback] = [self._del_cb]
|
||||
self._ins_callbacks: list[InsCallback] = [self._ins_cb]
|
||||
|
||||
self._positions: List[Optional[Tuple[int, ...]]] = []
|
||||
self._positions: list[tuple[int, ...] | None] = []
|
||||
|
||||
# read only interface
|
||||
|
||||
@@ -163,16 +162,16 @@ class Buf:
|
||||
self._ins_callbacks.remove(cb)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def record(self) -> Generator[List[Modification], None, None]:
|
||||
modifications: List[Modification] = []
|
||||
def record(self) -> Generator[list[Modification], None, None]:
|
||||
modifications: list[Modification] = []
|
||||
|
||||
def set_cb(buf: 'Buf', idx: int, victim: str) -> None:
|
||||
def set_cb(buf: Buf, idx: int, victim: str) -> None:
|
||||
modifications.append(SetModification(idx, victim))
|
||||
|
||||
def del_cb(buf: 'Buf', idx: int, victim: str) -> None:
|
||||
def del_cb(buf: Buf, idx: int, victim: str) -> None:
|
||||
modifications.append(InsModification(idx, victim))
|
||||
|
||||
def ins_cb(buf: 'Buf', idx: int) -> None:
|
||||
def ins_cb(buf: Buf, idx: int) -> None:
|
||||
modifications.append(DelModification(idx))
|
||||
|
||||
self.add_set_callback(set_cb)
|
||||
@@ -185,7 +184,7 @@ class Buf:
|
||||
self.remove_del_callback(del_cb)
|
||||
self.remove_set_callback(set_cb)
|
||||
|
||||
def apply(self, modifications: List[Modification]) -> List[Modification]:
|
||||
def apply(self, modifications: list[Modification]) -> list[Modification]:
|
||||
with self.record() as ret_modifications:
|
||||
for modification in reversed(modifications):
|
||||
modification(self)
|
||||
@@ -209,19 +208,19 @@ class Buf:
|
||||
def _extend_positions(self, idx: int) -> None:
|
||||
self._positions.extend([None] * (1 + idx - len(self._positions)))
|
||||
|
||||
def _set_cb(self, buf: 'Buf', idx: int, victim: str) -> None:
|
||||
def _set_cb(self, buf: Buf, idx: int, victim: str) -> None:
|
||||
self._extend_positions(idx)
|
||||
self._positions[idx] = None
|
||||
|
||||
def _del_cb(self, buf: 'Buf', idx: int, victim: str) -> None:
|
||||
def _del_cb(self, buf: Buf, idx: int, victim: str) -> None:
|
||||
self._extend_positions(idx)
|
||||
del self._positions[idx]
|
||||
|
||||
def _ins_cb(self, buf: 'Buf', idx: int) -> None:
|
||||
def _ins_cb(self, buf: Buf, idx: int) -> None:
|
||||
self._extend_positions(idx)
|
||||
self._positions.insert(idx, None)
|
||||
|
||||
def line_positions(self, idx: int) -> Tuple[int, ...]:
|
||||
def line_positions(self, idx: int) -> tuple[int, ...]:
|
||||
self._extend_positions(idx)
|
||||
value = self._positions[idx]
|
||||
if value is None:
|
||||
@@ -236,7 +235,7 @@ class Buf:
|
||||
def _cursor_x(self) -> int:
|
||||
return self.line_positions(self.y)[self.x]
|
||||
|
||||
def cursor_position(self, margin: Margin) -> Tuple[int, int]:
|
||||
def cursor_position(self, margin: Margin) -> tuple[int, int]:
|
||||
y = self.y - self.file_y + margin.header
|
||||
x = self._cursor_x - self.line_x(margin)
|
||||
return y, x
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8): # pragma: no cover (>=py38)
|
||||
@@ -5,8 +7,6 @@ if sys.version_info >= (3, 8): # pragma: no cover (>=py38)
|
||||
else: # pragma: no cover (<py38)
|
||||
from typing import Callable
|
||||
from typing import Generic
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
|
||||
TSelf = TypeVar('TSelf')
|
||||
@@ -18,8 +18,8 @@ else: # pragma: no cover (<py38)
|
||||
|
||||
def __get__(
|
||||
self,
|
||||
instance: Optional[TSelf],
|
||||
owner: Optional[Type[TSelf]] = None,
|
||||
instance: TSelf | None,
|
||||
owner: type[TSelf] | None = None,
|
||||
) -> TRet:
|
||||
assert instance is not None
|
||||
ret = instance.__dict__[self._func.__name__] = self._func(instance)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
# TODO: find a standard which defines these
|
||||
@@ -11,7 +13,7 @@ class Color(NamedTuple):
|
||||
b: int
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s: str) -> 'Color':
|
||||
def parse(cls, s: str) -> Color:
|
||||
if s.startswith('#') and len(s) >= 7:
|
||||
return cls(r=int(s[1:3], 16), g=int(s[3:5], 16), b=int(s[5:7], 16))
|
||||
elif s.startswith('#'):
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi._types import Protocol
|
||||
from babi.color import Color
|
||||
@@ -19,19 +18,19 @@ class KD(Protocol):
|
||||
@property
|
||||
def n(self) -> int: ...
|
||||
@property
|
||||
def left(self) -> Optional['KD']: ...
|
||||
def left(self) -> KD | None: ...
|
||||
@property
|
||||
def right(self) -> Optional['KD']: ...
|
||||
def right(self) -> KD | None: ...
|
||||
|
||||
|
||||
class _KD(NamedTuple):
|
||||
color: Color
|
||||
n: int
|
||||
left: Optional[KD]
|
||||
right: Optional[KD]
|
||||
left: KD | None
|
||||
right: KD | None
|
||||
|
||||
|
||||
def _build(colors: List[Tuple[Color, int]], depth: int = 0) -> Optional[KD]:
|
||||
def _build(colors: list[tuple[Color, int]], depth: int = 0) -> KD | None:
|
||||
if not colors:
|
||||
return None
|
||||
|
||||
@@ -46,11 +45,11 @@ def _build(colors: List[Tuple[Color, int]], depth: int = 0) -> Optional[KD]:
|
||||
)
|
||||
|
||||
|
||||
def nearest(color: Color, colors: Optional[KD]) -> int:
|
||||
def nearest(color: Color, colors: KD | None) -> int:
|
||||
best = 0
|
||||
dist = 2 ** 32
|
||||
|
||||
def _search(kd: Optional[KD], *, depth: int) -> None:
|
||||
def _search(kd: KD | None, *, depth: int) -> None:
|
||||
nonlocal best
|
||||
nonlocal dist
|
||||
|
||||
@@ -77,7 +76,7 @@ def nearest(color: Color, colors: Optional[KD]) -> int:
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def make_256() -> Optional[KD]:
|
||||
def make_256() -> KD | None:
|
||||
vals = (0, 95, 135, 175, 215, 255)
|
||||
colors = [
|
||||
(Color(r, g, b), i)
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
from typing import Dict
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi import color_kd
|
||||
from babi.color import Color
|
||||
|
||||
|
||||
def _color_to_curses(color: Color) -> Tuple[int, int, int]:
|
||||
def _color_to_curses(color: Color) -> tuple[int, int, int]:
|
||||
factor = 1000 / 255
|
||||
return int(color.r * factor), int(color.g * factor), int(color.b * factor)
|
||||
|
||||
|
||||
class ColorManager(NamedTuple):
|
||||
colors: Dict[Color, int]
|
||||
raw_pairs: Dict[Tuple[int, int], int]
|
||||
colors: dict[Color, int]
|
||||
raw_pairs: dict[tuple[int, int], int]
|
||||
|
||||
def init_color(self, color: Color) -> None:
|
||||
if curses.can_change_color():
|
||||
@@ -27,7 +26,7 @@ class ColorManager(NamedTuple):
|
||||
else:
|
||||
self.colors[color] = -1
|
||||
|
||||
def color_pair(self, fg: Optional[Color], bg: Optional[Color]) -> int:
|
||||
def color_pair(self, fg: Color | None, bg: Color | None) -> int:
|
||||
fg_i = self.colors[fg] if fg is not None else -1
|
||||
bg_i = self.colors[bg] if bg is not None else -1
|
||||
return self.raw_color_pair(fg_i, bg_i)
|
||||
@@ -46,5 +45,5 @@ class ColorManager(NamedTuple):
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def make(cls) -> 'ColorManager':
|
||||
def make(cls) -> ColorManager:
|
||||
return cls({}, {})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Mapping
|
||||
|
||||
58
babi/file.py
58
babi/file.py
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import curses
|
||||
@@ -12,15 +14,11 @@ from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Generator
|
||||
from typing import IO
|
||||
from typing import List
|
||||
from typing import Match
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from babi.buf import Buf
|
||||
from babi.buf import Modification
|
||||
@@ -42,7 +40,7 @@ TCallable = TypeVar('TCallable', bound=Callable[..., Any])
|
||||
WS_RE = re.compile(r'^\s*')
|
||||
|
||||
|
||||
def get_lines(sio: IO[str]) -> Tuple[List[str], str, bool, str]:
|
||||
def get_lines(sio: IO[str]) -> tuple[list[str], str, bool, str]:
|
||||
sha256 = hashlib.sha256()
|
||||
lines = []
|
||||
newlines = collections.Counter({'\n': 0}) # default to `\n`
|
||||
@@ -64,7 +62,7 @@ def get_lines(sio: IO[str]) -> Tuple[List[str], str, bool, str]:
|
||||
|
||||
class Action:
|
||||
def __init__(
|
||||
self, *, name: str, modifications: List[Modification],
|
||||
self, *, name: str, modifications: list[Modification],
|
||||
start_x: int, start_y: int, start_modified: bool,
|
||||
end_x: int, end_y: int, end_modified: bool,
|
||||
final: bool,
|
||||
@@ -79,7 +77,7 @@ class Action:
|
||||
self.end_modified = end_modified
|
||||
self.final = final
|
||||
|
||||
def apply(self, file: 'File') -> 'Action':
|
||||
def apply(self, file: File) -> Action:
|
||||
action = Action(
|
||||
name=self.name, modifications=file.buf.apply(self.modifications),
|
||||
start_x=self.end_x, start_y=self.end_y,
|
||||
@@ -98,7 +96,7 @@ class Action:
|
||||
|
||||
def action(func: TCallable) -> TCallable:
|
||||
@functools.wraps(func)
|
||||
def action_inner(self: 'File', *args: Any, **kwargs: Any) -> Any:
|
||||
def action_inner(self: File, *args: Any, **kwargs: Any) -> Any:
|
||||
self.finalize_previous_action()
|
||||
return func(self, *args, **kwargs)
|
||||
return cast(TCallable, action_inner)
|
||||
@@ -111,7 +109,7 @@ def edit_action(
|
||||
) -> Callable[[TCallable], TCallable]:
|
||||
def edit_action_decorator(func: TCallable) -> TCallable:
|
||||
@functools.wraps(func)
|
||||
def edit_action_inner(self: 'File', *args: Any, **kwargs: Any) -> Any:
|
||||
def edit_action_inner(self: File, *args: Any, **kwargs: Any) -> Any:
|
||||
with self.edit_action_context(name, final=final):
|
||||
return func(self, *args, **kwargs)
|
||||
return cast(TCallable, edit_action_inner)
|
||||
@@ -120,7 +118,7 @@ def edit_action(
|
||||
|
||||
def keep_selection(func: TCallable) -> TCallable:
|
||||
@functools.wraps(func)
|
||||
def keep_selection_inner(self: 'File', *args: Any, **kwargs: Any) -> Any:
|
||||
def keep_selection_inner(self: File, *args: Any, **kwargs: Any) -> Any:
|
||||
with self.select():
|
||||
return func(self, *args, **kwargs)
|
||||
return cast(TCallable, keep_selection_inner)
|
||||
@@ -128,7 +126,7 @@ def keep_selection(func: TCallable) -> TCallable:
|
||||
|
||||
def clear_selection(func: TCallable) -> TCallable:
|
||||
@functools.wraps(func)
|
||||
def clear_selection_inner(self: 'File', *args: Any, **kwargs: Any) -> Any:
|
||||
def clear_selection_inner(self: File, *args: Any, **kwargs: Any) -> Any:
|
||||
ret = func(self, *args, **kwargs)
|
||||
self.selection.clear()
|
||||
return ret
|
||||
@@ -143,7 +141,7 @@ class Found(NamedTuple):
|
||||
class _SearchIter:
|
||||
def __init__(
|
||||
self,
|
||||
file: 'File',
|
||||
file: File,
|
||||
reg: Pattern[str],
|
||||
*,
|
||||
offset: int,
|
||||
@@ -155,7 +153,7 @@ class _SearchIter:
|
||||
self._start_x = file.buf.x + offset
|
||||
self._start_y = file.buf.y
|
||||
|
||||
def __iter__(self) -> '_SearchIter':
|
||||
def __iter__(self) -> _SearchIter:
|
||||
return self
|
||||
|
||||
def _stop_if_past_original(self, y: int, match: Match[str]) -> Found:
|
||||
@@ -168,7 +166,7 @@ class _SearchIter:
|
||||
raise StopIteration()
|
||||
return Found(y, match)
|
||||
|
||||
def __next__(self) -> Tuple[int, Match[str]]:
|
||||
def __next__(self) -> tuple[int, Match[str]]:
|
||||
x = self.file.buf.x + self.offset
|
||||
y = self.file.buf.y
|
||||
|
||||
@@ -200,25 +198,25 @@ class _SearchIter:
|
||||
class File:
|
||||
def __init__(
|
||||
self,
|
||||
filename: Optional[str],
|
||||
filename: str | None,
|
||||
initial_line: int,
|
||||
color_manager: ColorManager,
|
||||
hl_factories: Tuple[HLFactory, ...],
|
||||
hl_factories: tuple[HLFactory, ...],
|
||||
) -> None:
|
||||
self.filename = filename
|
||||
self.initial_line = initial_line
|
||||
self.modified = False
|
||||
self.buf = Buf([])
|
||||
self.nl = '\n'
|
||||
self.sha256: Optional[str] = None
|
||||
self.sha256: str | None = None
|
||||
self._in_edit_action = False
|
||||
self.undo_stack: List[Action] = []
|
||||
self.redo_stack: List[Action] = []
|
||||
self.undo_stack: list[Action] = []
|
||||
self.redo_stack: list[Action] = []
|
||||
self._hl_factories = hl_factories
|
||||
self._trailing_whitespace = TrailingWhitespace(color_manager)
|
||||
self._replace_hl = Replace()
|
||||
self.selection = Selection()
|
||||
self._file_hls: Tuple[FileHL, ...] = ()
|
||||
self._file_hls: tuple[FileHL, ...] = ()
|
||||
|
||||
def ensure_loaded(
|
||||
self,
|
||||
@@ -395,14 +393,14 @@ class File:
|
||||
@clear_selection
|
||||
def replace(
|
||||
self,
|
||||
screen: 'Screen',
|
||||
screen: Screen,
|
||||
reg: Pattern[str],
|
||||
replace: str,
|
||||
) -> None:
|
||||
self.finalize_previous_action()
|
||||
|
||||
count = 0
|
||||
res: Union[str, PromptResult] = ''
|
||||
res: str | PromptResult = ''
|
||||
search = _SearchIter(self, reg, offset=0)
|
||||
for line_y, match in search:
|
||||
end = match.end()
|
||||
@@ -597,7 +595,7 @@ class File:
|
||||
|
||||
@edit_action('cut selection', final=True)
|
||||
@clear_selection
|
||||
def cut_selection(self, margin: Margin) -> Tuple[str, ...]:
|
||||
def cut_selection(self, margin: Margin) -> tuple[str, ...]:
|
||||
ret = []
|
||||
(s_y, s_x), (e_y, e_x) = self.selection.get()
|
||||
if s_y == e_y:
|
||||
@@ -617,7 +615,7 @@ class File:
|
||||
self.buf.scroll_screen_if_needed(margin)
|
||||
return tuple(ret)
|
||||
|
||||
def cut(self, cut_buffer: Tuple[str, ...]) -> Tuple[str, ...]:
|
||||
def cut(self, cut_buffer: tuple[str, ...]) -> tuple[str, ...]:
|
||||
# only continue a cut if the last action is a non-final cut
|
||||
if not self._continue_last_action('cut'):
|
||||
cut_buffer = ()
|
||||
@@ -630,7 +628,7 @@ class File:
|
||||
self.buf.x = 0
|
||||
return cut_buffer + (victim,)
|
||||
|
||||
def _uncut(self, cut_buffer: Tuple[str, ...], margin: Margin) -> None:
|
||||
def _uncut(self, cut_buffer: tuple[str, ...], margin: Margin) -> None:
|
||||
for cut_line in cut_buffer:
|
||||
line = self.buf[self.buf.y]
|
||||
before, after = line[:self.buf.x], line[self.buf.x:]
|
||||
@@ -641,14 +639,14 @@ class File:
|
||||
|
||||
@edit_action('uncut', final=True)
|
||||
@clear_selection
|
||||
def uncut(self, cut_buffer: Tuple[str, ...], margin: Margin) -> None:
|
||||
def uncut(self, cut_buffer: tuple[str, ...], margin: Margin) -> None:
|
||||
self._uncut(cut_buffer, margin)
|
||||
|
||||
@edit_action('uncut selection', final=True)
|
||||
@clear_selection
|
||||
def uncut_selection(
|
||||
self,
|
||||
cut_buffer: Tuple[str, ...], margin: Margin,
|
||||
cut_buffer: tuple[str, ...], margin: Margin,
|
||||
) -> None:
|
||||
self._uncut(cut_buffer, margin)
|
||||
self.buf.up(margin)
|
||||
@@ -666,7 +664,7 @@ class File:
|
||||
self.buf.x = 0
|
||||
self.buf.scroll_screen_if_needed(margin)
|
||||
|
||||
def _selection_lines(self) -> Tuple[int, int]:
|
||||
def _selection_lines(self) -> tuple[int, int]:
|
||||
(s_y, _), (e_y, _) = self.selection.get()
|
||||
e_y = min(e_y + 1, len(self.buf) - 1)
|
||||
if self.buf[e_y - 1] == '':
|
||||
@@ -852,12 +850,12 @@ class File:
|
||||
|
||||
def move_cursor(
|
||||
self,
|
||||
stdscr: 'curses._CursesWindow',
|
||||
stdscr: curses._CursesWindow,
|
||||
margin: Margin,
|
||||
) -> None:
|
||||
stdscr.move(*self.buf.cursor_position(margin))
|
||||
|
||||
def draw(self, stdscr: 'curses._CursesWindow', margin: Margin) -> None:
|
||||
def draw(self, stdscr: curses._CursesWindow, margin: Margin) -> None:
|
||||
to_display = min(self.buf.displayable_count, margin.body_lines)
|
||||
|
||||
for file_hl in self._file_hls:
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
import os.path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import FrozenSet
|
||||
from typing import List
|
||||
from typing import Match
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TypeVar
|
||||
|
||||
@@ -34,7 +32,7 @@ def uniquely_constructed(t: T) -> T:
|
||||
return t
|
||||
|
||||
|
||||
def _split_name(s: Optional[str]) -> Tuple[str, ...]:
|
||||
def _split_name(s: str | None) -> tuple[str, ...]:
|
||||
if s is None:
|
||||
return ()
|
||||
else:
|
||||
@@ -44,17 +42,17 @@ def _split_name(s: Optional[str]) -> Tuple[str, ...]:
|
||||
class _Rule(Protocol):
|
||||
"""hax for recursive types python/mypy#731"""
|
||||
@property
|
||||
def name(self) -> Tuple[str, ...]: ...
|
||||
def name(self) -> tuple[str, ...]: ...
|
||||
@property
|
||||
def match(self) -> Optional[str]: ...
|
||||
def match(self) -> str | None: ...
|
||||
@property
|
||||
def begin(self) -> Optional[str]: ...
|
||||
def begin(self) -> str | None: ...
|
||||
@property
|
||||
def end(self) -> Optional[str]: ...
|
||||
def end(self) -> str | None: ...
|
||||
@property
|
||||
def while_(self) -> Optional[str]: ...
|
||||
def while_(self) -> str | None: ...
|
||||
@property
|
||||
def content_name(self) -> Tuple[str, ...]: ...
|
||||
def content_name(self) -> tuple[str, ...]: ...
|
||||
@property
|
||||
def captures(self) -> Captures: ...
|
||||
@property
|
||||
@@ -64,39 +62,39 @@ class _Rule(Protocol):
|
||||
@property
|
||||
def while_captures(self) -> Captures: ...
|
||||
@property
|
||||
def include(self) -> Optional[str]: ...
|
||||
def include(self) -> str | None: ...
|
||||
@property
|
||||
def patterns(self) -> 'Tuple[_Rule, ...]': ...
|
||||
def patterns(self) -> tuple[_Rule, ...]: ...
|
||||
@property
|
||||
def repository(self) -> 'FChainMap[str, _Rule]': ...
|
||||
def repository(self) -> FChainMap[str, _Rule]: ...
|
||||
|
||||
|
||||
@uniquely_constructed
|
||||
class Rule(NamedTuple):
|
||||
name: Tuple[str, ...]
|
||||
match: Optional[str]
|
||||
begin: Optional[str]
|
||||
end: Optional[str]
|
||||
while_: Optional[str]
|
||||
content_name: Tuple[str, ...]
|
||||
name: tuple[str, ...]
|
||||
match: str | None
|
||||
begin: str | None
|
||||
end: str | None
|
||||
while_: str | None
|
||||
content_name: tuple[str, ...]
|
||||
captures: Captures
|
||||
begin_captures: Captures
|
||||
end_captures: Captures
|
||||
while_captures: Captures
|
||||
include: Optional[str]
|
||||
patterns: Tuple[_Rule, ...]
|
||||
include: str | None
|
||||
patterns: tuple[_Rule, ...]
|
||||
repository: FChainMap[str, _Rule]
|
||||
|
||||
@classmethod
|
||||
def make(
|
||||
cls,
|
||||
dct: Dict[str, Any],
|
||||
dct: dict[str, Any],
|
||||
parent_repository: FChainMap[str, _Rule],
|
||||
) -> _Rule:
|
||||
if 'repository' in dct:
|
||||
# this looks odd, but it's so we can have a self-referential
|
||||
# immutable-after-construction chain map
|
||||
repository_dct: Dict[str, _Rule] = {}
|
||||
repository_dct: dict[str, _Rule] = {}
|
||||
repository = FChainMap(parent_repository, repository_dct)
|
||||
for k, sub_dct in dct['repository'].items():
|
||||
repository_dct[k] = Rule.make(sub_dct, repository)
|
||||
@@ -183,15 +181,15 @@ class Rule(NamedTuple):
|
||||
class Grammar(NamedTuple):
|
||||
scope_name: str
|
||||
repository: FChainMap[str, _Rule]
|
||||
patterns: Tuple[_Rule, ...]
|
||||
patterns: tuple[_Rule, ...]
|
||||
|
||||
@classmethod
|
||||
def make(cls, data: Dict[str, Any]) -> 'Grammar':
|
||||
def make(cls, data: dict[str, Any]) -> Grammar:
|
||||
scope_name = data['scopeName']
|
||||
if 'repository' in data:
|
||||
# this looks odd, but it's so we can have a self-referential
|
||||
# immutable-after-construction chain map
|
||||
repository_dct: Dict[str, _Rule] = {}
|
||||
repository_dct: dict[str, _Rule] = {}
|
||||
repository = FChainMap(repository_dct)
|
||||
for k, dct in data['repository'].items():
|
||||
repository_dct[k] = Rule.make(dct, repository)
|
||||
@@ -212,54 +210,54 @@ class Region(NamedTuple):
|
||||
|
||||
|
||||
class State(NamedTuple):
|
||||
entries: Tuple['Entry', ...]
|
||||
while_stack: Tuple[Tuple['WhileRule', int], ...]
|
||||
entries: tuple[Entry, ...]
|
||||
while_stack: tuple[tuple[WhileRule, int], ...]
|
||||
|
||||
@classmethod
|
||||
def root(cls, entry: 'Entry') -> 'State':
|
||||
def root(cls, entry: Entry) -> State:
|
||||
return cls((entry,), ())
|
||||
|
||||
@property
|
||||
def cur(self) -> 'Entry':
|
||||
def cur(self) -> Entry:
|
||||
return self.entries[-1]
|
||||
|
||||
def push(self, entry: 'Entry') -> 'State':
|
||||
def push(self, entry: Entry) -> State:
|
||||
return self._replace(entries=(*self.entries, entry))
|
||||
|
||||
def pop(self) -> 'State':
|
||||
def pop(self) -> State:
|
||||
return self._replace(entries=self.entries[:-1])
|
||||
|
||||
def push_while(self, rule: 'WhileRule', entry: 'Entry') -> 'State':
|
||||
def push_while(self, rule: WhileRule, entry: Entry) -> State:
|
||||
entries = (*self.entries, entry)
|
||||
while_stack = (*self.while_stack, (rule, len(entries)))
|
||||
return self._replace(entries=entries, while_stack=while_stack)
|
||||
|
||||
def pop_while(self) -> 'State':
|
||||
def pop_while(self) -> State:
|
||||
entries, while_stack = self.entries[:-1], self.while_stack[:-1]
|
||||
return self._replace(entries=entries, while_stack=while_stack)
|
||||
|
||||
|
||||
class CompiledRule(Protocol):
|
||||
@property
|
||||
def name(self) -> Tuple[str, ...]: ...
|
||||
def name(self) -> tuple[str, ...]: ...
|
||||
|
||||
def start(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
match: Match[str],
|
||||
state: State,
|
||||
) -> Tuple[State, bool, Regions]:
|
||||
) -> tuple[State, bool, Regions]:
|
||||
...
|
||||
|
||||
def search(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
...
|
||||
|
||||
|
||||
@@ -267,19 +265,19 @@ class CompiledRegsetRule(CompiledRule, Protocol):
|
||||
@property
|
||||
def regset(self) -> _RegSet: ...
|
||||
@property
|
||||
def u_rules(self) -> Tuple[_Rule, ...]: ...
|
||||
def u_rules(self) -> tuple[_Rule, ...]: ...
|
||||
|
||||
|
||||
class Entry(NamedTuple):
|
||||
scope: Tuple[str, ...]
|
||||
scope: tuple[str, ...]
|
||||
rule: CompiledRule
|
||||
start: Tuple[str, int]
|
||||
start: tuple[str, int]
|
||||
reg: _Reg = ERR_REG
|
||||
boundary: bool = False
|
||||
|
||||
|
||||
def _inner_capture_parse(
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
start: int,
|
||||
s: str,
|
||||
scope: Scope,
|
||||
@@ -293,12 +291,12 @@ def _inner_capture_parse(
|
||||
|
||||
|
||||
def _captures(
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
scope: Scope,
|
||||
match: Match[str],
|
||||
captures: Captures,
|
||||
) -> Regions:
|
||||
ret: List[Region] = []
|
||||
ret: list[Region] = []
|
||||
pos, pos_end = match.span()
|
||||
for i, u_rule in captures:
|
||||
try:
|
||||
@@ -347,12 +345,12 @@ def _captures(
|
||||
|
||||
def _do_regset(
|
||||
idx: int,
|
||||
match: Optional[Match[str]],
|
||||
match: Match[str] | None,
|
||||
rule: CompiledRegsetRule,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
pos: int,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
if match is None:
|
||||
return None
|
||||
|
||||
@@ -369,73 +367,73 @@ def _do_regset(
|
||||
|
||||
@uniquely_constructed
|
||||
class PatternRule(NamedTuple):
|
||||
name: Tuple[str, ...]
|
||||
name: tuple[str, ...]
|
||||
regset: _RegSet
|
||||
u_rules: Tuple[_Rule, ...]
|
||||
u_rules: tuple[_Rule, ...]
|
||||
|
||||
def start(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
match: Match[str],
|
||||
state: State,
|
||||
) -> Tuple[State, bool, Regions]:
|
||||
) -> tuple[State, bool, Regions]:
|
||||
raise AssertionError(f'unreachable {self}')
|
||||
|
||||
def search(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
idx, match = self.regset.search(line, pos, first_line, boundary)
|
||||
return _do_regset(idx, match, self, compiler, state, pos)
|
||||
|
||||
|
||||
@uniquely_constructed
|
||||
class MatchRule(NamedTuple):
|
||||
name: Tuple[str, ...]
|
||||
name: tuple[str, ...]
|
||||
captures: Captures
|
||||
|
||||
def start(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
match: Match[str],
|
||||
state: State,
|
||||
) -> Tuple[State, bool, Regions]:
|
||||
) -> tuple[State, bool, Regions]:
|
||||
scope = state.cur.scope + self.name
|
||||
return state, False, _captures(compiler, scope, match, self.captures)
|
||||
|
||||
def search(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
raise AssertionError(f'unreachable {self}')
|
||||
|
||||
|
||||
@uniquely_constructed
|
||||
class EndRule(NamedTuple):
|
||||
name: Tuple[str, ...]
|
||||
content_name: Tuple[str, ...]
|
||||
name: tuple[str, ...]
|
||||
content_name: tuple[str, ...]
|
||||
begin_captures: Captures
|
||||
end_captures: Captures
|
||||
end: str
|
||||
regset: _RegSet
|
||||
u_rules: Tuple[_Rule, ...]
|
||||
u_rules: tuple[_Rule, ...]
|
||||
|
||||
def start(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
match: Match[str],
|
||||
state: State,
|
||||
) -> Tuple[State, bool, Regions]:
|
||||
) -> tuple[State, bool, Regions]:
|
||||
scope = state.cur.scope + self.name
|
||||
next_scope = scope + self.content_name
|
||||
|
||||
@@ -448,11 +446,11 @@ class EndRule(NamedTuple):
|
||||
|
||||
def _end_ret(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
pos: int,
|
||||
m: Match[str],
|
||||
) -> Tuple[State, int, bool, Regions]:
|
||||
) -> tuple[State, int, bool, Regions]:
|
||||
ret = []
|
||||
if m.start() > pos:
|
||||
ret.append(Region(pos, m.start(), state.cur.scope))
|
||||
@@ -470,13 +468,13 @@ class EndRule(NamedTuple):
|
||||
|
||||
def search(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
end_match = state.cur.reg.search(line, pos, first_line, boundary)
|
||||
if end_match is not None and end_match.start() == pos:
|
||||
return self._end_ret(compiler, state, pos, end_match)
|
||||
@@ -493,20 +491,20 @@ class EndRule(NamedTuple):
|
||||
|
||||
@uniquely_constructed
|
||||
class WhileRule(NamedTuple):
|
||||
name: Tuple[str, ...]
|
||||
content_name: Tuple[str, ...]
|
||||
name: tuple[str, ...]
|
||||
content_name: tuple[str, ...]
|
||||
begin_captures: Captures
|
||||
while_captures: Captures
|
||||
while_: str
|
||||
regset: _RegSet
|
||||
u_rules: Tuple[_Rule, ...]
|
||||
u_rules: tuple[_Rule, ...]
|
||||
|
||||
def start(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
match: Match[str],
|
||||
state: State,
|
||||
) -> Tuple[State, bool, Regions]:
|
||||
) -> tuple[State, bool, Regions]:
|
||||
scope = state.cur.scope + self.name
|
||||
next_scope = scope + self.content_name
|
||||
|
||||
@@ -520,13 +518,13 @@ class WhileRule(NamedTuple):
|
||||
|
||||
def continues(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[int, bool, Regions]]:
|
||||
) -> tuple[int, bool, Regions] | None:
|
||||
match = state.cur.reg.match(line, pos, first_line, boundary)
|
||||
if match is None:
|
||||
return None
|
||||
@@ -536,23 +534,23 @@ class WhileRule(NamedTuple):
|
||||
|
||||
def search(
|
||||
self,
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Tuple[State, int, bool, Regions]]:
|
||||
) -> tuple[State, int, bool, Regions] | None:
|
||||
idx, match = self.regset.search(line, pos, first_line, boundary)
|
||||
return _do_regset(idx, match, self, compiler, state, pos)
|
||||
|
||||
|
||||
class Compiler:
|
||||
def __init__(self, grammar: Grammar, grammars: 'Grammars') -> None:
|
||||
def __init__(self, grammar: Grammar, grammars: Grammars) -> None:
|
||||
self._root_scope = grammar.scope_name
|
||||
self._grammars = grammars
|
||||
self._rule_to_grammar: Dict[_Rule, Grammar] = {}
|
||||
self._c_rules: Dict[_Rule, CompiledRule] = {}
|
||||
self._rule_to_grammar: dict[_Rule, Grammar] = {}
|
||||
self._c_rules: dict[_Rule, CompiledRule] = {}
|
||||
root = self._compile_root(grammar)
|
||||
self.root_state = State.root(Entry(root.name, root, ('', 0)))
|
||||
|
||||
@@ -566,7 +564,7 @@ class Compiler:
|
||||
grammar: Grammar,
|
||||
repository: FChainMap[str, _Rule],
|
||||
s: str,
|
||||
) -> Tuple[List[str], Tuple[_Rule, ...]]:
|
||||
) -> tuple[list[str], tuple[_Rule, ...]]:
|
||||
if s == '$self':
|
||||
return self._patterns(grammar, grammar.patterns)
|
||||
elif s == '$base':
|
||||
@@ -586,10 +584,10 @@ class Compiler:
|
||||
def _patterns(
|
||||
self,
|
||||
grammar: Grammar,
|
||||
rules: Tuple[_Rule, ...],
|
||||
) -> Tuple[List[str], Tuple[_Rule, ...]]:
|
||||
rules: tuple[_Rule, ...],
|
||||
) -> tuple[list[str], tuple[_Rule, ...]]:
|
||||
ret_regs = []
|
||||
ret_rules: List[_Rule] = []
|
||||
ret_rules: list[_Rule] = []
|
||||
for rule in rules:
|
||||
if rule.include is not None:
|
||||
tmp_regs, tmp_rules = self._include(
|
||||
@@ -676,12 +674,12 @@ class Grammars:
|
||||
|
||||
unknown_grammar = {'scopeName': 'source.unknown', 'patterns': []}
|
||||
self._raw = {'source.unknown': unknown_grammar}
|
||||
self._file_types: List[Tuple[FrozenSet[str], str]] = []
|
||||
self._first_line: List[Tuple[_Reg, str]] = []
|
||||
self._parsed: Dict[str, Grammar] = {}
|
||||
self._compiled: Dict[str, Compiler] = {}
|
||||
self._file_types: list[tuple[frozenset[str], str]] = []
|
||||
self._first_line: list[tuple[_Reg, str]] = []
|
||||
self._parsed: dict[str, Grammar] = {}
|
||||
self._compiled: dict[str, Compiler] = {}
|
||||
|
||||
def _raw_for_scope(self, scope: str) -> Dict[str, Any]:
|
||||
def _raw_for_scope(self, scope: str) -> dict[str, Any]:
|
||||
try:
|
||||
return self._raw[scope]
|
||||
except KeyError:
|
||||
@@ -747,12 +745,12 @@ class Grammars:
|
||||
|
||||
|
||||
def highlight_line(
|
||||
compiler: 'Compiler',
|
||||
compiler: Compiler,
|
||||
state: State,
|
||||
line: str,
|
||||
first_line: bool,
|
||||
) -> Tuple[State, Regions]:
|
||||
ret: List[Region] = []
|
||||
) -> tuple[State, Regions]:
|
||||
ret: list[Region] = []
|
||||
pos = 0
|
||||
boundary = state.cur.boundary
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import os.path
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
|
||||
from babi.user_data import xdg_data
|
||||
|
||||
|
||||
class History:
|
||||
def __init__(self) -> None:
|
||||
self._orig_len: Dict[str, int] = collections.defaultdict(int)
|
||||
self.data: Dict[str, List[str]] = collections.defaultdict(list)
|
||||
self.prev: Dict[str, str] = {}
|
||||
self._orig_len: dict[str, int] = collections.defaultdict(int)
|
||||
self.data: dict[str, list[str]] = collections.defaultdict(list)
|
||||
self.prev: dict[str, str] = {}
|
||||
|
||||
@contextlib.contextmanager
|
||||
def save(self) -> Generator[None, None, None]:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import curses
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
|
||||
from babi.buf import Buf
|
||||
@@ -13,7 +14,7 @@ class Replace:
|
||||
include_edge = True
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.regions: Dict[int, HLs] = collections.defaultdict(tuple)
|
||||
self.regions: dict[int, HLs] = collections.defaultdict(tuple)
|
||||
|
||||
def highlight_until(self, lines: Buf, idx: int) -> None:
|
||||
"""our highlight regions are populated in other ways"""
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import curses
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi.buf import Buf
|
||||
from babi.hl.interface import HL
|
||||
@@ -13,9 +12,9 @@ class Selection:
|
||||
include_edge = True
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.regions: Dict[int, HLs] = collections.defaultdict(tuple)
|
||||
self.start: Optional[Tuple[int, int]] = None
|
||||
self.end: Optional[Tuple[int, int]] = None
|
||||
self.regions: dict[int, HLs] = collections.defaultdict(tuple)
|
||||
self.start: tuple[int, int] | None = None
|
||||
self.end: tuple[int, int] | None = None
|
||||
|
||||
def register_callbacks(self, buf: Buf) -> None:
|
||||
"""our highlight regions are populated in other ways"""
|
||||
@@ -39,7 +38,7 @@ class Selection:
|
||||
)
|
||||
self.regions[e_y] = (HL(x=0, end=e_x, attr=attr),)
|
||||
|
||||
def get(self) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
||||
def get(self) -> tuple[tuple[int, int], tuple[int, int]]:
|
||||
assert self.start is not None and self.end is not None
|
||||
if self.start < self.end:
|
||||
return self.start, self.end
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
import functools
|
||||
import math
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi.buf import Buf
|
||||
from babi.color_manager import ColorManager
|
||||
@@ -21,8 +20,6 @@ from babi.user_data import prefix_data
|
||||
from babi.user_data import xdg_config
|
||||
from babi.user_data import xdg_data
|
||||
|
||||
A_ITALIC = getattr(curses, 'A_ITALIC', 0x80000000) # new in py37
|
||||
|
||||
|
||||
class FileSyntax:
|
||||
include_edge = False
|
||||
@@ -37,12 +34,12 @@ class FileSyntax:
|
||||
self._theme = theme
|
||||
self._color_manager = color_manager
|
||||
|
||||
self.regions: List[HLs] = []
|
||||
self._states: List[State] = []
|
||||
self.regions: list[HLs] = []
|
||||
self._states: list[State] = []
|
||||
|
||||
# this will be assigned a functools.lru_cache per instance for
|
||||
# better hit rate and memory usage
|
||||
self._hl: Optional[Callable[[State, str, bool], Tuple[State, HLs]]]
|
||||
self._hl: Callable[[State, str, bool], tuple[State, HLs]] | None
|
||||
self._hl = None
|
||||
|
||||
def attr(self, style: Style) -> int:
|
||||
@@ -50,7 +47,7 @@ class FileSyntax:
|
||||
return (
|
||||
curses.color_pair(pair) |
|
||||
curses.A_BOLD * style.b |
|
||||
A_ITALIC * style.i |
|
||||
curses.A_ITALIC * style.i |
|
||||
curses.A_UNDERLINE * style.u
|
||||
)
|
||||
|
||||
@@ -59,7 +56,7 @@ class FileSyntax:
|
||||
state: State,
|
||||
line: str,
|
||||
first_line: bool,
|
||||
) -> Tuple[State, HLs]:
|
||||
) -> tuple[State, HLs]:
|
||||
new_state, regions = highlight_line(
|
||||
self._compiler, state, f'{line}\n', first_line=first_line,
|
||||
)
|
||||
@@ -68,7 +65,7 @@ class FileSyntax:
|
||||
new_end = regions[-1]._replace(end=regions[-1].end - 1)
|
||||
regions = regions[:-1] + (new_end,)
|
||||
|
||||
regs: List[HL] = []
|
||||
regs: list[HL] = []
|
||||
for r in regions:
|
||||
style = self._theme.select(r.scope)
|
||||
if style == self._theme.default:
|
||||
@@ -133,7 +130,7 @@ class Syntax(NamedTuple):
|
||||
compiler = self.grammars.blank_compiler()
|
||||
return FileSyntax(compiler, self.theme, self.color_manager)
|
||||
|
||||
def _init_screen(self, stdscr: 'curses._CursesWindow') -> None:
|
||||
def _init_screen(self, stdscr: curses._CursesWindow) -> None:
|
||||
default_fg, default_bg = self.theme.default.fg, self.theme.default.bg
|
||||
all_colors = {c for c in (default_fg, default_bg) if c is not None}
|
||||
todo = list(self.theme.rules.children.values())
|
||||
@@ -154,9 +151,9 @@ class Syntax(NamedTuple):
|
||||
@classmethod
|
||||
def from_screen(
|
||||
cls,
|
||||
stdscr: 'curses._CursesWindow',
|
||||
stdscr: curses._CursesWindow,
|
||||
color_manager: ColorManager,
|
||||
) -> 'Syntax':
|
||||
) -> Syntax:
|
||||
grammars = Grammars(prefix_data('grammar_v1'), xdg_data('grammar_v1'))
|
||||
theme = Theme.from_filename(xdg_config('theme.json'))
|
||||
ret = cls(grammars, theme, color_manager)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
from typing import List
|
||||
|
||||
from babi.buf import Buf
|
||||
from babi.color_manager import ColorManager
|
||||
@@ -13,7 +14,7 @@ class TrailingWhitespace:
|
||||
def __init__(self, color_manager: ColorManager) -> None:
|
||||
self._color_manager = color_manager
|
||||
|
||||
self.regions: List[HLs] = []
|
||||
self.regions: list[HLs] = []
|
||||
|
||||
def _trailing_ws(self, line: str) -> HLs:
|
||||
if not line:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
|
||||
from babi.cached_property import cached_property
|
||||
@@ -34,7 +36,7 @@ def scrolled_line(s: str, x: int, width: int) -> str:
|
||||
|
||||
class _CalcWidth:
|
||||
@cached_property
|
||||
def _window(self) -> 'curses._CursesWindow':
|
||||
def _window(self) -> curses._CursesWindow:
|
||||
return curses.newwin(1, 10)
|
||||
|
||||
def wcwidth(self, c: str) -> int:
|
||||
|
||||
19
babi/main.py
19
babi/main.py
@@ -1,13 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import curses
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
from babi.buf import Buf
|
||||
from babi.file import File
|
||||
@@ -44,9 +43,9 @@ def _edit(screen: Screen, stdin: str) -> EditResult:
|
||||
|
||||
|
||||
def c_main(
|
||||
stdscr: 'curses._CursesWindow',
|
||||
filenames: List[Optional[str]],
|
||||
positions: List[int],
|
||||
stdscr: curses._CursesWindow,
|
||||
filenames: list[str | None],
|
||||
positions: list[int],
|
||||
stdin: str,
|
||||
perf: Perf,
|
||||
) -> int:
|
||||
@@ -73,7 +72,7 @@ def c_main(
|
||||
return 0
|
||||
|
||||
|
||||
def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
|
||||
def _key_debug(stdscr: curses._CursesWindow, perf: Perf) -> int:
|
||||
screen = Screen(stdscr, ['<<key debug>>'], [0], perf)
|
||||
screen.file.buf = Buf([''])
|
||||
|
||||
@@ -91,11 +90,11 @@ def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]:
|
||||
def _filenames(filenames: list[str]) -> tuple[list[str | None], list[int]]:
|
||||
if not filenames:
|
||||
return [None], [0]
|
||||
|
||||
ret_filenames: List[Optional[str]] = []
|
||||
ret_filenames: list[str | None] = []
|
||||
ret_positions = []
|
||||
|
||||
filenames_iter = iter(filenames)
|
||||
@@ -122,7 +121,7 @@ def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]:
|
||||
return ret_filenames, ret_positions
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', metavar='filename', nargs='*')
|
||||
parser.add_argument('--perf-log')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
from typing import NamedTuple
|
||||
|
||||
@@ -31,5 +33,5 @@ class Margin(NamedTuple):
|
||||
return int(self.lines / 2 + .5)
|
||||
|
||||
@classmethod
|
||||
def from_current_screen(cls) -> 'Margin':
|
||||
def from_current_screen(cls) -> Margin:
|
||||
return cls(curses.LINES, curses.COLS)
|
||||
|
||||
15
babi/perf.py
15
babi/perf.py
@@ -1,18 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import cProfile
|
||||
import time
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Perf:
|
||||
def __init__(self) -> None:
|
||||
self._prof: Optional[cProfile.Profile] = None
|
||||
self._records: List[Tuple[str, float]] = []
|
||||
self._name: Optional[str] = None
|
||||
self._time: Optional[float] = None
|
||||
self._prof: cProfile.Profile | None = None
|
||||
self._records: list[tuple[str, float]] = []
|
||||
self._name: str | None = None
|
||||
self._time: float | None = None
|
||||
|
||||
def start(self, name: str) -> None:
|
||||
if self._prof:
|
||||
@@ -43,7 +42,7 @@ class Perf:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def perf_log(filename: Optional[str]) -> Generator[Perf, None, None]:
|
||||
def perf_log(filename: str | None) -> Generator[Perf, None, None]:
|
||||
perf = Perf()
|
||||
if filename is None:
|
||||
yield perf
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
import enum
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from babi.horizontal_scrolling import line_x
|
||||
from babi.horizontal_scrolling import scrolled_line
|
||||
@@ -16,7 +14,7 @@ PromptResult = enum.Enum('PromptResult', 'CANCELLED')
|
||||
|
||||
|
||||
class Prompt:
|
||||
def __init__(self, screen: 'Screen', prompt: str, lst: List[str]) -> None:
|
||||
def __init__(self, screen: Screen, prompt: str, lst: list[str]) -> None:
|
||||
self._screen = screen
|
||||
self._prompt = prompt
|
||||
self._lst = lst
|
||||
@@ -31,7 +29,7 @@ class Prompt:
|
||||
def _s(self, s: str) -> None:
|
||||
self._lst[self._y] = s
|
||||
|
||||
def _render_prompt(self, *, base: Optional[str] = None) -> None:
|
||||
def _render_prompt(self, *, base: str | None = None) -> None:
|
||||
base = base or self._prompt
|
||||
if not base or self._screen.margin.cols < 7:
|
||||
prompt_s = ''
|
||||
@@ -100,7 +98,7 @@ class Prompt:
|
||||
def _resize(self) -> None:
|
||||
self._screen.resize()
|
||||
|
||||
def _check_failed(self, idx: int, s: str) -> Tuple[bool, int]:
|
||||
def _check_failed(self, idx: int, s: str) -> tuple[bool, int]:
|
||||
failed = False
|
||||
for search_idx in range(idx, -1, -1):
|
||||
if s in self._lst[search_idx]:
|
||||
@@ -111,7 +109,7 @@ class Prompt:
|
||||
failed = True
|
||||
return failed, idx
|
||||
|
||||
def _reverse_search(self) -> Union[None, str, PromptResult]:
|
||||
def _reverse_search(self) -> None | str | PromptResult:
|
||||
reverse_s = ''
|
||||
idx = self._y
|
||||
while True:
|
||||
@@ -177,7 +175,7 @@ class Prompt:
|
||||
self._s = self._s[:self._x] + c + self._s[self._x:]
|
||||
self._x += len(c)
|
||||
|
||||
def run(self) -> Union[PromptResult, str]:
|
||||
def run(self) -> PromptResult | str:
|
||||
while True:
|
||||
self._render_prompt()
|
||||
|
||||
|
||||
10
babi/reg.py
10
babi/reg.py
@@ -1,8 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import re
|
||||
from typing import Match
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import onigurumacffi
|
||||
|
||||
@@ -42,7 +42,7 @@ class _Reg:
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Match[str]]:
|
||||
) -> Match[str] | None:
|
||||
return self._reg.search(line, pos, flags=_FLAGS[first_line, boundary])
|
||||
|
||||
def match(
|
||||
@@ -51,7 +51,7 @@ class _Reg:
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Optional[Match[str]]:
|
||||
) -> Match[str] | None:
|
||||
return self._reg.match(line, pos, flags=_FLAGS[first_line, boundary])
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class _RegSet:
|
||||
pos: int,
|
||||
first_line: bool,
|
||||
boundary: bool,
|
||||
) -> Tuple[int, Optional[Match[str]]]:
|
||||
) -> tuple[int, Match[str] | None]:
|
||||
return self._set.search(line, pos, flags=_FLAGS[first_line, boundary])
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import curses
|
||||
import enum
|
||||
@@ -7,12 +9,8 @@ import re
|
||||
import signal
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from babi.color_manager import ColorManager
|
||||
from babi.file import Action
|
||||
@@ -102,16 +100,16 @@ KEYNAME_REWRITE = {
|
||||
|
||||
|
||||
class Key(NamedTuple):
|
||||
wch: Union[int, str]
|
||||
wch: int | str
|
||||
keyname: bytes
|
||||
|
||||
|
||||
class Screen:
|
||||
def __init__(
|
||||
self,
|
||||
stdscr: 'curses._CursesWindow',
|
||||
filenames: List[Optional[str]],
|
||||
initial_lines: List[int],
|
||||
stdscr: curses._CursesWindow,
|
||||
filenames: list[str | None],
|
||||
initial_lines: list[int],
|
||||
perf: Perf,
|
||||
) -> None:
|
||||
self.stdscr = stdscr
|
||||
@@ -126,9 +124,9 @@ class Screen:
|
||||
self.perf = perf
|
||||
self.status = Status()
|
||||
self.margin = Margin.from_current_screen()
|
||||
self.cut_buffer: Tuple[str, ...] = ()
|
||||
self.cut_buffer: tuple[str, ...] = ()
|
||||
self.cut_selection = False
|
||||
self._buffered_input: Union[int, str, None] = None
|
||||
self._buffered_input: int | str | None = None
|
||||
|
||||
@property
|
||||
def file(self) -> File:
|
||||
@@ -271,8 +269,8 @@ class Screen:
|
||||
def quick_prompt(
|
||||
self,
|
||||
prompt: str,
|
||||
opt_strs: Tuple[str, ...],
|
||||
) -> Union[str, PromptResult]:
|
||||
opt_strs: tuple[str, ...],
|
||||
) -> str | PromptResult:
|
||||
opts = {opt[0] for opt in opt_strs}
|
||||
while True:
|
||||
x = 0
|
||||
@@ -318,10 +316,10 @@ class Screen:
|
||||
prompt: str,
|
||||
*,
|
||||
allow_empty: bool = False,
|
||||
history: Optional[str] = None,
|
||||
history: str | None = None,
|
||||
default_prev: bool = False,
|
||||
default: Optional[str] = None,
|
||||
) -> Union[str, PromptResult]:
|
||||
default: str | None = None,
|
||||
) -> str | PromptResult:
|
||||
default = default or ''
|
||||
self.status.clear()
|
||||
if history is not None:
|
||||
@@ -378,7 +376,7 @@ class Screen:
|
||||
else:
|
||||
self.file.uncut(self.cut_buffer, self.margin)
|
||||
|
||||
def _get_search_re(self, prompt: str) -> Union[Pattern[str], PromptResult]:
|
||||
def _get_search_re(self, prompt: str) -> Pattern[str] | PromptResult:
|
||||
response = self.prompt(prompt, history='search', default_prev=True)
|
||||
if response is PromptResult.CANCELLED:
|
||||
return response
|
||||
@@ -391,8 +389,8 @@ class Screen:
|
||||
def _undo_redo(
|
||||
self,
|
||||
op: str,
|
||||
from_stack: List[Action],
|
||||
to_stack: List[Action],
|
||||
from_stack: list[Action],
|
||||
to_stack: list[Action],
|
||||
) -> None:
|
||||
if not from_stack:
|
||||
self.status.update(f'nothing to {op}!')
|
||||
@@ -423,7 +421,7 @@ class Screen:
|
||||
if response is not PromptResult.CANCELLED:
|
||||
self.file.replace(self, search_response, response)
|
||||
|
||||
def command(self) -> Optional[EditResult]:
|
||||
def command(self) -> EditResult | None:
|
||||
response = self.prompt('', history='command')
|
||||
if response is PromptResult.CANCELLED:
|
||||
pass
|
||||
@@ -480,7 +478,7 @@ class Screen:
|
||||
self.status.update(f'invalid command: {response}')
|
||||
return None
|
||||
|
||||
def save(self) -> Optional[PromptResult]:
|
||||
def save(self) -> PromptResult | None:
|
||||
self.file.finalize_previous_action()
|
||||
|
||||
# TODO: make directories if they don't exist
|
||||
@@ -495,7 +493,7 @@ class Screen:
|
||||
self.file.filename = filename
|
||||
|
||||
if not os.path.isfile(self.file.filename):
|
||||
sha256: Optional[str] = None
|
||||
sha256: str | None = None
|
||||
else:
|
||||
with open(self.file.filename, encoding='UTF-8', newline='') as f:
|
||||
*_, sha256 = get_lines(f)
|
||||
@@ -532,7 +530,7 @@ class Screen:
|
||||
first = False
|
||||
return None
|
||||
|
||||
def save_filename(self) -> Optional[PromptResult]:
|
||||
def save_filename(self) -> PromptResult | None:
|
||||
response = self.prompt('enter filename', default=self.file.filename)
|
||||
if response is PromptResult.CANCELLED:
|
||||
return PromptResult.CANCELLED
|
||||
@@ -540,7 +538,7 @@ class Screen:
|
||||
self.file.filename = response
|
||||
return self.save()
|
||||
|
||||
def open_file(self) -> Optional[EditResult]:
|
||||
def open_file(self) -> EditResult | None:
|
||||
response = self.prompt('enter filename', history='open')
|
||||
if response is not PromptResult.CANCELLED:
|
||||
opened = File(response, 0, self.color_manager, self.hl_factories)
|
||||
@@ -549,7 +547,7 @@ class Screen:
|
||||
else:
|
||||
return None
|
||||
|
||||
def quit_save_modified(self) -> Optional[EditResult]:
|
||||
def quit_save_modified(self) -> EditResult | None:
|
||||
if self.file.modified:
|
||||
response = self.quick_prompt(
|
||||
'file is modified - save', ('yes', 'no'),
|
||||
@@ -597,7 +595,7 @@ class Screen:
|
||||
}
|
||||
|
||||
|
||||
def _init_screen() -> 'curses._CursesWindow':
|
||||
def _init_screen() -> curses._CursesWindow:
|
||||
# set the escape delay so curses does not pause waiting for sequences
|
||||
if (
|
||||
sys.version_info >= (3, 9) and
|
||||
@@ -623,7 +621,7 @@ def _init_screen() -> 'curses._CursesWindow':
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def make_stdscr() -> Generator['curses._CursesWindow', None, None]:
|
||||
def make_stdscr() -> Generator[curses._CursesWindow, None, None]:
|
||||
"""essentially `curses.wrapper` but split out to implement ^Z"""
|
||||
try:
|
||||
yield _init_screen()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
|
||||
from babi.margin import Margin
|
||||
@@ -16,7 +18,7 @@ class Status:
|
||||
def clear(self) -> None:
|
||||
self._status = ''
|
||||
|
||||
def draw(self, stdscr: 'curses._CursesWindow', margin: Margin) -> None:
|
||||
def draw(self, stdscr: curses._CursesWindow, margin: Margin) -> None:
|
||||
if margin.footer or self._status:
|
||||
stdscr.insstr(margin.lines - 1, 0, ' ' * margin.cols)
|
||||
if self._status:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from babi.highlight import Compiler
|
||||
@@ -47,7 +48,7 @@ def _highlight_output(theme: Theme, compiler: Compiler, filename: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--theme', default=xdg_config('theme.json'))
|
||||
parser.add_argument('--grammar-dir', default=prefix_data('grammar_v1'))
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
import os.path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from babi._types import Protocol
|
||||
from babi.color import Color
|
||||
@@ -13,32 +12,32 @@ from babi.fdict import FDict
|
||||
|
||||
|
||||
class Style(NamedTuple):
|
||||
fg: Optional[Color]
|
||||
bg: Optional[Color]
|
||||
fg: Color | None
|
||||
bg: Color | None
|
||||
b: bool
|
||||
i: bool
|
||||
u: bool
|
||||
|
||||
@classmethod
|
||||
def blank(cls) -> 'Style':
|
||||
def blank(cls) -> Style:
|
||||
return cls(fg=None, bg=None, b=False, i=False, u=False)
|
||||
|
||||
|
||||
class PartialStyle(NamedTuple):
|
||||
fg: Optional[Color] = None
|
||||
bg: Optional[Color] = None
|
||||
b: Optional[bool] = None
|
||||
i: Optional[bool] = None
|
||||
u: Optional[bool] = None
|
||||
fg: Color | None = None
|
||||
bg: Color | None = None
|
||||
b: bool | None = None
|
||||
i: bool | None = None
|
||||
u: bool | None = None
|
||||
|
||||
def overlay_on(self, dct: Dict[str, Any]) -> None:
|
||||
def overlay_on(self, dct: dict[str, Any]) -> None:
|
||||
for attr in self._fields:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
dct[attr] = value
|
||||
|
||||
@classmethod
|
||||
def from_dct(cls, dct: Dict[str, Any]) -> 'PartialStyle':
|
||||
def from_dct(cls, dct: dict[str, Any]) -> PartialStyle:
|
||||
kv = cls()._asdict()
|
||||
if 'foreground' in dct:
|
||||
kv['fg'] = Color.parse(dct['foreground'])
|
||||
@@ -57,7 +56,7 @@ class _TrieNode(Protocol):
|
||||
@property
|
||||
def style(self) -> PartialStyle: ...
|
||||
@property
|
||||
def children(self) -> FDict[str, '_TrieNode']: ...
|
||||
def children(self) -> FDict[str, _TrieNode]: ...
|
||||
|
||||
|
||||
class TrieNode(NamedTuple):
|
||||
@@ -65,7 +64,7 @@ class TrieNode(NamedTuple):
|
||||
children: FDict[str, _TrieNode]
|
||||
|
||||
@classmethod
|
||||
def from_dct(cls, dct: Dict[str, Any]) -> _TrieNode:
|
||||
def from_dct(cls, dct: dict[str, Any]) -> _TrieNode:
|
||||
children = FDict({
|
||||
k: TrieNode.from_dct(v) for k, v in dct['children'].items()
|
||||
})
|
||||
@@ -77,7 +76,7 @@ class Theme(NamedTuple):
|
||||
rules: _TrieNode
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def select(self, scope: Tuple[str, ...]) -> Style:
|
||||
def select(self, scope: tuple[str, ...]) -> Style:
|
||||
if not scope:
|
||||
return self.default
|
||||
else:
|
||||
@@ -92,7 +91,7 @@ class Theme(NamedTuple):
|
||||
return Style(**style)
|
||||
|
||||
@classmethod
|
||||
def from_dct(cls, data: Dict[str, Any]) -> 'Theme':
|
||||
def from_dct(cls, data: dict[str, Any]) -> Theme:
|
||||
default = Style.blank()._asdict()
|
||||
|
||||
for k in ('foreground', 'editor.foreground'):
|
||||
@@ -105,7 +104,7 @@ class Theme(NamedTuple):
|
||||
default['bg'] = Color.parse(data['colors'][k])
|
||||
break
|
||||
|
||||
root: Dict[str, Any] = {'children': {}}
|
||||
root: dict[str, Any] = {'children': {}}
|
||||
rules = data.get('tokenColors', []) + data.get('settings', [])
|
||||
for rule in rules:
|
||||
if 'scope' not in rule:
|
||||
@@ -139,11 +138,11 @@ class Theme(NamedTuple):
|
||||
return cls(Style(**default), TrieNode.from_dct(root))
|
||||
|
||||
@classmethod
|
||||
def blank(cls) -> 'Theme':
|
||||
def blank(cls) -> Theme:
|
||||
return cls(Style.blank(), TrieNode.from_dct({'children': {}}))
|
||||
|
||||
@classmethod
|
||||
def from_filename(cls, filename: str) -> 'Theme':
|
||||
def from_filename(cls, filename: str) -> Theme:
|
||||
if not os.path.exists(filename):
|
||||
return cls.blank()
|
||||
else:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
|
||||
@@ -13,7 +13,6 @@ classifiers =
|
||||
License :: OSI Approved :: MIT License
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
@@ -28,7 +27,7 @@ install_requires =
|
||||
onigurumacffi>=0.0.18
|
||||
importlib_metadata>=1;python_version<"3.8"
|
||||
windows-curses;sys_platform=="win32"
|
||||
python_requires = >=3.6.1
|
||||
python_requires = >=3.7
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
|
||||
2
setup.py
2
setup.py
@@ -1,2 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from setuptools import setup
|
||||
setup()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import curses
|
||||
import enum
|
||||
import re
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
from hecate import Runner
|
||||
|
||||
@@ -34,7 +34,7 @@ def to_attrs(screen, width):
|
||||
fg = bg = -1
|
||||
attr = 0
|
||||
idx = 0
|
||||
ret: List[List[Tuple[int, int, int]]]
|
||||
ret: list[list[tuple[int, int, int]]]
|
||||
ret = [[] for _ in range(len(screen.splitlines()))]
|
||||
|
||||
for tp, match in tokenize_colors(screen):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.buf import Buf
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from babi import color_kd
|
||||
from babi.color import Color
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.color import Color
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.color import Color
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.fdict import FChainMap
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import curses
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
@@ -148,7 +147,7 @@ class AssertScreenLineEquals(NamedTuple):
|
||||
|
||||
class AssertScreenAttrEquals(NamedTuple):
|
||||
n: int
|
||||
attr: List[Tuple[int, int, int]]
|
||||
attr: list[tuple[int, int, int]]
|
||||
|
||||
def __call__(self, screen: Screen) -> None:
|
||||
assert screen.attrs[self.n] == self.attr
|
||||
@@ -170,7 +169,7 @@ class Resize(NamedTuple):
|
||||
|
||||
|
||||
class KeyPress(NamedTuple):
|
||||
wch: Union[int, str]
|
||||
wch: int | str
|
||||
|
||||
def __call__(self, screen: Screen) -> None:
|
||||
raise AssertionError('unreachable')
|
||||
@@ -236,7 +235,7 @@ class CursesScreen:
|
||||
class Key(NamedTuple):
|
||||
tmux: str
|
||||
curses: bytes
|
||||
wch: Union[int, str]
|
||||
wch: int | str
|
||||
|
||||
@property
|
||||
def value(self) -> int:
|
||||
@@ -300,7 +299,7 @@ class DeferredRunner:
|
||||
def __init__(self, command, width=80, height=24, term='screen'):
|
||||
self.command = command
|
||||
self._i = 0
|
||||
self._ops: List[Op] = []
|
||||
self._ops: list[Op] = []
|
||||
self.color_pairs = {0: (7, 0)}
|
||||
self.screen = Screen(width, height)
|
||||
self._n_colors, self._can_change_color = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
from testing.runner import trigger_command_mode
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
from testing.runner import trigger_command_mode
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
|
||||
from babi.screen import VERSION_STR
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from babi.screen import VERSION_STR
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
import json
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import curses
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from testing.runner import and_exit
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.highlight import highlight_line
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import curses
|
||||
from unittest import mock
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi import main
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import onigurumacffi
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from babi.color import Color
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
|
||||
Reference in New Issue
Block a user