From 9fd53a5ed390a40ddf6db006a22aee23a7848eb4 Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Sat, 18 Feb 2023 01:02:21 +0800 Subject: [PATCH] refactor: tidy codes Signed-off-by: Jack Cherng --- boot.py | 2 +- plugin/__init__.py | 18 ++-- plugin/commands/abstract.py | 2 +- plugin/commands/open_uri.py | 2 +- plugin/{constant.py => constants.py} | 0 plugin/{functions.py => helpers.py} | 89 +-------------- plugin/listener.py | 11 +- plugin/logger.py | 8 +- plugin/renderer.py | 56 +++++++--- plugin/settings.py | 111 +++++++++---------- plugin/shared.py | 22 ++-- plugin/timer.py | 32 ------ plugin/types.py | 13 ++- plugin/{image_processing.py => ui/image.py} | 27 +++-- plugin/{ => ui}/phantom_set.py | 10 +- plugin/{ => ui}/phatom_sets_manager.py | 0 plugin/{ => ui}/popup.py | 10 +- plugin/{ => ui}/region_drawing.py | 3 +- plugin/utils.py | 113 ++++++++++++++------ 19 files changed, 233 insertions(+), 296 deletions(-) rename plugin/{constant.py => constants.py} (100%) rename plugin/{functions.py => helpers.py} (71%) delete mode 100644 plugin/timer.py rename plugin/{image_processing.py => ui/image.py} (91%) rename plugin/{ => ui}/phantom_set.py (90%) rename plugin/{ => ui}/phatom_sets_manager.py (100%) rename plugin/{ => ui}/popup.py (84%) rename plugin/{ => ui}/region_drawing.py (90%) diff --git a/boot.py b/boot.py index 1b1d110..3789be5 100644 --- a/boot.py +++ b/boot.py @@ -1,7 +1,7 @@ def reload_plugin() -> None: import sys - # Remove all previously loaded plugin modules. + # remove all previously loaded plugin modules. prefix = f"{__package__}." for module_name in tuple(filter(lambda m: m.startswith(prefix) and m != __name__, sys.modules)): del sys.modules[module_name] diff --git a/plugin/__init__.py b/plugin/__init__.py index 66b1fc4..b7ba319 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -1,18 +1,16 @@ -import sublime - # import all listeners and commands from .commands.copy_uri import CopyUriFromContextMenuCommand, CopyUriFromCursorsCommand, CopyUriFromViewCommand from .commands.open_uri import OpenUriFromCursorsCommand, OpenUriFromViewCommand from .commands.select_uri import SelectUriFromCursorsCommand, SelectUriFromViewCommand -from .constant import PLUGIN_NAME -from .functions import compile_uri_regex, view_is_dirty_val +from .constants import PLUGIN_NAME +from .helpers import compile_uri_regex from .listener import OpenUriViewEventListener from .logger import apply_user_log_level, init_plugin_logger, log -from .phatom_sets_manager import PhatomSetsManager from .renderer import RendererThread from .settings import get_image_info, get_setting_renderer_interval, get_settings_object from .shared import global_get, global_set -from .utils import is_processable_view +from .ui.phatom_sets_manager import PhatomSetsManager +from .utils import is_processable_view, list_all_views, view_is_dirty_val __all__ = ( # ST: core @@ -72,8 +70,6 @@ def _set_is_dirty_for_all_views(is_dirty: bool) -> None: @param is_dirty Indicate if views are dirty """ - - for w in sublime.windows(): - for v in w.views(): - if is_processable_view(v): - view_is_dirty_val(v, is_dirty) + for view in list_all_views(): + if is_processable_view(view): + view_is_dirty_val(view, is_dirty) diff --git a/plugin/commands/abstract.py b/plugin/commands/abstract.py index 2459b91..e341450 100644 --- a/plugin/commands/abstract.py +++ b/plugin/commands/abstract.py @@ -5,7 +5,7 @@ import sublime import sublime_plugin -from ..functions import find_uri_regions_by_regions +from ..helpers import find_uri_regions_by_regions from ..shared import is_plugin_ready from ..types import EventDict, RegionLike diff --git a/plugin/commands/open_uri.py b/plugin/commands/open_uri.py index ed61b45..8ede772 100644 --- a/plugin/commands/open_uri.py +++ b/plugin/commands/open_uri.py @@ -3,7 +3,7 @@ import sublime -from ..functions import open_uri_with_browser +from ..helpers import open_uri_with_browser from ..types import EventDict from .abstract import AbstractUriCommand, UriSource diff --git a/plugin/constant.py b/plugin/constants.py similarity index 100% rename from plugin/constant.py rename to plugin/constants.py diff --git a/plugin/functions.py b/plugin/helpers.py similarity index 71% rename from plugin/functions.py rename to plugin/helpers.py index 2a7e035..2486fbd 100644 --- a/plugin/functions.py +++ b/plugin/helpers.py @@ -1,13 +1,13 @@ import re import urllib.parse as urllib_parse import webbrowser -from typing import Any, Dict, Iterable, List, Optional, Pattern, Tuple, overload +from typing import Any, Dict, Iterable, List, Optional, Pattern, Tuple import sublime from .libs import triegex from .logger import log -from .settings import get_setting, get_timestamp +from .settings import get_setting from .shared import global_get from .types import RegionLike from .utils import convert_to_st_region, is_regions_intersected, merge_regions, region_expand, region_shift @@ -20,7 +20,6 @@ def open_uri_with_browser(uri: str, browser: Optional[str] = "") -> None: @param uri The uri @param browser The browser """ - parsed_uri = urllib_parse.urlparse(uri) log("debug", f"Parsed URI: {parsed_uri}") @@ -50,7 +49,6 @@ def compile_uri_regex() -> Tuple[Optional[Pattern[str]], Tuple[str, ...]]: @return (compiled regex object, activated schemes) """ - detect_schemes: Dict[str, Dict[str, Any]] = get_setting("detect_schemes") uri_path_regexes: Dict[str, str] = get_setting("uri_path_regexes") @@ -109,7 +107,6 @@ def find_uri_regions_by_region( @return Found URI regions """ - return find_uri_regions_by_regions(view, (region,), search_radius) @@ -126,7 +123,6 @@ def find_uri_regions_by_regions( @return Found URI regions """ - st_regions = sorted(convert_to_st_region(region, sort=True) for region in regions) search_regions = merge_regions( ( @@ -163,84 +159,3 @@ def find_uri_regions_by_regions( break return uri_regions_intersected - - -@overload -def view_last_typing_timestamp_val(view: sublime.View) -> float: - ... - - -@overload -def view_last_typing_timestamp_val(view: sublime.View, timestamp_s: float) -> None: - ... - - -def view_last_typing_timestamp_val(view: sublime.View, timestamp_s: Optional[float] = None) -> Optional[float]: - """ - @brief Set/Get the last timestamp (in sec) when "OUIB_uri_regions" is updated - - @param view The view - @param timestamp_s The last timestamp (in sec) - - @return None if the set mode, otherwise the value - """ - - if timestamp_s is None: - return float(view.settings().get("OUIB_last_update_timestamp", 0)) - - view.settings().set("OUIB_last_update_timestamp", timestamp_s) - return None - - -@overload -def view_is_dirty_val(view: sublime.View) -> bool: - ... - - -@overload -def view_is_dirty_val(view: sublime.View, is_dirty: bool) -> None: - ... - - -def view_is_dirty_val(view: sublime.View, is_dirty: Optional[bool] = None) -> Optional[bool]: - """ - @brief Set/Get the is_dirty of the current view - - @param view The view - @param is_dirty Indicates if dirty - - @return None if the set mode, otherwise the is_dirty - """ - - if is_dirty is None: - return bool(view.settings().get("OUIB_is_dirty", True)) - - view.settings().set("OUIB_is_dirty", is_dirty) - return None - - -def is_view_typing(view: sublime.View) -> bool: - """ - @brief Determine if the view typing. - - @param view The view - - @return `True` if the view is typing, `False` otherwise. - """ - - now_s = get_timestamp() - last_typing_s = view_last_typing_timestamp_val(view) or 0 - - return (now_s - last_typing_s) * 1000 < get_setting("typing_period") - - -def is_view_too_large(view: sublime.View) -> bool: - """ - @brief Determine if the view is too large. Note that size will be `0` if the view is loading. - - @param view The view - - @return `True` if the view is too large, `False` otherwise. - """ - - return view.size() > get_setting("large_file_threshold") diff --git a/plugin/listener.py b/plugin/listener.py index 6ecd644..995b359 100644 --- a/plugin/listener.py +++ b/plugin/listener.py @@ -3,11 +3,12 @@ import sublime import sublime_plugin -from .functions import find_uri_regions_by_region, view_is_dirty_val, view_last_typing_timestamp_val -from .phantom_set import delete_phantom_set, init_phantom_set -from .popup import show_popup -from .region_drawing import draw_uri_regions -from .settings import get_setting, get_setting_show_open_button, get_timestamp +from .helpers import find_uri_regions_by_region +from .settings import get_setting, get_setting_show_open_button +from .ui.phantom_set import delete_phantom_set, init_phantom_set +from .ui.popup import show_popup +from .ui.region_drawing import draw_uri_regions +from .utils import get_timestamp, view_is_dirty_val, view_last_typing_timestamp_val class OpenUriViewEventListener(sublime_plugin.ViewEventListener): diff --git a/plugin/logger.py b/plugin/logger.py index 3a025d3..e3c54f3 100644 --- a/plugin/logger.py +++ b/plugin/logger.py @@ -12,8 +12,7 @@ def init_plugin_logger() -> logging.Logger: @return The initiated plugin logger. """ - - from .constant import PLUGIN_NAME + from .constants import PLUGIN_NAME def set_logger_hander(logger: logging.Logger) -> None: # remove all existing log handlers @@ -39,7 +38,6 @@ def apply_user_log_level(logger: logging.Logger) -> None: @param logger The logger """ - from .settings import get_setting log_level = get_setting("log_level").upper() @@ -61,7 +59,6 @@ def log(level: str, msg: str) -> None: @param level The log level @param msg The message """ - level_upper = level.upper() level_int = logging.getLevelName(level_upper) @@ -79,7 +76,6 @@ def msg(msg: str) -> str: @return The plugin message. """ - - from .constant import PLUGIN_NAME + from .constants import PLUGIN_NAME return f"[{PLUGIN_NAME}] {msg}" diff --git a/plugin/renderer.py b/plugin/renderer.py index 22911a6..89c515a 100644 --- a/plugin/renderer.py +++ b/plugin/renderer.py @@ -1,22 +1,44 @@ -from typing import Generator +import threading +from typing import Any, Callable, Optional import sublime -from .functions import is_view_too_large, is_view_typing, view_is_dirty_val from .logger import log -from .phantom_set import erase_phantom_set, update_phantom_set -from .region_drawing import draw_uri_regions, erase_uri_regions -from .settings import get_setting, get_setting_show_open_button +from .settings import get_setting, get_setting_show_open_button, is_view_too_large, is_view_typing from .shared import global_get -from .timer import RepeatingTimer -from .utils import is_processable_view, is_transient_view, view_find_all +from .ui.phantom_set import erase_phantom_set, update_phantom_set +from .ui.region_drawing import draw_uri_regions, erase_uri_regions +from .utils import is_processable_view, is_transient_view, list_foreground_views, view_find_all, view_is_dirty_val -def foreground_views() -> Generator[sublime.View, None, None]: - for window in sublime.windows(): - for group_idx in range(window.num_groups()): - if view := window.active_view_in_group(group_idx): - yield view +class RepeatingTimer: + def __init__(self, interval_ms: int, func: Callable, *args: Any, **kwargs: Any) -> None: + self.interval_s = interval_ms / 1000 + self.timer: Optional[threading.Timer] = None + self.is_running = False + self.set_func(func, *args, **kwargs) + + def set_func(self, func: Callable, *args: Any, **kwargs: Any) -> None: + self.func = func + self.args = args + self.kwargs = kwargs + + def set_interval(self, interval_ms: int) -> None: + self.interval_s = interval_ms / 1000 + + def start(self) -> None: + self.timer = threading.Timer(self.interval_s, self._callback) + self.timer.start() + self.is_running = True + + def cancel(self) -> None: + if self.timer: + self.timer.cancel() + self.is_running = False + + def _callback(self) -> None: + self.func(*self.args, **self.kwargs) + self.start() class RendererThread(RepeatingTimer): @@ -24,16 +46,16 @@ def __init__(self, interval_ms: int = 1000) -> None: super().__init__(interval_ms, self._update_foreground_views) # to prevent from overlapped processes when using a low interval - self.is_rendering = False + self._is_rendering = False def _update_foreground_views(self) -> None: - if self.is_rendering: + if self._is_rendering: return - self.is_rendering = True - for view in foreground_views(): + self._is_rendering = True + for view in list_foreground_views(): self._update_view(view) - self.is_rendering = False + self._is_rendering = False def _update_view(self, view: sublime.View) -> None: if ( diff --git a/plugin/settings.py b/plugin/settings.py index 574f8d7..745b188 100644 --- a/plugin/settings.py +++ b/plugin/settings.py @@ -1,20 +1,21 @@ import base64 -import os import tempfile -import time +from pathlib import Path from typing import Any, Dict, Optional import sublime -from .constant import PLUGIN_NAME, SETTINGS_FILE_NAME +from .constants import PLUGIN_NAME, SETTINGS_FILE_NAME from .libs import imagesize from .logger import log +from .shared import global_get from .types import ImageDict +from .utils import get_timestamp, view_last_typing_timestamp_val def get_expanding_variables(window: Optional[sublime.Window]) -> Dict[str, str]: variables: Dict[str, Any] = { - "home": os.path.expanduser("~"), + "home": str(Path.home()), "package_name": PLUGIN_NAME, "package_path": f"Packages/{PLUGIN_NAME}", "temp_dir": tempfile.gettempdir(), @@ -26,6 +27,27 @@ def get_expanding_variables(window: Optional[sublime.Window]) -> Dict[str, str]: return variables +def get_settings_object() -> sublime.Settings: + """ + @brief Get the plugin settings object. This function will call `sublime.load_settings()`. + + @return The settings object. + """ + return sublime.load_settings(SETTINGS_FILE_NAME) + + +def get_setting(dotted: str, default: Optional[Any] = None) -> Any: + """ + @brief Get the plugin setting with the dotted key. + + @param dotted The dotted key + @param default The default value if the key doesn't exist + + @return The setting's value. + """ + return global_get(f"settings.{dotted}", default) + + def get_image_path(img_name: str) -> str: """ @brief Get the image resource path from plugin settings. @@ -34,7 +56,6 @@ def get_image_path(img_name: str) -> str: @return The image resource path. """ - return sublime.expand_variables( get_setting("image_files")[img_name], get_expanding_variables(sublime.active_window()), @@ -50,7 +71,7 @@ def get_image_info(img_name: str) -> ImageDict: @return The image information. """ img_path = get_image_path(img_name) - img_ext = os.path.splitext(img_path)[1] + img_ext = Path(img_path).suffix img_mime = "image/png" assert img_ext.lower() == ".png" @@ -75,63 +96,12 @@ def get_image_info(img_name: str) -> ImageDict: } -def get_image_color(img_name: str, region: sublime.Region) -> str: - """ - @brief Get the image color from plugin settings in the form of #RRGGBBAA. - - @param img_name The image name - @param region The region - - @return The color code in the form of #RRGGBBAA - """ - - from .image_processing import color_code_to_rgba - - return color_code_to_rgba(get_setting("image_colors")[img_name], region) - - -def get_settings_object() -> sublime.Settings: - """ - @brief Get the plugin settings object. This function will call `sublime.load_settings()`. - - @return The settings object. - """ - - return sublime.load_settings(SETTINGS_FILE_NAME) - - -def get_setting(dotted: str, default: Optional[Any] = None) -> Any: - """ - @brief Get the plugin setting with the dotted key. - - @param dotted The dotted key - @param default The default value if the key doesn't exist - - @return The setting's value. - """ - - from .shared import global_get - - return global_get(f"settings.{dotted}", default) - - -def get_timestamp() -> float: - """ - @brief Get the current timestamp (in second). - - @return The timestamp. - """ - - return time.time() - - def get_setting_renderer_interval() -> int: """ @brief Get the renderer interval. @return The renderer interval. """ - if (interval := get_setting("renderer_interval", 250)) < 0: interval = float("inf") @@ -140,11 +110,34 @@ def get_setting_renderer_interval() -> int: def get_setting_show_open_button(view: sublime.View) -> str: - from .functions import is_view_too_large - return get_setting( # ... "show_open_button_fallback" if not view.is_loading() and is_view_too_large(view) else "show_open_button" ) + + +def is_view_too_large(view: sublime.View) -> bool: + """ + @brief Determine if the view is too large. Note that size will be `0` if the view is loading. + + @param view The view + + @return `True` if the view is too large, `False` otherwise. + """ + return view.size() > get_setting("large_file_threshold") + + +def is_view_typing(view: sublime.View) -> bool: + """ + @brief Determine if the view typing. + + @param view The view + + @return `True` if the view is typing, `False` otherwise. + """ + now_s = get_timestamp() + last_typing_s = view_last_typing_timestamp_val(view) or 0 + + return (now_s - last_typing_s) * 1000 < get_setting("typing_period") diff --git a/plugin/shared.py b/plugin/shared.py index 566d1ee..51ed928 100644 --- a/plugin/shared.py +++ b/plugin/shared.py @@ -4,36 +4,28 @@ import sublime +from .types import ImageDict from .utils import dotted_get, dotted_set class G: """This class stores application-level global variables.""" - # the plugin settings object settings: Optional[sublime.Settings] = None + """the plugin settings object""" - # the logger to log messages logger: Optional[logging.Logger] = None + """the logger to log messages""" - # the background thread for managing phantoms for views renderer_thread: Optional[threading.Thread] = None + """the background thread for managing phantoms for views""" activated_schemes: Tuple[str, ...] = tuple() uri_regex_obj: Optional[Pattern[str]] = None - images: Dict[str, Dict[str, Any]] = { - # image informations - # key/value structure is - # - "base64": "", - # - "bytes": b"", - # - "ext": "", - # - "mime": "", - # - "path": "", - # - "ratio_wh": 0, - # - "size": (0, 0), - "phantom": {}, - "popup": {}, + images: Dict[str, ImageDict] = { + "phantom": {}, # type: ignore + "popup": {}, # type: ignore } diff --git a/plugin/timer.py b/plugin/timer.py deleted file mode 100644 index 2ba6dd0..0000000 --- a/plugin/timer.py +++ /dev/null @@ -1,32 +0,0 @@ -import threading -from typing import Callable, Optional - - -class RepeatingTimer: - def __init__(self, interval_ms: int, func: Callable, *args, **kwargs) -> None: - self.interval_s = interval_ms / 1000 - self.timer: Optional[threading.Timer] = None - self.is_running = False - self.set_func(func, *args, **kwargs) - - def set_func(self, func: Callable, *args, **kwargs) -> None: - self.func = func - self.args = args - self.kwargs = kwargs - - def set_interval(self, interval_ms: int) -> None: - self.interval_s = interval_ms / 1000 - - def start(self) -> None: - self.timer = threading.Timer(self.interval_s, self._callback) - self.timer.start() - self.is_running = True - - def cancel(self) -> None: - if self.timer: - self.timer.cancel() - self.is_running = False - - def _callback(self) -> None: - self.func(*self.args, **self.kwargs) - self.start() diff --git a/plugin/types.py b/plugin/types.py index 6d6d562..0b83661 100644 --- a/plugin/types.py +++ b/plugin/types.py @@ -1,17 +1,16 @@ from __future__ import annotations -from typing import List, Tuple, TypedDict, Union +from typing import Any, Callable, List, Tuple, TypedDict, TypeVar, Union import sublime +T_AnyCallable = TypeVar("T_AnyCallable", bound=Callable[..., Any]) + RegionLike = Union[ sublime.Region, - # point - int, - # region in list form - List[int], - # region in tuple form - Tuple[int, int], + int, # point + List[int], # region in list form + Tuple[int, int], # region in tuple form ] diff --git a/plugin/image_processing.py b/plugin/ui/image.py similarity index 91% rename from plugin/image_processing.py rename to plugin/ui/image.py index eb82d38..0c86136 100644 --- a/plugin/image_processing.py +++ b/plugin/ui/image.py @@ -6,10 +6,22 @@ import sublime -from .libs import png -from .settings import get_image_color -from .shared import global_get -from .utils import simple_decorator +from ..libs import png +from ..settings import get_setting +from ..shared import global_get +from ..utils import simple_decorator + + +def get_image_color(img_name: str, region: sublime.Region) -> str: + """ + @brief Get the image color from plugin settings in the form of #RRGGBBAA. + + @param img_name The image name + @param region The region + + @return The color code in the form of #RRGGBBAA + """ + return color_code_to_rgba(get_setting("image_colors")[img_name], region) @lru_cache @@ -22,7 +34,6 @@ def get_colored_image_base64_by_color(img_name: str, rgba_code: str) -> str: @return The image base64 string """ - if not rgba_code: return global_get(f"images.{img_name}.base64") @@ -41,7 +52,6 @@ def get_colored_image_base64_by_region(img_name: str, region: sublime.Region) -> @return The image base64 string """ - return get_colored_image_base64_by_color(img_name, get_image_color(img_name, region)) @@ -55,7 +65,6 @@ def change_png_bytes_color(img_bytes: bytes, rgba_code: str) -> bytes: @return Color-changed PNG image bytes. """ - if not rgba_code: return img_bytes @@ -101,7 +110,6 @@ def calculate_gray(rgb: Sequence[int]) -> int: @return The gray scale. """ - return int(rgb[0] * 38 + rgb[1] * 75 + rgb[2] * 15) >> 7 @@ -113,7 +121,6 @@ def is_img_light(img_bytes: bytes) -> bool: @return True if image is light, False otherwise. """ - w, h, rows, img_info = png.Reader(bytes=img_bytes).asRGBA() gray_sum = 0 @@ -132,7 +139,6 @@ def add_alpha_to_rgb(color_code: str) -> str: @return The color code in the form of #RRGGBBAA """ - if not color_code: return "" @@ -155,7 +161,6 @@ def color_code_to_rgba(color_code: str, region: sublime.Region) -> str: @return The color code in the form of #RRGGBBAA """ - if not color_code: return "" diff --git a/plugin/phantom_set.py b/plugin/ui/phantom_set.py similarity index 90% rename from plugin/phantom_set.py rename to plugin/ui/phantom_set.py index 94cd2d6..4423817 100644 --- a/plugin/phantom_set.py +++ b/plugin/ui/phantom_set.py @@ -2,12 +2,12 @@ import sublime -from .constant import PLUGIN_NAME -from .functions import open_uri_with_browser -from .image_processing import get_colored_image_base64_by_region +from ..constants import PLUGIN_NAME +from ..helpers import open_uri_with_browser +from ..shared import global_get +from ..types import ImageDict +from .image import get_colored_image_base64_by_region from .phatom_sets_manager import PhatomSetsManager -from .shared import global_get -from .types import ImageDict PHANTOM_TEMPLATE = """ diff --git a/plugin/phatom_sets_manager.py b/plugin/ui/phatom_sets_manager.py similarity index 100% rename from plugin/phatom_sets_manager.py rename to plugin/ui/phatom_sets_manager.py diff --git a/plugin/popup.py b/plugin/ui/popup.py similarity index 84% rename from plugin/popup.py rename to plugin/ui/popup.py index 7b5e19b..87f223e 100644 --- a/plugin/popup.py +++ b/plugin/ui/popup.py @@ -1,10 +1,10 @@ import sublime -from .functions import open_uri_with_browser -from .image_processing import get_colored_image_base64_by_region -from .settings import get_setting -from .shared import global_get -from .types import ImageDict +from ..helpers import open_uri_with_browser +from ..settings import get_setting +from ..shared import global_get +from ..types import ImageDict +from .image import get_colored_image_base64_by_region POPUP_TEMPLATE = """ diff --git a/plugin/region_drawing.py b/plugin/ui/region_drawing.py similarity index 90% rename from plugin/region_drawing.py rename to plugin/ui/region_drawing.py index de6697e..97b1039 100644 --- a/plugin/region_drawing.py +++ b/plugin/ui/region_drawing.py @@ -4,7 +4,7 @@ import sublime -from .settings import get_setting +from ..settings import get_setting def erase_uri_regions(view: sublime.View) -> None: @@ -24,7 +24,6 @@ def draw_uri_regions(view: sublime.View, uri_regions: Iterable[sublime.Region]) def parse_draw_region_flags(flags: Union[int, Sequence[str]]) -> int: - # deprecated because it's not self-explanatory if isinstance(flags, int): return flags diff --git a/plugin/utils.py b/plugin/utils.py index 34a1e64..e797e46 100644 --- a/plugin/utils.py +++ b/plugin/utils.py @@ -1,38 +1,44 @@ import itertools -from typing import ( - Any, - Callable, - Generator, - Iterable, - List, - Optional, - Pattern, - Sequence, - Tuple, - TypeVar, - Union, - cast, - overload, -) +import time +from typing import Any, Callable, Generator, Iterable, List, Optional, Pattern, Sequence, Tuple, Union, cast, overload import sublime -from .constant import ST_SUPPORT_EXPAND_TO_SCOPE -from .types import RegionLike +from .constants import ST_SUPPORT_EXPAND_TO_SCOPE +from .types import RegionLike, T_AnyCallable -AnyCallable = TypeVar("AnyCallable", bound=Callable[..., Any]) +def get_timestamp() -> float: + return time.time() + + +def list_all_views(*, include_transient: bool = False) -> Generator[sublime.View, None, None]: + for window in sublime.windows(): + yield from window.views(include_transient=include_transient) + + +def list_background_views() -> Generator[sublime.View, None, None]: + foreground_views = set(list_foreground_views()) + for view in list_all_views(include_transient=True): + if view not in foreground_views: + yield view -def simple_decorator(decorator: Callable) -> Callable[[AnyCallable], AnyCallable]: - """ - @brief A decorator that turns a function into a decorator. - """ - def wrapper(decoratee: AnyCallable) -> AnyCallable: +def list_foreground_views() -> Generator[sublime.View, None, None]: + for window in sublime.windows(): + for group_idx in range(window.num_groups()): + if view := window.active_view_in_group(group_idx): + yield view + + +def simple_decorator(decorator: Callable) -> Callable[[T_AnyCallable], T_AnyCallable]: + """A decorator that turns a function into a decorator.""" + + def wrapper(decoratee: T_AnyCallable) -> T_AnyCallable: def wrapped(*args, **kwargs) -> Any: return decorator(decoratee(*args, **kwargs)) - return cast(AnyCallable, wrapped) + return cast(T_AnyCallable, wrapped) return wrapper @@ -47,7 +53,6 @@ def dotted_get(var: Any, dotted: str, default: Optional[Any] = None) -> Any: @return The value or the default if dotted not found """ - keys = dotted.split(".") try: @@ -72,7 +77,6 @@ def dotted_set(var: Any, dotted: str, value: Any) -> None: @param dotted The dotted @param default The default """ - keys = dotted.split(".") last_key = keys.pop() @@ -129,6 +133,58 @@ def expand(region: sublime.Region) -> sublime.Region: yield expand(sublime.Region(*m.span())) +@overload +def view_last_typing_timestamp_val(view: sublime.View) -> float: + ... + + +@overload +def view_last_typing_timestamp_val(view: sublime.View, timestamp_s: float) -> None: + ... + + +def view_last_typing_timestamp_val(view: sublime.View, timestamp_s: Optional[float] = None) -> Optional[float]: + """ + @brief Set/Get the last timestamp (in sec) when "OUIB_uri_regions" is updated + + @param view The view + @param timestamp_s The last timestamp (in sec) + + @return None if the set mode, otherwise the value + """ + if timestamp_s is None: + return float(view.settings().get("OUIB_last_update_timestamp", 0)) + + view.settings().set("OUIB_last_update_timestamp", timestamp_s) + return None + + +@overload +def view_is_dirty_val(view: sublime.View) -> bool: + ... + + +@overload +def view_is_dirty_val(view: sublime.View, is_dirty: bool) -> None: + ... + + +def view_is_dirty_val(view: sublime.View, is_dirty: Optional[bool] = None) -> Optional[bool]: + """ + @brief Set/Get the is_dirty of the current view + + @param view The view + @param is_dirty Indicates if dirty + + @return None if the set mode, otherwise the is_dirty + """ + if is_dirty is None: + return bool(view.settings().get("OUIB_is_dirty", True)) + + view.settings().set("OUIB_is_dirty", is_dirty) + return None + + @overload def region_shift(region: sublime.Region, shift: int) -> sublime.Region: ... @@ -148,7 +204,6 @@ def region_shift(region: RegionLike, shift: int) -> Union[Tuple[int, int], subli @return the shifted region """ - if isinstance(region, int): return (region + shift, region + shift) @@ -186,7 +241,6 @@ def region_expand( @return the expanded region """ - if isinstance(expansion, int): expansion = (expansion, expansion) @@ -208,7 +262,6 @@ def convert_to_region_tuple(region: RegionLike, sort: bool = False) -> Tuple[int @return the "region" in tuple form """ - seq: Sequence[int] if isinstance(region, sublime.Region): @@ -233,7 +286,6 @@ def convert_to_st_region(region: RegionLike, sort: bool = False) -> sublime.Regi @return the "region" in ST's region form """ - return sublime.Region(*convert_to_region_tuple(region, sort)) @@ -246,7 +298,6 @@ def merge_regions(regions: Iterable[sublime.Region], allow_boundary: bool = Fals @return Merged regions """ - merged_regions: List[sublime.Region] = [] for region in sorted(regions): if not merged_regions: