diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 8fc4b982..1322d45d 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -584,13 +584,17 @@ class LanguageServerSymbolRetriever: name_path: str kind: int + @classmethod + def from_symbol(cls, symbol: LanguageServerSymbol) -> Self: + return cls(name_path=symbol.get_name_path(), kind=int(symbol.symbol_kind)) + def get_symbol_overview(self, relative_path: str) -> dict[str, list[SymbolOverviewElement]]: - path_to_symbol_infos = self._lang_server.request_overview(relative_path) + path_to_unified_symbols = self._lang_server.request_overview(relative_path) result = {} - for file_path, symbols in path_to_symbol_infos.items(): + for file_path, unified_symbols in path_to_unified_symbols.items(): # TODO: maybe include not just top-level symbols? We could filter by kind to exclude variables # The language server methods would need to be adjusted for this. - result[file_path] = [self.SymbolOverviewElement(name_path=symbol[0], kind=int(symbol[1])) for symbol in symbols] + result[file_path] = [self.SymbolOverviewElement.from_symbol(LanguageServerSymbol(s)) for s in unified_symbols] return result diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 53ed740a..f4ff544f 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -25,6 +25,7 @@ from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_exceptions import SolidLSPException from solidlsp.ls_handler import SolidLanguageServerHandler from solidlsp.ls_logger import LanguageServerLogger +from solidlsp.ls_types import UnifiedSymbolInformation from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils from solidlsp.lsp_protocol_handler import lsp_types from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes @@ -1165,33 +1166,21 @@ class SolidLanguageServer(ABC): end_column = len(lines[-1]) return ls_types.Range(start=ls_types.Position(line=0, character=0), end=ls_types.Position(line=end_line, character=end_column)) - def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: + def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[UnifiedSymbolInformation]]: """ - An overview of the given directory. - - Maps relative paths of all contained files to info about top-level symbols in the file - (name, kind, line, column). + :return: A mapping of all relative paths analyzed to lists of top-level symbols in the corresponding file. """ symbol_tree = self.request_full_symbol_tree(relative_dir_path) # Initialize result dictionary - result: dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]] = defaultdict(list) + result: dict[str, list[UnifiedSymbolInformation]] = defaultdict(list) # Helper function to process a symbol and its children def process_symbol(symbol: ls_types.UnifiedSymbolInformation): if symbol["kind"] == ls_types.SymbolKind.File: # For file symbols, process their children (top-level symbols) for child in symbol["children"]: - assert "location" in child - assert "selectionRange" in child path = Path(child["location"]["absolutePath"]).resolve().relative_to(self.repository_root_path) - result[str(path)].append( - ( - child["name"], - child["kind"], - child["selectionRange"]["start"]["line"], - child["selectionRange"]["start"]["character"], - ) - ) + result[str(path)].append(child) # For package/directory symbols, process their children for child in symbol["children"]: process_symbol(child) @@ -1201,28 +1190,19 @@ class SolidLanguageServer(ABC): process_symbol(root) return result - def request_document_overview(self, relative_file_path: str) -> list[tuple[str, ls_types.SymbolKind, int, int]]: + def request_document_overview(self, relative_file_path: str) -> list[UnifiedSymbolInformation]: """ - An overview of the given file. - Returns the list of tuples (name, kind, line, column) of all top-level symbols in the file. + :return: the top-level symbols in the given file. """ _, document_roots = self.request_document_symbols(relative_file_path) - result = [] - for root in document_roots: - try: - result.append( - (root["name"], root["kind"], root["selectionRange"]["start"]["line"], root["selectionRange"]["start"]["character"]) - ) - except KeyError as e: - raise KeyError(f"Could not process symbol of name {root.get('name', 'unknown')} in {relative_file_path=}") from e - return result + return document_roots - def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, ls_types.SymbolKind, int, int]]]: + def request_overview(self, within_relative_path: str) -> dict[str, list[UnifiedSymbolInformation]]: """ An overview of all symbols in the given file or directory. :param within_relative_path: the relative path to the file or directory to get the overview of. - :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. + :return: A mapping of all relative paths analyzed to lists of top-level symbols in the corresponding file. """ abs_path = (Path(self.repository_root_path) / within_relative_path).resolve() if not abs_path.exists(): diff --git a/test/solidlsp/python/test_symbol_retrieval.py b/test/solidlsp/python/test_symbol_retrieval.py index c19bf8bb..931cb20e 100644 --- a/test/solidlsp/python/test_symbol_retrieval.py +++ b/test/solidlsp/python/test_symbol_retrieval.py @@ -10,6 +10,7 @@ import os import pytest +from serena.symbol import LanguageServerSymbol from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_types import SymbolKind @@ -427,16 +428,15 @@ class TestLanguageServerSymbols: assert len(services_symbols) > 0 # Check for specific symbols from services.py - expected_symbols = [ - ("UserService", SymbolKind.Class, 9, 6), - ("ItemService", SymbolKind.Class, 40, 6), - ("create_service_container", SymbolKind.Function, 67, 4), - ("user_var_str", SymbolKind.Variable, 73, 0), - ("user_service", SymbolKind.Variable, 76, 0), - ] - - for symbol in expected_symbols: - assert symbol in services_symbols + expected_symbols = { + "UserService", + "ItemService", + "create_service_container", + "user_var_str", + "user_service", + } + retrieved_symbols = {symbol["name"] for symbol in services_symbols if "name" in symbol} + assert expected_symbols.issubset(retrieved_symbols) @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) def test_request_document_overview(self, language_server: SolidLanguageServer) -> None: @@ -445,7 +445,7 @@ class TestLanguageServerSymbols: overview = language_server.request_document_overview(os.path.join("examples", "user_management.py")) # Verify that we have entries for both files - symbol_names = {s_info[0] for s_info in overview} + symbol_names = {LanguageServerSymbol(s_info).name for s_info in overview} assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names) @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)