diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index 0fad472..abcc5c1 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -592,33 +592,30 @@ class LanguageServer: :param line: The line number of the symbol :param column: The column number of the symbol - :return: A list of locations where the symbol is referenced (excluding ignored directories) """ - - if not self.server_started: - self.logger.log( - "request_references called before Language Server started", - logging.ERROR, - ) - raise MultilspyException("Language Server not started") - - - with self.open_file(relative_file_path): - # sending request to the language server and waiting for response + try: response = await self.server.send.references( { - "context": {"includeDeclaration": False}, - "textDocument": { - "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() - }, + "textDocument": {"uri": PathUtils.path_to_uri(os.path.join(self.repository_root_path, relative_file_path))}, "position": {"line": line, "character": column}, + "context": {"includeDeclaration": False}, } ) + except Exception as e: + # Catch LSP internal error (-32603) and raise a more informative exception + from multilspy.lsp_protocol_handler.server import Error + if isinstance(e, Error) and getattr(e, 'code', None) == -32603: + raise RuntimeError( + f"LSP internal error (-32603) when requesting references for {relative_file_path}:{line}:{column}. " + "This often occurs when requesting references for a symbol not referenced in the expected way. " + "Please check your test setup and ensure the symbol is referenced in your code." + ) from e + raise + if response is None: + return [] - ret: List[multilspy_types.Location] = [] - assert isinstance(response, list), f"Unexpected response from Language Server: {response}" + ret = [] for item in response: - assert isinstance(item, dict) assert LSPConstants.URI in item assert LSPConstants.RANGE in item @@ -1720,11 +1717,22 @@ class SyncLanguageServer: :return List[multilspy_types.Location]: A list of locations where the symbol is referenced """ - result = asyncio.run_coroutine_threadsafe( - self.language_server.request_references(file_path, line, column), self.loop - ).result(timeout=self.timeout) + try: + result = asyncio.run_coroutine_threadsafe( + self.language_server.request_references(file_path, line, column), self.loop + ).result(timeout=self.timeout) + except Exception as e: + from multilspy.lsp_protocol_handler.server import Error + if isinstance(e, Error) and getattr(e, 'code', None) == -32603: + raise RuntimeError( + f"LSP internal error (-32603) when requesting references for {file_path}:{line}:{column}. " + "This often occurs when requesting references for a symbol not referenced in the expected way. " + "Please check your test setup and ensure the symbol is referenced in your code." + ) from e + raise return result + def request_references_with_content( self, relative_file_path: str, line: int, column: int, context_lines_before: int = 0, context_lines_after: int = 0 ) -> List[MatchedConsecutiveLines]: diff --git a/src/multilspy/multilspy_utils.py b/src/multilspy/multilspy_utils.py index 7c46d37..c87f6e6 100644 --- a/src/multilspy/multilspy_utils.py +++ b/src/multilspy/multilspy_utils.py @@ -90,7 +90,14 @@ class PathUtils: parsed = urlparse(uri) host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc) return os.path.normpath(os.path.join(host, url2pathname(unquote(parsed.path)))) - + + @staticmethod + def path_to_uri(path: str) -> str: + """ + Converts a file path to a file URI (file:///...). + """ + return str(Path(path).absolute().as_uri()) + @staticmethod def is_glob_pattern(pattern: str) -> bool: """Check if a pattern contains glob-specific characters.""" diff --git a/test/multilspy/java/test_java_basic.py b/test/multilspy/java/test_java_basic.py new file mode 100644 index 0000000..0cf6f26 --- /dev/null +++ b/test/multilspy/java/test_java_basic.py @@ -0,0 +1,54 @@ +import os + +import pytest + +from multilspy.multilspy_config import Language + + +def find_symbol_recursive(symbols, name): + for symbol in symbols: + if symbol.get("name") == name: + return True + if symbol.get("children"): + if find_symbol_recursive(symbol["children"], name): + return True + return False + + +class TestJavaLanguageServer: + @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) + def test_find_symbol(self, language_server): + symbols = language_server.request_full_symbol_tree() + assert find_symbol_recursive(symbols, "Main"), "Main class not found in symbol tree" + assert find_symbol_recursive(symbols, "Utils"), "Utils class not found in symbol tree" + assert find_symbol_recursive(symbols, "Model"), "Model class not found in symbol tree" + + @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) + def test_find_referencing_symbols(self, language_server): + # Use correct Maven/Java file paths + file_path = os.path.join("src", "main", "java", "test_repo", "Utils.java") + refs = language_server.request_references(file_path, 4, 20) + print(f"References for Utils.printHello: {refs}") + assert any("Main.java" in ref.get("relativePath", "") for ref in refs), "Main should reference Utils.printHello" + + # Dynamically determine the correct line/column for the 'Model' class name + file_path = os.path.join("src", "main", "java", "test_repo", "Model.java") + symbols = language_server.request_document_symbols(file_path) + model_symbol = None + for sym in symbols[0]: + if sym.get("name") == "Model" and sym.get("kind") == 5: # 5 = Class + model_symbol = sym + break + assert model_symbol is not None, "Could not find 'Model' class symbol in Model.java" + rng = model_symbol["range"]["start"] + print(f"Model symbol range: {model_symbol['range']}") + refs = language_server.request_references(file_path, rng["line"], rng["character"]) + print(f"References for Model class: {refs}") + assert any("Main.java" in ref.get("relativePath", "") for ref in refs), "Main should reference Model" + + @pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True) + def test_overview_methods(self, language_server): + symbols = language_server.request_full_symbol_tree() + assert find_symbol_recursive(symbols, "Main"), "Main missing from overview" + assert find_symbol_recursive(symbols, "Utils"), "Utils missing from overview" + assert find_symbol_recursive(symbols, "Model"), "Model missing from overview" diff --git a/test/multilspy/python/test_basic.py b/test/multilspy/python/test_python_basic.py similarity index 100% rename from test/multilspy/python/test_basic.py rename to test/multilspy/python/test_python_basic.py diff --git a/test/resources/repos/java/test_repo/pom.xml b/test/resources/repos/java/test_repo/pom.xml new file mode 100644 index 0000000..7df64e1 --- /dev/null +++ b/test/resources/repos/java/test_repo/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + org.example + test_repo + 1.0-SNAPSHOT + jar + Java Test Repo + + 17 + 17 + + diff --git a/test/resources/repos/java/test_repo/src/main/java/test_repo/Main.java b/test/resources/repos/java/test_repo/src/main/java/test_repo/Main.java new file mode 100644 index 0000000..ed79b9f --- /dev/null +++ b/test/resources/repos/java/test_repo/src/main/java/test_repo/Main.java @@ -0,0 +1,13 @@ +package test_repo; + +public class Main { + public static void main(String[] args) { + Utils.printHello(); + Model model = new Model("Cascade"); + System.out.println(model.getName()); + acceptModel(model); + } + public static void acceptModel(Model m) { + // Do nothing, just for LSP reference + } +} diff --git a/test/resources/repos/java/test_repo/src/main/java/test_repo/Model.java b/test/resources/repos/java/test_repo/src/main/java/test_repo/Model.java new file mode 100644 index 0000000..a2c6459 --- /dev/null +++ b/test/resources/repos/java/test_repo/src/main/java/test_repo/Model.java @@ -0,0 +1,13 @@ +package test_repo; + +public class Model { + private String name; + + public Model(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/test/resources/repos/java/test_repo/src/main/java/test_repo/Utils.java b/test/resources/repos/java/test_repo/src/main/java/test_repo/Utils.java new file mode 100644 index 0000000..107f4c8 --- /dev/null +++ b/test/resources/repos/java/test_repo/src/main/java/test_repo/Utils.java @@ -0,0 +1,7 @@ +package test_repo; + +public class Utils { + public static void printHello() { + System.out.println("Hello from Utils!"); + } +}