From 184ac8fa60eb392cd501294beb0299162a1baf7c Mon Sep 17 00:00:00 2001 From: TheNuclearNexus <39636175+TheNuclearNexus@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:20:40 -0500 Subject: [PATCH] fix(language_server): error when getting compilation data from unknown function, plugin execution errors caused server to fail to start --- language_server/server/features/completion.py | 33 +++++++++++++------ language_server/server/features/semantics.py | 11 ++++--- language_server/server/features/validate.py | 16 ++++++--- language_server/server/shadows.py | 16 ++++++--- 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/language_server/server/features/completion.py b/language_server/server/features/completion.py index 246a0e6..e64ed19 100644 --- a/language_server/server/features/completion.py +++ b/language_server/server/features/completion.py @@ -3,8 +3,9 @@ import inspect import logging import types -from typing import Any +from typing import Any, cast +from beet import File, NamespaceFile from bolt import AstAttribute, AstIdentifier, Runtime, UndefinedIdentifier, Variable from lsprotocol import types as lsp from mecha import ( @@ -107,7 +108,9 @@ def get_completions( text_doc: TextDocument, ) -> list[lsp.CompletionItem]: mecha = ctx.inject(Mecha) - compiled_doc = get_compilation_data(ls, ctx, text_doc) + + if not (compiled_doc := get_compilation_data(ls, ctx, text_doc)): + return [] ast = compiled_doc.ast diagnostics = compiled_doc.diagnostics @@ -118,14 +121,24 @@ def get_completions( elif ast is not None: current_node = get_node_at_position(ast, pos) - if isinstance(current_node, AstResourceLocation): - represents = current_node.__dict__.get("represents") - # logging.debug(GAME_REGISTRIES) - if represents is not None: - add_registry_items(items, represents) - add_registry_items( - items, "tag/" + represents, "#", lsp.CompletionItemKind.Constant - ) + # if isinstance(current_node, AstResourceLocation): + # resolved_path = current_node.__dict__.get("resolved_path") + # represents = current_node.__dict__.get("represents") + # # logging.debug(GAME_REGISTRIES) + # if represents and issubclass(represents, File): + # file_type = cast(type[NamespaceFile], represents) + # for pack in ctx.packs: + # if file_type not in pack: + # continue + # logging.debug(ctx.data[file_type]) + # for path in pack[file_type]: + # items.append(lsp.CompletionItem(label=path)) + + # elif isinstance(represents, str): + # add_registry_items(items, represents) + # add_registry_items( + # items, "tag/" + represents, "#", lsp.CompletionItemKind.Constant + # ) if isinstance(current_node, AstItemSlot): items.extend( diff --git a/language_server/server/features/semantics.py b/language_server/server/features/semantics.py index c468a89..6aecd1a 100644 --- a/language_server/server/features/semantics.py +++ b/language_server/server/features/semantics.py @@ -173,9 +173,8 @@ def command(self, node: AstCommand): else: return - logging.debug(f"semantic token for {node}") temp_node = AstNode(location=node.location, end_location=end_location) - logging.debug(f"{temp_node.location} {temp_node.end_location}") + self.nodes.append( ( temp_node, @@ -358,9 +357,11 @@ def semantic_tokens(ls: MechaLanguageServer, params: lsp.SemanticTokensParams): if ctx is None: data = [] else: - compiled_doc = get_compilation_data(ls, ctx, text_doc) - ast = compiled_doc.ast + if compiled_doc := get_compilation_data(ls, ctx, text_doc): + ast = compiled_doc.ast - data = SemanticTokenCollector(ctx=ctx).walk(ast) if ast else [] + data = SemanticTokenCollector(ctx=ctx).walk(ast) if ast else [] + else: + data = [] return lsp.SemanticTokens(data=data) diff --git a/language_server/server/features/validate.py b/language_server/server/features/validate.py index 1624f83..cfd1bd0 100644 --- a/language_server/server/features/validate.py +++ b/language_server/server/features/validate.py @@ -73,13 +73,20 @@ def tokenstream_error_to_lsp_diag( def get_compilation_data( ls: MechaLanguageServer, ctx: LanguageServerContext, text_doc: TextDocument ): - location = ctx.path_to_resource[text_doc.path][0] - if location in COMPILATION_RESULTS: - return COMPILATION_RESULTS[location] + resource = ctx.path_to_resource.get(text_doc.path) + + if resource and resource[0] in COMPILATION_RESULTS: + return COMPILATION_RESULTS[resource[0]] validate_function(ls, ctx, text_doc) + + resource = resource or ctx.path_to_resource.get(text_doc.path) + + if resource is None: + return None + # logging.debug(COMPILATION_RESULTS) - return COMPILATION_RESULTS[location] + return COMPILATION_RESULTS[resource[0]] def validate_function( @@ -204,6 +211,7 @@ def parse_function( dependents = set() + compiled_module = None runtime = ctx.inject(Runtime) if location in COMPILATION_RESULTS: diff --git a/language_server/server/shadows.py b/language_server/server/shadows.py index 3063a00..d6692f6 100644 --- a/language_server/server/shadows.py +++ b/language_server/server/shadows.py @@ -66,10 +66,16 @@ def require(self, *args: GenericPlugin[Context] | str): self.plugins.add(plugin) - # Advance the plugin only once, ignore remaining work - # Most setup happens in the first half of the plugin - # where side effects happen in the latter - Task(plugin).advance(self.ctx) + try: + # Advance the plugin only once, ignore remaining work + # Most setup happens in the first half of the plugin + # where side effects happen in the latter + Task(plugin).advance(self.ctx) + except Exception as exc: + ls = cast(LanguageServerContext, self.ctx).ls + message = f"An issue occured while running first step of plugin: {plugin}\n{exc}" + ls.show_message(message.split("\n")[0], lsp.MessageType.Warning) + ls.show_message_log(message, lsp.MessageType.Warning) # We use this shadow of context in order to route calls to `ctx` @@ -122,7 +128,7 @@ def bootstrap(self, ctx: Context): for plugin in plugins: if plugin in excluded_plugins: continue - + ctx.require(plugin) # This stripped down version of build only handles loading the plugins from config