Skip to content

Commit

Permalink
refactor: use boot.py and plugin/ structure
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Cherng <jfcherng@gmail.com>
  • Loading branch information
jfcherng committed Jun 11, 2024
1 parent 9aa162c commit 1716e92
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 93 deletions.
15 changes: 15 additions & 0 deletions boot.py
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -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()
92 changes: 3 additions & 89 deletions plugin.py → plugin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,22 @@
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
from LSP.plugin.core.protocol import CompletionItem, Hover, SignatureHelp
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:
Expand All @@ -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")

Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions plugin/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from __future__ import annotations

from .lsp_basedpyright_create_configuration import LspBasedpyrightCreateConfigurationCommand

__all__ = (
# ST: commands
"LspBasedpyrightCreateConfigurationCommand",
)
File renamed without changes.
5 changes: 5 additions & 0 deletions plugin/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

assert __package__

PACKAGE_NAME = __package__.partition(".")[0]
11 changes: 11 additions & 0 deletions plugin/listener.py
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 1 addition & 4 deletions log.py → plugin/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions plugin/utils.py
Original file line number Diff line number Diff line change
@@ -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)
File renamed without changes.

0 comments on commit 1716e92

Please sign in to comment.