From 271774410316638c720abad06aafe8b43957dd4f Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Tue, 11 Jun 2024 23:37:04 +0800 Subject: [PATCH] refactor: use boot.py and plugin/ structure Signed-off-by: Jack Cherng --- boot.py | 15 +++ plugin/__init__.py | 28 ++++++ plugin.py => plugin/client.py | 92 +------------------ plugin/commands/__init__.py | 8 ++ .../lsp_basedpyright_create_configuration.py | 0 plugin/constants.py | 5 + plugin/listener.py | 11 +++ log.py => plugin/log.py | 5 +- plugin/utils.py | 20 ++++ venv_finder.py => plugin/venv_finder.py | 0 10 files changed, 91 insertions(+), 93 deletions(-) create mode 100644 boot.py create mode 100644 plugin/__init__.py rename plugin.py => plugin/client.py (73%) create mode 100644 plugin/commands/__init__.py rename commands.py => plugin/commands/lsp_basedpyright_create_configuration.py (100%) create mode 100644 plugin/constants.py create mode 100644 plugin/listener.py rename log.py => plugin/log.py (89%) create mode 100644 plugin/utils.py rename venv_finder.py => plugin/venv_finder.py (100%) diff --git a/boot.py b/boot.py new file mode 100644 index 0000000..8d5b832 --- /dev/null +++ b/boot.py @@ -0,0 +1,15 @@ +from __future__ import annotations + + +def reload_plugin() -> None: + import sys + + # 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] + + +reload_plugin() + +from .plugin import * # noqa: E402, F401, F403 diff --git a/plugin/__init__.py b/plugin/__init__.py new file mode 100644 index 0000000..31f38af --- /dev/null +++ b/plugin/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from .client import LspBasedpyrightPlugin +from .commands import LspBasedpyrightCreateConfigurationCommand +from .listener import LspBasedpyrightEventListener + +__all__ = ( + # ST: core + "plugin_loaded", + "plugin_unloaded", + # ST: commands + "LspBasedpyrightCreateConfigurationCommand", + # ST: listeners + "LspBasedpyrightEventListener", + # ... + "LspBasedpyrightPlugin", +) + + +def plugin_loaded() -> None: + """Executed when this plugin is loaded.""" + LspBasedpyrightPlugin.setup() + + +def plugin_unloaded() -> None: + """Executed when this plugin is unloaded.""" + LspBasedpyrightPlugin.window_attrs.clear() + LspBasedpyrightPlugin.cleanup() diff --git a/plugin.py b/plugin/client.py similarity index 73% rename from plugin.py rename to plugin/client.py index b7514ba..98bd53e 100644 --- a/plugin.py +++ b/plugin/client.py @@ -3,13 +3,11 @@ import os import re import shutil -import subprocess import sys from collections import defaultdict -from collections.abc import Callable from dataclasses import dataclass from pathlib import Path -from typing import Any, cast +from typing import cast import sublime from LSP.plugin import ClientConfig, DottedDict, MarkdownLangMap, Response, WorkspaceFolder @@ -17,29 +15,10 @@ from lsp_utils import NpmClientHandler from sublime_lib import ResourcePath +from .constants import PACKAGE_NAME from .log import log_info, log_warning from .venv_finder import VenvInfo, find_venv_by_finder_names, get_finder_name_mapping -assert __package__ - - -def plugin_loaded() -> None: - LspBasedpyrightPlugin.setup() - - -def plugin_unloaded() -> None: - LspBasedpyrightPlugin.cleanup() - - -def get_default_startupinfo() -> Any: - if sublime.platform() == "windows": - # do not create a window for the process - STARTUPINFO = subprocess.STARTUPINFO() # type: ignore - STARTUPINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore - STARTUPINFO.wShowWindow = subprocess.SW_HIDE # type: ignore - return STARTUPINFO - return None - @dataclass class WindowAttr: @@ -54,7 +33,7 @@ def preferred_python_executable(self) -> Path | None: class LspBasedpyrightPlugin(NpmClientHandler): - package_name = __package__.partition(".")[0] + package_name = PACKAGE_NAME server_directory = "language-server" server_binary_path = os.path.join(server_directory, "node_modules", "basedpyright", "langserver.index.js") @@ -220,71 +199,6 @@ def find_package_dependency_dirs(self, py_ver: tuple[int, int] = (3, 3)) -> list return list(filter(os.path.isdir, dep_dirs)) - @classmethod - def python_path_from_venv(cls, workspace_folder: str | Path) -> Path | None: - """ - Resolves the python binary path depending on environment variables and files in the workspace. - - @see https://github.com/fannheyward/coc-pyright/blob/d58a468b1d7479a1b56906e386f44b997181e307/src/configSettings.ts#L47 - """ - workspace_folder = Path(workspace_folder) - - def binary_from_python_path(path: str | Path) -> Path | None: - path = Path(path) - if sublime.platform() == "windows": - binary_path = path / "Scripts/python.exe" - else: - binary_path = path / "bin/python" - return binary_path if binary_path.is_file() else None - - # Config file, venv resolution command, post-processing - venv_config_files: list[tuple[str, str, Callable[[str], Path | None] | None]] = [ - (".pdm-python", "pdm info --python", None), - (".python-version", "pyenv which python", None), - ("Pipfile", "pipenv --py", None), - ("poetry.lock", "poetry env info -p", binary_from_python_path), - ] - - for config_file, command, post_processing in venv_config_files: - if not (workspace_folder / config_file).is_file(): - continue - print(f"{cls.name()}: INFO: {config_file} detected. Run subprocess command: {command}") - try: - stdout, stderr = map( - str.rstrip, - subprocess.Popen( - command, - cwd=workspace_folder, - shell=True, - startupinfo=get_default_startupinfo(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ).communicate(), - ) - if stderr: - print(f"{cls.name()}: INFO: subprocess stderr: {stderr}") - python_path = stdout - if post_processing: - python_path = post_processing(python_path) - if python_path: - return Path(python_path) - except FileNotFoundError: - print(f"{cls.name()}: WARN: subprocess failed with file not found: {command[0]}") - except PermissionError as e: - print(f"{cls.name()}: WARN: subprocess failed with permission error: {e}") - except subprocess.CalledProcessError as e: - print(f"{cls.name()}: WARN: subprocess failed: {str(e.output).strip()}") - - # virtual environment as subfolder in project - for maybe_venv_path in workspace_folder.iterdir(): - try: - if (maybe_venv_path / "pyvenv.cfg").is_file() and (binary := binary_from_python_path(maybe_venv_path)): - return binary # found a venv - except PermissionError: - pass - return None - @classmethod def update_venv_info( cls, diff --git a/plugin/commands/__init__.py b/plugin/commands/__init__.py new file mode 100644 index 0000000..a8bae09 --- /dev/null +++ b/plugin/commands/__init__.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from .lsp_basedpyright_create_configuration import LspBasedpyrightCreateConfigurationCommand + +__all__ = ( + # ST: commands + "LspBasedpyrightCreateConfigurationCommand", +) diff --git a/commands.py b/plugin/commands/lsp_basedpyright_create_configuration.py similarity index 100% rename from commands.py rename to plugin/commands/lsp_basedpyright_create_configuration.py diff --git a/plugin/constants.py b/plugin/constants.py new file mode 100644 index 0000000..ce36a82 --- /dev/null +++ b/plugin/constants.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +assert __package__ + +PACKAGE_NAME = __package__.partition(".")[0] diff --git a/plugin/listener.py b/plugin/listener.py new file mode 100644 index 0000000..f0f8e46 --- /dev/null +++ b/plugin/listener.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import sublime +import sublime_plugin + +from .client import LspBasedpyrightPlugin + + +class LspBasedpyrightEventListener(sublime_plugin.EventListener): + def on_pre_close_window(self, window: sublime.Window) -> None: + LspBasedpyrightPlugin.window_attrs.pop(window.id(), None) diff --git a/log.py b/plugin/log.py similarity index 89% rename from log.py rename to plugin/log.py index b8dc4cc..a463713 100644 --- a/log.py +++ b/plugin/log.py @@ -24,10 +24,7 @@ def log_error(message: str) -> None: def pluginfy_msg(msg: str, *args: Any, **kwargs: Any) -> str: - assert __package__ - package_name = __package__.split(".")[0] - - return msg.format(*args, _=package_name, **kwargs) + return msg.format(*args, _=PACKAGE_NAME, **kwargs) def console_msg(msg: str, *args: Any, **kwargs: Any) -> None: diff --git a/plugin/utils.py b/plugin/utils.py new file mode 100644 index 0000000..8cc27f5 --- /dev/null +++ b/plugin/utils.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import os +import re +import subprocess +from typing import Any + + +def get_default_startupinfo() -> Any: + if os.name == "nt": + # do not create a window for the process + STARTUPINFO = subprocess.STARTUPINFO() # type: ignore + STARTUPINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore + STARTUPINFO.wShowWindow = subprocess.SW_HIDE # type: ignore + return STARTUPINFO + return None + + +def lowercase_drive_letter(path: str) -> str: + return re.sub(r"^[A-Z]+(?=:\\)", lambda m: m.group(0).lower(), path) diff --git a/venv_finder.py b/plugin/venv_finder.py similarity index 100% rename from venv_finder.py rename to plugin/venv_finder.py