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