Skip to main content
Glama

replace_content

Replace text patterns in files using literal or regex matching to modify content efficiently in Serena's coding environment.

Instructions

Replaces one or more occurrences of a given pattern in a file with new content.

This is the preferred way to replace content in a file whenever the symbol-level tools are not appropriate.

VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them! Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical! ALWAYS try to use wildcards to avoid specifying the exact content to be replaced, especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match multiple occurrences while you disabled allow_multiple_occurrences, an error will be returned, and you can retry with a revised regex. Therefore, using regex mode with suitable wildcards is usually the best choice!.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
relative_pathYesThe relative path to the file.
needleYesThe string or regex pattern to search for. If `mode` is "literal", this string will be matched exactly. If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module, with flags DOTALL and MULTILINE enabled).
replYesThe replacement string (verbatim). If mode is "regex", the string can contain backreferences to matched groups in the needle regex, specified using the syntax $!1, $!2, etc. for groups 1, 2, etc.
modeYesEither "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
allow_multiple_occurrencesNoIf True, the regex may match multiple occurrences in the file and all of them will be replaced. 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).

Implementation Reference

  • The ReplaceContentTool class provides the main handler (apply method) and core replacement logic (replace_content method) for the replace_content tool.
    class ReplaceContentTool(Tool, ToolMarkerCanEdit): """ Replaces content in a file (optionally using regular expressions). """ def apply( self, relative_path: str, needle: str, repl: str, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool = False, ) -> str: r""" Replaces one or more occurrences of a given pattern in a file with new content. This is the preferred way to replace content in a file whenever the symbol-level tools are not appropriate. VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them! Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical! ALWAYS try to use wildcards to avoid specifying the exact content to be replaced, especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match multiple occurrences while you disabled `allow_multiple_occurrences`, an error will be returned, and you can retry with a revised regex. Therefore, using regex mode with suitable wildcards is usually the best choice! :param relative_path: the relative path to the file :param needle: the string or regex pattern to search for. If `mode` is "literal", this string will be matched exactly. If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module, with flags DOTALL and MULTILINE enabled). :param repl: the replacement string (verbatim). If mode is "regex", the string can contain backreferences to matched groups in the needle regex, specified using the syntax $!1, $!2, etc. for groups 1, 2, etc. :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted. :param allow_multiple_occurrences: if True, the regex may match multiple occurrences in the file and all of them will be replaced. 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). """ return self.replace_content( relative_path, needle, repl, mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True ) @staticmethod def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]: """ Creates a replacement function that validates for ambiguity and handles backreferences. :param regex_pattern: The regex pattern being used for matching :param repl_template: The replacement template with $!1, $!2, etc. for backreferences :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE) :return: A function suitable for use with re.sub() or re.subn() """ def validate_and_replace(match: re.Match) -> str: matched_text = match.group(0) # For multi-line match, check if the same pattern matches again within the already-matched text, # rendering the match ambiguous. Typical pattern in the code: # <start><other-stuff><start><stuff><end> # When matching # <start>.*?<end> # this will match the entire span above, while only the suffix may have been intended. # (See test case for a practical example.) # To detect this, we check if the same pattern matches again within the matched text, if "\n" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags): raise ValueError( "Match is ambiguous: the search pattern matches multiple overlapping occurrences. " "Please revise the search pattern to be more specific to avoid ambiguity." ) # Handle backreferences: replace $!1, $!2, etc. with actual matched groups def expand_backreference(m: re.Match) -> str: group_num = int(m.group(1)) group_value = match.group(group_num) return group_value if group_value is not None else m.group(0) result = re.sub(r"\$!(\d+)", expand_backreference, repl_template) return result return validate_and_replace def replace_content( self, relative_path: str, needle: str, repl: str, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool = False, require_not_ignored: bool = True, ) -> str: """ Performs the replacement, with additional options not exposed in the tool. This function can be used internally by other tools. """ self.project.validate_relative_path(relative_path, require_not_ignored=require_not_ignored) with EditedFileContext(relative_path, self.create_code_editor()) as context: original_content = context.get_original_content() if mode == "literal": regex = re.escape(needle) elif mode == "regex": regex = needle else: raise ValueError(f"Invalid mode: '{mode}', expected 'literal' or 'regex'.") regex_flags = re.DOTALL | re.MULTILINE # create replacement function with validation and backreference handling repl_fn = self._create_replacement_function(regex, repl, regex_flags=regex_flags) # perform replacement updated_content, n = re.subn(regex, repl_fn, original_content, flags=regex_flags) if n == 0: raise ValueError(f"Error: No matches of search expression found in file '{relative_path}'.") if not allow_multiple_occurrences and n > 1: raise ValueError( f"Expression matches {n} occurrences in file '{relative_path}'. " "Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected." ) context.set_updated_content(updated_content) return SUCCESS_RESULT
  • The apply method signature and docstring define the input schema, parameters, and expected behavior for the tool.
    def apply( self, relative_path: str, needle: str, repl: str, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool = False, ) -> str: r""" Replaces one or more occurrences of a given pattern in a file with new content. This is the preferred way to replace content in a file whenever the symbol-level tools are not appropriate. VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them! Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical! ALWAYS try to use wildcards to avoid specifying the exact content to be replaced, especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match multiple occurrences while you disabled `allow_multiple_occurrences`, an error will be returned, and you can retry with a revised regex. Therefore, using regex mode with suitable wildcards is usually the best choice! :param relative_path: the relative path to the file :param needle: the string or regex pattern to search for. If `mode` is "literal", this string will be matched exactly. If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module, with flags DOTALL and MULTILINE enabled). :param repl: the replacement string (verbatim). If mode is "regex", the string can contain backreferences to matched groups in the needle regex, specified using the syntax $!1, $!2, etc. for groups 1, 2, etc. :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted. :param allow_multiple_occurrences: if True, the regex may match multiple occurrences in the file and all of them will be replaced. 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). """ return self.replace_content( relative_path, needle, repl, mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True )
  • Static helper method to create a replacement function for re.sub that handles backreferences ($!1 etc.) and validates for ambiguous multi-line matches.
    @staticmethod def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]: """ Creates a replacement function that validates for ambiguity and handles backreferences. :param regex_pattern: The regex pattern being used for matching :param repl_template: The replacement template with $!1, $!2, etc. for backreferences :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE) :return: A function suitable for use with re.sub() or re.subn() """ def validate_and_replace(match: re.Match) -> str: matched_text = match.group(0) # For multi-line match, check if the same pattern matches again within the already-matched text, # rendering the match ambiguous. Typical pattern in the code: # <start><other-stuff><start><stuff><end> # When matching # <start>.*?<end> # this will match the entire span above, while only the suffix may have been intended. # (See test case for a practical example.) # To detect this, we check if the same pattern matches again within the matched text, if "\n" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags): raise ValueError( "Match is ambiguous: the search pattern matches multiple overlapping occurrences. " "Please revise the search pattern to be more specific to avoid ambiguity." ) # Handle backreferences: replace $!1, $!2, etc. with actual matched groups def expand_backreference(m: re.Match) -> str: group_num = int(m.group(1)) group_value = match.group(group_num) return group_value if group_value is not None else m.group(0) result = re.sub(r"\$!(\d+)", expand_backreference, repl_template) return result return validate_and_replace
  • ToolRegistry singleton automatically discovers and registers all subclasses of Tool in the serena.tools package, including ReplaceContentTool.
    class ToolRegistry: def __init__(self) -> None: self._tool_dict: dict[str, RegisteredTool] = {} for cls in iter_subclasses(Tool): if not any(cls.__module__.startswith(pkg) for pkg in tool_packages): 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) def get_tool_class_by_name(self, tool_name: str) -> type[Tool]: return self._tool_dict[tool_name].tool_class def get_all_tool_classes(self) -> list[type[Tool]]: return list(t.tool_class for t in self._tool_dict.values()) def get_tool_classes_default_enabled(self) -> list[type[Tool]]: """ :return: the list of tool classes that are enabled by default (i.e. non-optional tools). """ return [t.tool_class for t in self._tool_dict.values() if not t.is_optional] def get_tool_classes_optional(self) -> list[type[Tool]]: """ :return: the list of tool classes that are optional (i.e. disabled by default). """ return [t.tool_class for t in self._tool_dict.values() if t.is_optional] 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] def get_tool_names_optional(self) -> list[str]: """ :return: the list of tool names that are optional (i.e. disabled by default). """ return [t.tool_name for t in self._tool_dict.values() if t.is_optional] def get_tool_names(self) -> list[str]: """ :return: the list of all tool names. """ return list(self._tool_dict.keys()) def print_tool_overview( self, tools: Iterable[type[Tool] | Tool] | None = None, include_optional: bool = False, only_optional: bool = False ) -> None: """ Print a summary of the tools. If no tools are passed, a summary of the selection of tools (all, default or only optional) is printed. """ if tools is None: if only_optional: tools = self.get_tool_classes_optional() elif include_optional: tools = self.get_all_tool_classes() else: tools = self.get_tool_classes_default_enabled() tool_dict: dict[str, type[Tool] | Tool] = {} for tool_class in tools: tool_dict[tool_class.get_name_from_cls()] = tool_class 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
  • Legacy tool name mapping redirects deprecated 'replace_regex' to the replace_content tool name.
    LEGACY_TOOL_NAME_MAPPING = {"replace_regex": ReplaceContentTool.get_name_from_cls()}

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oraios/serena'

If you have feedback or need assistance with the MCP directory API, please join our Discord server