diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index f889e725..1695e4db 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -178,11 +178,12 @@ class JetBrainsPluginClient(ToStringMixin): request_data = {"namePath": name_path, "relativePath": relative_path} return self._make_request("POST", "/findReferences", request_data) - def get_symbols_overview(self, relative_path: str) -> dict[str, Any]: + def get_symbols_overview(self, relative_path: str, depth: int) -> dict[str, Any]: """ :param relative_path: the relative path to a source file + :param depth: the depth of children to include (0 = no children) """ - request_data = {"relativePath": relative_path} + request_data = {"relativePath": relative_path, "depth": depth} return self._make_request("POST", "/getSymbolsOverview", request_data) def rename_symbol( diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index e2b84e6e..6407de7c 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -1,3 +1,6 @@ +from collections import defaultdict +from typing import Any + from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicRead from serena.tools.jetbrains_plugin_client import JetBrainsPluginClient @@ -98,9 +101,41 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp Retrieves an overview of the top-level symbols within a specified file using the JetBrains backend """ + @staticmethod + def _transform_symbols_to_compact_format(symbols: list[dict[str, Any]]) -> dict[str, list]: + """ + Transform symbol overview from verbose format to compact grouped format. + + Groups symbols by kind and uses names instead of full symbol objects. + For symbols with children, creates nested dictionaries. + + The name_path can be inferred from the hierarchical structure: + - Top-level symbols: name_path = name + - Nested symbols: name_path = parent_name + "/" + name + For example, "convert" under class "ProjectType" has name_path "ProjectType/convert". + """ + result = defaultdict(list) + + for symbol in symbols: + kind = symbol.get("type", "Unknown") + name_path = symbol["name_path"] + name = name_path.split("/")[-1] + children = symbol.get("children", []) + + if children: + # Symbol has children: create nested dict {name: children_dict} + children_dict = JetBrainsGetSymbolsOverviewTool._transform_symbols_to_compact_format(children) + result[kind].append({name: children_dict}) + else: + # Symbol has no children: just add the name + result[kind].append(name) + + return result + def apply( self, relative_path: str, + depth: int = 0, max_answer_chars: int = -1, ) -> str: """ @@ -111,13 +146,14 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp or by using the `list_dir` and `find_file` tools (or similar). :param relative_path: the relative path to the file to get the overview of + :param depth: depth up to which descendants shall be retrieved (e.g., use 1 to also retrieve immediate children). :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. -1 means the default value from the config will be used. - :return: a JSON object containing the symbols + :return: a JSON object containing the symbols grouped by kind in a compact format. """ with JetBrainsPluginClient.from_project(self.project) as client: - response_dict = client.get_symbols_overview( - relative_path=relative_path, - ) - result = self._to_json(response_dict) + response_dict = client.get_symbols_overview(relative_path=relative_path, depth=depth) + symbols = response_dict["symbols"] + compact_symbols = self._transform_symbols_to_compact_format(symbols) + result = self._to_json(compact_symbols) return self._limit_length(result, max_answer_chars) diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 75d0e5c9..bcd244a5 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -3,6 +3,7 @@ Language server-related tools """ import os +from collections import defaultdict from collections.abc import Sequence from copy import copy from typing import Any @@ -61,7 +62,7 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead): :param max_answer_chars: if the overview is longer than this number of characters, no content will be returned. -1 means the default value from the config will be used. Don't adjust unless there is really no other way to get the content required for the task. - :return: a JSON object containing info about top-level symbols in the file + :return: a JSON object containing symbols grouped by kind in a compact format. """ symbol_retriever = self.create_language_server_symbol_retriever() file_path = os.path.join(self.project.project_root, relative_path) @@ -73,9 +74,41 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead): if os.path.isdir(file_path): raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ") result = symbol_retriever.get_symbol_overview(relative_path, depth=depth)[relative_path] - result_json_str = self._to_json(result) + # Transform to compact format + compact_result = self._transform_symbols_to_compact_format(result) + result_json_str = self._to_json(compact_result) return self._limit_length(result_json_str, max_answer_chars) + @staticmethod + def _transform_symbols_to_compact_format(symbols: list[dict[str, Any]]) -> dict[str, list]: + """ + Transform symbol overview from verbose format to compact grouped format. + + Groups symbols by kind and uses names instead of full symbol objects. + For symbols with children, creates nested dictionaries. + + The name_path can be inferred from the hierarchical structure: + - Top-level symbols: name_path = name + - Nested symbols: name_path = parent_name + "/" + name + For example, "convert" under class "ProjectType" has name_path "ProjectType/convert". + """ + result = defaultdict(list) + + for symbol in symbols: + kind = symbol.get("kind", "Unknown") + name = symbol.get("name", "unknown") + children = symbol.get("children", []) + + if children: + # Symbol has children: create nested dict {name: children_dict} + children_dict = GetSymbolsOverviewTool._transform_symbols_to_compact_format(children) + result[kind].append({name: children_dict}) + else: + # Symbol has no children: just add the name + result[kind].append(name) + + return result + class FindSymbolTool(Tool, ToolMarkerSymbolicRead): """