From 8d6a434250470f4cb06ca3da67dc6d15aead1c85 Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Wed, 1 Nov 2023 22:28:46 +0800 Subject: [PATCH] refactor: tidy codes Signed-off-by: Jack Cherng --- plugin/commands/auto_set_syntax.py | 18 +++--- .../auto_set_syntax_debug_information.py | 4 +- plugin/listener.py | 10 +-- plugin/rules/constraint.py | 9 ++- plugin/shared.py | 61 +++++++++---------- plugin/snapshot.py | 60 ++++++++---------- plugin/types.py | 41 ++++++++++++- 7 files changed, 116 insertions(+), 87 deletions(-) diff --git a/plugin/commands/auto_set_syntax.py b/plugin/commands/auto_set_syntax.py index ce988581..6f275a42 100644 --- a/plugin/commands/auto_set_syntax.py +++ b/plugin/commands/auto_set_syntax.py @@ -20,7 +20,7 @@ from ..rules import SyntaxRuleCollection from ..settings import get_merged_plugin_setting, get_merged_plugin_settings, pref_trim_suffixes from ..shared import G -from ..snapshot import ViewSnapshot, ViewSnapshotCollection +from ..snapshot import ViewSnapshot from ..types import ListenerEvent from ..utils import ( find_syntax_by_syntax_like, @@ -147,11 +147,11 @@ def _view_snapshot_context(view: sublime.View) -> Generator[ViewSnapshot, None, try: settings.set(VIEW_RUN_ID_SETTINGS_KEY, run_id) - ViewSnapshotCollection.add(run_id, view) - yield ViewSnapshotCollection.get(run_id) # type: ignore + G.view_snapshot_collection.add(run_id, view) + yield G.view_snapshot_collection.get(run_id) # type: ignore finally: settings.erase(VIEW_RUN_ID_SETTINGS_KEY) - ViewSnapshotCollection.pop(run_id) + G.view_snapshot_collection.pop(run_id) def _snapshot_view(failed_ret: Any = None) -> Callable[[_T_Callable], _T_Callable]: @@ -184,7 +184,7 @@ def run_auto_set_syntax_on_view( if not ( (window := view.window()) and is_syntaxable_view(view, must_plaintext) - and (syntax_rule_collection := G.get_syntax_rule_collection(window)) + and (syntax_rule_collection := G.syntax_rule_collections.get(window)) ): return False @@ -258,7 +258,7 @@ def _assign_syntax_for_new_view(view: sublime.View, event: ListenerEvent | None def _assign_syntax_for_st_syntax_test(view: sublime.View, event: ListenerEvent | None = None) -> bool: if ( - (view_snapshot := ViewSnapshotCollection.get_by_view(view)) + (view_snapshot := G.view_snapshot_collection.get_by_view(view)) and (not view_snapshot.syntax or is_plaintext_syntax(view_snapshot.syntax)) and (m := RE_ST_SYNTAX_TEST_LINE.search(view_snapshot.first_line)) and (new_syntax := m.group("syntax")).endswith(".sublime-syntax") @@ -288,7 +288,7 @@ def _assign_syntax_with_plugin_rules( def _assign_syntax_with_first_line(view: sublime.View, event: ListenerEvent | None = None) -> bool: - if not (view_snapshot := ViewSnapshotCollection.get_by_view(view)): + if not (view_snapshot := G.view_snapshot_collection.get_by_view(view)): return False # Note that this only works for files under some circumstances. @@ -359,7 +359,7 @@ def _assign_syntax_with_trimmed_filename(view: sublime.View, event: ListenerEven def _assign_syntax_with_special_cases(view: sublime.View, event: ListenerEvent | None = None) -> bool: if not ( - (view_snapshot := ViewSnapshotCollection.get_by_view(view)) + (view_snapshot := G.view_snapshot_collection.get_by_view(view)) and view_snapshot.syntax and is_plaintext_syntax(view_snapshot.syntax) ): @@ -392,7 +392,7 @@ def is_json(view: sublime.View) -> bool: def _assign_syntax_with_guesslang_async(view: sublime.View, event: ListenerEvent | None = None) -> None: if not ( G.guesslang_client - and (view_snapshot := ViewSnapshotCollection.get_by_view(view)) + and (view_snapshot := G.view_snapshot_collection.get_by_view(view)) # don't apply on those have an extension and (event == ListenerEvent.COMMAND or "." not in view_snapshot.file_name_unhidden) # only apply on plain text syntax diff --git a/plugin/commands/auto_set_syntax_debug_information.py b/plugin/commands/auto_set_syntax_debug_information.py index c4ddc29c..c816173b 100644 --- a/plugin/commands/auto_set_syntax_debug_information.py +++ b/plugin/commands/auto_set_syntax_debug_information.py @@ -63,8 +63,8 @@ def run(self) -> None: }, } info["plugin_settings"] = get_merged_plugin_settings(window=self.window) - info["syntax_rule_collection"] = G.get_syntax_rule_collection(self.window) - info["dropped_rules"] = G.get_dropped_rules(self.window) + info["syntax_rule_collection"] = G.syntax_rule_collections.get(self.window) + info["dropped_rules"] = G.dropped_rules_collection.get(self.window, []) msg = TEMPLATE.format_map(_pythonize(info)) diff --git a/plugin/listener.py b/plugin/listener.py index 660aba9b..78bc33ac 100644 --- a/plugin/listener.py +++ b/plugin/listener.py @@ -39,8 +39,8 @@ def set_up_window(window: sublime.Window) -> None: def tear_down_window(window: sublime.Window) -> None: - G.clear_syntax_rule_collection(window) - G.clear_dropped_rules(window) + G.syntax_rule_collections.pop(window, None) + G.dropped_rules_collection.pop(window, None) Logger.log("👋 Bye!", window=window) Logger.destroy(window=window) @@ -59,11 +59,11 @@ def names_as_str(items: Iterable[Any], *, sep: str = ", ") -> str: Logger.log(f'🔍 Found "Constraint" implementations: {names_as_str(get_constraints())}', window=window) syntax_rule_collection = SyntaxRuleCollection.make(pref_syntax_rules(window=window)) - G.set_syntax_rule_collection(window, syntax_rule_collection) + G.syntax_rule_collections[window] = syntax_rule_collection Logger.log(f"📜 Compiled syntax rule collection: {stringify(syntax_rule_collection)}", window=window) - dropped_rules = tuple(syntax_rule_collection.optimize()) - G.set_dropped_rules(window, dropped_rules) + dropped_rules = list(syntax_rule_collection.optimize()) + G.dropped_rules_collection[window] = dropped_rules Logger.log(f"✨ Optimized syntax rule collection: {stringify(syntax_rule_collection)}", window=window) Logger.log(f"💀 Dropped rules during optimizing: {stringify(dropped_rules)}", window=window) diff --git a/plugin/rules/constraint.py b/plugin/rules/constraint.py index 8efeed46..08528f34 100644 --- a/plugin/rules/constraint.py +++ b/plugin/rules/constraint.py @@ -10,7 +10,8 @@ from ..cache import clearable_lru_cache from ..constants import PLUGIN_NAME, ST_PLATFORM -from ..snapshot import ViewSnapshot, ViewSnapshotCollection +from ..shared import G +from ..snapshot import ViewSnapshot from ..types import Optimizable, ST_ConstraintRule from ..utils import ( camel_to_snake, @@ -164,7 +165,7 @@ def _handled_case_insensitive(kwargs: dict[str, Any]) -> bool: @staticmethod def get_view_snapshot(view: sublime.View) -> ViewSnapshot: """Gets the cached information for the `view`.""" - snapshot = ViewSnapshotCollection.get_by_view(view) + snapshot = G.view_snapshot_collection.get_by_view(view) assert snapshot # our workflow guarantees this won't be None return snapshot @@ -176,8 +177,10 @@ def find_parent_with_sibling(base: str | Path, sibling: str, *, use_exists: bool if use_exists: checker = Path.exists + elif sibling.endswith(("\\", "/")): + checker = Path.is_dir # type: ignore else: - checker = Path.is_dir if sibling.endswith(("\\", "/")) else Path.is_file + checker = Path.is_file # type: ignore return first_true(path.parents, pred=lambda p: checker(p / sibling)) diff --git a/plugin/shared.py b/plugin/shared.py index 915d25fa..43e532af 100644 --- a/plugin/shared.py +++ b/plugin/shared.py @@ -1,21 +1,37 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING, Iterable, List import sublime from .guesslang.server import GuesslangServer from .settings import get_merged_plugin_settings -from .types import Optimizable +from .snapshot import ViewSnapshotCollection +from .types import Optimizable, WindowKeyedDict if TYPE_CHECKING: from .guesslang.client import GuesslangClient from .rules import SyntaxRuleCollection -WindowId = int -DroppedRules = Tuple[Optimizable, ...] +DroppedRules = List[Optimizable] DroppedRulesArg = Iterable[Optimizable] +# `UserDict` is not subscriptable until Python 3.9... +if TYPE_CHECKING: + _WindowKeyedDict_DroppedRules = WindowKeyedDict[DroppedRules] + _WindowKeyedDict_SyntaxRuleCollection = WindowKeyedDict[SyntaxRuleCollection] +else: + _WindowKeyedDict_DroppedRules = WindowKeyedDict + _WindowKeyedDict_SyntaxRuleCollection = WindowKeyedDict + + +class DroppedRulesCollection(_WindowKeyedDict_DroppedRules): + pass + + +class SyntaxRuleCollections(_WindowKeyedDict_SyntaxRuleCollection): + pass + class G: """This class holds "G"lobal variables as its class variables.""" @@ -29,36 +45,15 @@ class G: startup_views: set[sublime.View] = set() """Views exist before this plugin is loaded when Sublime Text just starts.""" - windows_syntax_rule_collection: dict[WindowId, SyntaxRuleCollection] = {} - """(Per window) The compiled top-level plugin rules.""" + syntax_rule_collections = SyntaxRuleCollections() + """The compiled per-window top-level plugin rules.""" - windows_dropped_rules: dict[WindowId, DroppedRules] = {} - """(Per window) Those rules which are dropped after doing optimizations.""" + dropped_rules_collection = DroppedRulesCollection() + """Those per-window rules which are dropped after doing optimizations.""" - @classmethod - def is_plugin_ready(cls, window: sublime.Window) -> bool: - return bool(get_merged_plugin_settings(window=window) and cls.get_syntax_rule_collection(window)) - - @classmethod - def set_syntax_rule_collection(cls, window: sublime.Window, value: SyntaxRuleCollection) -> None: - cls.windows_syntax_rule_collection[window.id()] = value + view_snapshot_collection = ViewSnapshotCollection() + """Caches of view attributes.""" @classmethod - def get_syntax_rule_collection(cls, window: sublime.Window) -> SyntaxRuleCollection | None: - return cls.windows_syntax_rule_collection.get(window.id()) - - @classmethod - def clear_syntax_rule_collection(cls, window: sublime.Window) -> SyntaxRuleCollection | None: - return cls.windows_syntax_rule_collection.pop(window.id(), None) - - @classmethod - def set_dropped_rules(cls, window: sublime.Window, value: DroppedRulesArg) -> None: - cls.windows_dropped_rules[window.id()] = tuple(value) - - @classmethod - def get_dropped_rules(cls, window: sublime.Window) -> DroppedRules: - return cls.windows_dropped_rules.get(window.id()) or tuple() - - @classmethod - def clear_dropped_rules(cls, window: sublime.Window) -> DroppedRules | None: - return cls.windows_dropped_rules.pop(window.id(), None) + def is_plugin_ready(cls, window: sublime.Window) -> bool: + return bool(get_merged_plugin_settings(window=window) and cls.syntax_rule_collections.get(window)) diff --git a/plugin/snapshot.py b/plugin/snapshot.py index 6e99add7..9e427bb6 100644 --- a/plugin/snapshot.py +++ b/plugin/snapshot.py @@ -1,7 +1,9 @@ from __future__ import annotations +from collections import UserDict from dataclasses import dataclass from pathlib import Path +from typing import TYPE_CHECKING import sublime @@ -34,26 +36,30 @@ class ViewSnapshot: """The syntax object. Note that the value is as-is when it's cached.""" -class ViewSnapshotCollection: - _snapshots: dict[str, ViewSnapshot] = {} +# `UserDict` is not subscriptable until Python 3.9... +if TYPE_CHECKING: - @classmethod - def add(cls, cache_id: str, view: sublime.View) -> None: - window = view.window() or sublime.active_window() + class ViewSnapshotCollection(UserDict[str, ViewSnapshot]): + def add(self, cache_id: str, view: sublime.View) -> None: ... + def get_by_view(self, view: sublime.View) -> ViewSnapshot | None: ... - # is real file on a disk? - if (_path := view.file_name()) and (path := Path(_path).resolve()).is_file(): - file_name = path.name - file_path = path.as_posix() - file_size = path.stat().st_size - else: - file_name = "" - file_path = "" - file_size = -1 +else: - cls.set( - cache_id, - ViewSnapshot( + class ViewSnapshotCollection(UserDict): + def add(self, cache_id: str, view: sublime.View) -> None: + 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(): + file_name = path.name + file_path = path.as_posix() + file_size = path.stat().st_size + else: + file_name = "" + file_path = "" + file_size = -1 + + self[cache_id] = ViewSnapshot( id=view.id(), char_count=view.size(), content=get_view_pseudo_content(view, window), @@ -64,24 +70,10 @@ def add(cls, cache_id: str, view: sublime.View) -> None: first_line=get_view_pseudo_first_line(view, window), line_count=view.rowcol(view.size())[0] + 1, syntax=view.syntax(), - ), - ) - - @classmethod - def get(cls, cache_id: str) -> ViewSnapshot | None: - return cls._snapshots.get(cache_id, None) - - @classmethod - def get_by_view(cls, view: sublime.View) -> ViewSnapshot | None: - return cls.get(view.settings().get(VIEW_RUN_ID_SETTINGS_KEY)) - - @classmethod - def set(cls, cache_id: str, snapshot: ViewSnapshot) -> None: - cls._snapshots[cache_id] = snapshot + ) - @classmethod - def pop(cls, cache_id: str) -> ViewSnapshot | None: - return cls._snapshots.pop(cache_id, None) + def get_by_view(self, view: sublime.View) -> ViewSnapshot | None: + return self.get(view.settings().get(VIEW_RUN_ID_SETTINGS_KEY)) def get_view_pseudo_content(view: sublime.View, window: sublime.Window) -> str: diff --git a/plugin/types.py b/plugin/types.py index 2013e061..ccc78c2c 100644 --- a/plugin/types.py +++ b/plugin/types.py @@ -1,13 +1,18 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections import UserDict from dataclasses import dataclass from enum import Enum -from typing import Any, Generator, TypedDict, Union +from typing import TYPE_CHECKING, Any, Generator, KeysView, TypedDict, TypeVar, Union import sublime SyntaxLike = Union[str, sublime.Syntax] +WindowId = int +WindowIdAble = Union[WindowId, sublime.Window] + +_T = TypeVar("_T") class ListenerEvent(Enum): @@ -76,6 +81,40 @@ class ST_SyntaxRule(ST_MatchRule): on_events: str | list[str] | None +# `UserDict` is not subscriptable until Python 3.9... +if TYPE_CHECKING: + + class WindowKeyedDict(UserDict[WindowIdAble, _T]): + def __setitem__(self, key: WindowIdAble, value: _T) -> None: ... + def __getitem__(self, key: WindowIdAble) -> _T: ... + def __delitem__(self, key: WindowIdAble) -> None: ... + def keys(self) -> KeysView[WindowId]: ... + @staticmethod + def _to_window_id(value: WindowIdAble) -> WindowId: ... + +else: + + class WindowKeyedDict(UserDict): + def __setitem__(self, key: WindowIdAble, value: _T) -> None: + key = self._to_window_id(key) + super().__setitem__(key, value) + + def __getitem__(self, key: WindowIdAble) -> _T: + key = self._to_window_id(key) + return super().__getitem__(key) + + def __delitem__(self, key: WindowIdAble) -> None: + key = self._to_window_id(key) + super().__delitem__(key) + + def keys(self) -> KeysView[WindowId]: + return super().keys() + + @staticmethod + def _to_window_id(value: WindowIdAble) -> WindowId: + return value.id() if isinstance(value, sublime.Window) else value + + @dataclass class SemanticVersion: major: int