Skip to content

Commit

Permalink
fix: better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
TheNuclearNexus committed Sep 28, 2024
1 parent 3d3a280 commit 2ef974a
Showing 1 changed file with 121 additions and 91 deletions.
212 changes: 121 additions & 91 deletions language_server/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
normalize_string,
change_directory,
local_import_path,
required_field
)

from beet.contrib.load import load
Expand All @@ -33,6 +34,7 @@
from mecha import AstNode, CompilationUnit, Mecha, DiagnosticErrorSummary
from pygls.server import LanguageServer
from pygls.workspace import TextDocument
from lsprotocol import types as lsp
import os

from pathlib import Path
Expand Down Expand Up @@ -80,11 +82,18 @@ def require(self, *args: GenericPlugin[Context] | str):
# We use this shadow of context in order to route calls to `ctx`
# to our own methods, this allows us to bypass side effects without
# having to break plugins
class ContextShadow(Context):
@dataclass(frozen=True)
class LanguageServerContext(Context):
ls: "MechaLanguageServer" = required_field()

def require(self, *args: PluginSpec):
"""Execute the specified plugin."""
self.inject(PipelineShadow).require(*args)

for arg in args:
try:
self.inject(PipelineShadow).require(arg)
except PluginError as exc:
self.ls.show_message(f"Failed to load plugin: {arg}\n{exc}", lsp.MessageType.Error)

@contextmanager
def activate(self):
"""Push the context directory to sys.path and handle cleanup to allow module reloading."""
Expand Down Expand Up @@ -115,7 +124,7 @@ def bootstrap(self, ctx: Context):
# This stripped down version of build only handles loading the plugins from config
# all other operations are gone such as linking
@contextmanager
def build(self) -> Iterator[Context]:
def build(self, ls: "MechaLanguageServer") -> Iterator[LanguageServerContext]:
"""Create the context, run the pipeline, and return the context."""
with ExitStack() as stack:
name = self.config.name or self.project.directory.stem
Expand All @@ -124,7 +133,8 @@ def build(self) -> Iterator[Context]:
tmpdir = None
cache = self.project.cache

