mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 14:15:14 +00:00
Make dotall optional in affected tools
This commit is contained in:
1 parent
bc9d4e050c
commit
be5b60bddb
5 files changed
+60
-57
No files matched your search
@@ -10,6 +10,14 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Modal markup cleanup: extract shared CSS classes (`.modal-info`, `.modal-hint`, `.modal-prompt`, `.modal-field`, `.modal-input`, `.modal-select`, `.modal-textarea`, `.modal-actions`, `.btn-secondary`) and remove duplicated inline styles from all seven modals. Inputs and textareas get an accent-colored focus ring; the modal backdrop has a subtle blur.
|
||||
- Add Language: replace the native `<select>` with a filterable combobox — type to filter, keyboard navigation (Up/Down/Enter/Esc), substring highlight, click-outside to close. The typed value is validated against the available languages list before submission.
|
||||
|
||||
* Tools:
|
||||
- `edit_memory`: New `dotall` parameter (default `False`) controlling whether ``.`` matches
|
||||
newlines in regex mode. **Behavior change**: previously DOTALL was forced on; the new default
|
||||
restricts ``.`` to a single line, which is safer for typical line-bounded memory edits and
|
||||
avoids accidentally consuming large spans of content. Pass `dotall=True` for multi-line edits.
|
||||
- `search_for_pattern`: New `dotall` parameter (default `True`, preserving existing behavior)
|
||||
exposing the DOTALL flag, and the docstring was tightened.
|
||||
|
||||
# v1.3.0 (2026-05-11)
|
||||
|
||||
* General:
|
||||
|
||||
+14
-2
@@ -715,7 +715,14 @@ class MemoriesManager:
|
||||
return f"Memory renamed from {old_name} to {new_name}."
|
||||
|
||||
def edit_memory(
|
||||
self, name: str, needle: str, repl: str, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool, is_tool_context: bool
|
||||
self,
|
||||
name: str,
|
||||
needle: str,
|
||||
repl: str,
|
||||
mode: Literal["literal", "regex"],
|
||||
allow_multiple_occurrences: bool,
|
||||
is_tool_context: bool,
|
||||
dotall: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Edit a memory by replacing content matching a pattern.
|
||||
@@ -725,6 +732,8 @@ class MemoriesManager:
|
||||
:param repl: the replacement string
|
||||
:param mode: "literal" or "regex"
|
||||
:param allow_multiple_occurrences:
|
||||
:param is_tool_context: whether the call originates from a tool invocation (affects write-access checks)
|
||||
:param dotall: whether to compile the regex with the DOTALL flag (``.`` matches newlines). Only relevant in regex mode.
|
||||
"""
|
||||
name = self._prepare_name(name)
|
||||
self._check_not_ignored(name)
|
||||
@@ -734,7 +743,7 @@ class MemoriesManager:
|
||||
raise FileNotFoundError(f"Memory {name} not found.")
|
||||
with open(memory_file_path, encoding=self._encoding) as f:
|
||||
original_content = f.read()
|
||||
replacer = ContentReplacer(mode=mode, allow_multiple_occurrences=allow_multiple_occurrences)
|
||||
replacer = ContentReplacer(mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, dotall=dotall)
|
||||
updated_content = replacer.replace(original_content, needle, repl)
|
||||
with open(memory_file_path, "w", encoding=self._encoding) as f:
|
||||
f.write(updated_content)
|
||||
@@ -1291,6 +1300,7 @@ class Project(ToStringMixin):
|
||||
context_lines_after: int = 0,
|
||||
paths_include_glob: str | None = None,
|
||||
paths_exclude_glob: str | None = None,
|
||||
dotall: bool = True,
|
||||
) -> list[MatchedConsecutiveLines]:
|
||||
"""
|
||||
Search for a pattern across all (non-ignored) source files
|
||||
@@ -1301,6 +1311,7 @@ class Project(ToStringMixin):
|
||||
: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.
|
||||
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
|
||||
:return: List of matched consecutive lines with context
|
||||
"""
|
||||
relative_file_paths = self.gather_source_files(relative_path=relative_path)
|
||||
@@ -1313,6 +1324,7 @@ class Project(ToStringMixin):
|
||||
context_lines_after=context_lines_after,
|
||||
paths_include_glob=paths_include_glob,
|
||||
paths_exclude_glob=paths_exclude_glob,
|
||||
dotall=dotall,
|
||||
)
|
||||
|
||||
def retrieve_content_around_line(
|
||||
|
||||
@@ -332,59 +332,24 @@ class SearchForPatternTool(Tool):
|
||||
paths_exclude_glob: str = "",
|
||||
relative_path: str = "",
|
||||
restrict_search_to_code_files: bool = False,
|
||||
dotall: bool = True,
|
||||
max_answer_chars: int = -1,
|
||||
) -> str:
|
||||
"""
|
||||
Offers a flexible search for arbitrary patterns in the codebase, including the
|
||||
possibility to search in non-code files.
|
||||
Generally, symbolic operations like find_symbol or find_referencing_symbols
|
||||
should be preferred if you know which symbols you are looking for.
|
||||
Searches for a regex pattern across project files, returning whole matched lines (plus optional context).
|
||||
Prefer `find_symbol`/`find_referencing_symbols` when looking for known symbols.
|
||||
|
||||
Pattern Matching Logic:
|
||||
For each match, the returned result will contain the full lines where the
|
||||
substring pattern is found, as well as optionally some lines before and after it. The pattern will be compiled with
|
||||
DOTALL, meaning that the dot will match all characters including newlines.
|
||||
This also means that it never makes sense to have .* at the beginning or end of the pattern,
|
||||
but it may make sense to have it in the middle for complex patterns.
|
||||
If a pattern matches multiple lines, all those lines will be part of the match.
|
||||
Be careful to not use greedy quantifiers unnecessarily, it is usually better to use non-greedy quantifiers like .*? to avoid
|
||||
matching too much content.
|
||||
|
||||
File Selection Logic:
|
||||
The files in which the search is performed can be restricted very flexibly.
|
||||
Using `restrict_search_to_code_files` is useful if you are only interested in code symbols (i.e., those
|
||||
symbols that can be manipulated with symbolic tools like find_symbol).
|
||||
You can also restrict the search to a specific file or directory,
|
||||
and provide glob patterns to include or exclude certain files on top of that.
|
||||
The globs are matched against relative file paths from the project root (not to the `relative_path` parameter that
|
||||
is used to further restrict the search).
|
||||
Smartly combining the various restrictions allows you to perform very targeted searches.
|
||||
|
||||
:param substring_pattern: Regular expression for a substring pattern to search for
|
||||
: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: optional glob pattern specifying files to include in the search.
|
||||
Matches against relative file paths from the project root (e.g., "*.py", "src/**/*.ts").
|
||||
Supports standard glob patterns (*, ?, [seq], **, etc.) and brace expansion {a,b,c}.
|
||||
Only matches files, not directories. If left empty, all non-ignored files will be included.
|
||||
:param paths_exclude_glob: optional glob pattern specifying files to exclude from the search.
|
||||
Matches against relative file paths from the project root (e.g., "*test*", "**/*_generated.py").
|
||||
Supports standard glob patterns (*, ?, [seq], **, etc.) and brace expansion {a,b,c}.
|
||||
Takes precedence over paths_include_glob. Only matches files, not directories. If left empty, no files are excluded.
|
||||
:param relative_path: only subpaths of this path (relative to the repo root) will be analyzed. If a path to a single
|
||||
file is passed, only that will be searched. The path must exist, otherwise a `FileNotFoundError` is raised.
|
||||
:param max_answer_chars: if the output is longer than this number of characters,
|
||||
no content will be returned.
|
||||
-1 means the default value from the config will be used.
|
||||
Don't adjust unless there is really no other way to get the content
|
||||
required for the task. Instead, if the output is too long, you should
|
||||
make a stricter query.
|
||||
:param restrict_search_to_code_files: whether to restrict the search to only those files where
|
||||
analyzed code symbols can be found. Otherwise, will search all non-ignored files.
|
||||
Set this to True if your search is only meant to discover code that can be manipulated with symbolic tools.
|
||||
For example, for finding classes or methods from a name pattern.
|
||||
Setting to False is a better choice if you also want to search in non-code files, like in html or yaml files,
|
||||
which is why it is the default.
|
||||
:param substring_pattern: regular expression to search for.
|
||||
:param context_lines_before: number of context lines to include before each match.
|
||||
:param context_lines_after: number of context lines to include after each match.
|
||||
:param paths_include_glob: optional glob (relative to project root, e.g. ``"src/**/*.ts"``) restricting which files are searched.
|
||||
:param paths_exclude_glob: optional glob to exclude files; takes precedence over `paths_include_glob`.
|
||||
:param relative_path: restricts the search to this file or subdirectory of the project root. Must exist.
|
||||
:param restrict_search_to_code_files: whether to search only files containing analyzable code symbols
|
||||
(useful when looking for class/method definitions); otherwise also search non-code files.
|
||||
:param dotall: whether to compile the regex with the DOTALL flag (``.`` matches newlines, enabling multi-line matches).
|
||||
:param max_answer_chars: if the output exceeds this many characters, a progressively shortened summary is returned instead.
|
||||
``-1`` uses the configured default.
|
||||
:return: A mapping from file paths to matched consecutive lines (0-based line numbers).
|
||||
"""
|
||||
relative_path = relative_path.strip()
|
||||
@@ -403,6 +368,7 @@ class SearchForPatternTool(Tool):
|
||||
context_lines_after=context_lines_after,
|
||||
paths_include_glob=paths_include_glob.strip(),
|
||||
paths_exclude_glob=paths_exclude_glob.strip(),
|
||||
dotall=dotall,
|
||||
)
|
||||
else:
|
||||
if os.path.isfile(abs_path):
|
||||
@@ -426,6 +392,7 @@ class SearchForPatternTool(Tool):
|
||||
root_path=self.get_project_root(),
|
||||
paths_include_glob=paths_include_glob,
|
||||
paths_exclude_glob=paths_exclude_glob,
|
||||
dotall=dotall,
|
||||
)
|
||||
|
||||
# group matches by file
|
||||
|
||||
@@ -104,6 +104,7 @@ class EditMemoryTool(Tool, ToolMarkerCanEdit):
|
||||
repl: str,
|
||||
mode: Literal["literal", "regex"],
|
||||
allow_multiple_occurrences: bool = False,
|
||||
dotall: bool = False,
|
||||
) -> str:
|
||||
r"""
|
||||
Replace content matching a regular expression in a memory.
|
||||
@@ -112,10 +113,16 @@ class EditMemoryTool(Tool, ToolMarkerCanEdit):
|
||||
:param needle: the string or regex pattern to search for. In regex mode, be careful to not replace too much!
|
||||
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).
|
||||
with the MULTILINE flag enabled and DOTALL controlled by the `dotall` parameter).
|
||||
:param repl: the replacement string (verbatim).
|
||||
:param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
|
||||
:param allow_multiple_occurrences: whether to allow matching and replacing multiple occurrences.
|
||||
If false and multiple occurrences are found, an error will be returned.
|
||||
:param dotall: whether to compile the regex with the DOTALL flag, so that ``.`` matches newlines.
|
||||
Only relevant in regex mode. Defaults to False, restricting ``.`` to a single line, which is the safer
|
||||
choice for typical line-bounded memory edits and avoids accidentally consuming entire blocks of content.
|
||||
Set to True deliberately when you intend a multi-line replacement (alternatively, use ``[\s\S]*?``).
|
||||
"""
|
||||
return self.memories_manager.edit_memory(memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True)
|
||||
return self.memories_manager.edit_memory(
|
||||
memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True, dotall=dotall
|
||||
)
|
||||
@@ -145,6 +145,7 @@ def search_text(
|
||||
context_lines_before: int = 0,
|
||||
context_lines_after: int = 0,
|
||||
is_glob: bool = False,
|
||||
dotall: bool = True,
|
||||
) -> list[MatchedConsecutiveLines]:
|
||||
"""
|
||||
Search for a pattern in text content. Supports both regex and glob-like patterns.
|
||||
@@ -159,6 +160,8 @@ def search_text(
|
||||
:param context_lines_after: Number of context lines to include after matches
|
||||
:param is_glob: If True, pattern is treated as a glob-like pattern (e.g., "*.py", "test_??.py")
|
||||
and will be converted to regex internally
|
||||
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
|
||||
Only takes effect when `allow_multiline_match` is True (the line-by-line branch never spans newlines).
|
||||
|
||||
:return: List of `TextSearchMatch` objects
|
||||
|
||||
@@ -180,8 +183,8 @@ def search_text(
|
||||
if is_glob:
|
||||
pattern = glob_to_regex(pattern)
|
||||
if allow_multiline_match:
|
||||
# For multiline matches, we need to use the DOTALL flag to make '.' match newlines
|
||||
compiled_pattern = re.compile(pattern, re.DOTALL)
|
||||
# For multiline matches, optionally use DOTALL so '.' matches newlines
|
||||
compiled_pattern = re.compile(pattern, re.DOTALL if dotall else 0)
|
||||
# Search across the entire content as a single string
|
||||
for match in compiled_pattern.finditer(content):
|
||||
start_pos = match.start()
|
||||
@@ -327,6 +330,7 @@ def search_files(
|
||||
context_lines_after: int = 0,
|
||||
paths_include_glob: str | None = None,
|
||||
paths_exclude_glob: str | None = None,
|
||||
dotall: bool = True,
|
||||
) -> list[MatchedConsecutiveLines]:
|
||||
"""
|
||||
Search for a pattern in a list of files.
|
||||
@@ -340,6 +344,7 @@ def search_files(
|
||||
:param context_lines_after: Number of context lines to include after matches
|
||||
:param paths_include_glob: Optional glob pattern to include files from the list
|
||||
:param paths_exclude_glob: Optional glob pattern to exclude files from the list
|
||||
:param dotall: Whether to compile the regex with the DOTALL flag (``.`` matches newlines).
|
||||
:return: List of MatchedConsecutiveLines objects
|
||||
"""
|
||||
# Pre-filter paths (done sequentially to avoid overhead)
|
||||
@@ -375,6 +380,7 @@ def search_files(
|
||||
allow_multiline_match=True,
|
||||
context_lines_before=context_lines_before,
|
||||
context_lines_after=context_lines_after,
|
||||
dotall=dotall,
|
||||
)
|
||||
if len(search_results) > 0:
|
||||
log.debug(f"Found {len(search_results)} matches in {path}")
|
||||
@@ -430,16 +436,19 @@ class ContentReplacer:
|
||||
provides dual modes for maximum flexibility.
|
||||
"""
|
||||
|
||||
def __init__(self, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool):
|
||||
def __init__(self, mode: Literal["literal", "regex"], allow_multiple_occurrences: bool, dotall: bool = True):
|
||||
"""
|
||||
|
||||
:param mode: the mode indicating whether to the needle in replacements corresponds to a regular expression
|
||||
(mode "regex") or to a literal string (mode "literal")
|
||||
:param allow_multiple_occurrences: whether it is allowed that the search expression matches multiple occurrences.
|
||||
If False, an error will be raised if more than one match is found.
|
||||
:param dotall: whether to compile the regex with the DOTALL flag, which makes ``.`` match newlines.
|
||||
Only relevant in regex mode. The MULTILINE flag is always enabled (so ``^`` and ``$`` match line boundaries).
|
||||
"""
|
||||
self.mode = mode
|
||||
self.allow_multiple_occurrences = allow_multiple_occurrences
|
||||
self.dotall = dotall
|
||||
|
||||
@staticmethod
|
||||
def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]:
|
||||
@@ -505,7 +514,7 @@ class ContentReplacer:
|
||||
else:
|
||||
raise ValueError(f"Invalid mode: '{self.mode}', expected 'literal' or 'regex'.")
|
||||
|
||||
regex_flags = re.DOTALL | re.MULTILINE
|
||||
regex_flags = re.MULTILINE | (re.DOTALL if self.dotall else 0)
|
||||
|
||||
# create replacement function with validation and backreference handling
|
||||
repl_fn = self._create_replacement_function(regex, repl, regex_flags=regex_flags)
|
||||
|
||||
Reference in new issue
Block a user