diff --git a/plugin/__init__.py b/plugin/__init__.py index 812038f3..e9511d24 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -65,6 +65,7 @@ def plugin_loaded() -> None: + """Executed when this plugin is loaded.""" # somehow "AutoSetSyntaxAppendLogCommand" won't be ready if we don't wait a bit sublime.set_timeout(_plugin_loaded) @@ -86,6 +87,7 @@ def _plugin_loaded() -> None: def plugin_unloaded() -> None: + """Executed when this plugin is unloaded.""" AioSettings.clear_on_change(PLUGIN_NAME) AioSettings.tear_down() diff --git a/plugin/rules/constraint.py b/plugin/rules/constraint.py index 1595a0a9..55cff708 100644 --- a/plugin/rules/constraint.py +++ b/plugin/rules/constraint.py @@ -13,10 +13,18 @@ from ..cache import clearable_lru_cache from ..constants import PLUGIN_NAME, ST_PLATFORM from ..snapshot import ViewSnapshot -from ..types import Optimizable, ST_ConstraintRule -from ..utils import camel_to_snake, compile_regex, list_all_subclasses, merge_regexes, parse_regex_flags, remove_suffix +from ..types import Optimizable, StConstraintRule +from ..utils import ( + camel_to_snake, + compile_regex, + drop_falsy, + list_all_subclasses, + merge_regexes, + parse_regex_flags, + remove_suffix, +) -T = TypeVar("T") +_T = TypeVar("_T") def find_constraint(obj: Any) -> type[AbstractConstraint] | None: @@ -63,7 +71,7 @@ def test(self, view_snapshot: ViewSnapshot) -> bool: return not result if self.inverted else result @classmethod - def make(cls, constraint_rule: ST_ConstraintRule) -> Self: + def make(cls, constraint_rule: StConstraintRule) -> Self: """Build this object with the `constraint_rule`.""" obj = cls() @@ -114,9 +122,9 @@ def test(self, view_snapshot: ViewSnapshot) -> bool: """Tests whether the `view_snapshot` passes this constraint.""" @final - def _handled_args(self, normalizer: Callable[[T], T] | None = None) -> tuple[T, ...]: + def _handled_args(self, normalizer: Callable[[_T], _T] | None = None) -> tuple[_T, ...]: """Filter falsy args and normalize them. Note that `0`, `""` and `None` are falsy.""" - args: Iterable[T] = filter(None, self.args) + args: Iterable[_T] = drop_falsy(self.args) if normalizer: args = map(normalizer, args) return tuple(args) @@ -158,7 +166,10 @@ def _handled_case_insensitive(kwargs: dict[str, Any]) -> bool: @staticmethod def find_parent_with_sibling(base: str | Path, sibling: str, *, use_exists: bool = False) -> Path | None: """Find the first parent directory which contains `sibling`.""" - path = Path(base).resolve() + try: + path = Path(base).resolve() + except Exception: + return None if use_exists: checker = Path.exists diff --git a/plugin/rules/match.py b/plugin/rules/match.py index a6669b0a..f68befe6 100644 --- a/plugin/rules/match.py +++ b/plugin/rules/match.py @@ -10,7 +10,7 @@ from ..cache import clearable_lru_cache from ..snapshot import ViewSnapshot -from ..types import Optimizable, ST_MatchRule +from ..types import Optimizable, StMatchRule from ..utils import camel_to_snake, list_all_subclasses, remove_suffix from .constraint import ConstraintRule @@ -59,7 +59,7 @@ def test(self, view_snapshot: ViewSnapshot) -> bool: return self.match.test(view_snapshot, self.rules) @classmethod - def make(cls, match_rule: ST_MatchRule) -> Self: + def make(cls, match_rule: StMatchRule) -> Self: """Build this object with the `match_rule`.""" obj = cls() diff --git a/plugin/rules/syntax.py b/plugin/rules/syntax.py index 4d7b831c..f395751c 100644 --- a/plugin/rules/syntax.py +++ b/plugin/rules/syntax.py @@ -9,8 +9,8 @@ from ..constants import VERSION from ..snapshot import ViewSnapshot -from ..types import ListenerEvent, Optimizable, ST_SyntaxRule -from ..utils import find_syntax_by_syntax_likes +from ..types import ListenerEvent, Optimizable, StSyntaxRule +from ..utils import drop_falsy, find_syntax_by_syntax_likes from .match import MatchRule @@ -53,7 +53,7 @@ def test(self, view_snapshot: ViewSnapshot, event: ListenerEvent | None = None) return self.root_rule.test(view_snapshot) @classmethod - def make(cls, syntax_rule: ST_SyntaxRule) -> Self: + def make(cls, syntax_rule: StSyntaxRule) -> Self: """Build this object with the `syntax_rule`.""" obj = cls() @@ -74,7 +74,7 @@ def make(cls, syntax_rule: ST_SyntaxRule) -> Self: if (on_events := syntax_rule.get("on_events")) is not None: if isinstance(on_events, str): on_events = [on_events] - obj.on_events = set(filter(None, map(ListenerEvent.from_value, on_events))) + obj.on_events = set(drop_falsy(map(ListenerEvent.from_value, on_events))) if match_rule_compiled := MatchRule.make(syntax_rule): obj.root_rule = match_rule_compiled @@ -107,7 +107,7 @@ def test(self, view_snapshot: ViewSnapshot, event: ListenerEvent | None = None) return first_true(self.rules, pred=lambda rule: rule.test(view_snapshot, event)) @classmethod - def make(cls, syntax_rules: Iterable[ST_SyntaxRule]) -> Self: + def make(cls, syntax_rules: Iterable[StSyntaxRule]) -> Self: """Build this object with the `syntax_rules`.""" obj = cls() obj.rules = tuple(map(SyntaxRule.make, syntax_rules)) diff --git a/plugin/settings.py b/plugin/settings.py index e9fd24ef..78da692e 100644 --- a/plugin/settings.py +++ b/plugin/settings.py @@ -8,7 +8,8 @@ import sublime_plugin from more_itertools import unique_everseen -from .types import ST_SyntaxRule +from .types import StSyntaxRule +from .utils import drop_falsy def get_merged_plugin_setting( @@ -32,7 +33,7 @@ def get_st_settings() -> sublime.Settings: return sublime.load_settings("Preferences.sublime-settings") -def pref_syntax_rules(*, window: sublime.Window | None = None) -> list[ST_SyntaxRule]: +def pref_syntax_rules(*, window: sublime.Window | None = None) -> list[StSyntaxRule]: return get_merged_plugin_setting("syntax_rules", [], window=window) @@ -52,8 +53,7 @@ def extra_settings_producer(settings: MergedSettingsDict) -> dict[str, Any]: # use tuple to freeze setting for better performance (cache-able) ret["trim_suffixes"] = tuple( - filter( - None, # remove falsy values + drop_falsy( unique_everseen( chain( settings.get("project_trim_suffixes", []), diff --git a/plugin/snapshot.py b/plugin/snapshot.py index ec367450..8ed892f0 100644 --- a/plugin/snapshot.py +++ b/plugin/snapshot.py @@ -83,9 +83,7 @@ def from_view(cls, view: sublime.View) -> Self: window = view.window() or sublime.active_window() # is real file on a disk? - if (_path := view.file_name()) and (path := Path(_path).resolve()).is_file(): - pass - else: + if not (_path := view.file_name()) or not (path := Path(_path).resolve()).is_file(): path = None return cls( diff --git a/plugin/types.py b/plugin/types.py index be1a5feb..9078e6b7 100644 --- a/plugin/types.py +++ b/plugin/types.py @@ -3,7 +3,7 @@ import sys from abc import ABC, abstractmethod from collections import UserDict as BuiltinUserDict -from collections.abc import Generator, Iterator, KeysView +from collections.abc import Generator, Hashable, Iterator, KeysView from enum import Enum from typing import Any, Generic, TypedDict, TypeVar, Union, overload @@ -14,38 +14,38 @@ WindowId = int WindowIdAble = Union[WindowId, sublime.Window] -_K = TypeVar("_K") +_KT = TypeVar("_KT", bound=Hashable) +_KV = TypeVar("_KV") _T = TypeVar("_T") -_V = TypeVar("_V") if sys.version_info < (3, 9): - class UserDict(BuiltinUserDict, Generic[_K, _V]): + class UserDict(BuiltinUserDict, Generic[_KT, _KV]): """Workaround class for the fact that `UserDict` is not subscriptable until Python 3.9...""" def __init__(self, dict=None, /, **kwargs) -> None: - self.data: dict[_K, _V] = {} + self.data: dict[_KT, _KV] = {} super().__init__(dict, **kwargs) - def __getitem__(self, key: _K) -> _V: + def __getitem__(self, key: _KT) -> _KV: return super().__getitem__(key) - def __setitem__(self, key: _K, item: _V) -> None: + def __setitem__(self, key: _KT, item: _KV) -> None: super().__setitem__(key, item) - def __delitem__(self, key: _K) -> None: + def __delitem__(self, key: _KT) -> None: super().__delitem__(key) - def __iter__(self) -> Iterator[_K]: + def __iter__(self) -> Iterator[_KT]: return super().__iter__() @overload - def get(self, key: _K) -> _V | None: ... + def get(self, key: _KT) -> _KV | None: ... @overload - def get(self, key: _K, default: _T) -> _V | _T: ... - - def get(self, key: _K, default: _T | None = None) -> _V | _T | None: + def get(self, key: _KT, default: _T) -> _KV | _T: ... + def get(self, key: _KT, default: _T | None = None) -> _KV | _T | None: return super().get(key, default) + else: UserDict = BuiltinUserDict # noqa: F401 @@ -54,6 +54,7 @@ def get(self, key: _K, default: _T | None = None) -> _V | _T | None: class StrEnum(str, Enum): __format__ = str.__format__ # type: ignore __str__ = str.__str__ # type: ignore + else: from enum import StrEnum # noqa: F401 @@ -94,7 +95,7 @@ def optimize(self) -> Generator[Any, None, None]: """Does optimizations and returns a generator for dropped objects.""" -class ST_ConstraintRule(TypedDict): +class StConstraintRule(TypedDict): """Typed dict for corresponding ST settings.""" constraint: str @@ -103,16 +104,16 @@ class ST_ConstraintRule(TypedDict): inverted: bool -class ST_MatchRule(TypedDict): +class StMatchRule(TypedDict): """Typed dict for corresponding ST settings.""" match: str args: list[Any] | Any | None kwargs: dict[str, Any] | None - rules: list[ST_MatchRule | ST_ConstraintRule] + rules: list[StMatchRule | StConstraintRule] -class ST_SyntaxRule(ST_MatchRule): +class StSyntaxRule(StMatchRule): """Typed dict for corresponding ST settings.""" comment: str diff --git a/plugin/utils.py b/plugin/utils.py index 4aa90b84..90f5bc18 100644 --- a/plugin/utils.py +++ b/plugin/utils.py @@ -63,6 +63,11 @@ def compile_regex(regex: str | Pattern[str], flags: int = 0) -> Pattern[str]: return re.compile(regex, flags) +def drop_falsy(iterable: Iterable[_T | None]) -> Generator[_T, None, None]: + """Drops falsy values from the iterable.""" + yield from filter(None, iterable) + + def get_fqcn(obj: Any) -> str: if obj is None: return "None" @@ -86,7 +91,7 @@ def merge_regexes(regexes: Iterable[str]) -> str: """Merge regex strings into a single regex string.""" regexes = tuple(regexes) if len(regexes) == 0: - return "" + return r"~^(?#match nothing)" if len(regexes) == 1: merged = regexes[0] else: