From bdc76a5d571718c65117be83710d0b05e0b4c68f Mon Sep 17 00:00:00 2001 From: Ella Liu Date: Fri, 24 Jan 2025 19:28:25 +1100 Subject: [PATCH 1/4] Update Java config to adapt Mac M3 chip env(darwin-arm64) --- .../language_servers/eclipse_jdtls/eclipse_jdtls.py | 2 +- .../eclipse_jdtls/runtime_dependencies.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py index ed4bfae..6f49b87 100644 --- a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py @@ -283,7 +283,7 @@ def _get_initialize_params(self, repository_absolute_path: str) -> InitializePar {"name": "JavaSE-17", "path": "static/vscode-java/extension/jre/17.0.8.1-linux-x86_64", "default": True} ] d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [ - {"name": "JavaSE-17", "path": self.runtime_dependency_paths.jre_home_path, "default": True} + {"name": "JavaSE-17", "path": self.runtime_dependency_paths.jre_home_path} ] for runtime in d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]: diff --git a/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json b/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json index bc2cb74..88daf37 100644 --- a/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json +++ b/src/multilspy/language_servers/eclipse_jdtls/runtime_dependencies.json @@ -14,13 +14,13 @@ "relative_extraction_path": "vscode-java" }, "osx-arm64": { - "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.23.0/java@darwin-x64-1.23.0.vsix", + "url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.38.0/java-darwin-arm64-1.38.0-403.vsix", "archiveType": "zip", "relative_extraction_path": "vscode-java", - "jre_home_path": "extension/jre/17.0.8.1-macosx-x86_64", - "jre_path": "extension/jre/17.0.8.1-macosx-x86_64/bin/java", - "lombok_jar_path": "extension/lombok/lombok-1.18.30.jar", - "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.6.500.v20230717-2134.jar", + "jre_home_path": "extension/jre/17.0.13-macosx-aarch64", + "jre_path": "extension/jre/17.0.13-macosx-aarch64/bin/java", + "lombok_jar_path": "extension/lombok/lombok-1.18.34.jar", + "jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.6.900.v20240613-2009.jar", "jdtls_readonly_config_path": "extension/server/config_mac_arm" }, "linux-arm64": { From e88b8d50461b483cbaa244c85a1dd05fc9ea0d0f Mon Sep 17 00:00:00 2001 From: Ella Liu Date: Fri, 24 Jan 2025 19:29:51 +1100 Subject: [PATCH 2/4] add implementation of LSP incoming call hierarchy --- src/multilspy/language_server.py | 79 ++++++++++++++++++++++++++ src/multilspy/multilspy_types.py | 23 +++++++- tests/multilspy/test_multilspy_java.py | 68 ++++++++++++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index 270a571..c0d2d8d 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -641,6 +641,55 @@ async def request_hover(self, relative_file_path: str, line: int, column: int) - return multilspy_types.Hover(**response) + async def request_prepare_call_hierarchy(self, relative_file_path: str, line: int, column: int) -> List[multilspy_types.CallHierarchyItem]: + """ + Raise a [textDocument/prepareCallHierarchy](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareCallHierarchy) request to the Language Server + to prepare the call hierarchy at the given line and column in the given file. Wait for the response and return the result. + + :param relative_file_path: The relative path of the file that contains the target symbol + :param line: The line number of the symbol + :param column: The column number of the symbol + + :return List[multilspy_types.CallHierarchyItem]: A list of call hierarchy items + """ + with self.open_file(relative_file_path): + response = await self.server.send.prepare_call_hierarchy( + { + "textDocument": { + "uri": pathlib.Path(os.path.join(self.repository_root_path, relative_file_path)).as_uri() + }, + "position": { + "line": line, + "character": column, + }, + } + ) + + if response is None: + return [] + + assert isinstance(response, list) + + return [multilspy_types.CallHierarchyItem(**item) for item in response] + + async def request_incoming_calls(self, req_call_item: multilspy_types.CallHierarchyItem) -> List[multilspy_types.CallHierarchyItem]: + """ + Raise a [callHierarchy/incomingCalls](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#callHierarchy_incomingCalls) request to the Language Server + to find the incoming calls(one depth) to the given call hierarchy item. Wait for the response and return the result. + + :param req_call_item: The call hierarchy item for which incoming calls should be looked up + + :return List[multilspy_types.CallHierarchyItem]: A list of call hierarchy items + """ + incoming_call_response = await self.server.send.incoming_calls( + { + "item": req_call_item + } + ) + + return [multilspy_types.CallHierarchyItem(**item["from"]) for item in incoming_call_response] + + @ensure_all_methods_implemented(LanguageServer) class SyncLanguageServer: """ @@ -810,3 +859,33 @@ def request_hover(self, relative_file_path: str, line: int, column: int) -> Unio self.language_server.request_hover(relative_file_path, line, column), self.loop ).result() return result + + def request_prepare_call_hierarchy(self, relative_file_path: str, line: int, column: int) -> List[multilspy_types.CallHierarchyItem]: + """ + Raise a [textDocument/prepareCallHierarchy](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareCallHierarchy) request to the Language Server + to prepare the call hierarchy at the given line and column in the given file. Wait for the response and return the result. + + :param relative_file_path: The relative path of the file that contains the target symbol + :param line: The line number of the symbol + :param column: The column number of the symbol + + :return List[multilspy_types.CallHierarchyItem]: A list of call hierarchy items + """ + result = asyncio.run_coroutine_threadsafe( + self.language_server.request_prepare_call_hierarchy(relative_file_path, line, column), self.loop + ).result() + return result + + def request_incoming_calls(self, req_call_item: multilspy_types.CallHierarchyItem) -> List[multilspy_types.CallHierarchyItem]: + """ + Raise a [callHierarchy/incomingCalls](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#callHierarchy_incomingCalls) request to the Language Server + to find the incoming calls(one depth) to the given call hierarchy item. Wait for the response and return the result. + + :param req_call_item: The call hierarchy item for which incoming calls should be looked up + + :return List[multilspy_types.CallHierarchyItem]: A list of call hierarchy items + """ + result = asyncio.run_coroutine_threadsafe( + self.language_server.request_incoming_calls(req_call_item), self.loop + ).result() + return result diff --git a/src/multilspy/multilspy_types.py b/src/multilspy/multilspy_types.py index 936dd0e..867cc4d 100644 --- a/src/multilspy/multilspy_types.py +++ b/src/multilspy/multilspy_types.py @@ -280,4 +280,25 @@ class Hover(TypedDict): """ The hover's content """ range: NotRequired["Range"] """ An optional range inside the text document that is used to - visualize the hover, e.g. by changing the background color. """ \ No newline at end of file + visualize the hover, e.g. by changing the background color. """ + +class CallHierarchyItem(TypedDict): + """Represents a call hierarchy item.""" + + name: str + """ The name of this item. """ + kind: SymbolKind + """ The kind of this item. """ + tags: NotRequired[List[SymbolTag]] + """ Tags for this item. """ + detail: NotRequired[str] + """ More detail for this item, e.g the package and class name of the symbol. """ + uri: DocumentUri + """ The resource identifier of this item. """ + range: Range + """ The range enclosing this symbol not including leading/trailing whitespace but everything else + like comments. This information is typically used to determine if the clients cursor is + inside the symbol to reveal in the symbol in the UI. """ + selectionRange: Range + """ The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + Must be contained by the `range`. """ \ No newline at end of file diff --git a/tests/multilspy/test_multilspy_java.py b/tests/multilspy/test_multilspy_java.py index d466074..d17c3a2 100644 --- a/tests/multilspy/test_multilspy_java.py +++ b/tests/multilspy/test_multilspy_java.py @@ -424,3 +424,71 @@ async def test_multilspy_java_clickhouse_highlevel_sinker_modified_completion_me "kind": 2, } ] + +@pytest.mark.asyncio +async def test_multilspy_java_example_repo_prepare_and_incoming_call_hierarchy() -> None: + """ + Test the working of textDocument/callHierarchy with Java repository - clickhouse-highlevel-sinker + """ + code_language = Language.JAVA + params = { + "code_language": code_language, + "repo_url": "https://github.com/LakshyAAAgrawal/clickhouse-highlevel-sinker/", + "repo_commit": "5775fd7a67e7b60998e1614cf44a8a1fc3190ab0" + } + + with create_test_context(params) as context: + lsp = LanguageServer.create(context.config, context.logger, context.source_directory) + # All the communication with the language server must be performed inside the context manager + # The server process is started when the context manager is entered and is terminated when the context manager is exited. + # The context manager is an asynchronous context manager, so it must be used with async with. + async with lsp.start_server(): + filepath = str(PurePath("src/main/java/com/xlvchao/clickhouse/model/ClickHouseSinkRequest.java")) + + # prepare call hierarchy by resolve request method position to CallHierarchyItem + result = await lsp.request_prepare_call_hierarchy(filepath, 22, 16) + + assert len(result) == 1 + # method package and class name + assert result[0]['detail'] == 'com.xlvchao.clickhouse.model.ClickHouseSinkRequest' + # method signature + assert result[0]['name'] == 'incrementCounter() : void' + # method file uri + assert result[0]['uri'].endswith('src/main/java/com/xlvchao/clickhouse/model/ClickHouseSinkRequest.java') + # range of the method definition includes method signature and body + assert result[0]['range'] == { + 'start': {'line': 22, 'character': 4}, + 'end': {'line': 24, 'character': 5} + } + # selection range includes the method name + assert result[0]['selectionRange'] == { + 'start': {'line': 22, 'character': 16}, + 'end': {'line': 22, 'character': 32} + } + + # get incoming call hierarchy for the resolved method(only one depth) + incoming_call_dep_one = await lsp.request_incoming_calls(result[0]) + + assert len(incoming_call_dep_one) == 1 + # caller method is defined in nested class WriterTask inside ClickHouseWriter thus ClickHouseWriter$WriterTask + assert incoming_call_dep_one[0]['detail'] == 'com.xlvchao.clickhouse.component.ClickHouseWriter$WriterTask' + # caller method signature + assert incoming_call_dep_one[0]['name'] == 'handleUnsuccessfulResponse(ClickHouseSinkRequest, CompletableFuture) : void' + # caller method file uri + assert incoming_call_dep_one[0]['uri'].endswith('src/main/java/com/xlvchao/clickhouse/component/ClickHouseWriter.java') + # range of the caller method definition includes method signature and body + assert incoming_call_dep_one[0]['range'] == { + 'start': {'line': 240, 'character': 8}, + 'end': {'line': 264, 'character': 9} + } + # selection range where the requested method being called within this caller method. + assert incoming_call_dep_one[0]['selectionRange'] == { + 'start': {'line': 249, 'character': 32}, + 'end': {'line': 249, 'character': 50} + } + + # recursively get one more depth in incoming call hierarchy + incoming_call_dep_two = await lsp.request_incoming_calls(incoming_call_dep_one[0]) + print(incoming_call_dep_two) + assert len(incoming_call_dep_two) == 1 + assert incoming_call_dep_two[0]['name'] == 'flushToClickHouse(ClickHouseSinkRequest, CompletableFuture) : void' From cfb3b9f76810403483c61664cf426dc1dcdbc5b7 Mon Sep 17 00:00:00 2001 From: Ella Liu Date: Thu, 30 Jan 2025 10:06:08 +1100 Subject: [PATCH 3/4] update pydantic version --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66b1deb..2c9c7db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ dependencies = [ "jedi-language-server==0.41.1", - "pydantic==1.10.5", + "pydantic==2.10.0", "requests==2.32.3" ] diff --git a/requirements.txt b/requirements.txt index a6e83a6..3b9b7ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ jedi-language-server==0.41.1 pytest==7.3.1 -pydantic==1.10.5 +pydantic==2.10.0 pytest-asyncio==0.21.1 requests==2.32.3 From 8132279f54560edb4dff9868a576f8d8e060ee20 Mon Sep 17 00:00:00 2001 From: Ella Liu Date: Thu, 30 Jan 2025 10:11:43 +1100 Subject: [PATCH 4/4] remove print --- tests/multilspy/test_multilspy_java.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/multilspy/test_multilspy_java.py b/tests/multilspy/test_multilspy_java.py index d17c3a2..e4b80c3 100644 --- a/tests/multilspy/test_multilspy_java.py +++ b/tests/multilspy/test_multilspy_java.py @@ -489,6 +489,6 @@ async def test_multilspy_java_example_repo_prepare_and_incoming_call_hierarchy() # recursively get one more depth in incoming call hierarchy incoming_call_dep_two = await lsp.request_incoming_calls(incoming_call_dep_one[0]) - print(incoming_call_dep_two) + assert len(incoming_call_dep_two) == 1 assert incoming_call_dep_two[0]['name'] == 'flushToClickHouse(ClickHouseSinkRequest, CompletableFuture) : void'