Merge pull request #158 from asottile/drop_py36

drop python 3.6
This commit is contained in:
Anthony Sottile
2021-09-11 12:27:26 -07:00
committed by GitHub
75 changed files with 385 additions and 300 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from babi.main import main
if __name__ == '__main__':

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:

View File

@@ -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

View File

@@ -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)

View File

@@ -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('#'):

View File

@@ -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)

View File

@@ -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({}, {})

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import Generic
from typing import Iterable
from typing import Mapping

View File

@@ -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:

View File

@@ -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

View File

@@ -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]:

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from typing import NamedTuple
from typing import Tuple

View File

@@ -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"""

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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')

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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])

View File

@@ -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()

View File

@@ -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:

View File

@@ -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'))

View File

@@ -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:

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import os.path
import sys

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import io
import json

View File

@@ -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 =

View File

@@ -1,2 +1,4 @@
from __future__ import annotations
from setuptools import setup
setup()

View File

@@ -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):

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.buf import Buf

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from babi import color_kd
from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import json
import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.fdict import FChainMap

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -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 = {

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit
from testing.runner import trigger_command_mode

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit
from testing.runner import trigger_command_mode

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses
from babi.screen import VERSION_STR

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from babi.screen import VERSION_STR
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import shlex
import sys

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import shlex
import sys

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses
import json

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import curses
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from testing.runner import and_exit

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import io
import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.highlight import highlight_line

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib
import curses
from unittest import mock

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi import main

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import onigurumacffi
import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import json
import pytest

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import pytest
from babi.color import Color

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
import os
from unittest import mock

View File

@@ -1,5 +1,5 @@
[tox]
envlist = py36,py37,pre-commit
envlist = py37,pre-commit
[testenv]
deps = -rrequirements-dev.txt