From 40209b65d3839723a06af7efa0ecdb55980bb7e9 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 5 Jul 2025 20:42:53 +0200 Subject: [PATCH 01/41] Add jetbrains_plugin_client --- src/serena/tools/jetbrains_plugin_client.py | 130 ++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/serena/tools/jetbrains_plugin_client.py diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py new file mode 100644 index 0000000..76bfe78 --- /dev/null +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -0,0 +1,130 @@ +""" +Client for the Serena JetBrains Plugin +""" + +import json +from typing import Any, Optional, Self + +import requests + + +class SerenaClientError(Exception): + """Base exception for Serena client errors.""" + + +class ConnectionError(SerenaClientError): + """Raised when connection to the service fails.""" + + +class APIError(SerenaClientError): + """Raised when the API returns an error response.""" + + +class JetBrainsPluginClient: + """ + Python client for the Serena Backend Service. + + Provides simple methods to interact with all available endpoints. + """ + + def __init__(self, base_url: str = "http://localhost:8080", timeout: int = 30): + self.base_url = base_url.rstrip("/") + self.timeout = timeout + self.session = requests.Session() + self.session.headers.update({"Content-Type": "application/json", "Accept": "application/json"}) + + def _make_request(self, method: str, endpoint: str, data: Optional[dict] = None) -> dict[str, Any]: + url = f"{self.base_url}{endpoint}" + + try: + if method.upper() == "GET": + response = self.session.get(url, timeout=self.timeout) + elif method.upper() == "POST": + json_data = json.dumps(data) if data else None + response = self.session.post(url, data=json_data, timeout=self.timeout) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + response.raise_for_status() + + # Try to parse JSON response + try: + return response.json() + except json.JSONDecodeError: + # If response is not JSON, return raw text + return {"response": response.text} + + except requests.exceptions.ConnectionError as e: + raise ConnectionError(f"Failed to connect to Serena service at {url}: {e}") + except requests.exceptions.Timeout as e: + raise ConnectionError(f"Request to {url} timed out: {e}") + except requests.exceptions.HTTPError: + raise APIError(f"API request failed with status {response.status_code}: {response.text}") + except requests.exceptions.RequestException as e: + raise SerenaClientError(f"Request failed: {e}") + + def heartbeat(self) -> dict[str, Any]: + return self._make_request("GET", "/heartbeat") + + def find_symbol(self, name_path: str, include_body: bool = False, depth: int = 0) -> dict[str, Any]: + """ + Find symbols by name. + + :param name_path: the name path to match + :param include_body: whether to include symbol body content + :param depth: depth of children to include (0 = no children) + + :return: Dictionary containing 'symbols' list with matching symbols + """ + request_data = {"name": name_path, "includeBody": include_body, "depth": depth} + + return self._make_request("POST", "/findSymbol", request_data) + + def find_references(self, name_path: str, relative_path: str) -> dict[str, Any]: + """ + Find references to a symbol. + + :param name_path: the name path of the symbol + :param relative_path: the relative path + :return: dictionary containing 'symbols' list with symbol references + """ + request_data = {"namePath": name_path, "relativePath": relative_path} + + return self._make_request("POST", "/findReferences", request_data) + + def is_service_available(self) -> bool: + try: + response = self.heartbeat() + return response.get("status") == "OK" + except (ConnectionError, APIError): + return False + + def close(self) -> None: + self.session.close() + + def __enter__(self) -> Self: + return self + + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + self.close() + + +if __name__ == "__main__": + with JetBrainsPluginClient() as client: + # check heartbeat + heartbeat_response = client.heartbeat() + print(f"Heartbeat: {heartbeat_response}") + + # find symbol + symbols_response = client.find_symbol("DQN", include_body=False, depth=1) + symbols = symbols_response.get("symbols", []) + print(f"Found {len(symbols)} symbols") + from pprint import pprint + + pprint(symbols_response) + + # find references + if symbols: + first_symbol = symbols[0] + refs_response = client.find_references(name_path=first_symbol["namePath"], relative_path=first_symbol["relativePath"]) + pprint(refs_response) From 8ea2f866ce05c8e3149232544f063ab7c7d883b6 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 5 Jul 2025 21:35:43 +0200 Subject: [PATCH 02/41] Add JetBrainsFindSymbolTool --- src/serena/tools/__init__.py | 1 + src/serena/tools/jetbrains_plugin_client.py | 5 +- src/serena/tools/jetbrains_tools.py | 70 +++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/serena/tools/jetbrains_tools.py diff --git a/src/serena/tools/__init__.py b/src/serena/tools/__init__.py index 4e05360..c88a7e5 100644 --- a/src/serena/tools/__init__.py +++ b/src/serena/tools/__init__.py @@ -6,3 +6,4 @@ from .memory_tools import * from .cmd_tools import * from .config_tools import * from .workflow_tools import * +from .jetbrains_tools import * diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index 76bfe78..db87894 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -66,17 +66,18 @@ class JetBrainsPluginClient: def heartbeat(self) -> dict[str, Any]: return self._make_request("GET", "/heartbeat") - def find_symbol(self, name_path: str, include_body: bool = False, depth: int = 0) -> dict[str, Any]: + def find_symbol(self, name_path: str, relative_path: str | None = None, include_body: bool = False, depth: int = 0) -> dict[str, Any]: """ Find symbols by name. :param name_path: the name path to match + :param relative_path: the relative path to which to restrict the search :param include_body: whether to include symbol body content :param depth: depth of children to include (0 = no children) :return: Dictionary containing 'symbols' list with matching symbols """ - request_data = {"name": name_path, "includeBody": include_body, "depth": depth} + request_data = {"namePath": name_path, "relativePath": relative_path, "includeBody": include_body, "depth": depth} return self._make_request("POST", "/findSymbol", request_data) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py new file mode 100644 index 0000000..d7d276e --- /dev/null +++ b/src/serena/tools/jetbrains_tools.py @@ -0,0 +1,70 @@ +import json + +from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool +from serena.tools.jetbrains_plugin_client import JetBrainsPluginClient + + +class JetBrainsFindSymbolTool(Tool): + """ + Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). + """ + + def apply( + self, + name_path: str, + depth: int = 0, + relative_path: str | None = None, + include_body: bool = False, + max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH, + ) -> str: + """ + Retrieves information on all symbols/code entities (classes, methods, etc.) based on the given `name_path`, + which represents a pattern for the symbol's path within the symbol tree of a single file. + The returned symbol location can be used for edits or further queries. + Specify `depth > 0` to retrieve children (e.g., methods of a class). + + The matching behavior is determined by the structure of `name_path`, which can + either be a simple name (e.g. "method") or a name path like "class/method" (relative name path) + or "/class/method" (absolute name path). + Note that the name path is not a path in the file system but rather a path in the symbol tree + **within a single file**. Thus, file or directory names should never be included in the `name_path`. + For restricting the search to a single file or directory, pass the `relative_path` parameter. + The retrieved symbols' `name_path` attribute will always be composed of symbol names, never file + or directory names. + + Key aspects of the name path matching behavior: + - The name of the retrieved symbols will match the last segment of `name_path`, while preceding segments + will restrict the search to symbols that have a desired sequence of ancestors. + - If there is no `/` in `name_path`, there is no restriction on the ancestor symbols. + For example, passing `method` will match against all symbols with name paths like `method`, + `class/method`, `class/nested_class/method`, etc. + - If `name_path` contains at least one `/`, the matching is restricted to symbols + with the respective ancestors. For example, passing `class/method` will match against + `class/method` as well as `nested_class/class/method` but not `other_class/method`. + - If `name_path` starts with a `/`, it will be treated as an absolute name path pattern, i.e. + all ancestors are provided and must match. + For example, passing `/class` will match only against top-level symbols named `class` but + will not match `nested_class/class`. Passing `/class/method` will match `class/method` but + not `outer_class/class/method`. + + :param name_path: The name path pattern to search for, see above for details. + :param depth: Depth to retrieve descendants (e.g., 1 for class methods/attributes). + :param relative_path: Optional. Restrict search to this file or directory. + If None, searches entire codebase. + If a directory is passed, the search will be restricted to the files in that directory. + If a file is passed, the search will be restricted to that file. + If you have some knowledge about the codebase, you should use this parameter, as it will significantly + speed up the search as well as reduce the number of results. + :param include_body: If True, include the symbol's source code. Use judiciously. + :param max_answer_chars: Max characters for the JSON result. If exceeded, no content is returned. + :return: JSON string: a list of symbols (with locations) matching the name. + """ + with JetBrainsPluginClient() as client: + response_dict = client.find_symbol( + name_path=name_path, + relative_path=relative_path, + depth=depth, + include_body=include_body, + ) + result = json.dumps(response_dict) + return self._limit_length(result, max_answer_chars) From 044e979bf8f2b4c07547eec1a36cbbce41036183 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 5 Jul 2025 22:08:57 +0200 Subject: [PATCH 03/41] Add JetBrainsFindSymbolReferencesTool --- src/serena/tools/jetbrains_tools.py | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index d7d276e..22a4035 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -56,7 +56,7 @@ class JetBrainsFindSymbolTool(Tool): If you have some knowledge about the codebase, you should use this parameter, as it will significantly speed up the search as well as reduce the number of results. :param include_body: If True, include the symbol's source code. Use judiciously. - :param max_answer_chars: Max characters for the JSON result. If exceeded, no content is returned. + :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. :return: JSON string: a list of symbols (with locations) matching the name. """ with JetBrainsPluginClient() as client: @@ -68,3 +68,33 @@ class JetBrainsFindSymbolTool(Tool): ) result = json.dumps(response_dict) return self._limit_length(result, max_answer_chars) + + +class JetBrainsFindSymbolReferencesTool(Tool): + """ + Finds symbols that reference the given symbol + """ + + def apply( + self, + name_path: str, + relative_path: str, + max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH, + ) -> str: + """ + Finds symbols that reference the symbol at the given `name_path`. + The result will contain metadata about the referencing symbols. + + :param name_path: name path of the symbol for which to find references; matching logic as described in find symbol tool. + :param relative_path: the relative path to the file containing the symbol for which to find references. + Note that here you can't pass a directory but must pass a file. + :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. + :return: a list of JSON objects with the symbols referencing the requested symbol + """ + with JetBrainsPluginClient() as client: + response_dict = client.find_references( + name_path=name_path, + relative_path=relative_path, + ) + result = json.dumps(response_dict) + return self._limit_length(result, max_answer_chars) From 58bf161fd218321d0b997e634597161f864aaad2 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sat, 5 Jul 2025 23:42:12 +0200 Subject: [PATCH 04/41] More flexible handling of enabled tools: * In addition to exclusion, support notion of "optional tools" that are disabled by default and enabled only upon request * Unify the handling of inclusions/exclusions across - SerenaConfig - ProjectConfig - SerenaAgentContext - SerenaAgentMode * Make ToolRegistry a singleton and simplify all related operations --- pyproject.toml | 1 + scripts/print_tool_overview.py | 2 +- src/serena/agent.py | 50 ++++------- src/serena/config/context_mode.py | 27 ++---- src/serena/config/serena_config.py | 64 ++++++++++++-- .../resources/serena_config.template.yml | 6 ++ src/serena/tools/jetbrains_tools.py | 6 +- src/serena/tools/tools_base.py | 84 +++++++++---------- test/serena/test_mcp.py | 2 +- 9 files changed, 128 insertions(+), 114 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e0f4794..7eacf74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,6 +225,7 @@ ignore = [ "B904", # forces use of raise from other_exception "RUF012", # forbids mutable attributes as ClassVar "SIM117", # forbids nested with statements + "C400", # wants to unnecessarily force use of list comprehension ] unfixable = ["F841", "F601", "F602", "B018"] extend-fixable = ["F401", "B905", "W291"] diff --git a/scripts/print_tool_overview.py b/scripts/print_tool_overview.py index 955ee78..3649a7d 100644 --- a/scripts/print_tool_overview.py +++ b/scripts/print_tool_overview.py @@ -1,4 +1,4 @@ from serena.agent import ToolRegistry if __name__ == "__main__": - ToolRegistry.print_tool_overview() + ToolRegistry().print_tool_overview() diff --git a/src/serena/agent.py b/src/serena/agent.py index 5a22b2d..bc5e33a 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -23,7 +23,7 @@ from sensai.util.logging import LogTime from serena import serena_version from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode -from serena.config.serena_config import Project, SerenaConfig, get_serena_managed_dir +from serena.config.serena_config import Project, SerenaConfig, ToolSet, get_serena_managed_dir from serena.constants import ( SERENA_LOG_FORMAT, ) @@ -234,13 +234,13 @@ class SerenaAgent: self._context = context # instantiate all tool classes - self._all_tools: dict[type[Tool], Tool] = {tool_class: tool_class(self) for tool_class in ToolRegistry.get_all_tool_classes()} + self._all_tools: dict[type[Tool], Tool] = {tool_class: tool_class(self) for tool_class in ToolRegistry().get_all_tool_classes()} tool_names = [tool.get_name_from_cls() for tool in self._all_tools.values()] - # determine the set exposed tools (which e.g. the MCP shall see), limited by the context - # (which is fixed for the session) - excluded_tool_classes = set(self._context.get_excluded_tool_classes()) - self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if tc not in excluded_tool_classes} + # determine the set exposed tools (which e.g. the MCP shall see), limited by the Serena config + # as well as the context (which is fixed for the session) + tool_set = ToolSet().apply(self.serena_config, self._context) + self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if tool_set.includes_name(t.get_name())} # If GUI log window is enabled, set the tool names for highlighting if self._gui_log_handler is not None: @@ -408,42 +408,20 @@ class SerenaAgent: def _update_active_tools(self) -> None: """ Update the active tools based on context, modes, and project configuration. - All tool exclusions are merged together. """ - excluded_tool_classes: set[type[Tool]] = set() - # modes - for mode in self._modes: - mode_excluded_tool_classes = mode.get_excluded_tool_classes() - if len(mode_excluded_tool_classes) > 0: - log.info( - f"Mode {mode.name} excluded {len(mode_excluded_tool_classes)} tools: {', '.join([tool.get_name_from_cls() for tool in mode_excluded_tool_classes])}" - ) - excluded_tool_classes.update(mode_excluded_tool_classes) - # context - context_excluded_tool_classes = self._context.get_excluded_tool_classes() - if len(context_excluded_tool_classes) > 0: - log.info( - f"Context {self._context.name} excluded {len(context_excluded_tool_classes)} tools: {', '.join([tool.get_name_from_cls() for tool in context_excluded_tool_classes])}" - ) - excluded_tool_classes.update(context_excluded_tool_classes) - # project config + tool_set = ToolSet().apply(self.serena_config, self._context, *self._modes) if self._active_project is not None: - project_excluded_tool_classes = self._active_project.project_config.get_excluded_tool_classes() - if len(project_excluded_tool_classes) > 0: - log.info( - f"Project {self._active_project.project_name} excluded {len(project_excluded_tool_classes)} tools: {', '.join([tool.get_name_from_cls() for tool in project_excluded_tool_classes])}" - ) - excluded_tool_classes.update(project_excluded_tool_classes) + tool_set.apply(self._active_project.project_config) if self._active_project.project_config.read_only: - for tool_class in self._all_tools: - if tool_class.can_edit(): - excluded_tool_classes.add(tool_class) + tool_set.exclude_editing_tools() self._active_tools = { - tool_class: tool_instance for tool_class, tool_instance in self._all_tools.items() if tool_class not in excluded_tool_classes + tool_class: tool_instance + for tool_class, tool_instance in self._all_tools.items() + if tool_set.includes_name(tool_instance.get_name()) } - log.info(f"Active tools after all exclusions ({len(self._active_tools)}): {', '.join(self.get_active_tool_names())}") + log.info(f"Active tools ({len(self._active_tools)}): {', '.join(self.get_active_tool_names())}") def issue_task(self, task: Callable[[], Any], name: str | None = None) -> Future: """ @@ -648,7 +626,7 @@ class SerenaAgent: return self._all_tools[tool_class] # type: ignore def print_tool_overview(self) -> None: - ToolRegistry.print_tool_overview(self._active_tools.values()) + ToolRegistry().print_tool_overview(self._active_tools.values()) def mark_file_modified(self, relativ_path: str) -> None: assert self.lines_read is not None diff --git a/src/serena/config/context_mode.py b/src/serena/config/context_mode.py index b8cfb9f..0ee3c26 100644 --- a/src/serena/config/context_mode.py +++ b/src/serena/config/context_mode.py @@ -4,7 +4,7 @@ Context and Mode configuration loader import os from copy import copy -from dataclasses import asdict, dataclass, field +from dataclasses import asdict, dataclass from enum import Enum from pathlib import Path from typing import TYPE_CHECKING, Self @@ -13,16 +13,17 @@ import yaml from sensai.util import logging from sensai.util.string import ToStringMixin +from serena.config.serena_config import ToolInclusionDefinition from serena.constants import CONTEXT_YAMLS_DIR, DEFAULT_CONTEXT, DEFAULT_MODES, MODE_YAMLS_DIR if TYPE_CHECKING: - from serena.agent import Tool + pass log = logging.getLogger(__name__) -@dataclass -class SerenaAgentMode: +@dataclass(kw_only=True) +class SerenaAgentMode(ToolInclusionDefinition): """Represents a mode of operation for the agent, typically read off a YAML file. An agent can be in multiple modes simultaneously as long as they are not mutually exclusive. The modes can be adjusted after the agent is running, for example for switching from planning to editing. @@ -31,7 +32,6 @@ class SerenaAgentMode: name: str prompt: str description: str = "" - excluded_tools: set[str] = field(default_factory=set) def to_json_dict(self) -> dict[str, str | list[str]]: result = asdict(self) @@ -50,12 +50,6 @@ class SerenaAgentMode: if self.excluded_tools: print(" excluded tools:\n " + ", ".join(sorted(self.excluded_tools))) - def get_excluded_tool_classes(self) -> list[type["Tool"]]: - """Get the list of tool classes that are excluded from the mode.""" - from serena.agent import ToolRegistry - - return [ToolRegistry.get_tool_class_by_name(tool_name) for tool_name in self.excluded_tools] - @classmethod def from_yaml(cls, yaml_path: str | Path) -> Self: """Load a mode from a YAML file.""" @@ -93,8 +87,8 @@ class SerenaAgentMode: return cls.from_yaml(name_or_path) -@dataclass -class SerenaAgentContext(ToStringMixin): +@dataclass(kw_only=True) +class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin): """Represents a context where the agent is operating (an IDE, a chat, etc.), typically read off a YAML file. An agent can only be in a single context at a time. The contexts cannot be changed after the agent is running. @@ -103,7 +97,6 @@ class SerenaAgentContext(ToStringMixin): name: str prompt: str description: str = "" - excluded_tools: set[str] = field(default_factory=set) def _tostring_includes(self) -> list[str]: return ["name"] @@ -119,12 +112,6 @@ class SerenaAgentContext(ToStringMixin): data["excluded_tools"] = set(data["excluded_tools"]) return cls(**data) - def get_excluded_tool_classes(self) -> list[type["Tool"]]: - """Get the list of tool classes that are excluded from the context.""" - from serena.agent import ToolRegistry - - return [ToolRegistry.get_tool_class_by_name(tool_name) for tool_name in self.excluded_tools] - @classmethod def from_yaml(cls, yaml_path: str | Path) -> Self: """Load a context from a YAML file.""" diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 883c593..306393c 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -4,6 +4,7 @@ The Serena Model Context Protocol (MCP) Server import os import shutil +from collections.abc import Iterable from copy import deepcopy from dataclasses import dataclass, field from functools import cached_property @@ -22,7 +23,6 @@ from serena.constants import ( SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME, ) -from serena.tools import Tool, ToolRegistry from serena.util.general import load_yaml, save_yaml from serena.util.inspection import determine_programming_language_composition from solidlsp.ls_config import Language @@ -35,6 +35,53 @@ T = TypeVar("T") DEFAULT_TOOL_TIMEOUT: float = 240 +class ToolSet: + def __init__(self) -> None: + from serena.tools import ToolRegistry + + self._tool_names = set(ToolRegistry().get_tool_names_default_enabled()) + + def apply(self, *tool_inclusion_definitions: "ToolInclusionDefinition") -> Self: + from serena.tools import ToolRegistry + + registry = ToolRegistry() + for definition in tool_inclusion_definitions: + for included_tool in definition.included_optional_tools: + if not registry.is_valid_tool_name(included_tool): + raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") + self._tool_names.add(included_tool) + for excluded_tool in definition.excluded_tools: + if not registry.is_valid_tool_name(excluded_tool): + raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") + if excluded_tool in self._tool_names: + self._tool_names.remove(excluded_tool) + return self + + def exclude_editing_tools(self) -> Self: + from serena.tools import ToolRegistry + + registry = ToolRegistry() + for tool_name in list(self._tool_names): + if registry.get_tool_class_by_name(tool_name).can_edit(): + self._tool_names.remove(tool_name) + return self + + def get_tool_names(self) -> set[str]: + """ + Returns the names of the tools that are currently included in the tool set. + """ + return self._tool_names + + def includes_name(self, tool_name: str) -> bool: + return tool_name in self._tool_names + + +@dataclass +class ToolInclusionDefinition: + excluded_tools: Iterable[str] = () + included_optional_tools: Iterable[str] = () + + class SerenaConfigError(Exception): pass @@ -56,12 +103,11 @@ def is_running_in_docker() -> bool: return False -@dataclass -class ProjectConfig(ToStringMixin): +@dataclass(kw_only=True) +class ProjectConfig(ToolInclusionDefinition, ToStringMixin): project_name: str language: Language ignored_paths: list[str] = field(default_factory=list) - excluded_tools: set[str] = field(default_factory=set) read_only: bool = False ignore_all_files_in_gitignore: bool = True initial_prompt: str = "" @@ -123,7 +169,8 @@ class ProjectConfig(ToStringMixin): project_name=project_name, language=language, ignored_paths=data.get("ignored_paths", []), - excluded_tools=set(data.get("excluded_tools", [])), + excluded_tools=data.get("excluded_tools", []), + included_optional_tools=data.get("included_optional_tools", []), read_only=data.get("read_only", False), ignore_all_files_in_gitignore=data.get("ignore_all_files_in_gitignore", True), initial_prompt=data.get("initial_prompt", ""), @@ -148,9 +195,6 @@ class ProjectConfig(ToStringMixin): yaml_data["project_name"] = project_root.name return cls._from_dict(yaml_data) - def get_excluded_tool_classes(self) -> set[type["Tool"]]: - return set(ToolRegistry.get_tool_class_by_name(tool_name) for tool_name in self.excluded_tools) - @dataclass class Project: @@ -178,7 +222,7 @@ class Project: @dataclass(kw_only=True) -class SerenaConfig: +class SerenaConfig(ToolInclusionDefinition): """ Holds the Serena agent configuration, which is typically loaded from a YAML configuration file (when instantiated via :method:`from_config_file`), which is updated when projects are added or removed. @@ -287,6 +331,8 @@ class SerenaConfig: instance.web_dashboard_open_on_launch = loaded_commented_yaml.get("web_dashboard_open_on_launch", True) instance.tool_timeout = loaded_commented_yaml.get("tool_timeout", DEFAULT_TOOL_TIMEOUT) instance.trace_lsp_communication = loaded_commented_yaml.get("trace_lsp_communication", False) + instance.excluded_tools = loaded_commented_yaml.get("excluded_tools", []) + instance.included_optional_tools = loaded_commented_yaml.get("included_optional_tools", []) # re-save the configuration file if any migrations were performed if num_project_migrations > 0: diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index 0013f65..e0a6a09 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -36,6 +36,12 @@ trace_lsp_communication: False tool_timeout: 240 # timeout, in seconds, after which tool executions are terminated +excluded_tools: [] +# list of tools to be globally excluded + +included_optional_tools: [] +# list of optional tools (which are disabled by default) to be included + # MANAGED BY SERENA, KEEP AT THE BOTTOM OF THE YAML AND DON'T EDIT WITHOUT NEED # The list of registered projects. # To add a project, within a chat, simply ask Serena to "activate the project /path/to/project" or, diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 22a4035..751a312 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -1,10 +1,10 @@ import json -from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool +from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerOptional from serena.tools.jetbrains_plugin_client import JetBrainsPluginClient -class JetBrainsFindSymbolTool(Tool): +class JetBrainsFindSymbolTool(Tool, ToolMarkerOptional): """ Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). """ @@ -70,7 +70,7 @@ class JetBrainsFindSymbolTool(Tool): return self._limit_length(result, max_answer_chars) -class JetBrainsFindSymbolReferencesTool(Tool): +class JetBrainsFindSymbolReferencesTool(Tool, ToolMarkerOptional): """ Finds symbols that reference the given symbol """ diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index b0a3cd7..8cf53ca 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -2,8 +2,8 @@ import inspect import os import traceback from abc import ABC, abstractmethod -from collections.abc import Callable, Generator, Iterable -from copy import copy +from collections.abc import Callable, Iterable +from dataclasses import dataclass from types import TracebackType from typing import TYPE_CHECKING, Any, Self, TypeVar @@ -13,6 +13,7 @@ from sensai.util.string import dict_string from serena.prompt_factory import PromptFactory from serena.symbol import SymbolManager +from serena.util.class_decorators import singleton from serena.util.inspection import iter_subclasses from solidlsp import SolidLanguageServer @@ -72,6 +73,12 @@ class ToolMarkerDoesNotRequireActiveProject: pass +class ToolMarkerOptional: + """ + Marker class for optional tools that are disabled by default. + """ + + class ToolInterface(ABC): """Protocol defining the complete interface that make_tool() expects from a tool.""" @@ -311,58 +318,44 @@ class EditedFileContext: # If they do not, we may have to add a call to notify it. +@dataclass(kw_only=True) +class RegisteredTool: + tool_class: type[Tool] + is_optional: bool + tool_name: str + + +@singleton class ToolRegistry: - _tool_dict: dict[str, type[Tool]] | None = None - """maps tool name to the corresponding tool class""" - - @staticmethod - def _iter_tool_classes() -> Generator[type[Tool], None, None]: - """ - Iterate over Tool subclasses. - """ + def __init__(self) -> None: + self._tool_dict: dict[str, RegisteredTool] = {} for cls in iter_subclasses(Tool): - if cls.__module__.startswith("serena.tools"): - yield cls + if not cls.__module__.startswith("serena.tools"): + continue + is_optional = issubclass(cls, ToolMarkerOptional) + name = cls.get_name_from_cls() + if name in self._tool_dict: + raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.") + self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name) - @classmethod - def _get_tool_dict(cls) -> dict[str, type[Tool]]: - if cls._tool_dict is None: - cls._tool_dict = {} - for tool_class in cls._iter_tool_classes(): - name = tool_class.get_name_from_cls() - if name in cls._tool_dict: - raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.") - cls._tool_dict[name] = tool_class - return cls._tool_dict + def get_tool_class_by_name(self, tool_name: str) -> type[Tool]: + return self._tool_dict[tool_name].tool_class - @classmethod - def get_tool_class_by_name(cls, tool_name: str) -> type[Tool]: - try: - return cls._get_tool_dict()[tool_name] - except KeyError as e: - available_tools = "\n".join(ToolRegistry.get_tool_names()) - raise ValueError(f"Tool with name {tool_name} not found. Available tools:\n{available_tools}") from e + def get_all_tool_classes(self) -> list[type[Tool]]: + return list(t.tool_class for t in self._tool_dict.values()) - @classmethod - def get_all_tool_classes(cls) -> list[type[Tool]]: - return list(cls._get_tool_dict().values()) + def get_tool_names_default_enabled(self) -> list[str]: + """ + :return: the list of tool names that are enabled by default (i.e. non-optional tools). + """ + return [t.tool_name for t in self._tool_dict.values() if not t.is_optional] - @classmethod - def get_tool_names(cls) -> list[str]: - return list(cls._get_tool_dict().keys()) - - @classmethod - def tool_dict(cls) -> dict[str, type[Tool]]: - """Maps tool name to the corresponding tool class""" - return copy(cls._get_tool_dict()) - - @classmethod - def print_tool_overview(cls, tools: Iterable[type[Tool] | Tool] | None = None) -> None: + def print_tool_overview(self, tools: Iterable[type[Tool] | Tool] | None = None) -> None: """ Print a summary of the tools. If no tools are passed, a summary of all tools is printed. """ if tools is None: - tools = cls._get_tool_dict().values() + tools = [tool.tool_class for tool in self._tool_dict.values() if not tool.is_optional] tool_dict: dict[str, type[Tool] | Tool] = {} for tool_class in tools: @@ -370,3 +363,6 @@ class ToolRegistry: for tool_name in sorted(tool_dict.keys()): tool_class = tool_dict[tool_name] print(f" * `{tool_name}`: {tool_class.get_tool_description().strip()}") + + def is_valid_tool_name(self, tool_name: str) -> bool: + return tool_name in self._tool_dict diff --git a/test/serena/test_mcp.py b/test/serena/test_mcp.py index 8ffb2c2..37b6eca 100644 --- a/test/serena/test_mcp.py +++ b/test/serena/test_mcp.py @@ -271,7 +271,7 @@ def is_test_mock_class(tool_class: type) -> bool: ) -@pytest.mark.parametrize("tool_class", ToolRegistry.get_all_tool_classes()) +@pytest.mark.parametrize("tool_class", ToolRegistry().get_all_tool_classes()) def test_make_tool_all_tools(tool_class) -> None: """Test that make_tool works for all tools in the codebase.""" From f49802a040ae129194fd538c0afa9c56d957e2fe Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Jul 2025 01:04:26 +0200 Subject: [PATCH 05/41] Allow to enable JetBrains tools via config entry 'jetbrains' which enables an internal SerenaAgentMode * Added notion of internal SerenaAgentModes, which cannot be enabled dynamically * Introduce 'jetbrains' SerenaAgentMode * Add 'jetbrains' option to SerenaConfig, which enables the respective mode at startup, appropriately configuring the set of exposed tools * Refactoring: Made ToolSet immutable; methods return new instances --- src/serena/agent.py | 29 +++++----- src/serena/config/context_mode.py | 15 ++++- src/serena/config/serena_config.py | 58 +++++++++++++++---- src/serena/constants.py | 1 + .../config/internal_modes/jetbrains.yml | 11 ++++ .../resources/serena_config.template.yml | 6 ++ 6 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 src/serena/resources/config/internal_modes/jetbrains.yml diff --git a/src/serena/agent.py b/src/serena/agent.py index bc5e33a..f570ac3 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -237,11 +237,6 @@ class SerenaAgent: self._all_tools: dict[type[Tool], Tool] = {tool_class: tool_class(self) for tool_class in ToolRegistry().get_all_tool_classes()} tool_names = [tool.get_name_from_cls() for tool in self._all_tools.values()] - # determine the set exposed tools (which e.g. the MCP shall see), limited by the Serena config - # as well as the context (which is fixed for the session) - tool_set = ToolSet().apply(self.serena_config, self._context) - self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if tool_set.includes_name(t.get_name())} - # If GUI log window is enabled, set the tool names for highlighting if self._gui_log_handler is not None: self._gui_log_handler.log_viewer.set_tool_names(tool_names) @@ -261,6 +256,16 @@ class SerenaAgent: log.info(f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()})") log.info("Configuration file: %s", self.serena_config.config_file_path) log.info("Available projects: {}".format(", ".join(self.serena_config.project_names))) + log.info(f"Loaded tools ({len(self._all_tools)}): {', '.join([tool.get_name_from_cls() for tool in self._all_tools.values()])}") + + # determine the base toolset defining the set of exposed tools (which e.g. the MCP shall see), + # limited by the Serena config, the context (which is fixed for the session) and JetBrains mode + tool_inclusion_definitions = [self.serena_config, self._context] + if self.serena_config.jetbrains: + tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains")) + self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions) + self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if self._base_tool_set.includes_name(t.get_name())} + log.info(f"Number of exposed tools: {len(self._exposed_tools)}") # create executor for starting the language server and running tools in another thread # This executor is used to achieve linear task execution, so it is important to use a single-threaded executor. @@ -287,10 +292,6 @@ class SerenaAgent: modes = SerenaAgentMode.load_default_modes() self._modes = modes - # log tool information - log.info(f"Loaded tools ({len(self._all_tools)}): {', '.join([tool.get_name_from_cls() for tool in self._all_tools.values()])}") - log.info(f"Number of exposed tools given {self._context}: {len(self._exposed_tools)}") - self._active_tools: dict[type[Tool], Tool] = {} self._update_active_tools() @@ -407,13 +408,15 @@ class SerenaAgent: def _update_active_tools(self) -> None: """ - Update the active tools based on context, modes, and project configuration. + Update the active tools based on enabled modes and the active project. + The base tool set already takes the Serena configuration and the context into account + (as well as any internal modes that are not handled dynamically, such as JetBrains mode). """ - tool_set = ToolSet().apply(self.serena_config, self._context, *self._modes) + tool_set = self._base_tool_set.apply(*self._modes) if self._active_project is not None: - tool_set.apply(self._active_project.project_config) + tool_set = tool_set.apply(self._active_project.project_config) if self._active_project.project_config.read_only: - tool_set.exclude_editing_tools() + tool_set = tool_set.without_editing_tools() self._active_tools = { tool_class: tool_instance diff --git a/src/serena/config/context_mode.py b/src/serena/config/context_mode.py index 0ee3c26..dfd690a 100644 --- a/src/serena/config/context_mode.py +++ b/src/serena/config/context_mode.py @@ -14,7 +14,7 @@ from sensai.util import logging from sensai.util.string import ToStringMixin from serena.config.serena_config import ToolInclusionDefinition -from serena.constants import CONTEXT_YAMLS_DIR, DEFAULT_CONTEXT, DEFAULT_MODES, MODE_YAMLS_DIR +from serena.constants import CONTEXT_YAMLS_DIR, DEFAULT_CONTEXT, DEFAULT_MODES, INTERNAL_MODE_YAMLS_DIR, MODE_YAMLS_DIR if TYPE_CHECKING: pass @@ -23,7 +23,7 @@ log = logging.getLogger(__name__) @dataclass(kw_only=True) -class SerenaAgentMode(ToolInclusionDefinition): +class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin): """Represents a mode of operation for the agent, typically read off a YAML file. An agent can be in multiple modes simultaneously as long as they are not mutually exclusive. The modes can be adjusted after the agent is running, for example for switching from planning to editing. @@ -33,6 +33,9 @@ class SerenaAgentMode(ToolInclusionDefinition): prompt: str description: str = "" + def _tostring_includes(self) -> list[str]: + return ["name"] + def to_json_dict(self) -> dict[str, str | list[str]]: result = asdict(self) result["excluded_tools"] = list(result["excluded_tools"]) @@ -69,6 +72,14 @@ class SerenaAgentMode(ToolInclusionDefinition): ) return cls.from_yaml(yaml_path) + @classmethod + def from_name_internal(cls, name: str) -> Self: + """Loads an internal Serena mode""" + yaml_path = os.path.join(INTERNAL_MODE_YAMLS_DIR, f"{name}.yml") + if not os.path.exists(yaml_path): + raise FileNotFoundError(f"Internal mode '{name}' not found in {INTERNAL_MODE_YAMLS_DIR}") + return cls.from_yaml(yaml_path) + @classmethod def list_registered_mode_names(cls) -> list[str]: """Names of all registered modes (from the corresponding YAML files in the serena repo).""" diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 306393c..1f710bb 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -36,35 +36,60 @@ DEFAULT_TOOL_TIMEOUT: float = 240 class ToolSet: - def __init__(self) -> None: + def __init__(self, tool_names: set[str]) -> None: + self._tool_names = tool_names + + @classmethod + def default(cls) -> "ToolSet": + """ + :return: the default tool set, which contains all tools that are enabled by default + """ from serena.tools import ToolRegistry - self._tool_names = set(ToolRegistry().get_tool_names_default_enabled()) + return cls(set(ToolRegistry().get_tool_names_default_enabled())) - def apply(self, *tool_inclusion_definitions: "ToolInclusionDefinition") -> Self: + def apply(self, *tool_inclusion_definitions: "ToolInclusionDefinition") -> "ToolSet": + """ + :param tool_inclusion_definitions: the definitions to apply + :return: a new tool set with the definitions applied + """ from serena.tools import ToolRegistry registry = ToolRegistry() + tool_names = set(self._tool_names) for definition in tool_inclusion_definitions: + included_tools = [] + excluded_tools = [] for included_tool in definition.included_optional_tools: if not registry.is_valid_tool_name(included_tool): raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") - self._tool_names.add(included_tool) + if included_tool not in tool_names: + tool_names.add(included_tool) + included_tools.append(included_tool) for excluded_tool in definition.excluded_tools: if not registry.is_valid_tool_name(excluded_tool): raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") if excluded_tool in self._tool_names: - self._tool_names.remove(excluded_tool) - return self + tool_names.remove(excluded_tool) + excluded_tools.append(excluded_tool) + if included_tools: + log.info(f"{definition} included {len(included_tools)} tools: {', '.join(included_tools)}") + if excluded_tools: + log.info(f"{definition} excluded {len(excluded_tools)} tools: {', '.join(excluded_tools)}") + return ToolSet(tool_names) - def exclude_editing_tools(self) -> Self: + def without_editing_tools(self) -> "ToolSet": + """ + :return: a new tool set that excludes all tools that can edit + """ from serena.tools import ToolRegistry registry = ToolRegistry() - for tool_name in list(self._tool_names): + tool_names = set(self._tool_names) + for tool_name in self._tool_names: if registry.get_tool_class_by_name(tool_name).can_edit(): - self._tool_names.remove(tool_name) - return self + tool_names.remove(tool_name) + return ToolSet(tool_names) def get_tool_names(self) -> set[str]: """ @@ -115,6 +140,9 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin): SERENA_DEFAULT_PROJECT_FILE = "project.yml" + def _tostring_includes(self) -> list[str]: + return ["project_name"] + @classmethod def autogenerate(cls, project_root: str | Path, project_name: str | None = None, save_to_disk: bool = True) -> Self: """ @@ -222,7 +250,7 @@ class Project: @dataclass(kw_only=True) -class SerenaConfig(ToolInclusionDefinition): +class SerenaConfig(ToolInclusionDefinition, ToStringMixin): """ Holds the Serena agent configuration, which is typically loaded from a YAML configuration file (when instantiated via :method:`from_config_file`), which is updated when projects are added or removed. @@ -242,10 +270,17 @@ class SerenaConfig(ToolInclusionDefinition): the path to the configuration file to which updates of the configuration shall be saved; if None, the configuration is not saved to disk """ + jetbrains: bool = False + """ + whether to apply JetBrains mode + """ CONFIG_FILE = "serena_config.yml" CONFIG_FILE_DOCKER = "serena_config.docker.yml" # Docker-specific config file; auto-generated if missing, mounted via docker-compose for user customization + def _tostring_includes(self) -> list[str]: + return ["config_file_path"] + @classmethod def _generate_config_file(cls, config_file_path: str) -> None: """ @@ -333,6 +368,7 @@ class SerenaConfig(ToolInclusionDefinition): instance.trace_lsp_communication = loaded_commented_yaml.get("trace_lsp_communication", False) instance.excluded_tools = loaded_commented_yaml.get("excluded_tools", []) instance.included_optional_tools = loaded_commented_yaml.get("included_optional_tools", []) + instance.jetbrains = loaded_commented_yaml.get("jetbrains", False) # re-save the configuration file if any migrations were performed if num_project_migrations > 0: diff --git a/src/serena/constants.py b/src/serena/constants.py index 447364b..668c2f9 100644 --- a/src/serena/constants.py +++ b/src/serena/constants.py @@ -7,6 +7,7 @@ REPO_ROOT = str(_repo_root_path) PROMPT_TEMPLATES_DIR = str(_serena_pkg_path / "resources" / "config" / "prompt_templates") CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts") MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes") +INTERNAL_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "internal_modes") SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard") SERENA_ICON_DIR = str(_serena_pkg_path / "resources" / "icons") diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml new file mode 100644 index 0000000..fdeb41d --- /dev/null +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -0,0 +1,11 @@ +description: JetBrains tools replace language server-based tools +prompt: | + You have access to the very powerful JetBrains tools for symbolic operations: + * `jet_brains_find_symbol` replaces `find_symbol` + * `jet_brains_find_symbol_references` replaces `find_referencing_symbols` +excluded_tools: + - find_symbol + - find_referencing_symbols +included_optional_tools: + - jet_brains_find_symbol + - jet_brains_find_symbol_references \ No newline at end of file diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index e0a6a09..fcffbc4 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -42,6 +42,12 @@ excluded_tools: [] included_optional_tools: [] # list of optional tools (which are disabled by default) to be included +jetbrains: False +# whether to enable JetBrains mode and use tools based on the Serena JetBrains IDE plugin +# instead of language server-based tools +# NOTE: The plugin is yet unreleased. This is for Serena developers only. + + # MANAGED BY SERENA, KEEP AT THE BOTTOM OF THE YAML AND DON'T EDIT WITHOUT NEED # The list of registered projects. # To add a project, within a chat, simply ask Serena to "activate the project /path/to/project" or, From e306be796f6c3ebf84451a61a8e7a8f714befd92 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Jul 2025 20:15:53 +0200 Subject: [PATCH 06/41] Pythonify responses from JetBrains plugin server (camelCase -> snake_case) --- src/serena/tools/jetbrains_plugin_client.py | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index db87894..81476e4 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -3,10 +3,12 @@ Client for the Serena JetBrains Plugin """ import json -from typing import Any, Optional, Self +from typing import Any, Optional, Self, TypeVar import requests +T = TypeVar("T") + class SerenaClientError(Exception): """Base exception for Serena client errors.""" @@ -49,7 +51,7 @@ class JetBrainsPluginClient: # Try to parse JSON response try: - return response.json() + return self._pythonify_response(response.json()) except json.JSONDecodeError: # If response is not JSON, return raw text return {"response": response.text} @@ -63,6 +65,25 @@ class JetBrainsPluginClient: except requests.exceptions.RequestException as e: raise SerenaClientError(f"Request failed: {e}") + @staticmethod + def _pythonify_response(response: T) -> T: + """ + Converts dictionary keys from camelCase to snake_case recursively. + + :response: the response in which to convert keys (dictionary or list) + """ + to_snake_case = lambda s: "".join(["_" + c.lower() if c.isupper() else c for c in s]) + + def convert(x): + if isinstance(x, dict): + return {to_snake_case(k): convert(v) for k, v in x.items()} + elif isinstance(x, list): + return [convert(item) for item in x] + else: + return x + + return convert(response) + def heartbeat(self) -> dict[str, Any]: return self._make_request("GET", "/heartbeat") @@ -127,5 +148,5 @@ if __name__ == "__main__": # find references if symbols: first_symbol = symbols[0] - refs_response = client.find_references(name_path=first_symbol["namePath"], relative_path=first_symbol["relativePath"]) + refs_response = client.find_references(name_path=first_symbol["name_path"], relative_path=first_symbol["relative_path"]) pprint(refs_response) From 955c717c3f8ffa947db41234f1ebb548f7c06b03 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Jul 2025 21:13:32 +0200 Subject: [PATCH 07/41] Add JetBrainsGetSymbolsOverviewTool --- .../config/internal_modes/jetbrains.yml | 4 ++- src/serena/tools/jetbrains_plugin_client.py | 9 +++++-- src/serena/tools/jetbrains_tools.py | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index fdeb41d..a7de011 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -6,6 +6,8 @@ prompt: | excluded_tools: - find_symbol - find_referencing_symbols + - get_symbols_overview included_optional_tools: - jet_brains_find_symbol - - jet_brains_find_symbol_references \ No newline at end of file + - jet_brains_find_symbol_references + - jet_brains_get_symbols_overview diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index 81476e4..5822cdc 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -99,7 +99,6 @@ class JetBrainsPluginClient: :return: Dictionary containing 'symbols' list with matching symbols """ request_data = {"namePath": name_path, "relativePath": relative_path, "includeBody": include_body, "depth": depth} - return self._make_request("POST", "/findSymbol", request_data) def find_references(self, name_path: str, relative_path: str) -> dict[str, Any]: @@ -111,9 +110,15 @@ class JetBrainsPluginClient: :return: dictionary containing 'symbols' list with symbol references """ 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]: + """ + :param relative_path: the relative path to a source file + """ + request_data = {"relativePath": relative_path} + return self._make_request("POST", "/getSymbolsOverview", request_data) + def is_service_available(self) -> bool: try: response = self.heartbeat() diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 751a312..2a355d7 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -98,3 +98,29 @@ class JetBrainsFindSymbolReferencesTool(Tool, ToolMarkerOptional): ) result = json.dumps(response_dict) return self._limit_length(result, max_answer_chars) + + +class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerOptional): + """ + Retrieves an overview of the top-level symbols within a specified file + """ + + def apply( + self, + relative_path: str, + max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH, + ) -> str: + """ + Gets an overview of the top-level symbols in the given file. + Calling this is often a good idea before more targeted reading, searching or editing operations on the code symbols. + + :param relative_path: the relative path to the file to get the overview of + :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. + :return: a JSON object containing the symbols + """ + with JetBrainsPluginClient() as client: + response_dict = client.get_symbols_overview( + relative_path=relative_path, + ) + result = json.dumps(response_dict) + return self._limit_length(result, max_answer_chars) From 4d7f97bb0cf7104d77589f407adda3a8f23f8092 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 11:32:06 +0200 Subject: [PATCH 08/41] Split history src/serena/symbol.py to src/serena/code_editor.py - rename file to target-name --- src/serena/{symbol.py => code_editor.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/serena/{symbol.py => code_editor.py} (100%) diff --git a/src/serena/symbol.py b/src/serena/code_editor.py similarity index 100% rename from src/serena/symbol.py rename to src/serena/code_editor.py From 40743d19ad407cfd47ec63c56ed0142039c34f63 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 11:32:07 +0200 Subject: [PATCH 09/41] Split history src/serena/symbol.py to src/serena/code_editor.py - rename source-file to git-split-temp --- src/serena/symbol.py => git-split-temp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/serena/symbol.py => git-split-temp (100%) diff --git a/src/serena/symbol.py b/git-split-temp similarity index 100% rename from src/serena/symbol.py rename to git-split-temp From bd68883d27367cb837c914c2f6f39f2d15757601 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 11:32:08 +0200 Subject: [PATCH 10/41] Split history src/serena/symbol.py to src/serena/code_editor.py - restore name of source-file --- git-split-temp => src/serena/symbol.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename git-split-temp => src/serena/symbol.py (100%) diff --git a/git-split-temp b/src/serena/symbol.py similarity index 100% rename from git-split-temp rename to src/serena/symbol.py From 7ffca9b3cb3e7b79fb39a77beaa79060c4ef1c65 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 12:58:29 +0200 Subject: [PATCH 11/41] Add "type: ignore" to untyped inner function --- src/serena/tools/jetbrains_plugin_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index 5822cdc..5394150 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -74,7 +74,7 @@ class JetBrainsPluginClient: """ to_snake_case = lambda s: "".join(["_" + c.lower() if c.isupper() else c for c in s]) - def convert(x): + def convert(x): # type: ignore if isinstance(x, dict): return {to_snake_case(k): convert(v) for k, v in x.items()} elif isinstance(x, list): From 19b9bc4e3fc73fa4c8902deb9a182f095fa74010 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 12:48:33 +0200 Subject: [PATCH 12/41] Introduce CodeEditor abstraction, removing all editing functions from SymbolManager and adding LanguageServerCodeEditor as a specialization --- src/serena/code_editor.py | 927 +++++------------------------ src/serena/symbol.py | 354 +++-------- src/serena/tools/file_tools.py | 7 +- src/serena/tools/ls_tools.py | 11 +- test/serena/test_symbol_editing.py | 20 +- 5 files changed, 227 insertions(+), 1092 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 0f48bb5..19452e9 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -1,713 +1,93 @@ import json -import logging import os -from collections.abc import Iterable, Iterator, Reversible, Sequence +from abc import ABC, abstractmethod +from collections.abc import Iterable, Iterator, Reversible from contextlib import contextmanager -from dataclasses import asdict, dataclass, field -from difflib import SequenceMatcher -from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union - -from sensai.util.string import ToStringMixin +from typing import TYPE_CHECKING, Generic, Optional, TypeVar +from serena.symbol import AbstractSymbol, PositionInFile, Symbol, SymbolManager from solidlsp import SolidLanguageServer -from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol -from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation +from solidlsp.ls import LSPFileBuffer if TYPE_CHECKING: from .agent import SerenaAgent -log = logging.getLogger(__name__) +TSymbol = TypeVar("TSymbol", bound=AbstractSymbol) -class LineChange(NamedTuple): - """Represents a change to a specific line or range of lines.""" - operation: Literal["insert", "delete", "replace"] - original_start: int - original_end: int - modified_start: int - modified_end: int - original_lines: list[str] - modified_lines: list[str] - - -@dataclass -class CodeDiff: - """ - Represents the difference between original and modified code. - Provides object-oriented access to diff information including line numbers. - """ - - relative_path: str - original_content: str - modified_content: str - _line_changes: list[LineChange] = field(init=False) - - def __post_init__(self) -> None: - """Compute the diff using difflib's SequenceMatcher.""" - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - matcher = SequenceMatcher(None, original_lines, modified_lines) - self._line_changes = [] - - for tag, orig_start, orig_end, mod_start, mod_end in matcher.get_opcodes(): - if tag == "equal": - continue - if tag == "insert": - self._line_changes.append( - LineChange( - operation="insert", - original_start=orig_start, - original_end=orig_start, - modified_start=mod_start, - modified_end=mod_end, - original_lines=[], - modified_lines=modified_lines[mod_start:mod_end], - ) - ) - elif tag == "delete": - self._line_changes.append( - LineChange( - operation="delete", - original_start=orig_start, - original_end=orig_end, - modified_start=mod_start, - modified_end=mod_start, - original_lines=original_lines[orig_start:orig_end], - modified_lines=[], - ) - ) - elif tag == "replace": - self._line_changes.append( - LineChange( - operation="replace", - original_start=orig_start, - original_end=orig_end, - modified_start=mod_start, - modified_end=mod_end, - original_lines=original_lines[orig_start:orig_end], - modified_lines=modified_lines[mod_start:mod_end], - ) - ) - - @property - def line_changes(self) -> list[LineChange]: - """Get all line changes in the diff.""" - return self._line_changes - - @property - def has_changes(self) -> bool: - """Check if there are any changes.""" - return len(self._line_changes) > 0 - - @property - def added_lines(self) -> list[tuple[int, str]]: - """Get all added lines with their line numbers (0-based) in the modified file.""" - result = [] - for change in self._line_changes: - if change.operation in ("insert", "replace"): - for i, line in enumerate(change.modified_lines): - result.append((change.modified_start + i, line)) - return result - - @property - def deleted_lines(self) -> list[tuple[int, str]]: - """Get all deleted lines with their line numbers (0-based) in the original file.""" - result = [] - for change in self._line_changes: - if change.operation in ("delete", "replace"): - for i, line in enumerate(change.original_lines): - result.append((change.original_start + i, line)) - return result - - @property - def modified_line_numbers(self) -> list[int]: - """Get all line numbers (0-based) that were modified in the modified file.""" - line_nums: set[int] = set() - for change in self._line_changes: - if change.operation in ("insert", "replace"): - line_nums.update(range(change.modified_start, change.modified_end)) - return sorted(line_nums) - - @property - def affected_original_line_numbers(self) -> list[int]: - """Get all line numbers (0-based) that were affected in the original file.""" - line_nums: set[int] = set() - for change in self._line_changes: - if change.operation in ("delete", "replace"): - line_nums.update(range(change.original_start, change.original_end)) - return sorted(line_nums) - - def get_unified_diff(self, context_lines: int = 3) -> str: - """Get the unified diff as a string.""" - import difflib - - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - diff = difflib.unified_diff( - original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines - ) - return "".join(diff) - - def get_context_diff(self, context_lines: int = 3) -> str: - """Get the context diff as a string.""" - import difflib - - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - diff = difflib.context_diff( - original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines - ) - return "".join(diff) - - -@dataclass -class SymbolLocation: - """ - Represents the (start) location of a symbol identifier, which, within Serena, uniquely identifies the symbol. - """ - - relative_path: str | None - """ - the relative path of the file containing the symbol; if None, the symbol is defined outside of the project's scope - """ - line: int | None - """ - the line number in which the symbol identifier is defined (if the symbol is a function, class, etc.); - may be None for some types of symbols (e.g. SymbolKind.File) - """ - column: int | None - """ - the column number in which the symbol identifier is defined (if the symbol is a function, class, etc.); - may be None for some types of symbols (e.g. SymbolKind.File) - """ - - def __post_init__(self) -> None: - if self.relative_path is not None: - self.relative_path = self.relative_path.replace("/", os.path.sep) - - def to_dict(self) -> dict[str, Any]: - return asdict(self) - - def has_position_in_file(self) -> bool: - return self.relative_path is not None and self.line is not None and self.column is not None - - -class Symbol(ToStringMixin): - _NAME_PATH_SEP = "/" - - @staticmethod - def match_name_path( - name_path: str, - symbol_name_path_parts: list[str], - substring_matching: bool, - ) -> bool: - """ - Checks if a given `name_path` matches a symbol's qualified name parts. - See docstring of `Symbol.find` for more details. - """ - assert name_path, "name_path must not be empty" - assert symbol_name_path_parts, "symbol_name_path_parts must not be empty" - name_path_sep = Symbol._NAME_PATH_SEP - - is_absolute_pattern = name_path.startswith(name_path_sep) - pattern_parts = name_path.lstrip(name_path_sep).rstrip(name_path_sep).split(name_path_sep) - - # filtering based on ancestors - if len(pattern_parts) > len(symbol_name_path_parts): - # can't possibly match if pattern has more parts than symbol - return False - if is_absolute_pattern and len(pattern_parts) != len(symbol_name_path_parts): - # for absolute patterns, the number of parts must match exactly - return False - if symbol_name_path_parts[-len(pattern_parts) : -1] != pattern_parts[:-1]: - # ancestors must match - return False - - # matching the last part of the symbol name - name_to_match = pattern_parts[-1] - symbol_name = symbol_name_path_parts[-1] - if substring_matching: - return name_to_match in symbol_name - else: - return name_to_match == symbol_name - - def __init__(self, symbol_root_from_ls: UnifiedSymbolInformation) -> None: - self.symbol_root = symbol_root_from_ls - - def _tostring_includes(self) -> list[str]: - return [] - - def _tostring_additional_entries(self) -> dict[str, Any]: - return dict(name=self.name, kind=self.kind, num_children=len(self.symbol_root["children"])) - - @property - def name(self) -> str: - return self.symbol_root["name"] - - @property - def kind(self) -> str: - return SymbolKind(self.symbol_kind).name - - @property - def symbol_kind(self) -> SymbolKind: - return self.symbol_root["kind"] - - def is_neighbouring_definition_separated_by_empty_line(self) -> bool: - """ - :return: whether a symbol definition of this symbol's kind is usually separated from the - previous/next definition by at least one empty line. - """ - return self.symbol_kind in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Class, SymbolKind.Interface, SymbolKind.Struct) - - @property - def relative_path(self) -> str | None: - location = self.symbol_root.get("location") - if location: - return location.get("relativePath") - return None - - @property - def location(self) -> SymbolLocation: - """ - :return: the start location of the actual symbol identifier - """ - return SymbolLocation(relative_path=self.relative_path, line=self.line, column=self.column) - - @property - def body_start_position(self) -> Position | None: - location = self.symbol_root.get("location") - if location: - range_info = location.get("range") - if range_info: - start_pos = range_info.get("start") - if start_pos: - return start_pos - return None - - @property - def body_end_position(self) -> Position | None: - location = self.symbol_root.get("location") - if location: - range_info = location.get("range") - if range_info: - end_pos = range_info.get("end") - if end_pos: - return end_pos - return None - - def get_body_line_numbers(self) -> tuple[int | None, int | None]: - start_pos = self.body_start_position - end_pos = self.body_end_position - start_line = start_pos["line"] if start_pos else None - end_line = end_pos["line"] if end_pos else None - return start_line, end_line - - @property - def line(self) -> int | None: - """ - :return: the line in which the symbol identifier is defined. - """ - if "selectionRange" in self.symbol_root: - return self.symbol_root["selectionRange"]["start"]["line"] - else: - # line is expected to be undefined for some types of symbols (e.g. SymbolKind.File) - return None - - @property - def column(self) -> int | None: - if "selectionRange" in self.symbol_root: - return self.symbol_root["selectionRange"]["start"]["character"] - else: - # precise location is expected to be undefined for some types of symbols (e.g. SymbolKind.File) - return None - - @property - def body(self) -> str | None: - return self.symbol_root.get("body") - - def get_name_path(self) -> str: - """ - Get the name path of the symbol (e.g. "class/method/inner_function"). - """ - return self._NAME_PATH_SEP.join(self.get_name_path_parts()) - - def get_name_path_parts(self) -> list[str]: - """ - Get the parts of the name path of the symbol (e.g. ["class", "method", "inner_function"]). - """ - ancestors_within_file = list(self.iter_ancestors(up_to_symbol_kind=SymbolKind.File)) - ancestors_within_file.reverse() - return [a.name for a in ancestors_within_file] + [self.name] - - def iter_children(self) -> Iterator[Self]: - for c in self.symbol_root["children"]: - yield self.__class__(c) - - def iter_ancestors(self, up_to_symbol_kind: SymbolKind | None = None) -> Iterator[Self]: - """ - Iterate over all ancestors of the symbol, starting with the parent and going up to the root or - the given symbol kind. - - :param up_to_symbol_kind: if provided, iteration will stop *before* the first ancestor of the given kind. - A typical use case is to pass `SymbolKind.File` or `SymbolKind.Package`. - """ - parent = self.get_parent() - if parent is not None: - if up_to_symbol_kind is None or parent.symbol_kind != up_to_symbol_kind: - yield parent - yield from parent.iter_ancestors(up_to_symbol_kind=up_to_symbol_kind) - - def get_parent(self) -> Self | None: - parent_root = self.symbol_root.get("parent") - if parent_root is None: - return None - return self.__class__(parent_root) - - def find( - self, - name_path: str, - substring_matching: bool = False, - include_kinds: Sequence[SymbolKind] | None = None, - exclude_kinds: Sequence[SymbolKind] | None = None, - ) -> list[Self]: - """ - Find all symbols within the symbol's subtree that match the given `name_path`. - The matching behavior is determined by the structure of `name_path`, which can - either be a simple name (e.g. "method") or a name path like "class/method" (relative name path) - or "/class/method" (absolute name path). - - Key aspects of the name path matching behavior: - - Trailing slashes in `name_path` play no role and are ignored. - - The name of the retrieved symbols will match (either exactly or as a substring) - the last segment of `name_path`, while other segments will restrict the search to symbols that - have a desired sequence of ancestors. - - If there is no starting or intermediate slash in `name_path`, there is no - restriction on the ancestor symbols. For example, passing `method` will match - against symbols with name paths like `method`, `class/method`, `class/nested_class/method`, etc. - - If `name_path` contains a `/` but doesn't start with a `/`, the matching is restricted to symbols - with the same ancestors as the last segment of `name_path`. For example, passing `class/method` will match against - `class/method` as well as `nested_class/class/method` but not `method`. - - If `name_path` starts with a `/`, it will be treated as an absolute name path pattern, meaning - that the first segment of it must match the first segment of the symbol's name path. - For example, passing `/class` will match only against top-level symbols like `class` but not against `nested_class/class`. - Passing `/class/method` will match against `class/method` but not `nested_class/class/method` or `method`. - - :param name_path: the name path to match against - :param substring_matching: whether to use substring matching (as opposed to exact matching) - of the last segment of `name_path` against the symbol name. - :param include_kinds: an optional sequence of ints representing the LSP symbol kind. - If provided, only symbols of the given kinds will be included in the result. - :param exclude_kinds: If provided, symbols of the given kinds will be excluded from the result. - """ - result = [] - - def should_include(s: "Symbol") -> bool: - if include_kinds is not None and s.symbol_kind not in include_kinds: - return False - if exclude_kinds is not None and s.symbol_kind in exclude_kinds: - return False - return Symbol.match_name_path( - name_path=name_path, - symbol_name_path_parts=s.get_name_path_parts(), - substring_matching=substring_matching, - ) - - def traverse(s: "Symbol") -> None: - if should_include(s): - result.append(s) - for c in s.iter_children(): - traverse(c) - - traverse(self) - return result - - def to_dict( - self, kind: bool = False, location: bool = False, depth: int = 0, include_body: bool = False, include_children_body: bool = False - ) -> dict[str, Any]: - """ - Converts the symbol to a dictionary. - - :param kind: whether to include the kind of the symbol - :param location: whether to include the location of the symbol - :param depth: the depth of the symbol - :param include_body: whether to include the body of the top-level symbol. - :param include_children_body: whether to also include the body of the children. - Note that the body of the children is part of the body of the parent symbol, - so there is usually no need to set this to True unless you want process the output - and pass the children without passing the parent body to the LM. - :return: a dictionary representation of the symbol - """ - result: dict[str, Any] = {"name": self.name, "name_path": self.get_name_path()} - - if kind: - result["kind"] = self.kind - - if location: - result["location"] = self.location.to_dict() - body_start_line, body_end_line = self.get_body_line_numbers() - result["body_location"] = {"start_line": body_start_line, "end_line": body_end_line} - - if include_body: - if self.body is None: - log.warning("Requested body for symbol, but it is not present. The symbol might have been loaded with include_body=False.") - result["body"] = self.body - - def add_children(s: Self) -> list[dict[str, Any]]: - children = [] - for c in s.iter_children(): - children.append( - c.to_dict( - kind=kind, - location=location, - depth=depth - 1, - include_body=include_children_body, - include_children_body=include_children_body, - ) - ) - return children - - if depth > 0: - result["children"] = add_children(self) - - return result - - -@dataclass -class ReferenceInSymbol(ToStringMixin): - """Same as the class of the same name in the language server, but using Serena's Symbol class. - Be careful to not confuse it with counterpart! - """ - - symbol: Symbol - line: int - character: int - - def get_relative_path(self) -> str | None: - return self.symbol.location.relative_path - - @classmethod - def from_lsp_reference(cls, reference: LSPReferenceInSymbol) -> Self: - return cls(symbol=Symbol(reference.symbol), line=reference.line, character=reference.character) - - -class SymbolManager: - def __init__(self, lang_server: SolidLanguageServer, agent: Union["SerenaAgent", None] = None) -> None: - """ - :param lang_server: the language server to use for symbol retrieval as well as editing operations. - :param agent: the agent to use (only needed for marking files as modified). You can pass None if you don't - need an agent to be aware of file modifications performed by the symbol manager. - """ - self._lang_server = lang_server +class CodeEditor(Generic[TSymbol], ABC): + def __init__(self, project_root: str, agent: Optional["SerenaAgent"] = None) -> None: + self.project_root = project_root self.agent = agent - def set_language_server(self, lang_server: SolidLanguageServer) -> None: - """ - Set the language server to use for symbol retrieval and editing operations. - This is useful if you want to change the language server after initializing the SymbolManager. - """ - self._lang_server = lang_server + class EditedFile(ABC): + @abstractmethod + def get_contents(self) -> str: + """ + :return: the contents of the file. + """ - def find_by_name( - self, - name_path: str, - include_body: bool = False, - include_kinds: Sequence[SymbolKind] | None = None, - exclude_kinds: Sequence[SymbolKind] | None = None, - substring_matching: bool = False, - within_relative_path: str | None = None, - ) -> list[Symbol]: - """ - Find all symbols that match the given name. See docstring of `Symbol.find` for more details. - The only parameter not mentioned there is `within_relative_path`, which can be used to restrict the search - to symbols within a specific file or directory. - """ - symbols: list[Symbol] = [] - symbol_roots = self._lang_server.request_full_symbol_tree(within_relative_path=within_relative_path, include_body=include_body) - for root in symbol_roots: - symbols.extend( - Symbol(root).find( - name_path, include_kinds=include_kinds, exclude_kinds=exclude_kinds, substring_matching=substring_matching - ) - ) - return symbols + @abstractmethod + def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None: + pass - def get_document_symbols(self, relative_path: str) -> list[Symbol]: - symbol_dicts, roots = self._lang_server.request_document_symbols(relative_path, include_body=False) - symbols = [Symbol(s) for s in symbol_dicts] - return symbols - - def find_by_location(self, location: SymbolLocation) -> Symbol | None: - if location.relative_path is None: - return None - symbol_dicts, roots = self._lang_server.request_document_symbols(location.relative_path, include_body=False) - for symbol_dict in symbol_dicts: - symbol = Symbol(symbol_dict) - if symbol.location == location: - return symbol - return None - - def find_referencing_symbols( - self, - name_path: str, - relative_file_path: str, - include_body: bool = False, - include_kinds: Sequence[SymbolKind] | None = None, - exclude_kinds: Sequence[SymbolKind] | None = None, - ) -> list[ReferenceInSymbol]: - """ - Find all symbols that reference the symbol with the given name. - If multiple symbols fit the name (e.g. for variables that are overwritten), will use the first one. - - :param name_path: the name path of the symbol to find - :param relative_file_path: the relative path of the file in which the referenced symbol is defined. - :param include_body: whether to include the body of all symbols in the result. - Not recommended, as the referencing symbols will often be files, and thus the bodies will be very long. - :param include_kinds: which kinds of symbols to include in the result. - :param exclude_kinds: which kinds of symbols to exclude from the result. - """ - symbol_candidates = self.find_by_name(name_path, substring_matching=False, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - log.warning(f"No symbol with name {name_path} found in file {relative_file_path}") - return [] - if len(symbol_candidates) > 1: - log.error( - f"Found {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}." - f"May be an overwritten variable, in which case you can ignore this error. Proceeding with the first one. " - f"Found symbols for {name_path=} in {relative_file_path=}: \n" - f"{json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2)}" - ) - symbol = symbol_candidates[0] - return self.find_referencing_symbols_by_location( - symbol.location, include_body=include_body, include_kinds=include_kinds, exclude_kinds=exclude_kinds - ) - - def find_referencing_symbols_by_location( - self, - symbol_location: SymbolLocation, - include_body: bool = False, - include_kinds: Sequence[SymbolKind] | None = None, - exclude_kinds: Sequence[SymbolKind] | None = None, - ) -> list[ReferenceInSymbol]: - """ - Find all symbols that reference the symbol at the given location. - - :param symbol_location: the location of the symbol for which to find references. - Does not need to include an end_line, as it is unused in the search. - :param include_body: whether to include the body of all symbols in the result. - Not recommended, as the referencing symbols will often be files, and thus the bodies will be very long. - Note: you can filter out the bodies of the children if you set include_children_body=False - in the to_dict method. - :param include_kinds: an optional sequence of ints representing the LSP symbol kind. - If provided, only symbols of the given kinds will be included in the result. - :param exclude_kinds: If provided, symbols of the given kinds will be excluded from the result. - Takes precedence over include_kinds. - :return: a list of symbols that reference the given symbol - """ - if not symbol_location.has_position_in_file(): - raise ValueError("Symbol location does not contain a valid position in a file") - assert symbol_location.relative_path is not None - assert symbol_location.line is not None - assert symbol_location.column is not None - references = self._lang_server.request_referencing_symbols( - relative_file_path=symbol_location.relative_path, - line=symbol_location.line, - column=symbol_location.column, - include_imports=False, - include_self=False, - include_body=include_body, - include_file_symbols=True, - ) - - if include_kinds is not None: - references = [s for s in references if s.symbol["kind"] in include_kinds] - - if exclude_kinds is not None: - references = [s for s in references if s.symbol["kind"] not in exclude_kinds] - - return [ReferenceInSymbol.from_lsp_reference(r) for r in references] + @abstractmethod + def insert_text_at_position(self, pos: PositionInFile, text: str) -> None: + pass @contextmanager - def _edited_file(self, relative_path: str) -> Iterator[None]: - with self._lang_server.open_file(relative_path) as file_buffer: - yield - root_path = self._lang_server.language_server.repository_root_path - abs_path = os.path.join(root_path, relative_path) + def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: + """ + Context manager for opening a file + """ + raise NotImplementedError("This method must be overridden for each subclass") + + @contextmanager + def _edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: + """ + Context manager for editing a file. + """ + with self._open_file_context(relative_path) as edited_file: + yield edited_file + # save the file + abs_path = os.path.join(self.project_root, relative_path) with open(abs_path, "w", encoding="utf-8") as f: - f.write(file_buffer.contents) + f.write(edited_file.get_contents()) + # notify agent (if provided) if self.agent is not None: self.agent.mark_file_modified(relative_path) - @contextmanager - def _edited_symbol_location(self, location: SymbolLocation) -> Iterator[Symbol]: + @abstractmethod + def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> TSymbol: """ - Context manager for locating and editing a symbol in a file. - """ - symbol = self.find_by_location(location) - if symbol is None: - raise ValueError("Symbol not found/has no defined location within a file") - assert location.relative_path is not None - with self._edited_file(location.relative_path): - yield symbol + Finds the unique symbol with the given name in the given file. + If no such symbol exists, raises a ValueError. - def _get_code_file_content(self, relative_path: str) -> str: - """Get the content of a file using the language server.""" - return self._lang_server.language_server.retrieve_full_file_content(relative_path) - - def replace_body(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: + :param name_path: the name path + :param relative_file_path: the relative path of the file in which to search for the symbol. + :return: the unique symbol """ - Replace the body of the symbol with the given name_path in the given file. + + def replace_body(self, name_path: str, relative_file_path: str, body: str) -> None: + """ + Replaces the body of the symbol with the given name_path in the given file. :param name_path: the name path of the symbol to replace. :param relative_file_path: the relative path of the file in which the symbol is defined. :param body: the new body - :param use_same_indentation: whether to use the same indentation as the original body. This means that - the user doesn't have to provide the correct indentation, but can just write the body. """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - "Will not replace the body of any of them, but you can use `replace_body_at_location`, the replace lines tool or other editing " - "tools to perform your edits. Their locations are: \n " - + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[0] - return self.replace_body_at_location(symbol.location, body, use_same_indentation=use_same_indentation) - - def replace_body_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Replace the body of the symbol at the given location with the given body - - :param location: the location of the symbol to replace. - :param body: the new body - :param use_same_indentation: whether to use the same indentation as the original body. This means that - the user doesn't have to provide the correct indentation, but can just write the body. - """ - with self._edited_symbol_location(location) as symbol: - assert location.relative_path is not None - start_pos = symbol.body_start_position - end_pos = symbol.body_end_position - if start_pos is None or end_pos is None: - raise ValueError(f"Symbol at {location} does not have a defined body range.") - start_line, start_col = start_pos["line"], start_pos["character"] - - if use_same_indentation: - indent = " " * start_col - body_lines = body.splitlines() - body = body_lines[0] + "\n" + "\n".join(indent + line for line in body_lines[1:]) + symbol = self._find_unique_symbol(name_path, relative_file_path) + start_pos = symbol.get_body_start_position_or_raise() + end_pos = symbol.get_body_end_position_or_raise() + with self._edited_file_context(relative_file_path) as edited_file: # make sure the replacement adds no additional newlines (before or after) - all newlines # and whitespace before/after should remain the same, so we strip it entirely body = body.strip() - self._lang_server.delete_text_between_positions(location.relative_path, start_pos, end_pos) - self._lang_server.insert_text_at_position(location.relative_path, start_line, start_col, body) + edited_file.delete_text_between_positions(start_pos, end_pos) + edited_file.insert_text_at_position(start_pos, body) @staticmethod def _count_leading_newlines(text: Iterable) -> int: @@ -725,47 +105,22 @@ class SymbolManager: def _count_trailing_newlines(cls, text: Reversible) -> int: return cls._count_leading_newlines(reversed(text)) - def insert_after_symbol(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: + def insert_after_symbol(self, name_path: str, relative_file_path: str, body: str) -> None: """ Inserts content after the symbol with the given name in the given file. """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - f"May be an overwritten variable, in which case you can ignore this error. Proceeding with the last one. " - f"Found symbols at locations: \n" + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[-1] - return self.insert_after_symbol_at_location(symbol.location, body, use_same_indentation=use_same_indentation) + symbol = self._find_unique_symbol(name_path, relative_file_path) - def insert_after_symbol_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Appends content after the given symbol - - :param location: the location of the symbol after which to add new lines - :param body: the body of the entity to append - """ # make sure body always ends with at least one newline if not body.endswith("\n"): body += "\n" - assert location.relative_path is not None - - # Find the symbol to get its end position - symbol = self.find_by_location(location) - if symbol is None: - raise ValueError("Symbol not found/has no defined location within a file") - - pos = symbol.body_end_position - if pos is None: - raise ValueError(f"Symbol at {location} does not have a defined end position.") + pos = symbol.get_body_end_position_or_raise() # start at the beginning of the next line col = 0 - line = pos["line"] + 1 + line = pos.line + 1 + # make sure a suitable number of leading empty lines is used (at least 0/1 depending on the symbol type, # otherwise as many as the caller wanted to insert) original_leading_newlines = self._count_leading_newlines(body) @@ -776,136 +131,120 @@ class SymbolManager: num_leading_empty_lines = max(min_empty_lines, original_leading_newlines) if num_leading_empty_lines: body = ("\n" * num_leading_empty_lines) + body + # make sure the one line break succeeding the original symbol, which we repurposed as prefix via # `line += 1`, is replaced body = body.rstrip("\r\n") + "\n" - if use_same_indentation: - symbol_start_pos = symbol.body_start_position - assert symbol_start_pos is not None, f"Symbol at {location=} does not have a defined start position." - symbol_identifier_col = symbol_start_pos["character"] - indent = " " * (symbol_identifier_col) - body = "\n".join(indent + line for line in body.splitlines()) - # IMPORTANT: without this, the insertion does the wrong thing. See implementation of insert_text_at_position in TextUtils, - # it is somewhat counterintuitive (never inserts whitespace) - # I am not 100% sure whether col=0 is always the best choice here. - # - # Without col=0, inserting after dataclass_instance in variables.py: - # > dataclass_instance = VariableDataclass(id=1, name="Test") - # > test test - # > dataclass_instancetest test - # > second line - # > .status = "active" # Reassign dataclass field - # - # With col=0: - # > dataclass_instance = VariableDataclass(id=1, name="Test") - # > test test - # > second line - # > dataclass_instance.status = "active" # Reassign dataclass field + with self._edited_file_context(relative_file_path) as edited_file: + edited_file.insert_text_at_position(PositionInFile(line, col), body) - with self._edited_symbol_location(location): - self._lang_server.insert_text_at_position(location.relative_path, line=line, column=col, text_to_be_inserted=body) - - def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: + def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str) -> None: """ Inserts content before the symbol with the given name in the given file. """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - f"May be an overwritten variable, in which case you can ignore this error. Proceeding with the first one. " - f"Found symbols at locations: \n" + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[0] - self.insert_before_symbol_at_location(symbol.location, body, use_same_indentation=use_same_indentation) + symbol = self._find_unique_symbol(name_path, relative_file_path) + symbol_start_pos = symbol.get_body_start_position_or_raise() - def insert_before_symbol_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Inserts content before the given symbol + # insert position is the start of line where the symbol is defined + line = symbol_start_pos.line + col = 0 - :param location: the location of the symbol before which to add new lines - :param body: the body of the entity to insert - """ - with self._edited_symbol_location(location) as symbol: - symbol_start_pos = symbol.body_start_position - if symbol_start_pos is None: - raise ValueError(f"Symbol at {location} does not have a defined start position.") + original_trailing_empty_lines = self._count_trailing_newlines(body) - 1 - if use_same_indentation: - indent = " " * (symbol_start_pos["character"]) - body = "\n".join(indent + line for line in body.splitlines()) + # ensure eol is present at end + body = body.rstrip() + "\n" - # insert position is the start of line where the symbol is defined - line = symbol_start_pos["line"] - col = 0 + # add suitable number of trailing empty lines after the body (at least 0/1 depending on the symbol type, + # otherwise as many as the caller wanted to insert) + min_trailing_empty_lines = 0 + if symbol.is_neighbouring_definition_separated_by_empty_line(): + min_trailing_empty_lines = 1 + num_trailing_newlines = max(min_trailing_empty_lines, original_trailing_empty_lines) + body += "\n" * num_trailing_newlines - original_trailing_empty_lines = self._count_trailing_newlines(body) - 1 - - # ensure eol is present at end - body = body.rstrip() + "\n" - - # add suitable number of trailing empty lines after the body (at least 0/1 depending on the symbol type, - # otherwise as many as the caller wanted to insert) - min_trailing_empty_lines = 0 - if symbol.is_neighbouring_definition_separated_by_empty_line(): - min_trailing_empty_lines = 1 - num_trailing_newlines = max(min_trailing_empty_lines, original_trailing_empty_lines) - body += "\n" * num_trailing_newlines - - assert location.relative_path is not None - - self._lang_server.insert_text_at_position(location.relative_path, line=line, column=col, text_to_be_inserted=body) + # apply edit + with self._edited_file_context(relative_file_path) as edited_file: + edited_file.insert_text_at_position(PositionInFile(line=line, col=col), body) def insert_at_line(self, relative_path: str, line: int, content: str) -> None: """ Inserts content at the given line in the given file. + :param relative_path: the relative path of the file in which to insert content :param line: the 0-based index of the line to insert content at :param content: the content to insert """ - with self._edited_file(relative_path): - self._lang_server.insert_text_at_position(relative_path, line, 0, content) + with self._edited_file_context(relative_path) as edited_file: + edited_file.insert_text_at_position(PositionInFile(line, 0), content) def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> None: """ Deletes lines in the given file. + :param relative_path: the relative path of the file in which to delete lines :param start_line: the 0-based index of the first line to delete (inclusive) :param end_line: the 0-based index of the last line to delete (inclusive) """ start_col = 0 end_line_for_delete = end_line + 1 end_col = 0 - with self._edited_file(relative_path): - start_pos = Position(line=start_line, character=start_col) - end_pos = Position(line=end_line_for_delete, character=end_col) - self._lang_server.delete_text_between_positions(relative_path, start_pos, end_pos) - - def delete_symbol_at_location(self, location: SymbolLocation) -> None: - """ - Deletes the symbol at the given location. - """ - with self._edited_symbol_location(location) as symbol: - assert location.relative_path is not None - assert symbol.body_start_position is not None - assert symbol.body_end_position is not None - self._lang_server.delete_text_between_positions(location.relative_path, symbol.body_start_position, symbol.body_end_position) + with self._edited_file_context(relative_path) as edited_file: + start_pos = PositionInFile(line=start_line, col=start_col) + end_pos = PositionInFile(line=end_line_for_delete, col=end_col) + edited_file.delete_text_between_positions(start_pos, end_pos) def delete_symbol(self, name_path: str, relative_file_path: str) -> None: """ Deletes the symbol with the given name in the given file. """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) + symbol = self._find_unique_symbol(name_path, relative_file_path) + start_pos = symbol.get_body_start_position_or_raise() + end_pos = symbol.get_body_end_position_or_raise() + with self._edited_file_context(relative_file_path) as edited_file: + edited_file.delete_text_between_positions(start_pos, end_pos) + + +class LanguageServerCodeEditor(CodeEditor[Symbol]): + def __init__(self, symbol_manager: SymbolManager, agent: Optional["SerenaAgent"] = None): + super().__init__(project_root=symbol_manager.get_language_server().repository_root_path, agent=agent) + self._symbol_manager = symbol_manager + + @property + def _lang_server(self) -> SolidLanguageServer: + return self._symbol_manager.get_language_server() + + class EditedFile(CodeEditor.EditedFile): + def __init__(self, lang_server: SolidLanguageServer, relative_path: str, file_buffer: LSPFileBuffer): + self._lang_server = lang_server + self._relative_path = relative_path + self._file_buffer = file_buffer + + def get_contents(self) -> str: + return self._file_buffer.contents + + def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None: + self._lang_server.delete_text_between_positions(self._relative_path, start_pos.to_lsp_position(), end_pos.to_lsp_position()) + + def insert_text_at_position(self, pos: PositionInFile, text: str) -> None: + self._lang_server.insert_text_at_position(self._relative_path, pos.line, pos.col, text) + + @contextmanager + def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: + with self._lang_server.open_file(relative_path) as file_buffer: + yield self.EditedFile(self._lang_server, relative_path, file_buffer) + + def _get_code_file_content(self, relative_path: str) -> str: + """Get the content of a file using the language server.""" + return self._lang_server.language_server.retrieve_full_file_content(relative_path) + + def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> Symbol: + symbol_candidates = self._symbol_manager.find_by_name(name_path, within_relative_path=relative_file_path) if len(symbol_candidates) == 0: raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") if len(symbol_candidates) > 1: raise ValueError( f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - "Will not delete any of them, but you can use `delete_symbol_at_location` or a corresponding tool to perform your edits. " "Their locations are: \n " + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) ) - symbol = symbol_candidates[0] - self.delete_symbol_at_location(symbol.location) + return symbol_candidates[0] diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 0f48bb5..96644fd 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -1,8 +1,8 @@ import json import logging import os -from collections.abc import Iterable, Iterator, Reversible, Sequence -from contextlib import contextmanager +from abc import ABC, abstractmethod +from collections.abc import Iterator, Sequence from dataclasses import asdict, dataclass, field from difflib import SequenceMatcher from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union @@ -196,7 +196,60 @@ class SymbolLocation: return self.relative_path is not None and self.line is not None and self.column is not None -class Symbol(ToStringMixin): +@dataclass +class PositionInFile: + line: int + """ + the 0-based line number in the file + """ + col: int + """ + the 0-based column + """ + + def to_lsp_position(self) -> Position: + """ + Convert to LSP Position. + """ + return Position(line=self.line, character=self.col) + + +class AbstractSymbol(ABC): + @abstractmethod + def get_body_start_position(self) -> PositionInFile | None: + pass + + @abstractmethod + def get_body_end_position(self) -> PositionInFile | None: + pass + + def get_body_start_position_or_raise(self) -> PositionInFile: + """ + Get the start position of the symbol body, raising an error if it is not defined. + """ + pos = self.get_body_start_position() + if pos is None: + raise ValueError(f"Body start position is not defined for {self}") + return pos + + def get_body_end_position_or_raise(self) -> PositionInFile: + """ + Get the end position of the symbol body, raising an error if it is not defined. + """ + pos = self.get_body_end_position() + if pos is None: + raise ValueError(f"Body end position is not defined for {self}") + return pos + + @abstractmethod + def is_neighbouring_definition_separated_by_empty_line(self) -> bool: + """ + :return: whether a symbol definition of this symbol's kind is usually separated from the + previous/next definition by at least one empty line. + """ + + +class Symbol(AbstractSymbol, ToStringMixin): _NAME_PATH_SEP = "/" @staticmethod @@ -257,10 +310,6 @@ class Symbol(ToStringMixin): return self.symbol_root["kind"] def is_neighbouring_definition_separated_by_empty_line(self) -> bool: - """ - :return: whether a symbol definition of this symbol's kind is usually separated from the - previous/next definition by at least one empty line. - """ return self.symbol_kind in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Class, SymbolKind.Interface, SymbolKind.Struct) @property @@ -299,6 +348,18 @@ class Symbol(ToStringMixin): return end_pos return None + def get_body_start_position(self) -> PositionInFile | None: + start_pos = self.body_start_position + if start_pos is None: + return None + return PositionInFile(line=start_pos["line"], col=start_pos["character"]) + + def get_body_end_position(self) -> PositionInFile | None: + end_pos = self.body_end_position + if end_pos is None: + return None + return PositionInFile(line=end_pos["line"], col=end_pos["character"]) + def get_body_line_numbers(self) -> tuple[int | None, int | None]: start_pos = self.body_start_position end_pos = self.body_end_position @@ -511,6 +572,9 @@ class SymbolManager: """ self._lang_server = lang_server + def get_language_server(self) -> SolidLanguageServer: + return self._lang_server + def find_by_name( self, name_path: str, @@ -630,282 +694,6 @@ class SymbolManager: return [ReferenceInSymbol.from_lsp_reference(r) for r in references] - @contextmanager - def _edited_file(self, relative_path: str) -> Iterator[None]: - with self._lang_server.open_file(relative_path) as file_buffer: - yield - root_path = self._lang_server.language_server.repository_root_path - abs_path = os.path.join(root_path, relative_path) - with open(abs_path, "w", encoding="utf-8") as f: - f.write(file_buffer.contents) - if self.agent is not None: - self.agent.mark_file_modified(relative_path) - - @contextmanager - def _edited_symbol_location(self, location: SymbolLocation) -> Iterator[Symbol]: - """ - Context manager for locating and editing a symbol in a file. - """ - symbol = self.find_by_location(location) - if symbol is None: - raise ValueError("Symbol not found/has no defined location within a file") - assert location.relative_path is not None - with self._edited_file(location.relative_path): - yield symbol - def _get_code_file_content(self, relative_path: str) -> str: """Get the content of a file using the language server.""" return self._lang_server.language_server.retrieve_full_file_content(relative_path) - - def replace_body(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: - """ - Replace the body of the symbol with the given name_path in the given file. - - :param name_path: the name path of the symbol to replace. - :param relative_file_path: the relative path of the file in which the symbol is defined. - :param body: the new body - :param use_same_indentation: whether to use the same indentation as the original body. This means that - the user doesn't have to provide the correct indentation, but can just write the body. - """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - "Will not replace the body of any of them, but you can use `replace_body_at_location`, the replace lines tool or other editing " - "tools to perform your edits. Their locations are: \n " - + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[0] - return self.replace_body_at_location(symbol.location, body, use_same_indentation=use_same_indentation) - - def replace_body_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Replace the body of the symbol at the given location with the given body - - :param location: the location of the symbol to replace. - :param body: the new body - :param use_same_indentation: whether to use the same indentation as the original body. This means that - the user doesn't have to provide the correct indentation, but can just write the body. - """ - with self._edited_symbol_location(location) as symbol: - assert location.relative_path is not None - start_pos = symbol.body_start_position - end_pos = symbol.body_end_position - if start_pos is None or end_pos is None: - raise ValueError(f"Symbol at {location} does not have a defined body range.") - start_line, start_col = start_pos["line"], start_pos["character"] - - if use_same_indentation: - indent = " " * start_col - body_lines = body.splitlines() - body = body_lines[0] + "\n" + "\n".join(indent + line for line in body_lines[1:]) - - # make sure the replacement adds no additional newlines (before or after) - all newlines - # and whitespace before/after should remain the same, so we strip it entirely - body = body.strip() - - self._lang_server.delete_text_between_positions(location.relative_path, start_pos, end_pos) - self._lang_server.insert_text_at_position(location.relative_path, start_line, start_col, body) - - @staticmethod - def _count_leading_newlines(text: Iterable) -> int: - cnt = 0 - for c in text: - if c == "\n": - cnt += 1 - elif c == "\r": - continue - else: - break - return cnt - - @classmethod - def _count_trailing_newlines(cls, text: Reversible) -> int: - return cls._count_leading_newlines(reversed(text)) - - def insert_after_symbol(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: - """ - Inserts content after the symbol with the given name in the given file. - """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - f"May be an overwritten variable, in which case you can ignore this error. Proceeding with the last one. " - f"Found symbols at locations: \n" + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[-1] - return self.insert_after_symbol_at_location(symbol.location, body, use_same_indentation=use_same_indentation) - - def insert_after_symbol_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Appends content after the given symbol - - :param location: the location of the symbol after which to add new lines - :param body: the body of the entity to append - """ - # make sure body always ends with at least one newline - if not body.endswith("\n"): - body += "\n" - - assert location.relative_path is not None - - # Find the symbol to get its end position - symbol = self.find_by_location(location) - if symbol is None: - raise ValueError("Symbol not found/has no defined location within a file") - - pos = symbol.body_end_position - if pos is None: - raise ValueError(f"Symbol at {location} does not have a defined end position.") - - # start at the beginning of the next line - col = 0 - line = pos["line"] + 1 - # make sure a suitable number of leading empty lines is used (at least 0/1 depending on the symbol type, - # otherwise as many as the caller wanted to insert) - original_leading_newlines = self._count_leading_newlines(body) - body = body.lstrip("\r\n") - min_empty_lines = 0 - if symbol.is_neighbouring_definition_separated_by_empty_line(): - min_empty_lines = 1 - num_leading_empty_lines = max(min_empty_lines, original_leading_newlines) - if num_leading_empty_lines: - body = ("\n" * num_leading_empty_lines) + body - # make sure the one line break succeeding the original symbol, which we repurposed as prefix via - # `line += 1`, is replaced - body = body.rstrip("\r\n") + "\n" - - if use_same_indentation: - symbol_start_pos = symbol.body_start_position - assert symbol_start_pos is not None, f"Symbol at {location=} does not have a defined start position." - symbol_identifier_col = symbol_start_pos["character"] - indent = " " * (symbol_identifier_col) - body = "\n".join(indent + line for line in body.splitlines()) - # IMPORTANT: without this, the insertion does the wrong thing. See implementation of insert_text_at_position in TextUtils, - # it is somewhat counterintuitive (never inserts whitespace) - # I am not 100% sure whether col=0 is always the best choice here. - # - # Without col=0, inserting after dataclass_instance in variables.py: - # > dataclass_instance = VariableDataclass(id=1, name="Test") - # > test test - # > dataclass_instancetest test - # > second line - # > .status = "active" # Reassign dataclass field - # - # With col=0: - # > dataclass_instance = VariableDataclass(id=1, name="Test") - # > test test - # > second line - # > dataclass_instance.status = "active" # Reassign dataclass field - - with self._edited_symbol_location(location): - self._lang_server.insert_text_at_position(location.relative_path, line=line, column=col, text_to_be_inserted=body) - - def insert_before_symbol(self, name_path: str, relative_file_path: str, body: str, *, use_same_indentation: bool = True) -> None: - """ - Inserts content before the symbol with the given name in the given file. - """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - f"May be an overwritten variable, in which case you can ignore this error. Proceeding with the first one. " - f"Found symbols at locations: \n" + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[0] - self.insert_before_symbol_at_location(symbol.location, body, use_same_indentation=use_same_indentation) - - def insert_before_symbol_at_location(self, location: SymbolLocation, body: str, *, use_same_indentation: bool = True) -> None: - """ - Inserts content before the given symbol - - :param location: the location of the symbol before which to add new lines - :param body: the body of the entity to insert - """ - with self._edited_symbol_location(location) as symbol: - symbol_start_pos = symbol.body_start_position - if symbol_start_pos is None: - raise ValueError(f"Symbol at {location} does not have a defined start position.") - - if use_same_indentation: - indent = " " * (symbol_start_pos["character"]) - body = "\n".join(indent + line for line in body.splitlines()) - - # insert position is the start of line where the symbol is defined - line = symbol_start_pos["line"] - col = 0 - - original_trailing_empty_lines = self._count_trailing_newlines(body) - 1 - - # ensure eol is present at end - body = body.rstrip() + "\n" - - # add suitable number of trailing empty lines after the body (at least 0/1 depending on the symbol type, - # otherwise as many as the caller wanted to insert) - min_trailing_empty_lines = 0 - if symbol.is_neighbouring_definition_separated_by_empty_line(): - min_trailing_empty_lines = 1 - num_trailing_newlines = max(min_trailing_empty_lines, original_trailing_empty_lines) - body += "\n" * num_trailing_newlines - - assert location.relative_path is not None - - self._lang_server.insert_text_at_position(location.relative_path, line=line, column=col, text_to_be_inserted=body) - - def insert_at_line(self, relative_path: str, line: int, content: str) -> None: - """ - Inserts content at the given line in the given file. - - :param line: the 0-based index of the line to insert content at - :param content: the content to insert - """ - with self._edited_file(relative_path): - self._lang_server.insert_text_at_position(relative_path, line, 0, content) - - def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> None: - """ - Deletes lines in the given file. - - :param start_line: the 0-based index of the first line to delete (inclusive) - :param end_line: the 0-based index of the last line to delete (inclusive) - """ - start_col = 0 - end_line_for_delete = end_line + 1 - end_col = 0 - with self._edited_file(relative_path): - start_pos = Position(line=start_line, character=start_col) - end_pos = Position(line=end_line_for_delete, character=end_col) - self._lang_server.delete_text_between_positions(relative_path, start_pos, end_pos) - - def delete_symbol_at_location(self, location: SymbolLocation) -> None: - """ - Deletes the symbol at the given location. - """ - with self._edited_symbol_location(location) as symbol: - assert location.relative_path is not None - assert symbol.body_start_position is not None - assert symbol.body_end_position is not None - self._lang_server.delete_text_between_positions(location.relative_path, symbol.body_start_position, symbol.body_end_position) - - def delete_symbol(self, name_path: str, relative_file_path: str) -> None: - """ - Deletes the symbol with the given name in the given file. - """ - symbol_candidates = self.find_by_name(name_path, within_relative_path=relative_file_path) - if len(symbol_candidates) == 0: - raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") - if len(symbol_candidates) > 1: - raise ValueError( - f"Found multiple {len(symbol_candidates)} symbols with name {name_path} in file {relative_file_path}. " - "Will not delete any of them, but you can use `delete_symbol_at_location` or a corresponding tool to perform your edits. " - "Their locations are: \n " + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) - ) - symbol = symbol_candidates[0] - self.delete_symbol_at_location(symbol.location) diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 47c0447..81c7faf 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -13,6 +13,7 @@ from collections import defaultdict from fnmatch import fnmatch from pathlib import Path +from serena.code_editor import LanguageServerCodeEditor from serena.text_utils import search_files from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, EditedFileContext, Tool, ToolMarkerCanEdit from serena.util.file_system import scan_directory @@ -223,7 +224,8 @@ class DeleteLinesTool(Tool, ToolMarkerCanEdit): if not self.lines_read.were_lines_read(relative_path, (start_line, end_line)): read_lines_tool = self.agent.get_tool(ReadFileTool) return f"Error: Must call `{read_lines_tool.get_name_from_cls()}` first to read exactly the affected lines." - self.symbol_manager.delete_lines(relative_path, start_line, end_line) + code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor.delete_lines(relative_path, start_line, end_line) return SUCCESS_RESULT @@ -281,7 +283,8 @@ class InsertAtLineTool(Tool, ToolMarkerCanEdit): """ if not content.endswith("\n"): content += "\n" - self.symbol_manager.insert_at_line(relative_path, line, content) + code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor.insert_at_line(relative_path, line, content) return SUCCESS_RESULT diff --git a/src/serena/tools/ls_tools.py b/src/serena/tools/ls_tools.py index f88bb7a..859cd4f 100644 --- a/src/serena/tools/ls_tools.py +++ b/src/serena/tools/ls_tools.py @@ -7,6 +7,7 @@ from collections.abc import Sequence from copy import copy from typing import Any +from serena.code_editor import LanguageServerCodeEditor from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerCanEdit from solidlsp.ls_types import SymbolKind @@ -222,11 +223,11 @@ class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit): :param body: the new symbol body. Important: Begin directly with the symbol definition and provide no leading indentation for the first line (but do indent the rest of the body according to the context). """ - self.symbol_manager.replace_body( + code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor.replace_body( name_path, relative_file_path=relative_path, body=body, - use_same_indentation=False, ) return SUCCESS_RESULT @@ -251,7 +252,8 @@ class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit): :param body: the body/content to be inserted. The inserted code shall begin with the next line after the symbol. """ - self.symbol_manager.insert_after_symbol(name_path, relative_file_path=relative_path, body=body, use_same_indentation=False) + code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor.insert_after_symbol(name_path, relative_file_path=relative_path, body=body) return SUCCESS_RESULT @@ -275,5 +277,6 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit): :param relative_path: the relative path to the file containing the symbol :param body: the body/content to be inserted before the line in which the referenced symbol is defined """ - self.symbol_manager.insert_before_symbol(name_path, relative_file_path=relative_path, body=body, use_same_indentation=False) + code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor.insert_before_symbol(name_path, relative_file_path=relative_path, body=body) return SUCCESS_RESULT diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index 652411e..3b10524 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -11,6 +11,7 @@ from typing import Literal import pytest +from serena.code_editor import CodeEditor, LanguageServerCodeEditor from serena.symbol import CodeDiff from solidlsp.ls_config import Language from src.serena.symbol import SymbolManager @@ -76,13 +77,14 @@ class EditingTest: def run_test(self, content_after_ground_truth: str) -> None: with self._setup() as symbol_manager: content_before = self._read_file(self.rel_path) - self._apply_edit(symbol_manager) + code_editor = LanguageServerCodeEditor(symbol_manager) + self._apply_edit(code_editor) content_after = self._read_file(self.rel_path) code_diff = CodeDiff(self.rel_path, original_content=content_before, modified_content=content_after) self._test_diff(code_diff, content_after_ground_truth) @abstractmethod - def _apply_edit(self, symbol_manager: SymbolManager) -> None: + def _apply_edit(self, code_editor: CodeEditor) -> None: pass def _test_diff(self, code_diff: CodeDiff, snapshot: str) -> None: @@ -102,8 +104,8 @@ class DeleteSymbolTest(EditingTest): self.deleted_symbol = deleted_symbol self.rel_path = rel_path - def _apply_edit(self, symbol_manager: SymbolManager) -> None: - symbol_manager.delete_symbol(self.deleted_symbol, self.rel_path) + def _apply_edit(self, code_editor: CodeEditor) -> None: + code_editor.delete_symbol(self.deleted_symbol, self.rel_path) @pytest.mark.parametrize( @@ -170,12 +172,12 @@ class InsertInRelToSymbolTest(EditingTest): def set_mode(self, mode: Literal["before", "after"]): self.mode = mode - def _apply_edit(self, symbol_manager: SymbolManager) -> None: + def _apply_edit(self, code_editor: CodeEditor) -> None: assert self.mode is not None if self.mode == "before": - symbol_manager.insert_before_symbol(self.symbol_name, self.rel_path, self.new_content, use_same_indentation=False) + code_editor.insert_before_symbol(self.symbol_name, self.rel_path, self.new_content) elif self.mode == "after": - symbol_manager.insert_after_symbol(self.symbol_name, self.rel_path, self.new_content, use_same_indentation=False) + code_editor.insert_after_symbol(self.symbol_name, self.rel_path, self.new_content) @pytest.mark.parametrize("mode", ["before", "after"]) @@ -266,8 +268,8 @@ class ReplaceBodyTest(EditingTest): self.symbol_name = symbol_name self.new_body = new_body - def _apply_edit(self, symbol_manager: SymbolManager) -> None: - symbol_manager.replace_body(self.symbol_name, self.rel_path, self.new_body, use_same_indentation=False) + def _apply_edit(self, code_editor: CodeEditor) -> None: + code_editor.replace_body(self.symbol_name, self.rel_path, self.new_body) @pytest.mark.parametrize( From e989d1f87a9d98d35885db817988a31db1512e9d Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 17:48:40 +0200 Subject: [PATCH 13/41] Implement JetBrainsCodeEditor --- src/serena/code_editor.py | 49 +++++++++++++++++- src/serena/symbol.py | 56 +++++++++++++++++++++ src/serena/tools/jetbrains_plugin_client.py | 12 ++++- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 19452e9..16a108b 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -1,18 +1,24 @@ import json +import logging import os from abc import ABC, abstractmethod from collections.abc import Iterable, Iterator, Reversible from contextlib import contextmanager from typing import TYPE_CHECKING, Generic, Optional, TypeVar -from serena.symbol import AbstractSymbol, PositionInFile, Symbol, SymbolManager +from serena.symbol import AbstractSymbol, JetBrainsSymbol, PositionInFile, Symbol, SymbolManager from solidlsp import SolidLanguageServer from solidlsp.ls import LSPFileBuffer +from solidlsp.ls_utils import TextUtils + +from .config.serena_config import Project +from .tools import JetBrainsPluginClient if TYPE_CHECKING: from .agent import SerenaAgent +log = logging.getLogger(__name__) TSymbol = TypeVar("TSymbol", bound=AbstractSymbol) @@ -248,3 +254,44 @@ class LanguageServerCodeEditor(CodeEditor[Symbol]): "Their locations are: \n " + json.dumps([s.location.to_dict() for s in symbol_candidates], indent=2) ) return symbol_candidates[0] + + +class JetBrainsCodeEditor(CodeEditor[JetBrainsSymbol]): + def __init__(self, project: Project, agent: Optional["SerenaAgent"] = None) -> None: + self._project = project + super().__init__(project_root=project.project_root, agent=agent) + + class EditedFile(CodeEditor.EditedFile): + def __init__(self, relative_path: str, project: Project): + path = os.path.join(project.project_root, relative_path) + log.info("Editing file: %s", path) + with open(path, encoding=project.project_config.encoding) as f: + self._content = f.read() + + def get_contents(self) -> str: + return self._content + + def delete_text_between_positions(self, start_pos: PositionInFile, end_pos: PositionInFile) -> None: + self._content, _ = TextUtils.delete_text_between_positions( + self._content, start_pos.line, start_pos.col, end_pos.line, end_pos.col + ) + + def insert_text_at_position(self, pos: PositionInFile, text: str) -> None: + self._content, _, _ = TextUtils.insert_text_at_position(self._content, pos.line, pos.col, text) + + @contextmanager + def _open_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]: + yield self.EditedFile(relative_path, self._project) + + def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> JetBrainsSymbol: + with JetBrainsPluginClient() as client: + result = client.find_symbol(name_path, relative_path=relative_file_path, include_body=False, depth=0, include_location=True) + symbols = result["symbols"] + if not symbols: + raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") + if len(symbols) > 1: + raise ValueError( + f"Found multiple {len(symbols)} symbols with name {name_path} in file {relative_file_path}. " + "Their locations are: \n " + json.dumps([s["location"] for s in symbols], indent=2) + ) + return JetBrainsSymbol(symbols[0], self._project) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 96644fd..c088228 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -7,11 +7,15 @@ from dataclasses import asdict, dataclass, field from difflib import SequenceMatcher from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union +from sensai.util.datastruct import Maybe from sensai.util.string import ToStringMixin from solidlsp import SolidLanguageServer from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation +from solidlsp.ls_utils import TextUtils + +from .config.serena_config import Project if TYPE_CHECKING: from .agent import SerenaAgent @@ -697,3 +701,55 @@ class SymbolManager: def _get_code_file_content(self, relative_path: str) -> str: """Get the content of a file using the language server.""" return self._lang_server.language_server.retrieve_full_file_content(relative_path) + + +class JetBrainsSymbol(AbstractSymbol): + def __init__(self, symbol_dict: dict, project: Project) -> None: + """ + :param symbol_dict: dictionary as returned by the JetBrains plugin client. + """ + self._project = project + self._dict = symbol_dict + self._cached_file_content: str | None = None + self._cached_body_start_position: PositionInFile | None = None + self._cached_body_end_position: PositionInFile | None = None + + def get_relative_path(self) -> str: + return self._dict["relative_path"] + + def get_file_content(self) -> str: + if self._cached_file_content is None: + path = os.path.join(self._project.project_root, self.get_relative_path()) + with open(path, encoding=self._project.project_config.encoding) as f: + self._cached_file_content = f.read() + return self._cached_file_content + + def is_position_in_file_available(self) -> bool: + return "text_range" in self._dict + + @property + def _body_indices(self) -> tuple[int, int]: + text_range = self._dict["text_range"] + return text_range["start_offset"], text_range["end_offset"] + + def get_body_start_position(self) -> PositionInFile | None: + if not self.is_position_in_file_available(): + return None + if self._cached_body_start_position is None: + index = self._dict["text_range"]["start_offset"] + line, col = TextUtils.get_line_col_from_index(self.get_file_content(), index) + self._cached_body_start_position = PositionInFile(line=line, col=col) + return self._cached_body_start_position + + def get_body_end_position(self) -> PositionInFile | None: + if not self.is_position_in_file_available(): + return None + if self._cached_body_end_position is None: + index = self._dict["text_range"]["end_offset"] + line, col = TextUtils.get_line_col_from_index(self.get_file_content(), index) + self._cached_body_end_position = PositionInFile(line=line, col=col) + return self._cached_body_end_position + + def is_neighbouring_definition_separated_by_empty_line(self) -> bool: + # NOTE: Symbol types cannot really be differentiated, because types are not handled in a language-agnostic way. + return False diff --git a/src/serena/tools/jetbrains_plugin_client.py b/src/serena/tools/jetbrains_plugin_client.py index 5394150..8a607cc 100644 --- a/src/serena/tools/jetbrains_plugin_client.py +++ b/src/serena/tools/jetbrains_plugin_client.py @@ -87,7 +87,9 @@ class JetBrainsPluginClient: def heartbeat(self) -> dict[str, Any]: return self._make_request("GET", "/heartbeat") - def find_symbol(self, name_path: str, relative_path: str | None = None, include_body: bool = False, depth: int = 0) -> dict[str, Any]: + def find_symbol( + self, name_path: str, relative_path: str | None = None, include_body: bool = False, depth: int = 0, include_location: bool = False + ) -> dict[str, Any]: """ Find symbols by name. @@ -98,7 +100,13 @@ class JetBrainsPluginClient: :return: Dictionary containing 'symbols' list with matching symbols """ - request_data = {"namePath": name_path, "relativePath": relative_path, "includeBody": include_body, "depth": depth} + request_data = { + "namePath": name_path, + "relativePath": relative_path, + "includeBody": include_body, + "depth": depth, + "includeLocation": include_location, + } return self._make_request("POST", "/findSymbol", request_data) def find_references(self, name_path: str, relative_path: str) -> dict[str, Any]: From 3873ca350624d2d2c7756692a4976d463bdfa175 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 18:02:03 +0200 Subject: [PATCH 14/41] Add CodeEditor creation to Component/Tool (switching between JetBrains- and LS-based variants) --- src/serena/code_editor.py | 2 +- src/serena/tools/file_tools.py | 5 ++--- src/serena/tools/ls_tools.py | 7 +++---- src/serena/tools/tools_base.py | 9 +++++++++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 16a108b..b0d6de3 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -12,7 +12,7 @@ from solidlsp.ls import LSPFileBuffer from solidlsp.ls_utils import TextUtils from .config.serena_config import Project -from .tools import JetBrainsPluginClient +from .tools.jetbrains_plugin_client import JetBrainsPluginClient if TYPE_CHECKING: from .agent import SerenaAgent diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 81c7faf..0bd091e 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -13,7 +13,6 @@ from collections import defaultdict from fnmatch import fnmatch from pathlib import Path -from serena.code_editor import LanguageServerCodeEditor from serena.text_utils import search_files from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, EditedFileContext, Tool, ToolMarkerCanEdit from serena.util.file_system import scan_directory @@ -224,7 +223,7 @@ class DeleteLinesTool(Tool, ToolMarkerCanEdit): if not self.lines_read.were_lines_read(relative_path, (start_line, end_line)): read_lines_tool = self.agent.get_tool(ReadFileTool) return f"Error: Must call `{read_lines_tool.get_name_from_cls()}` first to read exactly the affected lines." - code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor = self.create_code_editor() code_editor.delete_lines(relative_path, start_line, end_line) return SUCCESS_RESULT @@ -283,7 +282,7 @@ class InsertAtLineTool(Tool, ToolMarkerCanEdit): """ if not content.endswith("\n"): content += "\n" - code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor = self.create_code_editor() code_editor.insert_at_line(relative_path, line, content) return SUCCESS_RESULT diff --git a/src/serena/tools/ls_tools.py b/src/serena/tools/ls_tools.py index 859cd4f..1910928 100644 --- a/src/serena/tools/ls_tools.py +++ b/src/serena/tools/ls_tools.py @@ -7,7 +7,6 @@ from collections.abc import Sequence from copy import copy from typing import Any -from serena.code_editor import LanguageServerCodeEditor from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerCanEdit from solidlsp.ls_types import SymbolKind @@ -223,7 +222,7 @@ class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit): :param body: the new symbol body. Important: Begin directly with the symbol definition and provide no leading indentation for the first line (but do indent the rest of the body according to the context). """ - code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor = self.create_code_editor() code_editor.replace_body( name_path, relative_file_path=relative_path, @@ -252,7 +251,7 @@ class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit): :param body: the body/content to be inserted. The inserted code shall begin with the next line after the symbol. """ - code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor = self.create_code_editor() code_editor.insert_after_symbol(name_path, relative_file_path=relative_path, body=body) return SUCCESS_RESULT @@ -277,6 +276,6 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit): :param relative_path: the relative path to the file containing the symbol :param body: the body/content to be inserted before the line in which the referenced symbol is defined """ - code_editor = LanguageServerCodeEditor(self.symbol_manager) + code_editor = self.create_code_editor() code_editor.insert_before_symbol(name_path, relative_file_path=relative_path, body=body) return SUCCESS_RESULT diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 8cf53ca..67f43f9 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -19,6 +19,7 @@ from solidlsp import SolidLanguageServer if TYPE_CHECKING: from serena.agent import LinesRead, MemoriesManager, SerenaAgent + from serena.code_editor import CodeEditor log = logging.getLogger(__name__) T = TypeVar("T") @@ -54,6 +55,14 @@ class Component(ABC): assert self.agent.symbol_manager is not None return self.agent.symbol_manager + def create_code_editor(self) -> "CodeEditor": + from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor + + if self.agent.serena_config.jetbrains: + return JetBrainsCodeEditor(project=self.agent.get_active_project(), agent=self.agent) + else: + return LanguageServerCodeEditor(self.symbol_manager, agent=self.agent) + @property def lines_read(self) -> "LinesRead": assert self.agent.lines_read is not None From 021a6b0831009acaf7af8a6f68130c0332058b3b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 19:35:26 +0200 Subject: [PATCH 15/41] JetBrainsSymbol: Use line/column as directly returned by client --- src/serena/symbol.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index c088228..3a599b5 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -7,13 +7,11 @@ from dataclasses import asdict, dataclass, field from difflib import SequenceMatcher from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union -from sensai.util.datastruct import Maybe from sensai.util.string import ToStringMixin from solidlsp import SolidLanguageServer from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation -from solidlsp.ls_utils import TextUtils from .config.serena_config import Project @@ -736,8 +734,8 @@ class JetBrainsSymbol(AbstractSymbol): if not self.is_position_in_file_available(): return None if self._cached_body_start_position is None: - index = self._dict["text_range"]["start_offset"] - line, col = TextUtils.get_line_col_from_index(self.get_file_content(), index) + pos = self._dict["text_range"]["start_pos"] + line, col = pos["line"], pos["col"] self._cached_body_start_position = PositionInFile(line=line, col=col) return self._cached_body_start_position @@ -745,8 +743,8 @@ class JetBrainsSymbol(AbstractSymbol): if not self.is_position_in_file_available(): return None if self._cached_body_end_position is None: - index = self._dict["text_range"]["end_offset"] - line, col = TextUtils.get_line_col_from_index(self.get_file_content(), index) + pos = self._dict["text_range"]["end_pos"] + line, col = pos["line"], pos["col"] self._cached_body_end_position = PositionInFile(line=line, col=col) return self._cached_body_end_position From 851188876015e5ba9a526693df99c6440851d0a5 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 19:40:50 +0200 Subject: [PATCH 16/41] Rename module ls_tools -> symbol_tools (They are no longer limited to LS-based tools, as the editing tools can use a different underlying CodeEditor) --- src/serena/tools/__init__.py | 2 +- src/serena/tools/{ls_tools.py => symbol_tools.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/serena/tools/{ls_tools.py => symbol_tools.py} (100%) diff --git a/src/serena/tools/__init__.py b/src/serena/tools/__init__.py index c88a7e5..e6fb4b0 100644 --- a/src/serena/tools/__init__.py +++ b/src/serena/tools/__init__.py @@ -1,7 +1,7 @@ # ruff: noqa from .tools_base import * from .file_tools import * -from .ls_tools import * +from .symbol_tools import * from .memory_tools import * from .cmd_tools import * from .config_tools import * diff --git a/src/serena/tools/ls_tools.py b/src/serena/tools/symbol_tools.py similarity index 100% rename from src/serena/tools/ls_tools.py rename to src/serena/tools/symbol_tools.py From edb07bc823c401837a65826d0ad0c1bddd2d0e60 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 19:49:42 +0200 Subject: [PATCH 17/41] Move class Project to new module serena.project --- src/serena/agent.py | 3 ++- src/serena/code_editor.py | 2 +- src/serena/config/serena_config.py | 26 +----------------------- src/serena/project.py | 32 ++++++++++++++++++++++++++++++ src/serena/symbol.py | 2 +- test/serena/test_serena_agent.py | 3 ++- 6 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 src/serena/project.py diff --git a/src/serena/agent.py b/src/serena/agent.py index f570ac3..c812863 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -23,11 +23,12 @@ from sensai.util.logging import LogTime from serena import serena_version from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode -from serena.config.serena_config import Project, SerenaConfig, ToolSet, get_serena_managed_dir +from serena.config.serena_config import SerenaConfig, ToolSet, get_serena_managed_dir from serena.constants import ( SERENA_LOG_FORMAT, ) from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI +from serena.project import Project from serena.prompt_factory import SerenaPromptFactory from serena.symbol import SymbolManager from serena.tools import Tool, ToolRegistry diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index b0d6de3..fccf047 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -11,7 +11,7 @@ from solidlsp import SolidLanguageServer from solidlsp.ls import LSPFileBuffer from solidlsp.ls_utils import TextUtils -from .config.serena_config import Project +from .project import Project from .tools.jetbrains_plugin_client import JetBrainsPluginClient if TYPE_CHECKING: diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 1f710bb..7b1b2be 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -23,6 +23,7 @@ from serena.constants import ( SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME, ) +from serena.project import Project from serena.util.general import load_yaml, save_yaml from serena.util.inspection import determine_programming_language_composition from solidlsp.ls_config import Language @@ -224,31 +225,6 @@ class ProjectConfig(ToolInclusionDefinition, ToStringMixin): return cls._from_dict(yaml_data) -@dataclass -class Project: - project_root: str - project_config: ProjectConfig - - @property - def project_name(self) -> str: - return self.project_config.project_name - - @property - def language(self) -> Language: - return self.project_config.language - - @classmethod - def load(cls, project_root: str | Path, autogenerate: bool = True) -> Self: - project_root = Path(project_root).resolve() - if not project_root.exists(): - raise FileNotFoundError(f"Project root not found: {project_root}") - project_config = ProjectConfig.load(project_root, autogenerate=autogenerate) - return cls(project_root=str(project_root), project_config=project_config) - - def path_to_project_yml(self) -> str: - return os.path.join(self.project_root, self.project_config.rel_path_to_project_yml()) - - @dataclass(kw_only=True) class SerenaConfig(ToolInclusionDefinition, ToStringMixin): """ diff --git a/src/serena/project.py b/src/serena/project.py new file mode 100644 index 0000000..321a622 --- /dev/null +++ b/src/serena/project.py @@ -0,0 +1,32 @@ +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Self + +from serena.config.serena_config import ProjectConfig +from solidlsp.ls_config import Language + + +@dataclass +class Project: + project_root: str + project_config: ProjectConfig + + @property + def project_name(self) -> str: + return self.project_config.project_name + + @property + def language(self) -> Language: + return self.project_config.language + + @classmethod + def load(cls, project_root: str | Path, autogenerate: bool = True) -> Self: + project_root = Path(project_root).resolve() + if not project_root.exists(): + raise FileNotFoundError(f"Project root not found: {project_root}") + project_config = ProjectConfig.load(project_root, autogenerate=autogenerate) + return cls(project_root=str(project_root), project_config=project_config) + + def path_to_project_yml(self) -> str: + return os.path.join(self.project_root, self.project_config.rel_path_to_project_yml()) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 3a599b5..8161e94 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -13,7 +13,7 @@ from solidlsp import SolidLanguageServer from solidlsp.ls import ReferenceInSymbol as LSPReferenceInSymbol from solidlsp.ls_types import Position, SymbolKind, UnifiedSymbolInformation -from .config.serena_config import Project +from .project import Project if TYPE_CHECKING: from .agent import SerenaAgent diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index 460c124..8b2a98c 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -6,7 +6,8 @@ import pytest import test.solidlsp.clojure as clj from serena.agent import SerenaAgent -from serena.config.serena_config import Project, ProjectConfig, SerenaConfig +from serena.config.serena_config import ProjectConfig, SerenaConfig +from serena.project import Project from serena.tools import FindReferencingSymbolsTool, FindSymbolTool from solidlsp.ls_config import Language from test.conftest import get_repo_path From 34db0f1446f760a6b820fe4f68f868f11e8d0d96 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 20:00:03 +0200 Subject: [PATCH 18/41] Make ReadFileTool independent of language server, adding a read function in Project --- src/serena/project.py | 12 ++++++++++++ src/serena/tools/file_tools.py | 2 +- src/serena/tools/tools_base.py | 10 +++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index 321a622..4a37b7a 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -30,3 +30,15 @@ class Project: def path_to_project_yml(self) -> str: return os.path.join(self.project_root, self.project_config.rel_path_to_project_yml()) + + def read_file(self, relative_path: str) -> str: + """ + Reads a file relative to the project root. + + :param relative_path: the path to the file relative to the project root + :return: the content of the file + """ + abs_path = Path(self.project_root) / relative_path + if not abs_path.exists(): + raise FileNotFoundError(f"File not found: {abs_path}") + return abs_path.read_text(encoding=self.project_config.encoding) diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 0bd091e..526561f 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -41,7 +41,7 @@ class ReadFileTool(Tool): """ self.agent.validate_relative_path(relative_path) - result = self.language_server.retrieve_full_file_content(relative_path) + result = self.project.read_file(relative_path) result_lines = result.splitlines() if end_line is None: result_lines = result_lines[start_line:] diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 67f43f9..a3eca53 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -11,6 +11,7 @@ from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metada from sensai.util import logging from sensai.util.string import dict_string +from serena.project import Project from serena.prompt_factory import PromptFactory from serena.symbol import SymbolManager from serena.util.class_decorators import singleton @@ -55,11 +56,18 @@ class Component(ABC): assert self.agent.symbol_manager is not None return self.agent.symbol_manager + @property + def project(self) -> Project: + project = self.agent.get_active_project() + if project is None: + raise ValueError("No active project") + return project + def create_code_editor(self) -> "CodeEditor": from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor if self.agent.serena_config.jetbrains: - return JetBrainsCodeEditor(project=self.agent.get_active_project(), agent=self.agent) + return JetBrainsCodeEditor(project=self.project, agent=self.agent) else: return LanguageServerCodeEditor(self.symbol_manager, agent=self.agent) From c4d89f21e70a16cc33d4909f9a17bb73d276c85f Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 22:10:03 +0200 Subject: [PATCH 19/41] Move "search for pattern" functionality from LS to Project, adding all necessary related functionality: * Handling of ignored files * Enumeration of source files Functions removed from SolidLanguageServer: * index_repository * request_parsed_files * search_files_for_pattern Implement SearchForPatternTool without language server --- src/serena/agent.py | 34 ++++- src/serena/config/serena_config.py | 15 +- src/serena/project.py | 143 +++++++++++++++++++- src/serena/tools/file_tools.py | 2 +- src/serena/tools/tools_base.py | 5 +- src/solidlsp/ls.py | 85 +----------- test/conftest.py | 38 +++++- test/solidlsp/clojure/test_clojure_basic.py | 35 ++--- test/solidlsp/python/test_python_basic.py | 21 +-- 9 files changed, 245 insertions(+), 133 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index c812863..157b78c 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -17,9 +17,9 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, TypeVar, Union import click -from pathspec import PathSpec from sensai.util import logging from sensai.util.logging import LogTime +from tqdm import tqdm from serena import serena_version from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode @@ -140,6 +140,7 @@ def create_ls_for_project( else: project_instance = project + # TODO: This is duplicated in Project.__init__ project_config = project_instance.project_config ignored_paths = project_config.ignored_paths if len(ignored_paths) > 0: @@ -178,11 +179,22 @@ def index_project(project: str, log_level: str = "INFO") -> None: :param project: the project to index. By default, the current working directory is used. """ log_level_int = logging.getLevelNamesMapping()[log_level.upper()] - project = os.path.abspath(project) + project_instance = Project.load(os.path.abspath(project)) print(f"Indexing symbols in project {project}") ls = create_ls_for_project(project, log_level=log_level_int) + save_after_n_files = 10 with ls.start_server(): - ls.index_repository() + parsed_files = project_instance.request_parsed_files() + files_processed = 0 + pbar = tqdm(parsed_files, disable=False) + for relative_file_path in pbar: + pbar.set_description(f"Indexing ({os.path.basename(relative_file_path)})") + ls.request_document_symbols(relative_file_path, include_body=False) + ls.request_document_symbols(relative_file_path, include_body=True) + files_processed += 1 + if files_processed % save_after_n_files == 0: + ls.save_cache() + ls.save_cache() print(f"Symbols saved to {ls.cache_path}") @@ -285,8 +297,6 @@ class SerenaAgent: self.symbol_manager: SymbolManager | None = None self.memories_manager: MemoriesManager | None = None self.lines_read: LinesRead | None = None - self.ignore_spec: PathSpec # not set to None to avoid assert statements - """Ignore spec, extracted from the project's gitignore files and the explicitly configured ignored paths.""" # set the active modes if modes is None: @@ -340,6 +350,7 @@ class SerenaAgent: path = path.resolve() return path.is_relative_to(_proj_root) + # TODO: Move to project def path_is_gitignored(self, path: str | Path) -> bool: """ Checks if the given path is ignored by git. Non absolute paths are assumed to be relative to the project root. @@ -354,7 +365,8 @@ class SerenaAgent: if len(relative_path.parts) > 0 and relative_path.parts[0] == ".git": return True - return match_path(str(relative_path), self.ignore_spec, root_path=self.get_project_root()) + project = self.get_active_project_or_raise() + return match_path(str(relative_path), project.get_ignore_spec(), root_path=self.get_project_root()) def validate_relative_path(self, relative_path: str) -> None: """ @@ -384,6 +396,15 @@ class SerenaAgent: """ return self._active_project + def get_active_project_or_raise(self) -> Project: + """ + :return: the active project or raises an exception if no project is active + """ + project = self.get_active_project() + if project is None: + raise ValueError("No active project. Please activate a project first.") + return project + def set_modes(self, modes: list[SerenaAgentMode]) -> None: """ Set the current mode configurations. @@ -475,7 +496,6 @@ class SerenaAgent: with LogTime("Language server initialization", logger=log): self.reset_language_server() assert self.language_server is not None - self.ignore_spec = self.language_server.get_ignore_spec() # initialize project-specific instances which depend on the language server log.debug(f"Initializing symbol and memories manager for {project.project_name} at {project.project_root}") diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 7b1b2be..bffad29 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -9,7 +9,7 @@ from copy import deepcopy from dataclasses import dataclass, field from functools import cached_property from pathlib import Path -from typing import TYPE_CHECKING, Any, Self, TypeVar +from typing import TYPE_CHECKING, Any, Optional, Self, TypeVar import yaml from ruamel.yaml.comments import CommentedMap @@ -23,13 +23,12 @@ from serena.constants import ( SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME, ) -from serena.project import Project from serena.util.general import load_yaml, save_yaml from serena.util.inspection import determine_programming_language_composition from solidlsp.ls_config import Language if TYPE_CHECKING: - pass + from ..project import Project log = logging.getLogger(__name__) T = TypeVar("T") @@ -233,7 +232,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): For testing purposes, it can also be instantiated directly with the desired parameters. """ - projects: list[Project] = field(default_factory=list) + projects: list["Project"] = field(default_factory=list) gui_log_window_enabled: bool = False log_level: int = logging.INFO trace_lsp_communication: bool = False @@ -293,6 +292,8 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): """ Static constructor to create SerenaConfig from the configuration file """ + from ..project import Project + config_file_path = cls._determine_config_file_path() # create the configuration file from the template if necessary @@ -387,7 +388,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): def project_names(self) -> list[str]: return sorted(project.project_config.project_name for project in self.projects) - def get_project(self, project_root_or_name: str) -> Project | None: + def get_project(self, project_root_or_name: str) -> Optional["Project"]: for project in self.projects: if project.project_config.project_name == project_root_or_name: return project @@ -398,7 +399,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): return project return None - def add_project_from_path(self, project_root: Path | str, project_name: str | None = None) -> tuple[Project, bool]: + def add_project_from_path(self, project_root: Path | str, project_name: str | None = None) -> tuple["Project", bool]: """ Add a project to the Serena configuration from a given path. Will raise a FileExistsError if the name or path is already registered. @@ -410,6 +411,8 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin): saved to disk. It may be that no new project configuration was generated if the project configuration already exists on disk but the project itself was not added yet to the Serena configuration. """ + from ..project import Project + project_root = Path(project_root).resolve() if not project_root.exists(): raise FileNotFoundError(f"Error: Path does not exist: {project_root}") diff --git a/src/serena/project.py b/src/serena/project.py index 4a37b7a..c9d0510 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -1,16 +1,45 @@ +import logging import os -from dataclasses import dataclass from pathlib import Path from typing import Self +import pathspec + from serena.config.serena_config import ProjectConfig +from serena.text_utils import MatchedConsecutiveLines, search_files +from serena.util.file_system import GitignoreParser, match_path from solidlsp.ls_config import Language +log = logging.getLogger(__name__) + -@dataclass class Project: - project_root: str - project_config: ProjectConfig + def __init__(self, project_root: str, project_config: ProjectConfig): + self.project_root = project_root + self.project_config = project_config + + # gather ignored paths from the project configuration and gitignore files + ignored_paths = project_config.ignored_paths + if len(ignored_paths) > 0: + log.info(f"Using {len(ignored_paths)} ignored paths from the explicit project configuration.") + log.debug(f"Ignored paths: {ignored_paths}") + if project_config.ignore_all_files_in_gitignore: + log.info(f"Parsing all gitignore files in {self.project_root}") + gitignore_parser = GitignoreParser(self.project_root) + log.info(f"Found {len(gitignore_parser.get_ignore_specs())} gitignore files.") + for spec in gitignore_parser.get_ignore_specs(): + log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") + ignored_paths.extend(spec.patterns) + + # Set up the pathspec matcher for the ignored paths + # for all absolute paths in ignored_paths, convert them to relative paths + processed_patterns = [] + for pattern in set(ignored_paths): + # Normalize separators (pathspec expects forward slashes) + pattern = pattern.replace(os.path.sep, "/") + processed_patterns.append(pattern) + log.debug(f"Processing {len(processed_patterns)} ignored paths") + self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, processed_patterns) @property def project_name(self) -> str: @@ -42,3 +71,109 @@ class Project: if not abs_path.exists(): raise FileNotFoundError(f"File not found: {abs_path}") return abs_path.read_text(encoding=self.project_config.encoding) + + def get_ignore_spec(self) -> pathspec.PathSpec: + """Returns the pathspec matcher for the paths that were configured to be ignored through + the multilspy config. + + This is is a subset of the full language-specific ignore spec that determines + which files are relevant for the language server. + + This matcher is useful for operations outside of the language server, + such as when searching for relevant non-language files in the project. + """ + return self._ignore_spec + + def is_ignored_dirname(self, dirname: str) -> bool: + return dirname.startswith(".") + + def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: + """ + Determine whether a path should be ignored based on file type and ignore patterns. + + :param relative_path: Relative path to check + :param ignore_unsupported_files: whether files that are not source files shall be ignored + + :return: whether the path should be ignored + """ + abs_path = os.path.join(self.project_root, relative_path) + if not os.path.exists(abs_path): + raise FileNotFoundError(f"File {abs_path} not found, the ignore check cannot be performed") + + # Check file extension if it's a file + is_file = os.path.isfile(abs_path) + if is_file and ignore_unsupported_files: + fn_matcher = self.language.get_source_fn_matcher() + if not fn_matcher.is_relevant_filename(abs_path): + return True + + # Create normalized path for consistent handling + rel_path = Path(relative_path) + + # Check each part of the path against always fulfilled ignore conditions + dir_parts = rel_path.parts + if is_file: + dir_parts = dir_parts[:-1] + for part in dir_parts: + if not part: # Skip empty parts (e.g., from leading '/') + continue + if self.is_ignored_dirname(part): + return True + + return match_path(relative_path, self.get_ignore_spec(), root_path=self.project_root) + + def request_parsed_files(self, relative_path: str = "") -> list[str]: + """Retrieves relative paths of all files analyzed by the Language Server. + + :param relative_path: will only retrieve files that are subpaths of this. + """ + rel_file_paths = [] + start_path = os.path.join(self.project_root, relative_path) + if not os.path.exists(start_path): + raise FileNotFoundError(f"Relative path {start_path} not found.") + if os.path.isfile(start_path): + return [relative_path] + else: + for root, dirs, files in os.walk(start_path, followlinks=True): + dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] + for file in files: + rel_file_path = os.path.relpath(os.path.join(root, file), start=self.project_root) + try: + if not self.is_ignored_path(rel_file_path): + rel_file_paths.append(rel_file_path) + except FileNotFoundError: + log.warning( + f"File {rel_file_path} not found (possibly due it being a symlink), skipping it in request_parsed_files", + ) + return rel_file_paths + + def search_files_for_pattern( + self, + pattern: str, + relative_path: str = "", + context_lines_before: int = 0, + context_lines_after: int = 0, + paths_include_glob: str | None = None, + paths_exclude_glob: str | None = None, + ) -> list[MatchedConsecutiveLines]: + """ + Search for a pattern across all (non-ignored) source files + + :param pattern: Regular expression pattern to search for, either as a compiled Pattern or string + :param relative_path: + :param context_lines_before: Number of lines of context to include before each match + :param context_lines_after: Number of lines of context to include after each match + :param paths_include_glob: Glob pattern to filter which files to include in the search + :param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob. + :return: List of matched consecutive lines with context + """ + relative_file_paths = self.request_parsed_files(relative_path=relative_path) + return search_files( + relative_file_paths, + pattern, + root_path=self.project_root, + context_lines_before=context_lines_before, + context_lines_after=context_lines_after, + paths_include_glob=paths_include_glob, + paths_exclude_glob=paths_exclude_glob, + ) diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 526561f..4618d8b 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -358,7 +358,7 @@ class SearchForPatternTool(Tool): raise FileNotFoundError(f"Relative path {relative_path} does not exist.") if restrict_search_to_code_files: - matches = self.language_server.search_files_for_pattern( + matches = self.project.search_files_for_pattern( pattern=substring_pattern, relative_path=relative_path, context_lines_before=context_lines_before, diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index a3eca53..a4d087e 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -58,10 +58,7 @@ class Component(ABC): @property def project(self) -> Project: - project = self.agent.get_active_project() - if project is None: - raise ValueError("No active project") - return project + return self.agent.get_active_project_or_raise() def create_code_editor(self) -> "CodeEditor": from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 4b8163c..415807e 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -16,9 +16,8 @@ from pathlib import Path, PurePath from typing import Self, Union, cast import pathspec -import tqdm -from serena.text_utils import MatchedConsecutiveLines, search_files +from serena.text_utils import MatchedConsecutiveLines from serena.util.file_system import match_path from solidlsp import ls_types from solidlsp.ls_config import Language, LanguageServerConfig @@ -1190,70 +1189,6 @@ class SolidLanguageServer(ABC): symbol_body = symbol_body[symbol_start_column:] return symbol_body - def request_parsed_files(self, relative_path: str = "") -> list[str]: - """Retrieves relative paths of all files analyzed by the Language Server. - - :param relative_path: will only retrieve files that are subpaths of this. - """ - if not self.server_started: - self.logger.log( - "request_parsed_files called before Language Server started", - logging.ERROR, - ) - raise LanguageServerException("Language Server not started") - rel_file_paths = [] - start_path = os.path.join(self.repository_root_path, relative_path) - if not os.path.exists(start_path): - raise FileNotFoundError(f"Relative path {start_path} not found.") - if os.path.isfile(start_path): - return [relative_path] - else: - for root, dirs, files in os.walk(start_path, followlinks=True): - dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] - for file in files: - rel_file_path = os.path.relpath(os.path.join(root, file), start=self.repository_root_path) - try: - if not self.is_ignored_path(rel_file_path): - rel_file_paths.append(rel_file_path) - except FileNotFoundError: - self.logger.log( - f"File {rel_file_path} not found (possibly due it being a symlink), skipping it in request_parsed_files", - logging.WARNING, - ) - return rel_file_paths - - def search_files_for_pattern( - self, - pattern: str, - relative_path: str = "", - context_lines_before: int = 0, - context_lines_after: int = 0, - paths_include_glob: str | None = None, - paths_exclude_glob: str | None = None, - ) -> list[MatchedConsecutiveLines]: - """ - Search for a pattern across all files analyzed by the Language Server. - - :param pattern: Regular expression pattern to search for, either as a compiled Pattern or string - :param relative_path: - :param context_lines_before: Number of lines of context to include before each match - :param context_lines_after: Number of lines of context to include after each match - :param paths_include_glob: Glob pattern to filter which files to include in the search - :param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob. - :return: List of matched consecutive lines with context - """ - relative_file_paths = self.request_parsed_files(relative_path=relative_path) - return search_files( - relative_file_paths, - pattern, - file_reader=self.retrieve_full_file_content, - root_path=self.repository_root_path, - context_lines_before=context_lines_before, - context_lines_after=context_lines_after, - paths_include_glob=paths_include_glob, - paths_exclude_glob=paths_exclude_glob, - ) - def request_referencing_symbols( self, relative_file_path: str, @@ -1584,24 +1519,6 @@ class SolidLanguageServer(ABC): """ return Path(self.repository_root_path) / ".serena" / "cache" / self.language_id / "document_symbols_cache_v23-06-25.pkl" - def index_repository(self, progress_bar: bool = True, save_after_n_files: int = 10) -> None: - """Will go through the entire repository and "index" all files, meaning save their symbols to the cache. - - :param progress_bar: Whether to show a progress bar while indexing the repository. - :param save_after_n_files: How many files to process before saving a checkpoint of the cache. - """ - parsed_files = self.request_parsed_files() - files_processed = 0 - pbar = tqdm.tqdm(parsed_files, disable=not progress_bar) - for relative_file_path in pbar: - pbar.set_description(f"Indexing ({os.path.basename(relative_file_path)})") - self.request_document_symbols(relative_file_path, include_body=False) - self.request_document_symbols(relative_file_path, include_body=True) - files_processed += 1 - if files_processed % save_after_n_files == 0: - self.save_cache() - self.save_cache() - def save_cache(self): with self._cache_lock: if not self._cache_has_changed: diff --git a/test/conftest.py b/test/conftest.py index 6632f3a..0fe8352 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest from sensai.util.logging import configure +from serena.project import Project from serena.util.file_system import GitignoreParser from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import Language, LanguageServerConfig @@ -50,6 +51,11 @@ def create_default_ls(language: Language) -> SolidLanguageServer: return create_ls(language, repo_path) +def create_default_project(language: Language) -> Project: + repo_path = str(get_repo_path(language)) + return Project.load(repo_path) + + @pytest.fixture(scope="session") def repo_path(request: LanguageParamRequest) -> Path: """Get the repository path for a specific language. @@ -73,7 +79,7 @@ def repo_path(request: LanguageParamRequest) -> Path: @pytest.fixture(scope="session") def language_server(request: LanguageParamRequest): - """Create a SyncLanguageServer instance configured for the specified language. + """Create a language server instance configured for the specified language. This fixture requires a language parameter via pytest.mark.parametrize: @@ -104,3 +110,33 @@ def language_server(request: LanguageParamRequest): yield server finally: server.stop() + + +@pytest.fixture(scope="session") +def project(request: LanguageParamRequest): + """Create a Project for the specified language. + + This fixture requires a language parameter via pytest.mark.parametrize: + + Example: + ``` + @pytest.mark.parametrize("project", [Language.PYTHON], indirect=True) + def test_python_project(project: Project) -> None: + # Use the Python project to test something + pass + ``` + + You can also test multiple languages in a single test: + ``` + @pytest.mark.parametrize("project", [Language.PYTHON, Language.TYPESCRIPT], indirect=True) + def test_multiple_languages(project: SyncLanguageServer) -> None: + # This test will run once for each language + pass + ``` + + """ + if not hasattr(request, "param"): + raise ValueError("Language parameter must be provided via pytest.mark.parametrize") + + language = request.param + yield create_default_project(language) diff --git a/test/solidlsp/clojure/test_clojure_basic.py b/test/solidlsp/clojure/test_clojure_basic.py index e50ad0b..56b80ac 100644 --- a/test/solidlsp/clojure/test_clojure_basic.py +++ b/test/solidlsp/clojure/test_clojure_basic.py @@ -1,5 +1,6 @@ import pytest +from serena.project import Project from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_types import UnifiedSymbolInformation @@ -120,22 +121,6 @@ class TestLanguageServerBasics: definition = result[0] assert definition["relativePath"] == CORE_PATH, "Should find the definition of greet in core.clj" - @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) - def test_search_files_for_pattern(self, language_server: SolidLanguageServer): - result = language_server.search_files_for_pattern("defn.*greet") - - assert result is not None, "Pattern search should return results" - assert len(result) > 0, "Should find at least one match for 'defn.*greet'" - - core_matches = [match for match in result if match.source_file_path and "core.clj" in match.source_file_path] - assert len(core_matches) > 0, "Should find greet function in core.clj" - - result = language_server.search_files_for_pattern(":require") - - assert result is not None, "Should find require statements" - utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path] - assert len(utils_matches) > 0, "Should find require statement in utils.clj" - @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) def test_request_references_with_content(self, language_server: SolidLanguageServer): """Test references to multiply function with content""" @@ -214,3 +199,21 @@ class TestLanguageServerBasics: break assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}" + + +class TestProjectBasics: + @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) + def test_search_files_for_pattern(self, project: Project) -> None: + result = project.search_files_for_pattern("defn.*greet") + + assert result is not None, "Pattern search should return results" + assert len(result) > 0, "Should find at least one match for 'defn.*greet'" + + core_matches = [match for match in result if match.source_file_path and "core.clj" in match.source_file_path] + assert len(core_matches) > 0, "Should find greet function in core.clj" + + result = project.search_files_for_pattern(":require") + + assert result is not None, "Should find require statements" + utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path] + assert len(utils_matches) > 0, "Should find require statement in utils.clj" diff --git a/test/solidlsp/python/test_python_basic.py b/test/solidlsp/python/test_python_basic.py index 74737c4..77df693 100644 --- a/test/solidlsp/python/test_python_basic.py +++ b/test/solidlsp/python/test_python_basic.py @@ -9,6 +9,7 @@ import os import pytest +from serena.project import Project from serena.text_utils import LineType from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language @@ -185,42 +186,42 @@ class TestLanguageServerBasics: else: assert line.match_type == LineType.AFTER_MATCH - @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_search_files_for_pattern(self, language_server: SolidLanguageServer) -> None: + +class TestProjectBasics: + @pytest.mark.parametrize("project", [Language.PYTHON], indirect=True) + def test_search_files_for_pattern(self, project: Project) -> None: """Test search_files_for_pattern with various patterns and glob filters.""" # Test 1: Search for class definitions across all files class_pattern = r"class\s+\w+\s*(?:\([^{]*\)|:)" - matches = language_server.search_files_for_pattern(class_pattern) + matches = project.search_files_for_pattern(class_pattern) assert len(matches) > 0 # Should find multiple classes like User, Item, BaseModel, etc. assert len(matches) >= 5 # Test 2: Search for specific class with include glob user_class_pattern = r"class\s+User\s*(?:\([^{]*\)|:)" - matches = language_server.search_files_for_pattern(user_class_pattern, paths_include_glob="**/models.py") + matches = project.search_files_for_pattern(user_class_pattern, paths_include_glob="**/models.py") assert len(matches) == 1 # Should only find User class in models.py assert matches[0].source_file_path is not None assert "models.py" in matches[0].source_file_path # Test 3: Search for method definitions with exclude glob method_pattern = r"def\s+\w+\s*\([^)]*\):" - matches = language_server.search_files_for_pattern(method_pattern, paths_exclude_glob="**/models.py") + matches = project.search_files_for_pattern(method_pattern, paths_exclude_glob="**/models.py") assert len(matches) > 0 # Should find methods in services.py but not in models.py assert all(match.source_file_path is not None and "models.py" not in match.source_file_path for match in matches) # Test 4: Search for specific method with both include and exclude globs create_user_pattern = r"def\s+create_user\s*\([^)]*\)(?:\s*->[^:]+)?:" - matches = language_server.search_files_for_pattern( - create_user_pattern, paths_include_glob="**/*.py", paths_exclude_glob="**/models.py" - ) + matches = project.search_files_for_pattern(create_user_pattern, paths_include_glob="**/*.py", paths_exclude_glob="**/models.py") assert len(matches) == 1 # Should only find create_user in services.py assert matches[0].source_file_path is not None assert "services.py" in matches[0].source_file_path # Test 5: Search for a pattern that should appear in multiple files init_pattern = r"def\s+__init__\s*\([^)]*\):" - matches = language_server.search_files_for_pattern(init_pattern) + matches = project.search_files_for_pattern(init_pattern) assert len(matches) > 1 # Should find __init__ in multiple classes # Should find __init__ in both models.py and services.py assert any(match.source_file_path is not None and "models.py" in match.source_file_path for match in matches) @@ -228,5 +229,5 @@ class TestLanguageServerBasics: # Test 6: Search with a pattern that should have no matches no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):" - matches = language_server.search_files_for_pattern(no_match_pattern) + matches = project.search_files_for_pattern(no_match_pattern) assert len(matches) == 0 From 75436727d058ff4528ee56dad15cbc16368c8537 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 22:42:20 +0200 Subject: [PATCH 20/41] Refactoring: Remove names of methods moved to Project: * search_files_for_pattern -> search_source_files_for_patterh * request_parsed_files -> gather_source_files --- src/serena/agent.py | 2 +- src/serena/project.py | 10 +++++----- src/serena/tools/file_tools.py | 2 +- test/solidlsp/clojure/test_clojure_basic.py | 4 ++-- test/solidlsp/python/test_python_basic.py | 14 ++++++++------ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 157b78c..b6f66ee 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -184,7 +184,7 @@ def index_project(project: str, log_level: str = "INFO") -> None: ls = create_ls_for_project(project, log_level=log_level_int) save_after_n_files = 10 with ls.start_server(): - parsed_files = project_instance.request_parsed_files() + parsed_files = project_instance.gather_source_files() files_processed = 0 pbar = tqdm(parsed_files, disable=False) for relative_file_path in pbar: diff --git a/src/serena/project.py b/src/serena/project.py index c9d0510..97edbde 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -122,10 +122,10 @@ class Project: return match_path(relative_path, self.get_ignore_spec(), root_path=self.project_root) - def request_parsed_files(self, relative_path: str = "") -> list[str]: - """Retrieves relative paths of all files analyzed by the Language Server. + def gather_source_files(self, relative_path: str = "") -> list[str]: + """Retrieves relative paths of all source files, optionally limited to the given path - :param relative_path: will only retrieve files that are subpaths of this. + :param relative_path: if provided, restrict search to this path """ rel_file_paths = [] start_path = os.path.join(self.project_root, relative_path) @@ -147,7 +147,7 @@ class Project: ) return rel_file_paths - def search_files_for_pattern( + def search_source_files_for_pattern( self, pattern: str, relative_path: str = "", @@ -167,7 +167,7 @@ class Project: :param paths_exclude_glob: Glob pattern to filter which files to exclude from the search. Takes precedence over paths_include_glob. :return: List of matched consecutive lines with context """ - relative_file_paths = self.request_parsed_files(relative_path=relative_path) + relative_file_paths = self.gather_source_files(relative_path=relative_path) return search_files( relative_file_paths, pattern, diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 4618d8b..5cff6d9 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -358,7 +358,7 @@ class SearchForPatternTool(Tool): raise FileNotFoundError(f"Relative path {relative_path} does not exist.") if restrict_search_to_code_files: - matches = self.project.search_files_for_pattern( + matches = self.project.search_source_files_for_pattern( pattern=substring_pattern, relative_path=relative_path, context_lines_before=context_lines_before, diff --git a/test/solidlsp/clojure/test_clojure_basic.py b/test/solidlsp/clojure/test_clojure_basic.py index 56b80ac..28c2cd3 100644 --- a/test/solidlsp/clojure/test_clojure_basic.py +++ b/test/solidlsp/clojure/test_clojure_basic.py @@ -204,7 +204,7 @@ class TestLanguageServerBasics: class TestProjectBasics: @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) def test_search_files_for_pattern(self, project: Project) -> None: - result = project.search_files_for_pattern("defn.*greet") + result = project.search_source_files_for_pattern("defn.*greet") assert result is not None, "Pattern search should return results" assert len(result) > 0, "Should find at least one match for 'defn.*greet'" @@ -212,7 +212,7 @@ class TestProjectBasics: core_matches = [match for match in result if match.source_file_path and "core.clj" in match.source_file_path] assert len(core_matches) > 0, "Should find greet function in core.clj" - result = project.search_files_for_pattern(":require") + result = project.search_source_files_for_pattern(":require") assert result is not None, "Should find require statements" utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path] diff --git a/test/solidlsp/python/test_python_basic.py b/test/solidlsp/python/test_python_basic.py index 77df693..d19c380 100644 --- a/test/solidlsp/python/test_python_basic.py +++ b/test/solidlsp/python/test_python_basic.py @@ -193,35 +193,37 @@ class TestProjectBasics: """Test search_files_for_pattern with various patterns and glob filters.""" # Test 1: Search for class definitions across all files class_pattern = r"class\s+\w+\s*(?:\([^{]*\)|:)" - matches = project.search_files_for_pattern(class_pattern) + matches = project.search_source_files_for_pattern(class_pattern) assert len(matches) > 0 # Should find multiple classes like User, Item, BaseModel, etc. assert len(matches) >= 5 # Test 2: Search for specific class with include glob user_class_pattern = r"class\s+User\s*(?:\([^{]*\)|:)" - matches = project.search_files_for_pattern(user_class_pattern, paths_include_glob="**/models.py") + matches = project.search_source_files_for_pattern(user_class_pattern, paths_include_glob="**/models.py") assert len(matches) == 1 # Should only find User class in models.py assert matches[0].source_file_path is not None assert "models.py" in matches[0].source_file_path # Test 3: Search for method definitions with exclude glob method_pattern = r"def\s+\w+\s*\([^)]*\):" - matches = project.search_files_for_pattern(method_pattern, paths_exclude_glob="**/models.py") + matches = project.search_source_files_for_pattern(method_pattern, paths_exclude_glob="**/models.py") assert len(matches) > 0 # Should find methods in services.py but not in models.py assert all(match.source_file_path is not None and "models.py" not in match.source_file_path for match in matches) # Test 4: Search for specific method with both include and exclude globs create_user_pattern = r"def\s+create_user\s*\([^)]*\)(?:\s*->[^:]+)?:" - matches = project.search_files_for_pattern(create_user_pattern, paths_include_glob="**/*.py", paths_exclude_glob="**/models.py") + matches = project.search_source_files_for_pattern( + create_user_pattern, paths_include_glob="**/*.py", paths_exclude_glob="**/models.py" + ) assert len(matches) == 1 # Should only find create_user in services.py assert matches[0].source_file_path is not None assert "services.py" in matches[0].source_file_path # Test 5: Search for a pattern that should appear in multiple files init_pattern = r"def\s+__init__\s*\([^)]*\):" - matches = project.search_files_for_pattern(init_pattern) + matches = project.search_source_files_for_pattern(init_pattern) assert len(matches) > 1 # Should find __init__ in multiple classes # Should find __init__ in both models.py and services.py assert any(match.source_file_path is not None and "models.py" in match.source_file_path for match in matches) @@ -229,5 +231,5 @@ class TestProjectBasics: # Test 6: Search with a pattern that should have no matches no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):" - matches = project.search_files_for_pattern(no_match_pattern) + matches = project.search_source_files_for_pattern(no_match_pattern) assert len(matches) == 0 From d497a07779e8edf0dbe7ebc708dc59471ac5264a Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 22:59:41 +0200 Subject: [PATCH 21/41] Move 'symbol overview' function to SymbolManager to avoid using the LS directly in GetSymbolsOverviewTool --- src/serena/symbol.py | 14 ++++++++++++++ src/serena/tools/symbol_tools.py | 11 +++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 8161e94..5a85bb5 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -700,6 +700,20 @@ class SymbolManager: """Get the content of a file using the language server.""" return self._lang_server.language_server.retrieve_full_file_content(relative_path) + @dataclass + class SymbolOverviewElement: + name_path: str + kind: int + + def get_symbol_overview(self, relative_path: str) -> dict[str, list[SymbolOverviewElement]]: + path_to_symbol_infos = self._lang_server.request_overview(relative_path) + result = {} + for file_path, symbols in path_to_symbol_infos.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] + return result + class JetBrainsSymbol(AbstractSymbol): def __init__(self, symbol_dict: dict, project: Project) -> None: diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 1910928..e513832 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -2,6 +2,7 @@ Language server-related tools """ +import dataclasses import json from collections.abc import Sequence from copy import copy @@ -60,14 +61,8 @@ class GetSymbolsOverviewTool(Tool): (e.g. a subdirectory). :return: a JSON object mapping relative paths of all contained files to info about top-level symbols in the file (name_path, kind). """ - path_to_symbol_infos = self.language_server.request_overview(relative_path) - result = {} - for file_path, symbols in path_to_symbol_infos.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] = [{"name_path": symbol[0], "kind": int(symbol[1])} for symbol in symbols] - - result_json_str = json.dumps(result) + result = self.symbol_manager.get_symbol_overview(relative_path) + result_json_str = json.dumps({k: [dataclasses.asdict(i) for i in l] for k, l in result.items()}) return self._limit_length(result_json_str, max_answer_chars) From 3d4d71206fc298322506b59d90deae834984d840 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 23:05:36 +0200 Subject: [PATCH 22/41] Update Elixir tests --- .../elixir/test_elixir_integration.py | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/test/solidlsp/elixir/test_elixir_integration.py b/test/solidlsp/elixir/test_elixir_integration.py index 25c86a8..d7a36b1 100644 --- a/test/solidlsp/elixir/test_elixir_integration.py +++ b/test/solidlsp/elixir/test_elixir_integration.py @@ -67,34 +67,6 @@ class TestElixirIntegration: # Should point to models.ex assert "models.ex" in defining_symbol["location"]["uri"] - @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) - def test_comprehensive_symbol_search(self, language_server: SolidLanguageServer): - """Test comprehensive symbol search across the entire project.""" - # Search for all function definitions - function_pattern = r"def\s+\w+\s*[\(\s]" - function_matches = language_server.search_files_for_pattern(function_pattern) - - # Should find functions across multiple files - if function_matches: - files_with_functions = set() - for match in function_matches: - if match.source_file_path: - files_with_functions.add(os.path.basename(match.source_file_path)) - - # Should find functions in multiple files - expected_files = {"models.ex", "services.ex", "examples.ex", "utils.ex", "test_repo.ex"} - found_files = expected_files.intersection(files_with_functions) - assert len(found_files) > 0, f"Expected functions in {expected_files}, found in {files_with_functions}" - - # Search for struct definitions - struct_pattern = r"defstruct\s+\[" - struct_matches = language_server.search_files_for_pattern(struct_pattern) - - if struct_matches: - # Should find structs primarily in models.ex - models_structs = [m for m in struct_matches if m.source_file_path and "models.ex" in m.source_file_path] - assert len(models_structs) > 0, "Should find struct definitions in models.ex" - @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) def test_module_hierarchy_understanding(self, language_server: SolidLanguageServer): """Test that the language server understands Elixir module hierarchy.""" @@ -136,12 +108,42 @@ class TestElixirIntegration: assert not matcher.is_relevant_filename("package.json") assert not matcher.is_relevant_filename("Cargo.toml") - @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True) - def test_protocol_and_implementation_understanding(self, language_server: SolidLanguageServer): + +class TestElixirProject: + @pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) + def test_comprehensive_symbol_search(self, project): + """Test comprehensive symbol search across the entire project.""" + # Search for all function definitions + function_pattern = r"def\s+\w+\s*[\(\s]" + function_matches = project.search_files_for_pattern(function_pattern) + + # Should find functions across multiple files + if function_matches: + files_with_functions = set() + for match in function_matches: + if match.source_file_path: + files_with_functions.add(os.path.basename(match.source_file_path)) + + # Should find functions in multiple files + expected_files = {"models.ex", "services.ex", "examples.ex", "utils.ex", "test_repo.ex"} + found_files = expected_files.intersection(files_with_functions) + assert len(found_files) > 0, f"Expected functions in {expected_files}, found in {files_with_functions}" + + # Search for struct definitions + struct_pattern = r"defstruct\s+\[" + struct_matches = project.search_files_for_pattern(struct_pattern) + + if struct_matches: + # Should find structs primarily in models.ex + models_structs = [m for m in struct_matches if m.source_file_path and "models.ex" in m.source_file_path] + assert len(models_structs) > 0, "Should find struct definitions in models.ex" + + @pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) + def test_protocol_and_implementation_understanding(self, project): """Test that the language server understands Elixir protocols and implementations.""" # Search for protocol definitions protocol_pattern = r"defprotocol\s+\w+" - protocol_matches = language_server.search_files_for_pattern(protocol_pattern, paths_include_glob="**/models.ex") + protocol_matches = project.search_files_for_pattern(protocol_pattern, paths_include_glob="**/models.ex") if protocol_matches: # Should find the Serializable protocol @@ -150,7 +152,7 @@ class TestElixirIntegration: # Search for protocol implementations impl_pattern = r"defimpl\s+\w+" - impl_matches = language_server.search_files_for_pattern(impl_pattern, paths_include_glob="**/models.ex") + impl_matches = project.search_files_for_pattern(impl_pattern, paths_include_glob="**/models.ex") if impl_matches: # Should find multiple implementations From ce9a4ef195b6e1e53bb97e0c3c736d9dd6ebd256 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 23:35:32 +0200 Subject: [PATCH 23/41] Add retrieve_content_around_line to Project (keeping LS version, too, for the time being), such that FindReferencingSymbolsTool is now also implemented without a direct reference to the language server All references to Component.language_server have been removed --- src/serena/project.py | 22 +++++++++++ src/serena/tools/symbol_tools.py | 2 +- src/serena/tools/tools_base.py | 9 +---- src/solidlsp/ls.py | 20 ---------- test/solidlsp/clojure/test_clojure_basic.py | 41 +++++++++++---------- test/solidlsp/python/test_python_basic.py | 24 ++++++------ 6 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index 97edbde..fd4813a 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -177,3 +177,25 @@ class Project: paths_include_glob=paths_include_glob, paths_exclude_glob=paths_exclude_glob, ) + + def retrieve_content_around_line( + self, relative_file_path: str, line: int, context_lines_before: int = 0, context_lines_after: int = 0 + ) -> MatchedConsecutiveLines: + """ + Retrieve the content of the given file around the given line. + + :param relative_file_path: The relative path of the file to retrieve the content from + :param line: The line number to retrieve the content around + :param context_lines_before: The number of lines to retrieve before the given line + :param context_lines_after: The number of lines to retrieve after the given line + + :return MatchedConsecutiveLines: A container with the desired lines. + """ + file_contents = self.read_file(relative_file_path) + return MatchedConsecutiveLines.from_file_contents( + file_contents, + line=line, + context_lines_before=context_lines_before, + context_lines_after=context_lines_after, + source_file_path=relative_file_path, + ) diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index e513832..792984d 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -189,7 +189,7 @@ class FindReferencingSymbolsTool(Tool): if not include_body: ref_relative_path = ref.symbol.location.relative_path assert ref_relative_path is not None, f"Referencing symbol {ref.symbol.name} has no relative path, this is likely a bug." - content_around_ref = self.language_server.retrieve_content_around_line( + content_around_ref = self.project.retrieve_content_around_line( relative_file_path=ref_relative_path, line=ref.line, context_lines_before=1, context_lines_after=1 ) ref_dict["content_around_reference"] = content_around_ref.to_display_string() diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index a4d087e..074119c 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -16,7 +16,6 @@ from serena.prompt_factory import PromptFactory from serena.symbol import SymbolManager from serena.util.class_decorators import singleton from serena.util.inspection import iter_subclasses -from solidlsp import SolidLanguageServer if TYPE_CHECKING: from serena.agent import LinesRead, MemoriesManager, SerenaAgent @@ -31,11 +30,6 @@ class Component(ABC): def __init__(self, agent: "SerenaAgent"): self.agent = agent - @property - def language_server(self) -> SolidLanguageServer: - assert self.agent.language_server is not None - return self.agent.language_server - def get_project_root(self) -> str: """ :return: the root directory of the active project, raises a ValueError if no active project configuration is set @@ -275,7 +269,8 @@ class Tool(Component, ToolInterface): log.info(f"Result: {result}") try: - self.language_server.save_cache() + if self.agent.language_server is not None: + self.agent.language_server.save_cache() except Exception as e: log.error(f"Error saving language server cache: {e}") diff --git a/src/solidlsp/ls.py b/src/solidlsp/ls.py index 5a3aa48..f80ef67 100644 --- a/src/solidlsp/ls.py +++ b/src/solidlsp/ls.py @@ -709,26 +709,6 @@ class SolidLanguageServer(ABC): return ret - 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]: - """ - Like request_references, but returns the content of the lines containing the references, not just the locations. - - :param relative_file_path: The relative path of the file that has the symbol for which references should be looked up - :param line: The line number of the symbol - :param column: The column number of the symbol - :param context_lines_before: The number of lines to include in the context before the line containing the reference - :param context_lines_after: The number of lines to include in the context after the line containing the reference - - :return: A list of MatchedConsecutiveLines objects, one for each reference. - """ - references = self.request_references(relative_file_path, line, column) - return [ - self.retrieve_content_around_line(ref["relativePath"], ref["range"]["start"]["line"], context_lines_before, context_lines_after) - for ref in references - ] - def retrieve_full_file_content(self, file_path: str) -> str: """ Retrieve the full content of the given file. diff --git a/test/solidlsp/clojure/test_clojure_basic.py b/test/solidlsp/clojure/test_clojure_basic.py index 28c2cd3..dc10b88 100644 --- a/test/solidlsp/clojure/test_clojure_basic.py +++ b/test/solidlsp/clojure/test_clojure_basic.py @@ -91,24 +91,6 @@ class TestLanguageServerBasics: symbol_names = [symbol["name"] for symbol in result] assert any("add" in name.lower() for name in symbol_names), f"Should find 'add' function in symbols: {symbol_names}" - @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) - def test_retrieve_content_around_line(self, language_server: SolidLanguageServer): - """Test retrieving content around specific lines""" - # Test retrieving content around the greet function definition (line 2) - result = language_server.retrieve_content_around_line(CORE_PATH, 2, 2) - - assert result is not None, "Should retrieve content around line 2" - content_str = result.to_display_string() - assert "greet" in content_str, "Should contain the greet function definition" - assert "defn" in content_str, "Should contain defn keyword" - - # Test retrieving content around multiply function (around line 13) - result = language_server.retrieve_content_around_line(CORE_PATH, 13, 1) - - assert result is not None, "Should retrieve content around line 13" - content_str = result.to_display_string() - assert "multiply" in content_str, "Should contain multiply function" - @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) def test_namespace_functions(self, language_server: SolidLanguageServer): """Test definition lookup for core/greet usage in utils.clj""" @@ -124,7 +106,10 @@ class TestLanguageServerBasics: @pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True) def test_request_references_with_content(self, language_server: SolidLanguageServer): """Test references to multiply function with content""" - result = language_server.request_references_with_content(CORE_PATH, 12, 6, 3) + references = language_server.request_references(CORE_PATH, 12, 6) + result = [ + language_server.retrieve_content_around_line(ref1["relativePath"], ref1["range"]["start"]["line"], 3, 0) for ref1 in references + ] assert result is not None, "Should find references with content" assert isinstance(result, list) @@ -202,6 +187,24 @@ class TestLanguageServerBasics: class TestProjectBasics: + @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) + def test_retrieve_content_around_line(self, project: Project): + """Test retrieving content around specific lines""" + # Test retrieving content around the greet function definition (line 2) + result = project.retrieve_content_around_line(CORE_PATH, 2, 2) + + assert result is not None, "Should retrieve content around line 2" + content_str = result.to_display_string() + assert "greet" in content_str, "Should contain the greet function definition" + assert "defn" in content_str, "Should contain defn keyword" + + # Test retrieving content around multiply function (around line 13) + result = project.retrieve_content_around_line(CORE_PATH, 13, 1) + + assert result is not None, "Should retrieve content around line 13" + content_str = result.to_display_string() + assert "multiply" in content_str, "Should contain multiply function" + @pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True) def test_search_files_for_pattern(self, project: Project) -> None: result = project.search_source_files_for_pattern("defn.*greet") diff --git a/test/solidlsp/python/test_python_basic.py b/test/solidlsp/python/test_python_basic.py index d19c380..dbfd8ce 100644 --- a/test/solidlsp/python/test_python_basic.py +++ b/test/solidlsp/python/test_python_basic.py @@ -79,20 +79,22 @@ class TestLanguageServerBasics: references = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) assert len(references) > 1, "Should get valid references for create_user (using selectionRange if present)" - @pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True) - def test_retrieve_content_around_line(self, language_server: SolidLanguageServer) -> None: + +class TestProjectBasics: + @pytest.mark.parametrize("project", [Language.PYTHON], indirect=True) + def test_retrieve_content_around_line(self, project: Project) -> None: """Test retrieve_content_around_line functionality with various scenarios.""" file_path = os.path.join("test_repo", "models.py") # Scenario 1: Just a single line (User class definition) - line_31 = language_server.retrieve_content_around_line(file_path, 31) + line_31 = project.retrieve_content_around_line(file_path, 31) assert len(line_31.lines) == 1 assert "class User(BaseModel):" in line_31.lines[0].line_content assert line_31.lines[0].line_number == 31 assert line_31.lines[0].match_type == LineType.MATCH # Scenario 2: Context above and below - with_context_around_user = language_server.retrieve_content_around_line(file_path, 31, 2, 2) + with_context_around_user = project.retrieve_content_around_line(file_path, 31, 2, 2) assert len(with_context_around_user.lines) == 5 # Check line content assert "class User(BaseModel):" in with_context_around_user.matched_lines[0].line_content @@ -112,7 +114,7 @@ class TestLanguageServerBasics: assert with_context_around_user.lines[4].match_type == LineType.AFTER_MATCH # Scenario 3a: Only context above - with_context_above = language_server.retrieve_content_around_line(file_path, 31, 3, 0) + with_context_above = project.retrieve_content_around_line(file_path, 31, 3, 0) assert len(with_context_above.lines) == 4 assert "return cls(id=id, name=name)" in with_context_above.lines[0].line_content assert "class User(BaseModel):" in with_context_above.matched_lines[0].line_content @@ -129,7 +131,7 @@ class TestLanguageServerBasics: assert with_context_above.lines[3].match_type == LineType.MATCH # Scenario 3b: Only context below - with_context_below = language_server.retrieve_content_around_line(file_path, 31, 0, 3) + with_context_below = project.retrieve_content_around_line(file_path, 31, 0, 3) assert len(with_context_below.lines) == 4 assert "class User(BaseModel):" in with_context_below.matched_lines[0].line_content assert with_context_below.num_matched_lines == 1 @@ -144,7 +146,7 @@ class TestLanguageServerBasics: assert with_context_below.lines[3].match_type == LineType.AFTER_MATCH # Scenario 4a: Edge case - context above but line is at 0 - first_line_with_context_around = language_server.retrieve_content_around_line(file_path, 0, 2, 1) + first_line_with_context_around = project.retrieve_content_around_line(file_path, 0, 2, 1) assert len(first_line_with_context_around.lines) <= 4 # Should have at most 4 lines (line 0 + 1 below + up to 2 above) assert first_line_with_context_around.lines[0].line_number <= 2 # First line should be at most line 2 # Check match type for the target line @@ -157,7 +159,7 @@ class TestLanguageServerBasics: assert line.match_type == LineType.AFTER_MATCH # Scenario 4b: Edge case - context above but line is at 1 - second_line_with_context_above = language_server.retrieve_content_around_line(file_path, 1, 3, 1) + second_line_with_context_above = project.retrieve_content_around_line(file_path, 1, 3, 1) assert len(second_line_with_context_above.lines) <= 5 # Should have at most 5 lines (line 1 + 1 below + up to 3 above) assert second_line_with_context_above.lines[0].line_number <= 1 # First line should be at most line 1 # Check match type for the target line @@ -171,10 +173,10 @@ class TestLanguageServerBasics: # Scenario 4c: Edge case - context below but line is at the end of file # First get the total number of lines in the file - all_content = language_server.retrieve_full_file_content(file_path) + all_content = project.read_file(file_path) total_lines = len(all_content.split("\n")) - last_line_with_context_around = language_server.retrieve_content_around_line(file_path, total_lines - 1, 1, 3) + last_line_with_context_around = project.retrieve_content_around_line(file_path, total_lines - 1, 1, 3) assert len(last_line_with_context_around.lines) <= 5 # Should have at most 5 lines (last line + 1 above + up to 3 below) assert last_line_with_context_around.lines[-1].line_number >= total_lines - 4 # Last line should be at least total_lines - 4 # Check match type for the target line @@ -186,8 +188,6 @@ class TestLanguageServerBasics: else: assert line.match_type == LineType.AFTER_MATCH - -class TestProjectBasics: @pytest.mark.parametrize("project", [Language.PYTHON], indirect=True) def test_search_files_for_pattern(self, project: Project) -> None: """Test search_files_for_pattern with various patterns and glob filters.""" From 51d45cfa549ff843f9e4fce3005080630c57d257 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 9 Jul 2025 23:45:01 +0200 Subject: [PATCH 24/41] Move create_ls_for_project to Project.create_language_server --- src/serena/agent.py | 61 +++---------------------------------------- src/serena/project.py | 53 +++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index b6f66ee..0ecd671 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -32,10 +32,8 @@ from serena.project import Project from serena.prompt_factory import SerenaPromptFactory from serena.symbol import SymbolManager from serena.tools import Tool, ToolRegistry -from serena.util.file_system import GitignoreParser, match_path +from serena.util.file_system import match_path from solidlsp import SolidLanguageServer -from solidlsp.ls_config import LanguageServerConfig -from solidlsp.ls_logger import LanguageServerLogger if TYPE_CHECKING: from serena.gui_log_viewer import GuiLogViewerHandler @@ -44,7 +42,6 @@ log = logging.getLogger(__name__) TTool = TypeVar("TTool", bound="Tool") T = TypeVar("T") SUCCESS_RESULT = "OK" -DEFAULT_TOOL_TIMEOUT: float = 240 class ProjectNotFoundError(Exception): @@ -118,57 +115,6 @@ class MemoriesManagerMDFilesInProject(MemoriesManager): return f"Memory {name} deleted." -def create_ls_for_project( - project: str | Project, - log_level: int = logging.INFO, - ls_timeout: float | None = DEFAULT_TOOL_TIMEOUT - 5, - trace_lsp_communication: bool = False, -) -> SolidLanguageServer: - """ - Create a language server for a project. Note that you will have to start it - before performing any LS operations. - - :param project: either a path to the project root or a ProjectConfig instance. - If no project.yml is found, the default project configuration will be used. - :param log_level: the log level for the language server - :param ls_timeout: the timeout for the language server - :param trace_lsp_communication: whether to trace LSP communication - :return: the language server - """ - if isinstance(project, str): - project_instance = Project.load(project, autogenerate=True) - else: - project_instance = project - - # TODO: This is duplicated in Project.__init__ - project_config = project_instance.project_config - ignored_paths = project_config.ignored_paths - if len(ignored_paths) > 0: - log.info(f"Using {len(ignored_paths)} ignored paths from the explicit project configuration.") - log.debug(f"Ignored paths: {ignored_paths}") - if project_config.ignore_all_files_in_gitignore: - log.info(f"Parsing all gitignore files in {project_instance.project_root}") - gitignore_parser = GitignoreParser(project_instance.project_root) - log.info(f"Found {len(gitignore_parser.get_ignore_specs())} gitignore files.") - for spec in gitignore_parser.get_ignore_specs(): - log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") - ignored_paths.extend(spec.patterns) - log.debug(f"Using {len(ignored_paths)} ignored paths in total.") - multilspy_config = LanguageServerConfig( - code_language=project_instance.language, - ignored_paths=ignored_paths, - trace_lsp_communication=trace_lsp_communication, - ) - ls_logger = LanguageServerLogger(log_level=log_level) - log.info(f"Creating language server instance for {project_instance.project_root}.") - return SolidLanguageServer.create( - multilspy_config, - ls_logger, - project_instance.project_root, - timeout=ls_timeout, - ) - - @click.command() @click.argument("project", type=click.Path(exists=True), required=False, default=os.getcwd()) @click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="WARNING") @@ -181,7 +127,7 @@ def index_project(project: str, log_level: str = "INFO") -> None: log_level_int = logging.getLevelNamesMapping()[log_level.upper()] project_instance = Project.load(os.path.abspath(project)) print(f"Indexing symbols in project {project}") - ls = create_ls_for_project(project, log_level=log_level_int) + ls = project_instance.create_language_server(log_level=log_level_int) save_after_n_files = 10 with ls.start_server(): parsed_files = project_instance.gather_source_files() @@ -628,8 +574,7 @@ class SerenaAgent: # instantiate and start the language server assert self._active_project is not None - self.language_server = create_ls_for_project( - self._active_project, + self.language_server = self._active_project.create_language_server( log_level=self.serena_config.log_level, ls_timeout=ls_timeout, trace_lsp_communication=self.serena_config.trace_lsp_communication, diff --git a/src/serena/project.py b/src/serena/project.py index fd4813a..4e99e0c 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -5,10 +5,12 @@ from typing import Self import pathspec -from serena.config.serena_config import ProjectConfig +from serena.config.serena_config import DEFAULT_TOOL_TIMEOUT, ProjectConfig from serena.text_utils import MatchedConsecutiveLines, search_files from serena.util.file_system import GitignoreParser, match_path -from solidlsp.ls_config import Language +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language, LanguageServerConfig +from solidlsp.ls_logger import LanguageServerLogger log = logging.getLogger(__name__) @@ -199,3 +201,50 @@ class Project: context_lines_after=context_lines_after, source_file_path=relative_file_path, ) + + def create_language_server( + self, + log_level: int = logging.INFO, + ls_timeout: float | None = DEFAULT_TOOL_TIMEOUT - 5, + trace_lsp_communication: bool = False, + ) -> SolidLanguageServer: + """ + Create a language server for a project. Note that you will have to start it + before performing any LS operations. + + :param project: either a path to the project root or a ProjectConfig instance. + If no project.yml is found, the default project configuration will be used. + :param log_level: the log level for the language server + :param ls_timeout: the timeout for the language server + :param trace_lsp_communication: whether to trace LSP communication + :return: the language server + """ + # TODO: This is duplicated in Project.__init__ + project_config = self.project_config + ignored_paths = project_config.ignored_paths + if len(ignored_paths) > 0: + log.info(f"Using {len(ignored_paths)} ignored paths from the explicit project configuration.") + log.debug(f"Ignored paths: {ignored_paths}") + if project_config.ignore_all_files_in_gitignore: + log.info(f"Parsing all gitignore files in {self.project_root}") + gitignore_parser = GitignoreParser(self.project_root) + log.info(f"Found {len(gitignore_parser.get_ignore_specs())} gitignore files.") + for spec in gitignore_parser.get_ignore_specs(): + log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") + ignored_paths.extend(spec.patterns) + log.debug(f"Using {len(ignored_paths)} ignored paths in total.") + + ls_config = LanguageServerConfig( + code_language=self.language, + ignored_paths=ignored_paths, + trace_lsp_communication=trace_lsp_communication, + ) + ls_logger = LanguageServerLogger(log_level=log_level) + + log.info(f"Creating language server instance for {self.project_root}.") + return SolidLanguageServer.create( + ls_config, + ls_logger, + self.project_root, + timeout=ls_timeout, + ) From aae5dc59b503079d4874f42b0cd8edac968b43f8 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 00:02:05 +0200 Subject: [PATCH 25/41] Refactoring * Project.is_path_ignored -> Project.is_ignored_relative_path * SerenaAgent.path_is_gitignored -> Project.is_ignored_path * SerenaAgent.path_is_inside_project -> Project.is_path_in_project * SerenaAgent.validate_relative_path -> Project.validate_relative_path --- src/serena/agent.py | 43 ------------------------------ src/serena/project.py | 48 +++++++++++++++++++++++++++++++--- src/serena/tools/file_tools.py | 22 ++++++++-------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 0ecd671..f05da6b 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -32,7 +32,6 @@ from serena.project import Project from serena.prompt_factory import SerenaPromptFactory from serena.symbol import SymbolManager from serena.tools import Tool, ToolRegistry -from serena.util.file_system import match_path from solidlsp import SolidLanguageServer if TYPE_CHECKING: @@ -283,48 +282,6 @@ class SerenaAgent: raise ValueError("Cannot get project root if no project is active.") return project.project_root - def path_is_inside_project(self, path: str | Path) -> bool: - """ - Checks if the given (absolute or relative) path is inside the project directory. - Note that even relative paths may be outside if the contain ".." or point to symlinks. - """ - path = Path(path) - _proj_root = Path(self.get_project_root()) - if not path.is_absolute(): - path = _proj_root / path - - path = path.resolve() - return path.is_relative_to(_proj_root) - - # TODO: Move to project - def path_is_gitignored(self, path: str | Path) -> bool: - """ - Checks if the given path is ignored by git. Non absolute paths are assumed to be relative to the project root. - """ - path = Path(path) - if path.is_absolute(): - relative_path = path.relative_to(self.get_project_root()) - else: - relative_path = path - - # always ignore paths inside .git - if len(relative_path.parts) > 0 and relative_path.parts[0] == ".git": - return True - - project = self.get_active_project_or_raise() - return match_path(str(relative_path), project.get_ignore_spec(), root_path=self.get_project_root()) - - def validate_relative_path(self, relative_path: str) -> None: - """ - Validates that the given relative path is safe to read or edit, - meaning it's inside the project directory and is not ignored by git. - """ - if not self.path_is_inside_project(relative_path): - raise ValueError(f"{relative_path=} points to path outside of the repository root, can't use it for safety reasons") - - if self.path_is_gitignored(relative_path): - raise ValueError(f"File {relative_path} is gitignored, can't read or edit it for safety reasons") - def get_exposed_tool_instances(self) -> list["Tool"]: """ :return: the tool instances which are exposed (e.g. to the MCP client). diff --git a/src/serena/project.py b/src/serena/project.py index 4e99e0c..a2360c3 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -89,7 +89,7 @@ class Project: def is_ignored_dirname(self, dirname: str) -> bool: return dirname.startswith(".") - def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: + def is_ignored_relative_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: """ Determine whether a path should be ignored based on file type and ignore patterns. @@ -124,6 +124,48 @@ class Project: return match_path(relative_path, self.get_ignore_spec(), root_path=self.project_root) + def is_ignored_path(self, path: str | Path) -> bool: + """ + Checks whether the given path is ignored + + :param path: the path to check, can be absolute or relative + """ + path = Path(path) + if path.is_absolute(): + relative_path = path.relative_to(self.project_root) + else: + relative_path = path + + # always ignore paths inside .git + if len(relative_path.parts) > 0 and relative_path.parts[0] == ".git": + return True + + return match_path(str(relative_path), self.get_ignore_spec(), root_path=self.project_root) + + def is_path_in_project(self, path: str | Path) -> bool: + """ + Checks if the given (absolute or relative) path is inside the project directory. + Note that even relative paths may be outside if they contain ".." or point to symlinks. + """ + path = Path(path) + _proj_root = Path(self.project_root) + if not path.is_absolute(): + path = _proj_root / path + + path = path.resolve() + return path.is_relative_to(_proj_root) + + def validate_relative_path(self, relative_path: str) -> None: + """ + Validates that the given relative path is safe to read or edit, + meaning it's inside the project directory and is not ignored by git. + """ + if not self.is_path_in_project(relative_path): + raise ValueError(f"{relative_path=} points to path outside of the repository root; cannot access for safety reasons") + + if self.is_ignored_path(relative_path): + raise ValueError(f"Path {relative_path} is ignored; cannot access for safety reasons") + def gather_source_files(self, relative_path: str = "") -> list[str]: """Retrieves relative paths of all source files, optionally limited to the given path @@ -137,11 +179,11 @@ class Project: return [relative_path] else: for root, dirs, files in os.walk(start_path, followlinks=True): - dirs[:] = [d for d in dirs if not self.is_ignored_path(os.path.join(root, d))] + dirs[:] = [d for d in dirs if not self.is_ignored_relative_path(os.path.join(root, d))] for file in files: rel_file_path = os.path.relpath(os.path.join(root, file), start=self.project_root) try: - if not self.is_ignored_path(rel_file_path): + if not self.is_ignored_relative_path(rel_file_path): rel_file_paths.append(rel_file_path) except FileNotFoundError: log.warning( diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index 5cff6d9..f79ffe0 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -39,7 +39,7 @@ class ReadFileTool(Tool): required for the task. :return: the full text of the file at the given relative path """ - self.agent.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path) result = self.project.read_file(relative_path) result_lines = result.splitlines() @@ -73,7 +73,7 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit): :param content: the (utf-8-encoded) content to write to the file :return: a message indicating success or failure """ - self.agent.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path) abs_path = (Path(self.get_project_root()) / relative_path).resolve() will_overwrite_existing = abs_path.exists() @@ -102,14 +102,14 @@ class ListDirTool(Tool): required for the task. :return: a JSON object with the names of directories and files within the given directory """ - self.agent.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path) dirs, files = scan_directory( os.path.join(self.get_project_root(), relative_path), relative_to=self.get_project_root(), recursive=recursive, - is_ignored_dir=self.agent.path_is_gitignored, - is_ignored_file=self.agent.path_is_gitignored, + is_ignored_dir=self.project.is_ignored_path, + is_ignored_file=self.project.is_ignored_path, ) result = json.dumps({"dirs": dirs, "files": files}) @@ -129,13 +129,13 @@ class FindFileTool(Tool): :param relative_path: the relative path to the directory to search in; pass "." to scan the project root :return: a JSON object with the list of matching files """ - self.agent.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path) dir_to_scan = os.path.join(self.get_project_root(), relative_path) # find the files by ignoring everything that doesn't match def is_ignored_file(abs_path: str) -> bool: - if self.agent.path_is_gitignored(abs_path): + if self.project.is_ignored_path(abs_path): return True filename = os.path.basename(abs_path) return not fnmatch(filename, file_mask) @@ -143,7 +143,7 @@ class FindFileTool(Tool): dirs, files = scan_directory( path=dir_to_scan, recursive=True, - is_ignored_dir=self.agent.path_is_gitignored, + is_ignored_dir=self.project.is_ignored_path, is_ignored_file=is_ignored_file, relative_to=self.get_project_root(), ) @@ -185,7 +185,7 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit): If this is set to False and the regex matches multiple occurrences, an error will be returned (and you may retry with a revised, more specific regex). """ - self.agent.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path) with EditedFileContext(relative_path, self.agent) as context: original_content = context.get_original_content() updated_content, n = re.subn(regex, repl, original_content, flags=re.DOTALL | re.MULTILINE) @@ -373,8 +373,8 @@ class SearchForPatternTool(Tool): dirs, rel_paths_to_search = scan_directory( path=abs_path, recursive=True, - is_ignored_dir=self.agent.path_is_gitignored, - is_ignored_file=self.agent.path_is_gitignored, + is_ignored_dir=self.project.is_ignored_path, + is_ignored_file=self.project.is_ignored_path, relative_to=self.get_project_root(), ) # TODO (maybe): not super efficient to walk through the files again and filter if glob patterns are provided From 84e066e64714f3bb6d787c024b9ba96641b6add9 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 00:07:05 +0200 Subject: [PATCH 26/41] Remove unnecessary abstraction: MemoriesManagerMDFilesInProject -> MemoriesManager --- src/serena/agent.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index f05da6b..9ecac87 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -8,7 +8,6 @@ import platform import sys import threading import webbrowser -from abc import ABC, abstractmethod from collections import defaultdict from collections.abc import Callable from concurrent.futures import Future, ThreadPoolExecutor @@ -63,25 +62,7 @@ class LinesRead: del self.files[relative_path] -class MemoriesManager(ABC): - @abstractmethod - def load_memory(self, name: str) -> str: - pass - - @abstractmethod - def save_memory(self, name: str, content: str) -> str: - pass - - @abstractmethod - def list_memories(self) -> list[str]: - pass - - @abstractmethod - def delete_memory(self, name: str) -> str: - pass - - -class MemoriesManagerMDFilesInProject(MemoriesManager): +class MemoriesManager: def __init__(self, project_root: str): self._memory_dir = Path(get_serena_managed_dir(project_root)) / "memories" self._memory_dir.mkdir(parents=True, exist_ok=True) @@ -388,7 +369,7 @@ class SerenaAgent: self._update_active_tools() # initialize project-specific instances which do not depend on the language server - self.memories_manager = MemoriesManagerMDFilesInProject(project.project_root) + self.memories_manager = MemoriesManager(project.project_root) self.lines_read = LinesRead() # reset project-specific instances that depend on the language server From ee88204cbab02b3abc2f7c7b4c9528f617073161 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 00:12:13 +0200 Subject: [PATCH 27/41] Remove obsolete base class ToolInterface --- src/serena/mcp.py | 8 ++++---- src/serena/tools/tools_base.py | 34 +++++----------------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/src/serena/mcp.py b/src/serena/mcp.py index 3f91819..28168b3 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -25,7 +25,7 @@ from serena.agent import ( ) from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES -from serena.tools import ToolInterface +from serena.tools import Tool from serena.util.exception import show_fatal_exception_safe log = logging.getLogger(__name__) @@ -62,7 +62,7 @@ class SerenaMCPFactory: self.project = project @staticmethod - def make_mcp_tool(tool: ToolInterface) -> MCPTool: + def make_mcp_tool(tool: Tool) -> MCPTool: func_name = tool.get_name() func_doc = tool.get_apply_docstring() or "" func_arg_metadata = tool.get_apply_fn_metadata() @@ -106,7 +106,7 @@ class SerenaMCPFactory: ) @abstractmethod - def _iter_tools(self) -> Iterator[ToolInterface]: + def _iter_tools(self) -> Iterator[Tool]: pass # noinspection PyProtectedMember @@ -204,7 +204,7 @@ class SerenaMCPFactorySingleProcess(SerenaMCPFactory): def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None: self.agent = SerenaAgent(project=self.project, serena_config=serena_config, context=self.context, modes=modes) - def _iter_tools(self) -> Iterator[ToolInterface]: + def _iter_tools(self) -> Iterator[Tool]: assert self.agent is not None yield from self.agent.get_exposed_tool_instances() diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 074119c..a68ffc3 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -1,7 +1,7 @@ import inspect import os import traceback -from abc import ABC, abstractmethod +from abc import ABC from collections.abc import Callable, Iterable from dataclasses import dataclass from types import TracebackType @@ -87,31 +87,7 @@ class ToolMarkerOptional: """ -class ToolInterface(ABC): - """Protocol defining the complete interface that make_tool() expects from a tool.""" - - @abstractmethod - def get_name(self) -> str: - """Get the tool name.""" - ... - - @abstractmethod - def get_apply_docstring(self) -> str: - """Get the docstring for the tool application, used by the MCP server.""" - ... - - @abstractmethod - def get_apply_fn_metadata(self) -> FuncMetadata: - """Get the metadata for the tool application function, used by the MCP server.""" - ... - - @abstractmethod - def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, **kwargs: Any) -> str: - """Apply the tool with logging and exception handling.""" - ... - - -class Tool(Component, ToolInterface): +class Tool(Component): # NOTE: each tool should implement the apply method, which is then used in # the central method of the Tool class `apply_ex`. # Failure to do so will result in a RuntimeError at tool execution time. @@ -176,11 +152,11 @@ class Tool(Component, ToolInterface): return docstring.strip() def get_apply_docstring(self) -> str: - """Get the docstring for the apply method (instance method implementing ToolProtocol).""" + """Gets the docstring for the tool application, used by the MCP server.""" return self.get_apply_docstring_from_cls() def get_apply_fn_metadata(self) -> FuncMetadata: - """Get the metadata for the apply method (instance method implementing ToolProtocol).""" + """Gets the metadata for the tool application function, used by the MCP server.""" return self.get_apply_fn_metadata_from_cls() @classmethod @@ -225,7 +201,7 @@ class Tool(Component, ToolInterface): def apply_ex(self, log_call: bool = True, catch_exceptions: bool = True, **kwargs) -> str: # type: ignore """ - Applies the tool with the given arguments + Applies the tool with logging and exception handling, using the given keyword arguments """ def task() -> str: From 403e8905cfe555fe75215ba4f680a439ce3b9e1d Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 00:27:51 +0200 Subject: [PATCH 28/41] Remove unused method _get_code_file_content [skip ci] --- src/serena/symbol.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 70b650a..d68cc65 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -709,10 +709,6 @@ class SymbolManager: return [ReferenceInSymbol.from_lsp_reference(r) for r in references] - def _get_code_file_content(self, relative_path: str) -> str: - """Get the content of a file using the language server.""" - return self._lang_server.language_server.retrieve_full_file_content(relative_path) - @dataclass class SymbolOverviewElement: name_path: str From 17c0b2a94951fda7dc3cb4928f4e873829af17c2 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 00:23:56 +0200 Subject: [PATCH 29/41] Fix method reference --- test/solidlsp/elixir/test_elixir_integration.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/solidlsp/elixir/test_elixir_integration.py b/test/solidlsp/elixir/test_elixir_integration.py index d7a36b1..8478332 100644 --- a/test/solidlsp/elixir/test_elixir_integration.py +++ b/test/solidlsp/elixir/test_elixir_integration.py @@ -10,6 +10,7 @@ from pathlib import Path import pytest +from serena.project import Project from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language @@ -111,11 +112,11 @@ class TestElixirIntegration: class TestElixirProject: @pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) - def test_comprehensive_symbol_search(self, project): + def test_comprehensive_symbol_search(self, project: Project): """Test comprehensive symbol search across the entire project.""" # Search for all function definitions function_pattern = r"def\s+\w+\s*[\(\s]" - function_matches = project.search_files_for_pattern(function_pattern) + function_matches = project.search_source_files_for_pattern(function_pattern) # Should find functions across multiple files if function_matches: @@ -131,7 +132,7 @@ class TestElixirProject: # Search for struct definitions struct_pattern = r"defstruct\s+\[" - struct_matches = project.search_files_for_pattern(struct_pattern) + struct_matches = project.search_source_files_for_pattern(struct_pattern) if struct_matches: # Should find structs primarily in models.ex @@ -139,11 +140,11 @@ class TestElixirProject: assert len(models_structs) > 0, "Should find struct definitions in models.ex" @pytest.mark.parametrize("project", [Language.ELIXIR], indirect=True) - def test_protocol_and_implementation_understanding(self, project): + def test_protocol_and_implementation_understanding(self, project: Project): """Test that the language server understands Elixir protocols and implementations.""" # Search for protocol definitions protocol_pattern = r"defprotocol\s+\w+" - protocol_matches = project.search_files_for_pattern(protocol_pattern, paths_include_glob="**/models.ex") + protocol_matches = project.search_source_files_for_pattern(protocol_pattern, paths_include_glob="**/models.ex") if protocol_matches: # Should find the Serializable protocol @@ -152,7 +153,7 @@ class TestElixirProject: # Search for protocol implementations impl_pattern = r"defimpl\s+\w+" - impl_matches = project.search_files_for_pattern(impl_pattern, paths_include_glob="**/models.ex") + impl_matches = project.search_source_files_for_pattern(impl_pattern, paths_include_glob="**/models.ex") if impl_matches: # Should find multiple implementations From 65b20c4276446d9cb4e7ba971ad9f9a1c063a6c8 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 01:17:30 +0200 Subject: [PATCH 30/41] Refactoring * Remove SymbolManager instance from agent; instead, create it ad hoc * Rename SymbolManager -> LanguageServerSymbolRetriever * Rename Symbol -> LanguageServerSymbol * Rename AbstractSymbol -> Symbol * Rename SymbolLocation -> LanguageServerSymbolLocation * Rename ReferenceInSymbol -> ReferenceInLanguageServerSymbol --- src/serena/agent.py | 14 ------ src/serena/code_editor.py | 10 ++-- src/serena/symbol.py | 76 ++++++++++++++++++------------ src/serena/tools/symbol_tools.py | 9 ++-- src/serena/tools/tools_base.py | 12 ++--- test/serena/test_symbol.py | 6 +-- test/serena/test_symbol_editing.py | 6 +-- 7 files changed, 69 insertions(+), 64 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index 9ecac87..b799c4e 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -29,7 +29,6 @@ from serena.constants import ( from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI from serena.project import Project from serena.prompt_factory import SerenaPromptFactory -from serena.symbol import SymbolManager from serena.tools import Tool, ToolRegistry from solidlsp import SolidLanguageServer @@ -220,7 +219,6 @@ class SerenaAgent: self._active_project: Project | None = None self._active_project_root: str | None = None self.language_server: SolidLanguageServer | None = None - self.symbol_manager: SymbolManager | None = None self.memories_manager: MemoriesManager | None = None self.lines_read: LinesRead | None = None @@ -372,19 +370,12 @@ class SerenaAgent: self.memories_manager = MemoriesManager(project.project_root) self.lines_read = LinesRead() - # reset project-specific instances that depend on the language server - self.symbol_manager = None - def init_language_server() -> None: # start the language server with LogTime("Language server initialization", logger=log): self.reset_language_server() assert self.language_server is not None - # initialize project-specific instances which depend on the language server - log.debug(f"Initializing symbol and memories manager for {project.project_name} at {project.project_root}") - self.symbol_manager = SymbolManager(self.language_server, self) - # initialize the language server in the background self.issue_task(init_language_server) @@ -523,11 +514,6 @@ class SerenaAgent: raise RuntimeError( f"Failed to start the language server for {self._active_project.project_name} at {self._active_project.project_root}" ) - if self.symbol_manager is not None: - log.debug("Setting the language server in the agent's symbol manager") - self.symbol_manager.set_language_server(self.language_server) - else: - log.debug("No symbol manager available yet, skipping setting the language server") def get_tool(self, tool_class: type[TTool]) -> TTool: return self._all_tools[tool_class] # type: ignore diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index fccf047..34096dc 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -6,7 +6,7 @@ from collections.abc import Iterable, Iterator, Reversible from contextlib import contextmanager from typing import TYPE_CHECKING, Generic, Optional, TypeVar -from serena.symbol import AbstractSymbol, JetBrainsSymbol, PositionInFile, Symbol, SymbolManager +from serena.symbol import JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, PositionInFile, Symbol from solidlsp import SolidLanguageServer from solidlsp.ls import LSPFileBuffer from solidlsp.ls_utils import TextUtils @@ -19,7 +19,7 @@ if TYPE_CHECKING: log = logging.getLogger(__name__) -TSymbol = TypeVar("TSymbol", bound=AbstractSymbol) +TSymbol = TypeVar("TSymbol", bound=Symbol) class CodeEditor(Generic[TSymbol], ABC): @@ -211,8 +211,8 @@ class CodeEditor(Generic[TSymbol], ABC): edited_file.delete_text_between_positions(start_pos, end_pos) -class LanguageServerCodeEditor(CodeEditor[Symbol]): - def __init__(self, symbol_manager: SymbolManager, agent: Optional["SerenaAgent"] = None): +class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): + def __init__(self, symbol_manager: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None): super().__init__(project_root=symbol_manager.get_language_server().repository_root_path, agent=agent) self._symbol_manager = symbol_manager @@ -244,7 +244,7 @@ class LanguageServerCodeEditor(CodeEditor[Symbol]): """Get the content of a file using the language server.""" return self._lang_server.language_server.retrieve_full_file_content(relative_path) - def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> Symbol: + def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> LanguageServerSymbol: symbol_candidates = self._symbol_manager.find_by_name(name_path, within_relative_path=relative_file_path) if len(symbol_candidates) == 0: raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") diff --git a/src/serena/symbol.py b/src/serena/symbol.py index d68cc65..4bea8f1 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -167,7 +167,7 @@ class CodeDiff: @dataclass -class SymbolLocation: +class LanguageServerSymbolLocation: """ Represents the (start) location of a symbol identifier, which, within Serena, uniquely identifies the symbol. """ @@ -203,6 +203,10 @@ class SymbolLocation: @dataclass class PositionInFile: + """ + Represents a character position within a file + """ + line: int """ the 0-based line number in the file @@ -219,7 +223,7 @@ class PositionInFile: return Position(line=self.line, character=self.col) -class AbstractSymbol(ABC): +class Symbol(ABC): @abstractmethod def get_body_start_position(self) -> PositionInFile | None: pass @@ -254,7 +258,7 @@ class AbstractSymbol(ABC): """ -class Symbol(AbstractSymbol, ToStringMixin): +class LanguageServerSymbol(Symbol, ToStringMixin): _NAME_PATH_SEP = "/" @staticmethod @@ -269,7 +273,7 @@ class Symbol(AbstractSymbol, ToStringMixin): """ assert name_path, "name_path must not be empty" assert symbol_name_path_parts, "symbol_name_path_parts must not be empty" - name_path_sep = Symbol._NAME_PATH_SEP + name_path_sep = LanguageServerSymbol._NAME_PATH_SEP is_absolute_pattern = name_path.startswith(name_path_sep) pattern_parts = name_path.lstrip(name_path_sep).rstrip(name_path_sep).split(name_path_sep) @@ -325,11 +329,11 @@ class Symbol(AbstractSymbol, ToStringMixin): return None @property - def location(self) -> SymbolLocation: + def location(self) -> LanguageServerSymbolLocation: """ :return: the start location of the actual symbol identifier """ - return SymbolLocation(relative_path=self.relative_path, line=self.line, column=self.column) + return LanguageServerSymbolLocation(relative_path=self.relative_path, line=self.line, column=self.column) @property def body_start_position(self) -> Position | None: @@ -471,18 +475,18 @@ class Symbol(AbstractSymbol, ToStringMixin): """ result = [] - def should_include(s: "Symbol") -> bool: + def should_include(s: "LanguageServerSymbol") -> bool: if include_kinds is not None and s.symbol_kind not in include_kinds: return False if exclude_kinds is not None and s.symbol_kind in exclude_kinds: return False - return Symbol.match_name_path( + return LanguageServerSymbol.match_name_path( name_path=name_path, symbol_name_path_parts=s.get_name_path_parts(), substring_matching=substring_matching, ) - def traverse(s: "Symbol") -> None: + def traverse(s: "LanguageServerSymbol") -> None: if should_include(s): result.append(s) for c in s.iter_children(): @@ -553,24 +557,36 @@ class Symbol(AbstractSymbol, ToStringMixin): @dataclass -class ReferenceInSymbol(ToStringMixin): - """Same as the class of the same name in the language server, but using Serena's Symbol class. - Be careful to not confuse it with counterpart! +class ReferenceInLanguageServerSymbol(ToStringMixin): + """ + Represents the location of a reference to another symbol within a symbol/file. + + The contained symbol is the symbol within which the reference is located, + not the symbol that is referenced. """ - symbol: Symbol + symbol: LanguageServerSymbol + """ + the symbol within which the reference is located + """ line: int + """ + the line number in which the reference is located (0-based) + """ character: int + """ + the column number in which the reference is located (0-based) + """ + + @classmethod + def from_lsp_reference(cls, reference: LSPReferenceInSymbol) -> Self: + return cls(symbol=LanguageServerSymbol(reference.symbol), line=reference.line, character=reference.character) def get_relative_path(self) -> str | None: return self.symbol.location.relative_path - @classmethod - def from_lsp_reference(cls, reference: LSPReferenceInSymbol) -> Self: - return cls(symbol=Symbol(reference.symbol), line=reference.line, character=reference.character) - -class SymbolManager: +class LanguageServerSymbolRetriever: def __init__(self, lang_server: SolidLanguageServer, agent: Union["SerenaAgent", None] = None) -> None: """ :param lang_server: the language server to use for symbol retrieval as well as editing operations. @@ -598,33 +614,33 @@ class SymbolManager: exclude_kinds: Sequence[SymbolKind] | None = None, substring_matching: bool = False, within_relative_path: str | None = None, - ) -> list[Symbol]: + ) -> list[LanguageServerSymbol]: """ Find all symbols that match the given name. See docstring of `Symbol.find` for more details. The only parameter not mentioned there is `within_relative_path`, which can be used to restrict the search to symbols within a specific file or directory. """ - symbols: list[Symbol] = [] + symbols: list[LanguageServerSymbol] = [] symbol_roots = self._lang_server.request_full_symbol_tree(within_relative_path=within_relative_path, include_body=include_body) for root in symbol_roots: symbols.extend( - Symbol(root).find( + LanguageServerSymbol(root).find( name_path, include_kinds=include_kinds, exclude_kinds=exclude_kinds, substring_matching=substring_matching ) ) return symbols - def get_document_symbols(self, relative_path: str) -> list[Symbol]: + def get_document_symbols(self, relative_path: str) -> list[LanguageServerSymbol]: symbol_dicts, roots = self._lang_server.request_document_symbols(relative_path, include_body=False) - symbols = [Symbol(s) for s in symbol_dicts] + symbols = [LanguageServerSymbol(s) for s in symbol_dicts] return symbols - def find_by_location(self, location: SymbolLocation) -> Symbol | None: + def find_by_location(self, location: LanguageServerSymbolLocation) -> LanguageServerSymbol | None: if location.relative_path is None: return None symbol_dicts, roots = self._lang_server.request_document_symbols(location.relative_path, include_body=False) for symbol_dict in symbol_dicts: - symbol = Symbol(symbol_dict) + symbol = LanguageServerSymbol(symbol_dict) if symbol.location == location: return symbol return None @@ -636,7 +652,7 @@ class SymbolManager: include_body: bool = False, include_kinds: Sequence[SymbolKind] | None = None, exclude_kinds: Sequence[SymbolKind] | None = None, - ) -> list[ReferenceInSymbol]: + ) -> list[ReferenceInLanguageServerSymbol]: """ Find all symbols that reference the symbol with the given name. If multiple symbols fit the name (e.g. for variables that are overwritten), will use the first one. @@ -666,11 +682,11 @@ class SymbolManager: def find_referencing_symbols_by_location( self, - symbol_location: SymbolLocation, + symbol_location: LanguageServerSymbolLocation, include_body: bool = False, include_kinds: Sequence[SymbolKind] | None = None, exclude_kinds: Sequence[SymbolKind] | None = None, - ) -> list[ReferenceInSymbol]: + ) -> list[ReferenceInLanguageServerSymbol]: """ Find all symbols that reference the symbol at the given location. @@ -707,7 +723,7 @@ class SymbolManager: if exclude_kinds is not None: references = [s for s in references if s.symbol["kind"] not in exclude_kinds] - return [ReferenceInSymbol.from_lsp_reference(r) for r in references] + return [ReferenceInLanguageServerSymbol.from_lsp_reference(r) for r in references] @dataclass class SymbolOverviewElement: @@ -724,7 +740,7 @@ class SymbolManager: return result -class JetBrainsSymbol(AbstractSymbol): +class JetBrainsSymbol(Symbol): def __init__(self, symbol_dict: dict, project: Project) -> None: """ :param symbol_dict: dictionary as returned by the JetBrains plugin client. diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 792984d..1c0fcac 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -61,7 +61,8 @@ class GetSymbolsOverviewTool(Tool): (e.g. a subdirectory). :return: a JSON object mapping relative paths of all contained files to info about top-level symbols in the file (name_path, kind). """ - result = self.symbol_manager.get_symbol_overview(relative_path) + symbol_retriever = self.create_language_server_symbol_retriever() + result = symbol_retriever.get_symbol_overview(relative_path) result_json_str = json.dumps({k: [dataclasses.asdict(i) for i in l] for k, l in result.items()}) return self._limit_length(result_json_str, max_answer_chars) @@ -132,7 +133,8 @@ class FindSymbolTool(Tool): """ parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None - symbols = self.symbol_manager.find_by_name( + symbol_retriever = self.create_language_server_symbol_retriever() + symbols = symbol_retriever.find_by_name( name_path, include_body=include_body, include_kinds=parsed_include_kinds, @@ -175,7 +177,8 @@ class FindReferencingSymbolsTool(Tool): include_body = False # It is probably never a good idea to include the body of the referencing symbols parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None - references_in_symbols = self.symbol_manager.find_referencing_symbols( + symbol_retriever = self.create_language_server_symbol_retriever() + references_in_symbols = symbol_retriever.find_referencing_symbols( name_path, relative_file_path=relative_path, include_body=include_body, diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index a68ffc3..c1851f5 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -13,7 +13,7 @@ from sensai.util.string import dict_string from serena.project import Project from serena.prompt_factory import PromptFactory -from serena.symbol import SymbolManager +from serena.symbol import LanguageServerSymbolRetriever from serena.util.class_decorators import singleton from serena.util.inspection import iter_subclasses @@ -45,10 +45,10 @@ class Component(ABC): assert self.agent.memories_manager is not None return self.agent.memories_manager - @property - def symbol_manager(self) -> SymbolManager: - assert self.agent.symbol_manager is not None - return self.agent.symbol_manager + def create_language_server_symbol_retriever(self) -> LanguageServerSymbolRetriever: + language_server = self.agent.language_server + assert language_server is not None + return LanguageServerSymbolRetriever(language_server, agent=self.agent) @property def project(self) -> Project: @@ -60,7 +60,7 @@ class Component(ABC): if self.agent.serena_config.jetbrains: return JetBrainsCodeEditor(project=self.project, agent=self.agent) else: - return LanguageServerCodeEditor(self.symbol_manager, agent=self.agent) + return LanguageServerCodeEditor(self.create_language_server_symbol_retriever(), agent=self.agent) @property def lines_read(self) -> "LinesRead": diff --git a/test/serena/test_symbol.py b/test/serena/test_symbol.py index 72fd8d4..179693f 100644 --- a/test/serena/test_symbol.py +++ b/test/serena/test_symbol.py @@ -1,6 +1,6 @@ import pytest -from src.serena.symbol import Symbol +from src.serena.symbol import LanguageServerSymbol class TestSymbolNameMatching: @@ -77,7 +77,7 @@ class TestSymbolNameMatching: ) def test_match_simple_name(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected): """Tests matching for simple names (no '/' in pattern).""" - result = Symbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match) + result = LanguageServerSymbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match) error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result) assert result == expected, error_msg @@ -157,6 +157,6 @@ class TestSymbolNameMatching: ) def test_match_name_path_pattern_path_len_2(self, name_path_pattern, symbol_name_path_parts, is_substring_match, expected): """Tests matching for qualified names (e.g. 'module/class/func').""" - result = Symbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match) + result = LanguageServerSymbol.match_name_path(name_path_pattern, symbol_name_path_parts, is_substring_match) error_msg = self._create_assertion_error_message(name_path_pattern, symbol_name_path_parts, is_substring_match, expected, result) assert result == expected, error_msg diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index 3b10524..ed9cf75 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -14,7 +14,7 @@ import pytest from serena.code_editor import CodeEditor, LanguageServerCodeEditor from serena.symbol import CodeDiff from solidlsp.ls_config import Language -from src.serena.symbol import SymbolManager +from src.serena.symbol import LanguageServerSymbolRetriever from test.conftest import create_ls, get_repo_path pytestmark = pytest.mark.snapshot @@ -34,7 +34,7 @@ class EditingTest: self.repo_path: Path | None = None @contextmanager - def _setup(self) -> Iterator[SymbolManager]: + def _setup(self) -> Iterator[LanguageServerSymbolRetriever]: """Context manager for setup/teardown with a temporary directory, providing the symbol manager.""" temp_dir = Path(tempfile.mkdtemp()) self.repo_path = temp_dir / self.original_repo_path.name @@ -51,7 +51,7 @@ class EditingTest: log.info(f"Starting language server for {self.language} {self.rel_path}") language_server.start() log.info(f"Language server started for {self.language} {self.rel_path}") - yield SymbolManager(lang_server=language_server) + yield LanguageServerSymbolRetriever(lang_server=language_server) finally: if language_server is not None and language_server.is_running(): log.info(f"Stopping language server for {self.language} {self.rel_path}") From 8c36377dc2d2c1345e7f9e24a2e4e0ef477b54ec Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 01:20:12 +0200 Subject: [PATCH 31/41] Move classes CodeDiff and LineChange to test_symbol_editing --- src/serena/symbol.py | 150 +---------------------------- test/serena/test_symbol_editing.py | 150 ++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 4bea8f1..9ece873 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -3,9 +3,8 @@ import logging import os from abc import ABC, abstractmethod from collections.abc import Iterator, Sequence -from dataclasses import asdict, dataclass, field -from difflib import SequenceMatcher -from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Self, Union +from dataclasses import asdict, dataclass +from typing import TYPE_CHECKING, Any, Self, Union from sensai.util.string import ToStringMixin @@ -21,151 +20,6 @@ if TYPE_CHECKING: log = logging.getLogger(__name__) -class LineChange(NamedTuple): - """Represents a change to a specific line or range of lines.""" - - operation: Literal["insert", "delete", "replace"] - original_start: int - original_end: int - modified_start: int - modified_end: int - original_lines: list[str] - modified_lines: list[str] - - -@dataclass -class CodeDiff: - """ - Represents the difference between original and modified code. - Provides object-oriented access to diff information including line numbers. - """ - - relative_path: str - original_content: str - modified_content: str - _line_changes: list[LineChange] = field(init=False) - - def __post_init__(self) -> None: - """Compute the diff using difflib's SequenceMatcher.""" - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - matcher = SequenceMatcher(None, original_lines, modified_lines) - self._line_changes = [] - - for tag, orig_start, orig_end, mod_start, mod_end in matcher.get_opcodes(): - if tag == "equal": - continue - if tag == "insert": - self._line_changes.append( - LineChange( - operation="insert", - original_start=orig_start, - original_end=orig_start, - modified_start=mod_start, - modified_end=mod_end, - original_lines=[], - modified_lines=modified_lines[mod_start:mod_end], - ) - ) - elif tag == "delete": - self._line_changes.append( - LineChange( - operation="delete", - original_start=orig_start, - original_end=orig_end, - modified_start=mod_start, - modified_end=mod_start, - original_lines=original_lines[orig_start:orig_end], - modified_lines=[], - ) - ) - elif tag == "replace": - self._line_changes.append( - LineChange( - operation="replace", - original_start=orig_start, - original_end=orig_end, - modified_start=mod_start, - modified_end=mod_end, - original_lines=original_lines[orig_start:orig_end], - modified_lines=modified_lines[mod_start:mod_end], - ) - ) - - @property - def line_changes(self) -> list[LineChange]: - """Get all line changes in the diff.""" - return self._line_changes - - @property - def has_changes(self) -> bool: - """Check if there are any changes.""" - return len(self._line_changes) > 0 - - @property - def added_lines(self) -> list[tuple[int, str]]: - """Get all added lines with their line numbers (0-based) in the modified file.""" - result = [] - for change in self._line_changes: - if change.operation in ("insert", "replace"): - for i, line in enumerate(change.modified_lines): - result.append((change.modified_start + i, line)) - return result - - @property - def deleted_lines(self) -> list[tuple[int, str]]: - """Get all deleted lines with their line numbers (0-based) in the original file.""" - result = [] - for change in self._line_changes: - if change.operation in ("delete", "replace"): - for i, line in enumerate(change.original_lines): - result.append((change.original_start + i, line)) - return result - - @property - def modified_line_numbers(self) -> list[int]: - """Get all line numbers (0-based) that were modified in the modified file.""" - line_nums: set[int] = set() - for change in self._line_changes: - if change.operation in ("insert", "replace"): - line_nums.update(range(change.modified_start, change.modified_end)) - return sorted(line_nums) - - @property - def affected_original_line_numbers(self) -> list[int]: - """Get all line numbers (0-based) that were affected in the original file.""" - line_nums: set[int] = set() - for change in self._line_changes: - if change.operation in ("delete", "replace"): - line_nums.update(range(change.original_start, change.original_end)) - return sorted(line_nums) - - def get_unified_diff(self, context_lines: int = 3) -> str: - """Get the unified diff as a string.""" - import difflib - - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - diff = difflib.unified_diff( - original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines - ) - return "".join(diff) - - def get_context_diff(self, context_lines: int = 3) -> str: - """Get the context diff as a string.""" - import difflib - - original_lines = self.original_content.splitlines(keepends=True) - modified_lines = self.modified_content.splitlines(keepends=True) - - diff = difflib.context_diff( - original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines - ) - return "".join(diff) - - @dataclass class LanguageServerSymbolLocation: """ diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index ed9cf75..5fa4401 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -6,13 +6,14 @@ import time from abc import abstractmethod from collections.abc import Iterator from contextlib import contextmanager +from dataclasses import dataclass, field +from difflib import SequenceMatcher from pathlib import Path -from typing import Literal +from typing import Literal, NamedTuple import pytest from serena.code_editor import CodeEditor, LanguageServerCodeEditor -from serena.symbol import CodeDiff from solidlsp.ls_config import Language from src.serena.symbol import LanguageServerSymbolRetriever from test.conftest import create_ls, get_repo_path @@ -22,6 +23,151 @@ pytestmark = pytest.mark.snapshot log = logging.getLogger(__name__) +class LineChange(NamedTuple): + """Represents a change to a specific line or range of lines.""" + + operation: Literal["insert", "delete", "replace"] + original_start: int + original_end: int + modified_start: int + modified_end: int + original_lines: list[str] + modified_lines: list[str] + + +@dataclass +class CodeDiff: + """ + Represents the difference between original and modified code. + Provides object-oriented access to diff information including line numbers. + """ + + relative_path: str + original_content: str + modified_content: str + _line_changes: list[LineChange] = field(init=False) + + def __post_init__(self) -> None: + """Compute the diff using difflib's SequenceMatcher.""" + original_lines = self.original_content.splitlines(keepends=True) + modified_lines = self.modified_content.splitlines(keepends=True) + + matcher = SequenceMatcher(None, original_lines, modified_lines) + self._line_changes = [] + + for tag, orig_start, orig_end, mod_start, mod_end in matcher.get_opcodes(): + if tag == "equal": + continue + if tag == "insert": + self._line_changes.append( + LineChange( + operation="insert", + original_start=orig_start, + original_end=orig_start, + modified_start=mod_start, + modified_end=mod_end, + original_lines=[], + modified_lines=modified_lines[mod_start:mod_end], + ) + ) + elif tag == "delete": + self._line_changes.append( + LineChange( + operation="delete", + original_start=orig_start, + original_end=orig_end, + modified_start=mod_start, + modified_end=mod_start, + original_lines=original_lines[orig_start:orig_end], + modified_lines=[], + ) + ) + elif tag == "replace": + self._line_changes.append( + LineChange( + operation="replace", + original_start=orig_start, + original_end=orig_end, + modified_start=mod_start, + modified_end=mod_end, + original_lines=original_lines[orig_start:orig_end], + modified_lines=modified_lines[mod_start:mod_end], + ) + ) + + @property + def line_changes(self) -> list[LineChange]: + """Get all line changes in the diff.""" + return self._line_changes + + @property + def has_changes(self) -> bool: + """Check if there are any changes.""" + return len(self._line_changes) > 0 + + @property + def added_lines(self) -> list[tuple[int, str]]: + """Get all added lines with their line numbers (0-based) in the modified file.""" + result = [] + for change in self._line_changes: + if change.operation in ("insert", "replace"): + for i, line in enumerate(change.modified_lines): + result.append((change.modified_start + i, line)) + return result + + @property + def deleted_lines(self) -> list[tuple[int, str]]: + """Get all deleted lines with their line numbers (0-based) in the original file.""" + result = [] + for change in self._line_changes: + if change.operation in ("delete", "replace"): + for i, line in enumerate(change.original_lines): + result.append((change.original_start + i, line)) + return result + + @property + def modified_line_numbers(self) -> list[int]: + """Get all line numbers (0-based) that were modified in the modified file.""" + line_nums: set[int] = set() + for change in self._line_changes: + if change.operation in ("insert", "replace"): + line_nums.update(range(change.modified_start, change.modified_end)) + return sorted(line_nums) + + @property + def affected_original_line_numbers(self) -> list[int]: + """Get all line numbers (0-based) that were affected in the original file.""" + line_nums: set[int] = set() + for change in self._line_changes: + if change.operation in ("delete", "replace"): + line_nums.update(range(change.original_start, change.original_end)) + return sorted(line_nums) + + def get_unified_diff(self, context_lines: int = 3) -> str: + """Get the unified diff as a string.""" + import difflib + + original_lines = self.original_content.splitlines(keepends=True) + modified_lines = self.modified_content.splitlines(keepends=True) + + diff = difflib.unified_diff( + original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines + ) + return "".join(diff) + + def get_context_diff(self, context_lines: int = 3) -> str: + """Get the context diff as a string.""" + import difflib + + original_lines = self.original_content.splitlines(keepends=True) + modified_lines = self.modified_content.splitlines(keepends=True) + + diff = difflib.context_diff( + original_lines, modified_lines, fromfile=f"a/{self.relative_path}", tofile=f"b/{self.relative_path}", n=context_lines + ) + return "".join(diff) + + class EditingTest: def __init__(self, language: Language, rel_path: str): """ From f20733fbfa3dbc8ced90354a86a4bd3767b9522b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 01:34:23 +0200 Subject: [PATCH 32/41] Only start the language server if in LS mode (not JetBrains mode) --- src/serena/agent.py | 11 +++++++++-- .../resources/config/internal_modes/jetbrains.yml | 1 + src/serena/tools/tools_base.py | 10 ++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index b799c4e..5d98695 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -361,6 +361,12 @@ class SerenaAgent: future = self.issue_task(task) return future.result() + def is_using_language_server(self) -> bool: + """ + :return: whether this agent uses language server-based code analysis + """ + return not self.serena_config.jetbrains + def _activate_project(self, project: Project) -> None: log.info(f"Activating {project.project_name} at {project.project_root}") self._active_project = project @@ -376,8 +382,9 @@ class SerenaAgent: self.reset_language_server() assert self.language_server is not None - # initialize the language server in the background - self.issue_task(init_language_server) + # initialize the language server in the background (if in language server mode) + if self.is_using_language_server(): + self.issue_task(init_language_server) if self._project_activation_callback is not None: self._project_activation_callback() diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index a7de011..937115c 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -7,6 +7,7 @@ excluded_tools: - find_symbol - find_referencing_symbols - get_symbols_overview + - restart_language_server included_optional_tools: - jet_brains_find_symbol - jet_brains_find_symbol_references diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index c1851f5..913eb66 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -46,6 +46,8 @@ class Component(ABC): return self.agent.memories_manager def create_language_server_symbol_retriever(self) -> LanguageServerSymbolRetriever: + if not self.agent.is_using_language_server(): + raise Exception("Cannot create LanguageServerSymbolRetriever; agent is not in language server mode.") language_server = self.agent.language_server assert language_server is not None return LanguageServerSymbolRetriever(language_server, agent=self.agent) @@ -57,10 +59,10 @@ class Component(ABC): def create_code_editor(self) -> "CodeEditor": from ..code_editor import JetBrainsCodeEditor, LanguageServerCodeEditor - if self.agent.serena_config.jetbrains: - return JetBrainsCodeEditor(project=self.project, agent=self.agent) - else: + if self.agent.is_using_language_server(): return LanguageServerCodeEditor(self.create_language_server_symbol_retriever(), agent=self.agent) + else: + return JetBrainsCodeEditor(project=self.project, agent=self.agent) @property def lines_read(self) -> "LinesRead": @@ -223,7 +225,7 @@ class Tool(Component): "Error: No active project. Ask to user to select a project from this list: " + f"{self.agent.serena_config.project_names}" ) - if not self.agent.is_language_server_running(): + if self.agent.is_using_language_server() and not self.agent.is_language_server_running(): log.info("Language server is not running. Starting it ...") self.agent.reset_language_server() From cb984d1fb7c67faeec24304e076320221f8d1773 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 19:45:51 +0200 Subject: [PATCH 33/41] Fix error in logo (background between arm and body) --- resources/serena-logo.cdr | Bin 189653 -> 189189 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/serena-logo.cdr b/resources/serena-logo.cdr index 7bea577459ed77dd0ce5cc56d98af2aa439b4cc6..9dd6623d54045260d57eb5f48ae57ccc4ffa5e93 100644 GIT binary patch literal 189189 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≷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

)hWa}_43b^#KBkg=I|Bt?u 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 Date: Thu, 10 Jul 2025 19:46:22 +0200 Subject: [PATCH 34/41] Rename JetBrainsFindSymbolReferencesTool -> JetBrainsFindReferencingSymbolsTool Update jetbrains mode definition --- src/serena/resources/config/internal_modes/jetbrains.yml | 5 +++-- src/serena/tools/jetbrains_tools.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index 937115c..7af49ef 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -2,7 +2,8 @@ description: JetBrains tools replace language server-based tools prompt: | You have access to the very powerful JetBrains tools for symbolic operations: * `jet_brains_find_symbol` replaces `find_symbol` - * `jet_brains_find_symbol_references` replaces `find_referencing_symbols` + * `jet_brains_find_referencing_symbols` replaces `find_referencing_symbols` + * `jet_brains_get_symbols_overview` replaces `get_symbols_overview` excluded_tools: - find_symbol - find_referencing_symbols @@ -10,5 +11,5 @@ excluded_tools: - restart_language_server included_optional_tools: - jet_brains_find_symbol - - jet_brains_find_symbol_references + - jet_brains_find_referencing_symbols - jet_brains_get_symbols_overview diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 2a355d7..e7dc8a8 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -70,7 +70,7 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerOptional): return self._limit_length(result, max_answer_chars) -class JetBrainsFindSymbolReferencesTool(Tool, ToolMarkerOptional): +class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerOptional): """ Finds symbols that reference the given symbol """ From fd08e798900947cb39bfd780e16ad3f85849ca06 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 19:53:12 +0200 Subject: [PATCH 35/41] Remove duplicate gathering of ignored patterns --- src/serena/project.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index a2360c3..f193b65 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -21,22 +21,23 @@ class Project: self.project_config = project_config # gather ignored paths from the project configuration and gitignore files - ignored_paths = project_config.ignored_paths - if len(ignored_paths) > 0: - log.info(f"Using {len(ignored_paths)} ignored paths from the explicit project configuration.") - log.debug(f"Ignored paths: {ignored_paths}") + ignored_patterns = project_config.ignored_paths + if len(ignored_patterns) > 0: + log.info(f"Using {len(ignored_patterns)} ignored paths from the explicit project configuration.") + log.debug(f"Ignored paths: {ignored_patterns}") if project_config.ignore_all_files_in_gitignore: log.info(f"Parsing all gitignore files in {self.project_root}") gitignore_parser = GitignoreParser(self.project_root) log.info(f"Found {len(gitignore_parser.get_ignore_specs())} gitignore files.") for spec in gitignore_parser.get_ignore_specs(): log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") - ignored_paths.extend(spec.patterns) + ignored_patterns.extend(spec.patterns) + self._ignored_patterns = ignored_patterns # Set up the pathspec matcher for the ignored paths # for all absolute paths in ignored_paths, convert them to relative paths processed_patterns = [] - for pattern in set(ignored_paths): + for pattern in set(ignored_patterns): # Normalize separators (pathspec expects forward slashes) pattern = pattern.replace(os.path.sep, "/") processed_patterns.append(pattern) @@ -261,24 +262,9 @@ class Project: :param trace_lsp_communication: whether to trace LSP communication :return: the language server """ - # TODO: This is duplicated in Project.__init__ - project_config = self.project_config - ignored_paths = project_config.ignored_paths - if len(ignored_paths) > 0: - log.info(f"Using {len(ignored_paths)} ignored paths from the explicit project configuration.") - log.debug(f"Ignored paths: {ignored_paths}") - if project_config.ignore_all_files_in_gitignore: - log.info(f"Parsing all gitignore files in {self.project_root}") - gitignore_parser = GitignoreParser(self.project_root) - log.info(f"Found {len(gitignore_parser.get_ignore_specs())} gitignore files.") - for spec in gitignore_parser.get_ignore_specs(): - log.debug(f"Adding {len(spec.patterns)} patterns from {spec.file_path} to the ignored paths.") - ignored_paths.extend(spec.patterns) - log.debug(f"Using {len(ignored_paths)} ignored paths in total.") - ls_config = LanguageServerConfig( code_language=self.language, - ignored_paths=ignored_paths, + ignored_paths=self._ignored_patterns, trace_lsp_communication=trace_lsp_communication, ) ls_logger = LanguageServerLogger(log_level=log_level) From 2302df3ad0fdf96ba395b0ecac66a980e780cba7 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:00:01 +0200 Subject: [PATCH 36/41] Fix docstring of get_ignore_spec --- src/serena/project.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index f193b65..490b3ac 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -76,14 +76,9 @@ class Project: return abs_path.read_text(encoding=self.project_config.encoding) def get_ignore_spec(self) -> pathspec.PathSpec: - """Returns the pathspec matcher for the paths that were configured to be ignored through - the multilspy config. - - This is is a subset of the full language-specific ignore spec that determines - which files are relevant for the language server. - - This matcher is useful for operations outside of the language server, - such as when searching for relevant non-language files in the project. + """ + :return: the pathspec matcher for the paths that were configured to be ignored, + either explicitly or implicitly through .gitignore files. """ return self._ignore_spec From 627c97fed5d453415ce37302a15651e7c8c0c246 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:03:30 +0200 Subject: [PATCH 37/41] Make is_ignored_dirname and is_ignored_relative_path private, improve parameter name --- src/serena/project.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/serena/project.py b/src/serena/project.py index 490b3ac..1a482f3 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -82,15 +82,16 @@ class Project: """ return self._ignore_spec - def is_ignored_dirname(self, dirname: str) -> bool: + def _is_ignored_dirname(self, dirname: str) -> bool: return dirname.startswith(".") - def is_ignored_relative_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool: + def _is_ignored_relative_path(self, relative_path: str, ignore_non_source_files: bool = True) -> bool: """ Determine whether a path should be ignored based on file type and ignore patterns. :param relative_path: Relative path to check - :param ignore_unsupported_files: whether files that are not source files shall be ignored + :param ignore_non_source_files: whether files that are not source files (according to the file masks + determined by the project's programming language) shall be ignored :return: whether the path should be ignored """ @@ -100,7 +101,7 @@ class Project: # Check file extension if it's a file is_file = os.path.isfile(abs_path) - if is_file and ignore_unsupported_files: + if is_file and ignore_non_source_files: fn_matcher = self.language.get_source_fn_matcher() if not fn_matcher.is_relevant_filename(abs_path): return True @@ -115,7 +116,7 @@ class Project: for part in dir_parts: if not part: # Skip empty parts (e.g., from leading '/') continue - if self.is_ignored_dirname(part): + if self._is_ignored_dirname(part): return True return match_path(relative_path, self.get_ignore_spec(), root_path=self.project_root) @@ -175,11 +176,11 @@ class Project: return [relative_path] else: for root, dirs, files in os.walk(start_path, followlinks=True): - dirs[:] = [d for d in dirs if not self.is_ignored_relative_path(os.path.join(root, d))] + dirs[:] = [d for d in dirs if not self._is_ignored_relative_path(os.path.join(root, d))] for file in files: rel_file_path = os.path.relpath(os.path.join(root, file), start=self.project_root) try: - if not self.is_ignored_relative_path(rel_file_path): + if not self._is_ignored_relative_path(rel_file_path): rel_file_paths.append(rel_file_path) except FileNotFoundError: log.warning( From a72adc53805badb49ecc1788af10cd50702ac44b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:04:14 +0200 Subject: [PATCH 38/41] Add port explanation --- src/serena/resources/serena_config.template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index fcffbc4..6c4cfca 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -23,7 +23,7 @@ web_dashboard: True web_dashboard_open_on_launch: True # whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard # is enabled). If set to False, you can still open the dashboard manually by navigating to -# http://localhost:24282/dashboard/ in your web browser. +# http://localhost:24282/dashboard/ in your web browser (24282 = 0x5EDA, SErena DAshboard). # If you have multiple instances running, a higher port will be used; try port 24283, 24284, etc. log_level: 20 From 687d6c16366a3a82329676c9961a1c98893b2759 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:12:12 +0200 Subject: [PATCH 39/41] Remove obsolete method [skip ci] --- src/serena/symbol.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/serena/symbol.py b/src/serena/symbol.py index 9ece873..8fc4b98 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -618,11 +618,6 @@ class JetBrainsSymbol(Symbol): def is_position_in_file_available(self) -> bool: return "text_range" in self._dict - @property - def _body_indices(self) -> tuple[int, int]: - text_range = self._dict["text_range"] - return text_range["start_offset"], text_range["end_offset"] - def get_body_start_position(self) -> PositionInFile | None: if not self.is_position_in_file_available(): return None From 6a8a1810ed6750c92838abf95381f2e2b1d78790 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:16:34 +0200 Subject: [PATCH 40/41] Rename variables: symbol_manager -> symbol_retriever --- src/serena/code_editor.py | 10 +++++----- test/serena/test_symbol_editing.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/serena/code_editor.py b/src/serena/code_editor.py index 34096dc..f2492b0 100644 --- a/src/serena/code_editor.py +++ b/src/serena/code_editor.py @@ -212,13 +212,13 @@ class CodeEditor(Generic[TSymbol], ABC): class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): - def __init__(self, symbol_manager: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None): - super().__init__(project_root=symbol_manager.get_language_server().repository_root_path, agent=agent) - self._symbol_manager = symbol_manager + def __init__(self, symbol_retriever: LanguageServerSymbolRetriever, agent: Optional["SerenaAgent"] = None): + super().__init__(project_root=symbol_retriever.get_language_server().repository_root_path, agent=agent) + self._symbol_retriever = symbol_retriever @property def _lang_server(self) -> SolidLanguageServer: - return self._symbol_manager.get_language_server() + return self._symbol_retriever.get_language_server() class EditedFile(CodeEditor.EditedFile): def __init__(self, lang_server: SolidLanguageServer, relative_path: str, file_buffer: LSPFileBuffer): @@ -245,7 +245,7 @@ class LanguageServerCodeEditor(CodeEditor[LanguageServerSymbol]): return self._lang_server.language_server.retrieve_full_file_content(relative_path) def _find_unique_symbol(self, name_path: str, relative_file_path: str) -> LanguageServerSymbol: - symbol_candidates = self._symbol_manager.find_by_name(name_path, within_relative_path=relative_file_path) + symbol_candidates = self._symbol_retriever.find_by_name(name_path, within_relative_path=relative_file_path) if len(symbol_candidates) == 0: raise ValueError(f"No symbol with name {name_path} found in file {relative_file_path}") if len(symbol_candidates) > 1: diff --git a/test/serena/test_symbol_editing.py b/test/serena/test_symbol_editing.py index 5fa4401..1f5cea0 100644 --- a/test/serena/test_symbol_editing.py +++ b/test/serena/test_symbol_editing.py @@ -221,9 +221,9 @@ class EditingTest: return f.read() def run_test(self, content_after_ground_truth: str) -> None: - with self._setup() as symbol_manager: + with self._setup() as symbol_retriever: content_before = self._read_file(self.rel_path) - code_editor = LanguageServerCodeEditor(symbol_manager) + code_editor = LanguageServerCodeEditor(symbol_retriever) self._apply_edit(code_editor) content_after = self._read_file(self.rel_path) code_diff = CodeDiff(self.rel_path, original_content=content_before, modified_content=content_after) From 915da8dc9f91d2335eb8834686230d122555a8db Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 10 Jul 2025 23:19:18 +0200 Subject: [PATCH 41/41] Fix demo script --- scripts/demo_run_tools.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/demo_run_tools.py b/scripts/demo_run_tools.py index 64929b3..394fe55 100644 --- a/scripts/demo_run_tools.py +++ b/scripts/demo_run_tools.py @@ -3,10 +3,13 @@ This script demonstrates how to use Serena's tools locally, useful for testing or development. Here the tools will be operation the serena repo itself. """ +import json from pprint import pprint -from serena.agent import * +from serena.agent import SerenaAgent +from serena.config.serena_config import SerenaConfig from serena.constants import REPO_ROOT +from serena.tools import FindFileTool, FindReferencingSymbolsTool, SearchForPatternTool if __name__ == "__main__": agent = SerenaAgent(project=REPO_ROOT, serena_config=SerenaConfig(gui_log_window_enabled=False, web_dashboard=False)) @@ -18,7 +21,7 @@ if __name__ == "__main__": result = agent.execute_task( lambda: search_pattern_tool.apply( - r"def request_parsed_files.*?\).*?\)", + r"def request_full_.*?\).*?\)", restrict_search_to_code_files=False, relative_path="src/solidlsp", paths_include_glob="**/ls.py",