Skip to content

Add implementation for incoming call hierarchy #1 #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ classifiers = [

dependencies = [
"jedi-language-server==0.41.1",
"pydantic==1.10.5",
"pydantic==2.10.0",
"requests==2.32.3"
]

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
79 changes: 79 additions & 0 deletions src/multilspy/language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 22 additions & 1 deletion src/multilspy/multilspy_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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. """
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`. """
68 changes: 68 additions & 0 deletions tests/multilspy/test_multilspy_java.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean>) : 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])

assert len(incoming_call_dep_two) == 1
assert incoming_call_dep_two[0]['name'] == 'flushToClickHouse(ClickHouseSinkRequest, CompletableFuture<Boolean>) : void'