ctx = ContextShadow(
ctx = LanguageServerContext(
ls=ls,
project_id=self.config.id or normalize_string(name),
project_name=name,
project_description=self.config.description,
Expand Down Expand Up @@ -171,110 +181,130 @@ def build(self) -> Iterator[Context]:

yield ctx

def load_registry(minecraft_version: str):
if len(minecraft_version) <= 0:
minecraft_version = LATEST_MINECRAFT_VERSION

cache_dir = Path("./.mls_cache")
if not cache_dir.exists():
os.mkdir(cache_dir)

if not (cache_dir / "registries").exists():
os.mkdir(cache_dir / "registries")

file_path = cache_dir / "registries" / (minecraft_version + ".json")

logging.debug(minecraft_version)

if not file_path.exists():
request.urlretrieve(f"https://raw.githubusercontent.com/misode/mcmeta/refs/tags/{minecraft_version}-summary/registries/data.min.json", file_path)

with open(file_path) as file:
registries = json.loads(file.read())
for k in registries:
GAME_REGISTRIES[k] = registries[k]


def create_context(config: ProjectConfig, config_path: Path) -> Context:
project = Project(config, None, config_path)
with ProjectBuilderShadow(project, root=True).build() as ctx:
mc = ctx.inject(Mecha)

logging.debug(f"Downloading game registries")
class MechaLanguageServer(LanguageServer):
instances: dict[Path, Context] = dict()
_sites: list[str] = []

logging.debug(f"Mecha created for {config_path} successfully")
for pack in ctx.packs:
for provider in mc.providers:
for file_instance, compilation_unit in provider(pack, mc.match):
mc.database[file_instance] = compilation_unit
mc.database.enqueue(file_instance)
def set_sites(self, sites: list[str]):
self._sites = sites

for location, file in pack.all():
try:
path = os.path.normpath(file.ensure_source_path())
path = os.path.normcase(path)
PATH_TO_RESOURCE[str(path)] = (location, file)
except:
continue
def __init__(self, *args):
super().__init__(*args)
self.instances = {}

def load_registry(self, minecraft_version: str):
"""Load the game registry from Misode's mcmeta repository"""
global GAME_REGISTRIES

return ctx
return None
if len(minecraft_version) <= 0:
minecraft_version = LATEST_MINECRAFT_VERSION

cache_dir = Path("./.mls_cache")
if not cache_dir.exists():
os.mkdir(cache_dir)

def create_instance(
config_path: Path, sites: list[str]
) -> Context | None:
config = load_config(config_path)
logging.debug(config)
# Ensure that we aren't loading in all project files
config.output = None

config.pipeline = list(filter(lambda p: isinstance(p, str), config.pipeline))
if not (cache_dir / "registries").exists():
os.mkdir(cache_dir / "registries")

file_path = cache_dir / "registries" / (minecraft_version + ".json")

logging.debug(minecraft_version)

if not file_path.exists():
try:
request.urlretrieve(f"https://raw.githubusercontent.com/misode/mcmeta/refs/tags/{minecraft_version}-summary/registries/data.min.json", file_path)
except Exception as exc:
self.show_message(f"Failed to download registry for version {minecraft_version}, completions will be disabled\n{exc}", lsp.MessageType.Error)

return

with open(file_path) as file:
try:
registries = json.loads(file.read())
for k in registries:
GAME_REGISTRIES[k] = registries[k]

except json.JSONDecodeError as exc:
self.show_message(f"Failed to parse registry for version {minecraft_version}, completions will be disabled\n{exc}", lsp.MessageType.Error)
os.remove(file_path)

except Exception as exc:
self.show_message(f"An unhandled exception occured loading registry for version {minecraft_version}\n{exc}", lsp.MessageType.Error)
os.remove(file_path)


def create_context(self, config: ProjectConfig, config_path: Path) -> Context:
"""Attempt to configure the project's context and run necessary plugins"""
project = Project(config, None, config_path)
with ProjectBuilderShadow(project, root=True).build(self) as ctx:
mc = ctx.inject(Mecha)

logging.debug(f"Mecha created for {config_path} successfully")

for pack in ctx.packs:
# Load all files into the compilation database
for provider in mc.providers:
for file_instance, compilation_unit in provider(pack, mc.match):
mc.database[file_instance] = compilation_unit
mc.database.enqueue(file_instance)

# Build a map of file path to resource location
for location, file in pack.all():
try:
path = os.path.normpath(file.ensure_source_path())
path = os.path.normcase(path)
PATH_TO_RESOURCE[str(path)] = (location, file)
except:
continue

return ctx
return None

og_cwd = os.getcwd()
og_sys_path = sys.path
og_modules = sys.modules

sys.path = [*sites, str(config_path.parent), *og_sys_path]

os.chdir(config_path.parent)

def create_instance(
self, config_path: Path
) -> Context | None:
config = load_config(config_path)
logging.debug(config)
# Ensure that we aren't loading in all project files
config.output = None

config.pipeline = list(filter(lambda p: isinstance(p, str), config.pipeline))

instance = None
og_cwd = os.getcwd()
og_sys_path = sys.path
og_modules = sys.modules

try:
instance = create_context(config, config_path)
sys.path = [*self._sites, str(config_path.parent), *og_sys_path]

except PluginError as plugin_error:
logging.error(plugin_error.__cause__)
raise plugin_error.__cause__
except DiagnosticErrorSummary as summary:
logging.error("Errors found in the following:")
for diag in summary.diagnostics.exceptions:
logging.error("\t" + str(diag.file.source_path if diag.file is not None else ""))
os.chdir(config_path.parent)

except Exception as e:
logging.error(f"Error occured while running beet: {type(e)} {e}")

os.chdir(og_cwd)
sys.path = og_sys_path
sys.modules = og_modules
instance = None

load_registry(config.minecraft)
try:
instance = self.create_context(config, config_path)

return instance
except PluginError as plugin_error:
logging.error(plugin_error.__cause__)
raise plugin_error.__cause__
except DiagnosticErrorSummary as summary:
logging.error("Errors found in the following:")
for diag in summary.diagnostics.exceptions:
logging.error("\t" + str(diag.file.source_path if diag.file is not None else ""))

except Exception as e:
logging.error(f"Error occured while running beet: {type(e)} {e}")

class MechaLanguageServer(LanguageServer):
instances: dict[Path, Context] = dict()
_sites: list[str] = []
os.chdir(og_cwd)
sys.path = og_sys_path
sys.modules = og_modules

def set_sites(self, sites: list[str]):
self._sites = sites
self.load_registry(config.minecraft)

def __init__(self, *args):
super().__init__(*args)
self.instances = {}
return instance

def setup_workspaces(self):
config_paths: list[Path] = []
Expand All @@ -288,7 +318,7 @@ def setup_workspaces(self):
logging.debug(config_path)

self.instances = { # type: ignore
c.parent: create_instance(c, self._sites) for c in config_paths
c.parent: self.create_instance(c) for c in config_paths
}
# logging.debug(self.mecha_instances)

Expand All @@ -304,7 +334,7 @@ def uri_to_path(self, uri: str):

def get_instance(self, config_path: Path):
if config_path not in self.instances or self.instances[config_path] is None:
instance = create_instance(config_path, self._sites)
instance = self.create_instance(config_path, self._sites)

if instance is not None:
self.instances[config_path] = instance
Expand Down

0 comments on commit 2ef974a

Please sign in to comment.