From 53db9e6864c8703e73d501f43af57d8f77724c19 Mon Sep 17 00:00:00 2001 From: Fly me to the moon Date: Wed, 5 Aug 2020 12:07:38 -0700 Subject: [PATCH 1/4] use void --- rplugin/python3/chadtree/da.py | 21 +++++++++--- rplugin/python3/chadtree/render.py | 5 +-- rplugin/python3/chadtree/state.py | 44 ++++++++++++++----------- rplugin/python3/chadtree/transitions.py | 29 ++++++++++++---- rplugin/python3/chadtree/types.py | 7 +++- 5 files changed, 72 insertions(+), 34 deletions(-) diff --git a/rplugin/python3/chadtree/da.py b/rplugin/python3/chadtree/da.py index 93de2a64e..f09a993d1 100644 --- a/rplugin/python3/chadtree/da.py +++ b/rplugin/python3/chadtree/da.py @@ -9,13 +9,28 @@ from os.path import dirname, exists from subprocess import CompletedProcess, run from sys import version_info -from typing import Any, Callable, Optional, TypeVar, cast +from typing import Any, Callable, Optional, TypeVar, Union, cast from .consts import folder_mode T = TypeVar("T") +class Void: + def __bool__(self) -> bool: + return False + + def __eq__(self, o: Any) -> bool: + return type(o) is Void + + def __str__(self) -> str: + return type(self).__name__ + + +def or_else(thing: Union[T, Void], default: T) -> T: + return default if thing == Void() else cast(T, thing) + + def merge(ds1: Any, ds2: Any, replace: bool = False) -> Any: if type(ds1) is dict and type(ds2) is dict: append = {k: merge(ds1.get(k), v, replace) for k, v in ds2.items()} @@ -36,10 +51,6 @@ def merge_all(ds1: Any, *dss: Any, replace: bool = False) -> Any: return res -def or_else(thing: Optional[T], default: T) -> T: - return default if thing is None else thing - - def constantly(val: T) -> Callable[[Any], T]: def ret(*args: Any, **kwargs: Any) -> T: return val diff --git a/rplugin/python3/chadtree/render.py b/rplugin/python3/chadtree/render.py index b776c7a05..5deeeaf3e 100644 --- a/rplugin/python3/chadtree/render.py +++ b/rplugin/python3/chadtree/render.py @@ -8,6 +8,7 @@ from .da import constantly from .types import ( Badge, + FilterPattern, Highlight, HLgroup, Index, @@ -176,7 +177,7 @@ def render( settings: Settings, index: Index, selection: Selection, - filter_pattern: str, + filter_pattern: Optional[FilterPattern], qf: QuickFix, vc: VCStatus, show_hidden: bool, @@ -191,7 +192,7 @@ def render( def render( node: Node, *, depth: int, cleared: bool ) -> Iterator[Tuple[Node, Render]]: - clear = cleared or not filter_pattern or fnmatch(node.name, filter_pattern) + clear = cleared or not filter_pattern or fnmatch(node.name, filter_pattern.pattern) rend = show(node, depth) def gen_children() -> Iterator[Tuple[Node, Render]]: diff --git a/rplugin/python3/chadtree/state.py b/rplugin/python3/chadtree/state.py index 7861179f6..0e06a75fa 100644 --- a/rplugin/python3/chadtree/state.py +++ b/rplugin/python3/chadtree/state.py @@ -1,25 +1,25 @@ from asyncio import gather from hashlib import sha1 from os.path import join -from typing import Optional +from typing import Optional, Set, Union, cast from pynvim import Nvim from .cartographer import new, update from .consts import session_dir -from .da import dump_json, load_json, or_else +from .da import Void, dump_json, load_json, or_else from .git import status from .nvim import getcwd from .quickfix import quickfix from .render import render from .types import ( + FilterPattern, Index, Mode, Node, QuickFix, Selection, Session, - Set, Settings, State, VCStatus, @@ -60,7 +60,7 @@ async def initial(nvim: Nvim, settings: Settings) -> State: node, qf = await gather(new(cwd, index=index), quickfix(nvim)) vc = VCStatus() if not version_ctl.enable or version_ctl.defer else await status() current = None - filter_pattern = "" + filter_pattern = None lookup, rendered = render( node, settings=settings, @@ -77,7 +77,7 @@ async def initial(nvim: Nvim, settings: Settings) -> State: state = State( index=index, selection=selection, - filter_pattern="", + filter_pattern=filter_pattern, show_hidden=settings.show_hidden, follow=settings.follow, enable_vc=settings.version_ctl.enable, @@ -97,25 +97,31 @@ async def forward( state: State, *, settings: Settings, - root: Optional[Node] = None, - index: Optional[Index] = None, - selection: Optional[Selection] = None, - filter_pattern: Optional[str] = None, - show_hidden: Optional[bool] = None, - follow: Optional[bool] = None, - enable_vc: Optional[bool] = None, - width: Optional[int] = None, - qf: Optional[QuickFix] = None, - vc: Optional[VCStatus] = None, - current: Optional[str] = None, - paths: Optional[Set[str]] = None, + root: Union[Node, Void] = Void(), + index: Union[Index, Void] = Void(), + selection: Union[Selection, Void] = Void(), + filter_pattern: Union[Optional[FilterPattern], Void] = Void(), + show_hidden: Union[bool, Void] = Void(), + follow: Union[bool, Void] = Void(), + enable_vc: Union[bool, Void] = Void(), + width: Union[int, Void] = Void(), + qf: Union[QuickFix, Void] = Void(), + vc: Union[VCStatus, Void] = Void(), + current: Union[str, Void] = Void(), + paths: Union[Set[str], Void] = Void(), ) -> State: new_index = or_else(index, state.index) new_selection = or_else(selection, state.selection) new_filter_pattern = or_else(filter_pattern, state.filter_pattern) new_current = or_else(current, state.current) - new_root = root or ( - await update(state.root, index=new_index, paths=paths) if paths else state.root + new_root = cast( + Node, + root + or ( + await update(state.root, index=new_index, paths=cast(Set[str], paths)) + if paths + else state.root + ), ) new_qf = or_else(qf, state.qf) new_vc = or_else(vc, state.vc) diff --git a/rplugin/python3/chadtree/transitions.py b/rplugin/python3/chadtree/transitions.py index e98f16969..a99d2fc3a 100644 --- a/rplugin/python3/chadtree/transitions.py +++ b/rplugin/python3/chadtree/transitions.py @@ -15,6 +15,7 @@ Sequence, Set, Tuple, + Union, cast, ) @@ -23,7 +24,7 @@ from pynvim.api.window import Window from .cartographer import new as new_root -from .da import human_readable_size +from .da import Void, human_readable_size from .fs import ( ancestors, copy, @@ -43,7 +44,17 @@ from .state import index as state_index from .state import is_dir from .system import SystemIntegrationError, open_gui, trash -from .types import ClickType, Index, Mode, Node, Selection, Settings, State, VCStatus +from .types import ( + ClickType, + FilterPattern, + Index, + Mode, + Node, + Selection, + Settings, + State, + VCStatus, +) from .wm import ( find_current_buffer_name, is_fm_buffer, @@ -324,7 +335,9 @@ def co() -> str: current = await call(nvim, co) cwd = state.root.path paths = {cwd} - new_current = current if is_parent(parent=cwd, child=current) else None + new_current = cast( + Union[str, Void], current if is_parent(parent=cwd, child=current) else Void() + ) def cont() -> Tuple[Index, Selection]: index = {i for i in state.index if exists(i)} | paths @@ -335,7 +348,7 @@ def cont() -> Tuple[Index, Selection]: index, selection = await loop.run_in_executor(None, cont) current_paths: Set[str] = {*ancestors(current)} if state.follow else set() - new_index = index if new_current is None else index | current_paths + new_index = index if new_current == Void() else index | current_paths qf, vc = await gather(quickfix(nvim), _vc_stat(state.enable_vc)) new_state = await forward( @@ -389,10 +402,12 @@ async def c_toggle_vc(nvim: Nvim, state: State, settings: Settings) -> State: async def c_new_filter(nvim: Nvim, state: State, settings: Settings) -> State: def ask() -> Optional[str]: - resp = nvim.funcs.input("New filter:", state.filter_pattern) + pattern = state.filter_pattern.pattern if state.filter_pattern else "" + resp = nvim.funcs.input("New filter:", pattern) return resp - filter_pattern = await call(nvim, ask) or "" + pattern = await call(nvim, ask) + filter_pattern = FilterPattern(pattern=pattern) if pattern else None new_state = await forward( state, settings=settings, selection=set(), filter_pattern=filter_pattern ) @@ -526,7 +541,7 @@ async def c_clear_selection(nvim: Nvim, state: State, settings: Settings) -> Sta async def c_clear_filter(nvim: Nvim, state: State, settings: Settings) -> State: - new_state = await forward(state, settings=settings, filter_pattern="") + new_state = await forward(state, settings=settings, filter_pattern=None) return new_state diff --git a/rplugin/python3/chadtree/types.py b/rplugin/python3/chadtree/types.py index 24851afc1..68b776d74 100644 --- a/rplugin/python3/chadtree/types.py +++ b/rplugin/python3/chadtree/types.py @@ -146,11 +146,16 @@ class Render: highlights: Sequence[Highlight] +@dataclass(frozen=True) +class FilterPattern: + pattern: str + + @dataclass(frozen=True) class State: index: Index selection: Selection - filter_pattern: str + filter_pattern: Optional[FilterPattern] show_hidden: bool follow: bool enable_vc: bool From 559fa16ebe82cb3a750b7ea65a3324b60b6efb0c Mon Sep 17 00:00:00 2001 From: Fly me to the moon Date: Wed, 5 Aug 2020 13:24:44 -0700 Subject: [PATCH 2/4] submodule --- rplugin/python3/chadtree/git.py | 38 ++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/rplugin/python3/chadtree/git.py b/rplugin/python3/chadtree/git.py index 85984adfe..387ed5a76 100644 --- a/rplugin/python3/chadtree/git.py +++ b/rplugin/python3/chadtree/git.py @@ -8,6 +8,9 @@ from .fs import ancestors from .types import VCStatus +GIT_LIST_CMD = ("git", "status", "--ignored", "--renames", "--porcelain", "-z") +GIT_SUBMODULE_MARKER = "Entering " + class GitError(Exception): pass @@ -21,21 +24,45 @@ async def root() -> str: return ret.out.rstrip() -async def stat() -> Dict[str, str]: +async def stat_main() -> Dict[str, str]: ret = await call("git", "status", "--ignored", "--renames", "--porcelain", "-z") if ret.code != 0: raise GitError(ret.err) else: + it = iter(ret.out.split("\0")) - def items() -> Iterator[Tuple[str, str]]: - it = iter(ret.out.split("\0")) + def cont() -> Iterator[Tuple[str, str]]: for line in it: prefix, file = line[:2], line[3:] yield prefix, file.rstrip(sep) if "R" in prefix: next(it, None) - entries = {file: prefix for prefix, file in items()} + entries = {file: prefix for prefix, file in cont()} + return entries + + +async def stat_sub_modules() -> Dict[str, str]: + ret = await call( + "git", "submodule", "foreach", "--recursive", " ".join(GIT_LIST_CMD) + ) + if ret.code != 0: + raise GitError(ret.err) + else: + it = iter(ret.out.split("\0")) + + def cont() -> Iterator[Tuple[str, str]]: + root = "" + for line in it: + if line.startswith(GIT_SUBMODULE_MARKER): + root = line[len(GIT_SUBMODULE_MARKER) + 1 : -1] + else: + prefix, file = line[:2], line[3:] + yield prefix, join(root, file.rstrip(sep)) + if "R" in prefix: + next(it, None) + + entries = {file: prefix for prefix, file in cont()} return entries @@ -64,7 +91,8 @@ def parse(root: str, stats: Dict[str, str]) -> VCStatus: async def status() -> VCStatus: if which("git"): try: - r, stats = await gather(root(), stat()) + r, s_main, s_sub = await gather(root(), stat_main(), stat_sub_modules()) + stats = {**s_sub, **s_main} return parse(r, stats) except GitError: return VCStatus() From 40c83ff88f432710c72f92958427e32acc97a5ca Mon Sep 17 00:00:00 2001 From: Fly me to the moon Date: Wed, 5 Aug 2020 13:36:50 -0700 Subject: [PATCH 3/4] LC_ALL --- rplugin/python3/chadtree/da.py | 16 ++++++++++------ rplugin/python3/chadtree/git.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/rplugin/python3/chadtree/da.py b/rplugin/python3/chadtree/da.py index f09a993d1..babea3004 100644 --- a/rplugin/python3/chadtree/da.py +++ b/rplugin/python3/chadtree/da.py @@ -5,11 +5,11 @@ from itertools import count from json import dump, load from operator import pow -from os import makedirs +from os import environ, makedirs from os.path import dirname, exists from subprocess import CompletedProcess, run from sys import version_info -from typing import Any, Callable, Optional, TypeVar, Union, cast +from typing import Any, Callable, Dict, Optional, TypeVar, Union, cast from .consts import folder_mode @@ -80,11 +80,12 @@ class ProcReturn: if (version_info.major, version_info.minor) == (3, 7): - async def call(prog: str, *args: str) -> ProcReturn: + async def call(prog: str, *args: str, env: Dict[str, str] = {}) -> ProcReturn: loop = get_running_loop() def cont() -> CompletedProcess: - return run((prog, *args), capture_output=True) + envi = {**environ, **env} + return run((prog, *args), capture_output=True, env=envi) ret = await loop.run_in_executor(None, cont) out, err = ret.stdout.decode(), ret.stderr.decode() @@ -94,8 +95,11 @@ def cont() -> CompletedProcess: else: - async def call(prog: str, *args: str) -> ProcReturn: - proc = await create_subprocess_exec(prog, *args, stdout=PIPE, stderr=PIPE) + async def call(prog: str, *args: str, env: Dict[str, str] = {}) -> ProcReturn: + envi = {**environ, **env} + proc = await create_subprocess_exec( + prog, *args, stdout=PIPE, stderr=PIPE, env=envi + ) stdout, stderr = await proc.communicate() code = cast(int, proc.returncode) return ProcReturn(code=code, out=stdout.decode(), err=stderr.decode()) diff --git a/rplugin/python3/chadtree/git.py b/rplugin/python3/chadtree/git.py index 387ed5a76..150bfc33a 100644 --- a/rplugin/python3/chadtree/git.py +++ b/rplugin/python3/chadtree/git.py @@ -1,5 +1,6 @@ from asyncio import gather from locale import strxfrm +from os import linesep from os.path import join, sep from shutil import which from typing import Dict, Iterator, Set, Tuple @@ -10,6 +11,7 @@ GIT_LIST_CMD = ("git", "status", "--ignored", "--renames", "--porcelain", "-z") GIT_SUBMODULE_MARKER = "Entering " +GIT_ENV = {"LC_ALL": "C"} class GitError(Exception): @@ -44,7 +46,12 @@ def cont() -> Iterator[Tuple[str, str]]: async def stat_sub_modules() -> Dict[str, str]: ret = await call( - "git", "submodule", "foreach", "--recursive", " ".join(GIT_LIST_CMD) + "git", + "submodule", + "foreach", + "--recursive", + " ".join(GIT_LIST_CMD), + env=GIT_ENV, ) if ret.code != 0: raise GitError(ret.err) @@ -55,7 +62,7 @@ def cont() -> Iterator[Tuple[str, str]]: root = "" for line in it: if line.startswith(GIT_SUBMODULE_MARKER): - root = line[len(GIT_SUBMODULE_MARKER) + 1 : -1] + root = line[len(GIT_SUBMODULE_MARKER) + 1 : -1].rstrip(linesep) else: prefix, file = line[:2], line[3:] yield prefix, join(root, file.rstrip(sep)) From e3b803849c4d2cb7ac71b28d3211fa4e48b1e04c Mon Sep 17 00:00:00 2001 From: Fly me to the moon Date: Wed, 5 Aug 2020 13:40:22 -0700 Subject: [PATCH 4/4] linesep --- rplugin/python3/chadtree/git.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rplugin/python3/chadtree/git.py b/rplugin/python3/chadtree/git.py index 150bfc33a..baf92e3e7 100644 --- a/rplugin/python3/chadtree/git.py +++ b/rplugin/python3/chadtree/git.py @@ -9,7 +9,7 @@ from .fs import ancestors from .types import VCStatus -GIT_LIST_CMD = ("git", "status", "--ignored", "--renames", "--porcelain", "-z") +GIT_LIST_CMD = ("git", "status", "--ignored", "--renames", "--porcelain") GIT_SUBMODULE_MARKER = "Entering " GIT_ENV = {"LC_ALL": "C"} @@ -27,7 +27,7 @@ async def root() -> str: async def stat_main() -> Dict[str, str]: - ret = await call("git", "status", "--ignored", "--renames", "--porcelain", "-z") + ret = await call(*GIT_LIST_CMD, "-z") if ret.code != 0: raise GitError(ret.err) else: @@ -56,7 +56,7 @@ async def stat_sub_modules() -> Dict[str, str]: if ret.code != 0: raise GitError(ret.err) else: - it = iter(ret.out.split("\0")) + it = iter(ret.out.split(linesep)) def cont() -> Iterator[Tuple[str, str]]: root = "" @@ -100,7 +100,7 @@ async def status() -> VCStatus: try: r, s_main, s_sub = await gather(root(), stat_main(), stat_sub_modules()) stats = {**s_sub, **s_main} - return parse(r, stats) + return parse(r, s_sub) except GitError: return VCStatus() else: