diff --git a/boot.py b/boot.py index 98fbbdd..8d5b832 100644 --- a/boot.py +++ b/boot.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def reload_plugin() -> None: import sys diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 0000000..954ec87 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,11 @@ +{ + "*": { + "*": [ + "annotated-types", + "more-itertools", + "pydantic", + "pydantic-core", + "typing-extensions" + ] + } +} diff --git a/plugin/completion.py b/plugin/completion.py index e6ae0f4..8a33a96 100644 --- a/plugin/completion.py +++ b/plugin/completion.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json from functools import lru_cache from itertools import groupby from typing import Generator, Iterable @@ -8,70 +7,49 @@ import sublime from .constant import DB_DIR -from .types import DatabaseItem, DbSchema, NormalizedDatabaseItem +from .data_types import DbItem, DbModel, NormalizedDbItem @lru_cache -def load_database(version: str) -> DbSchema: - return json.loads(sublime.load_resource(str(DB_DIR / f"{version}.json"))) +def load_db(version: str) -> DbModel: + return DbModel.model_validate_json(sublime.load_resource(str(DB_DIR / f"{version}.json"))) @lru_cache -def get_completion_list(version_str: str) -> sublime.CompletionList: - """ - Gets the completion items. - - :param version_str: Versions separated with "," - :type version_str: str - - :returns: The completion items. - :rtype: sublime.CompletionList - """ - - versions = sorted(set(filter(None, map(str.strip, version_str.split(","))))) - items = _get_database_items(versions) +def get_completion_list(versions: tuple[str, ...]) -> sublime.CompletionList: + """Gets the completion items.""" + db_items = [item for version in versions for item in _list_db_items(version)] return sublime.CompletionList( tuple( - map( - lambda item: sublime.CompletionItem( - trigger=item.item_name, - annotation=f"{item.lib_name} {'/'.join(item.lib_versions)}", - completion=item.item_name, - completion_format=sublime.COMPLETION_FORMAT_TEXT, - kind=(sublime.KIND_ID_MARKUP, "c", ""), - details="", - ), - _normalize_database_items(items), + sublime.CompletionItem( + trigger=item.item_name, + annotation=f"{item.lib_name} {'/'.join(item.lib_versions)}", + completion=item.item_name, + completion_format=sublime.COMPLETION_FORMAT_TEXT, + kind=(sublime.KIND_ID_MARKUP, "c", ""), + details="", ) + for item in _normalize_db_items_for_completion(db_items) ) ) -def _get_database_items(versions: Iterable[str]) -> Generator[DatabaseItem, None, None]: - for version in versions: - db = load_database(version) - for name in db["classes"]: - yield DatabaseItem( - lib_name=db["name"], - lib_version=db["version"], - item_name=name, - ) +def _list_db_items(version: str) -> Generator[DbItem, None, None]: + db = load_db(version) + yield from (DbItem(lib_name=db.name, lib_version=db.version, item_name=name) for name in db.classes) -def _normalize_database_items(items: Iterable[DatabaseItem]) -> Generator[NormalizedDatabaseItem, None, None]: - def sorter(item: DatabaseItem) -> tuple[str, str]: +def _normalize_db_items_for_completion(db_items: Iterable[DbItem]) -> Generator[NormalizedDbItem, None, None]: + def sorter(item: DbItem) -> tuple[str, str]: return (item.lib_name, item.item_name) # pre-sort for groupby - items = sorted(items, key=sorter) - + db_items = sorted(db_items, key=sorter) # merges same-name items which have different versions - for _, group in groupby(items, sorter): - group_items = tuple(group) - group_item = group_items[0] - yield NormalizedDatabaseItem( - lib_name=group_item.lib_name, - lib_versions=sorted(item.lib_version for item in group_items), - item_name=group_item.item_name, + for (lib_name, item_name), group in groupby(db_items, sorter): + yield NormalizedDbItem( + lib_name=lib_name, + lib_versions=sorted(item.lib_version for item in group), + item_name=item_name, ) diff --git a/plugin/types.py b/plugin/data_types.py similarity index 72% rename from plugin/types.py rename to plugin/data_types.py index 997000d..af58406 100644 --- a/plugin/types.py +++ b/plugin/data_types.py @@ -1,17 +1,15 @@ from __future__ import annotations -from dataclasses import dataclass -from typing import TypedDict +from pydantic import BaseModel -class DbSchema(TypedDict): +class DbModel(BaseModel): name: str version: str classes: list[str] -@dataclass -class DatabaseItem: +class DbItem(BaseModel): lib_name: str """The name of the lib.""" lib_version: str @@ -20,8 +18,7 @@ class DatabaseItem: """The trigger of the completion.""" -@dataclass -class NormalizedDatabaseItem: +class NormalizedDbItem(BaseModel): lib_name: str """The name of the lib.""" lib_versions: list[str] diff --git a/plugin/listener.py b/plugin/listener.py index e9e589b..6542fdf 100644 --- a/plugin/listener.py +++ b/plugin/listener.py @@ -5,6 +5,8 @@ import sublime import sublime_plugin +from plugin.utils import sort_uniq + from .completion import get_completion_list from .settings import get_merged_plugin_setting @@ -25,8 +27,15 @@ def on_query_completions( if not self._point_match_selectors(view, point, selectors): return None - return get_completion_list(",".join(get_merged_plugin_setting(window, "versions"))) + return get_completion_list(_get_versions(window)) @staticmethod def _point_match_selectors(view: sublime.View, point: int, selectors: Iterable[str]) -> bool: return any(view.match_selector(point, selector) for selector in selectors) + + +def _get_versions(window: sublime.Window) -> tuple[str, ...]: + versions = get_merged_plugin_setting(window, "versions") + if isinstance(versions, str): + versions = (versions,) + return tuple(sort_uniq(versions)) diff --git a/plugin/utils.py b/plugin/utils.py new file mode 100644 index 0000000..8120b3c --- /dev/null +++ b/plugin/utils.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from itertools import groupby +from operator import itemgetter +from typing import Any, Callable, Generator, Iterable, TypeVar + +_T = TypeVar("_T") + + +def sort_uniq( + seq: Iterable[_T], + *, + key: Callable[[_T], Any] | None = None, + reverse: bool = False, +) -> Generator[_T, None, None]: + key = key or (lambda x: x) + yield from map( + itemgetter(0), + groupby(sorted(seq, key=key, reverse=reverse), key=key), # type: ignore + ) diff --git a/requirements-dev.txt b/requirements-dev.txt index e555ca0..7ee09fb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,7 @@ # This file was autogenerated by uv via the following command: # uv pip compile requirements-dev.in -o requirements-dev.txt +annotated-types==0.6.0 + # via pydantic click==8.1.7 # via typer colorama==0.4.6 @@ -12,6 +14,10 @@ mypy==1.10.0 # via -r requirements-dev.in mypy-extensions==1.0.0 # via mypy +pydantic==2.7.1 + # via -r requirements.in +pydantic-core==2.18.2 + # via pydantic pygments==2.18.0 # via rich rich==13.7.1 @@ -28,6 +34,8 @@ typing-extensions==4.11.0 # via # -r requirements-dev.in # mypy + # pydantic + # pydantic-core # typer webencodings==0.5.1 # via tinycss2 diff --git a/requirements.in b/requirements.in index e69de29..0206ce8 100644 --- a/requirements.in +++ b/requirements.in @@ -0,0 +1 @@ +pydantic>=2.7,<3 diff --git a/requirements.txt b/requirements.txt index e9aaf47..bc6a323 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,12 @@ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt +annotated-types==0.6.0 + # via pydantic +pydantic==2.7.1 + # via -r requirements.in +pydantic-core==2.18.2 + # via pydantic +typing-extensions==4.11.0 + # via + # pydantic + # pydantic-core