diff --git a/src/serena/mcp.py b/src/serena/mcp.py index 8457b7a..a4204ad 100644 --- a/src/serena/mcp.py +++ b/src/serena/mcp.py @@ -289,7 +289,9 @@ def find_symbol( :param ctx: the context object, which will be created and provided automatically :param name: the name of the symbols to find :param depth: specifies the depth up to which descendants of the symbol are to be retrieved - (e.g. depth 1 will retrieve methods and attributes for the case where the symbol refers to a class) + (e.g. depth 1 will retrieve methods and attributes for the case where the symbol refers to a class). + Provide a non-zero depth if you intend to subsequently query symbols that are contained in the + retrieved symbol. :param dir_relative_path: pass a directory relative path to only consider symbols within this directory. If None, the entire codebase will be considered. :param include_body: whether to include the body of all symbols in the result. @@ -402,6 +404,64 @@ def replace_symbol_body( return ReplaceSymbolBodyTool(ctx).execute() +@mcp.tool() +def append_after_symbol( + ctx: Context, + relative_path: str, + line: int, + column: int, + body: str, +) -> str: + """ + Appends the given body/content after the end of the definition of the given symbol (via its location). + + :param ctx: the context object, which will be created and provided automatically + :param relative_path: the relative path to the file containing the symbol + :param line: the line number + :param column: the column + :param body: the body/content to be inserted + """ + + class AppendAfterSymbolTool(Tool): + def _execute(self) -> str: + SymbolManager(self.langsrv).append_after( + SymbolLocation(relative_path, line, column), + body=body, + ) + return "OK" + + return AppendAfterSymbolTool(ctx).execute() + + +@mcp.tool() +def insert_before_symbol( + ctx: Context, + relative_path: str, + line: int, + column: int, + body: str, +) -> str: + """ + Inserts the given body/content before the beginning of the definition of the given symbol (via its location). + + :param ctx: the context object, which will be created and provided automatically + :param relative_path: the relative path to the file containing the symbol + :param line: the line number + :param column: the column + :param body: the body/content to be inserted + """ + + class AppendAfterSymbolTool(Tool): + def _execute(self) -> str: + SymbolManager(self.langsrv).insert_before( + SymbolLocation(relative_path, line, column), + body=body, + ) + return "OK" + + return AppendAfterSymbolTool(ctx).execute() + + @mcp.tool() def onboarding(ctx: Context) -> str: """ diff --git a/src/serena/symbol.py b/src/serena/symbol.py index c6a21d4..72485d9 100644 --- a/src/serena/symbol.py +++ b/src/serena/symbol.py @@ -2,6 +2,7 @@ import logging import os from collections.abc import Iterator, Sequence from contextlib import contextmanager +from copy import copy from dataclasses import asdict, dataclass from typing import Any, Self @@ -261,6 +262,14 @@ class SymbolManager: with open(abs_path, "w") as f: f.write(file_buffer.contents) + @contextmanager + def _edited_symbol_location(self, location: SymbolLocation) -> Iterator[Symbol]: + symbol = self.find_by_location(location) + if symbol is None: + raise ValueError("Symbol not found") + with self._edited_file(location.relative_path): + yield symbol + def replace_body(self, location: SymbolLocation, body: str) -> None: """ Replace the body of the symbol at the given location with the given body @@ -268,11 +277,30 @@ class SymbolManager: :param location: the location of the symbol to replace :param body: the new body """ - symbol = self.find_by_location(location) - if symbol is None: - raise ValueError("Symbol not found") - with self._edited_file(location.relative_path): + with self._edited_symbol_location(location) as symbol: self.lang_server.delete_text_between_positions(location.relative_path, symbol.body_start_position, symbol.body_end_position) self.lang_server.insert_text_at_position( location.relative_path, symbol.body_start_position["line"], symbol.body_start_position["character"], body ) + + def append_after(self, location: SymbolLocation, body: str) -> 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 + """ + with self._edited_symbol_location(location) as symbol: + pos = symbol.body_end_position + self.lang_server.insert_text_at_position(location.relative_path, pos["line"], pos["character"], body) + + def insert_before(self, location: SymbolLocation, body: str) -> 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: + pos = copy(symbol.body_start_position) + self.lang_server.insert_text_at_position(location.relative_path, pos["line"], pos["character"], body)