From 2afa90fea0732e2d3f64658ae9001ff851139156 Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Tue, 17 Sep 2024 15:22:03 +0800 Subject: [PATCH] feat: also works on init views Useful when opening a file from the context menu with ST hasn't opened yet. Signed-off-by: Jack Cherng --- plugin/__init__.py | 3 +- plugin/listener.py | 146 ++++++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 69 deletions(-) diff --git a/plugin/__init__.py b/plugin/__init__.py index b0f1782..db371fa 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -1,11 +1,12 @@ # import all listeners and commands -from .listener import FollowLnkViewEventListener +from .listener import FollowLnkEventListener, FollowLnkViewEventListener __all__ = ( # ST: core "plugin_loaded", "plugin_unloaded", # ST: listeners + "FollowLnkEventListener", "FollowLnkViewEventListener", ) diff --git a/plugin/listener.py b/plugin/listener.py index 76c42fa..09218bd 100644 --- a/plugin/listener.py +++ b/plugin/listener.py @@ -13,83 +13,93 @@ from .libs.LnkParse3.lnk_file import LnkFile +class FollowLnkEventListener(sublime_plugin.EventListener): + @classmethod + def is_applicable(cls, settings: sublime.Settings) -> bool: + return sublime.platform() == "windows" + + def on_init(self, views: list[sublime.View]) -> None: + for view in views: + _handle_view(view) + + class FollowLnkViewEventListener(sublime_plugin.ViewEventListener): @classmethod def is_applicable(cls, settings: sublime.Settings) -> bool: return sublime.platform() == "windows" def on_load(self) -> None: - if not ( - (window := self.view.window()) - and (path := self.view.file_name()) - and (target_path := self._resolve_lnk(path)) - ): - return - - if os.path.isfile(target_path): - view_group = window.get_view_index(self.view)[0] - self.view.close() - window.open_file(target_path, 0, view_group) - return - - if os.path.isdir(target_path): - project = window.project_data() or {} - project.setdefault("folders", []) - if target_path not in project["folders"]: - project["folders"].append({"path": target_path}) - window.set_project_data(project) - self.view.close() - return - - raise RuntimeError(f"[{PLUGIN_NAME}] Uh, what's this: {path = }; {target_path = }") + _handle_view(self.view) - @classmethod - def _resolve_lnk(cls, path: str) -> str | None: - if not (lnk_file := cls._make_lnk_file(path)): - return None - # print(f"[DEBUG] {lnk_file.get_json() = }") +def _handle_view(view: sublime.View) -> None: + if not ((window := view.window()) and (path := view.file_name()) and (target_path := _resolve_lnk(path))): + return + + if os.path.isfile(target_path): + view_group = window.get_view_index(view)[0] + view.close() + window.open_file(target_path, 0, view_group) + return + + if os.path.isdir(target_path): + project = window.project_data() or {} + project.setdefault("folders", []) + if target_path not in project["folders"]: + project["folders"].append({"path": target_path}) + window.set_project_data(project) + view.close() + return + + raise RuntimeError(f"[{PLUGIN_NAME}] Uh, what's this: {path = }; {target_path = }") + + +def _resolve_lnk(path: str) -> str | None: + if not (lnk_file := _make_lnk_file(path)): + return None + # print(f"[DEBUG] {lnk_file.get_json() = }") + + try: + return first_true(_list_lnk_targets(lnk_file, lnk_path=path)) + except Exception: + return None + + +def _list_lnk_targets(lnk_file: LnkFile, *, lnk_path: str | None = None) -> Generator[str, None, None]: + string_data_dict = lnk_file.string_data.as_dict() + + # from property: relative path + if lnk_path and lnk_file.has_relative_path(): + path = Path(lnk_path).parent / string_data_dict["relative_path"] + yield str(path.resolve()) + + # from metadata + for extra in lnk_file.extras: + if isinstance(extra, Metadata): + for store in extra.property_store(): + assert isinstance(store, SerializedPropertyStorage) + if store.format_id() == "28636AA6-953D-11D2-B5D6-00C04FD918D0": + for prop in store.serialized_property_values(): + if isinstance(prop, SerializedPropertyValueIntegerName) and prop.id() == 30: + yield prop.value().value() # type: ignore + + # from info + if lnk_file.info: + try: + if target := lnk_file.info.local_base_path_unicode(): + yield target + except Exception: + pass try: - return first_true(cls._list_lnk_targets(lnk_file, lnk_path=path)) + if target := lnk_file.info.local_base_path(): + yield target except Exception: - return None + pass - @classmethod - def _list_lnk_targets(cls, lnk_file: LnkFile, *, lnk_path: str | None = None) -> Generator[str, None, None]: - string_data_dict = lnk_file.string_data.as_dict() - - # from property: relative path - if lnk_path and lnk_file.has_relative_path(): - path = Path(lnk_path).parent / string_data_dict["relative_path"] - yield str(path.resolve()) - - # from metadata - for extra in lnk_file.extras: - if isinstance(extra, Metadata): - for store in extra.property_store(): - assert isinstance(store, SerializedPropertyStorage) - if store.format_id() == "28636AA6-953D-11D2-B5D6-00C04FD918D0": - for prop in store.serialized_property_values(): - if isinstance(prop, SerializedPropertyValueIntegerName) and prop.id() == 30: - yield prop.value().value() # type: ignore - - # from info - if lnk_file.info: - try: - if target := lnk_file.info.local_base_path_unicode(): - yield target - except Exception: - pass - try: - if target := lnk_file.info.local_base_path(): - yield target - except Exception: - pass - - @staticmethod - def _make_lnk_file(path: str) -> LnkFile | None: - if path.lower().endswith(".lnk"): - with open(path, "rb") as indata: - return LnkFile(indata) - return None + +def _make_lnk_file(path: str) -> LnkFile | None: + if path.lower().endswith(".lnk"): + with open(path, "rb") as indata: + return LnkFile(indata) + return None