Fix: SearchForPatternTool returned 1-based line numbers (in contrast to all other tools)

Cause: Implementation of text_utils.search_text

Serena's tools use 0-based line numbers, and this information
is now also centrally added to the instructions (manual).
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-04-23 15:01:03 +02:00
1 parent 885266ffb0
commit d79e2c2a0f
4 files changed
+13 -13

No files matched your search

+1
View File
@@ -25,6 +25,7 @@ Status of the `main` branch. Changes prior to the next official version change w
- Project server:
- `query_project`: Support use of project root instead of project name #1388
- `list_queryable_projects`: Return both project names and project roots
- Fix: `search_for_pattern` tool returned 1-based line numbers (in contrast to all other tools); cause: implementation of `text_utils.search_text`
* JetBrains:
- `Move` and `SafeDelete` tools: transform empty string to None (counteracts client errors)
@@ -41,7 +41,9 @@ prompts:
The following global (not project-specific) memories are available to you: {{ global_memories_list }}
{%- endif -%}
{%- endif %}
Line numbers returned by Serena's tools are 0-based!
The context and modes of operation are described below. These determine how to interact with your user
and which kinds of interactions are expected of you.
+2 -2
View File
@@ -347,7 +347,6 @@ class SearchForPatternTool(Tool):
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
@@ -373,7 +372,7 @@ class SearchForPatternTool(Tool):
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.
:return: A mapping of file paths to lists of matched consecutive lines.
:return: A mapping from file paths to matched consecutive lines (0-based line numbers).
"""
abs_path = os.path.join(self.get_project_root(), relative_path)
if not os.path.exists(abs_path):
@@ -411,6 +410,7 @@ class SearchForPatternTool(Tool):
paths_include_glob=paths_include_glob,
paths_exclude_glob=paths_exclude_glob,
)
# group matches by file
file_to_matches: dict[str, list[str]] = defaultdict(list)
for match in matches:
+7 -10
View File
@@ -188,17 +188,16 @@ def search_text(
end_pos = match.end()
# Find the line numbers for the start and end positions
start_line_num = content[:start_pos].count("\n") + 1
end_line_num = content[:end_pos].count("\n") + 1
start_line_num = content[:start_pos].count("\n")
end_line_num = content[:end_pos].count("\n")
# Calculate the range of lines to include in the context
context_start = max(1, start_line_num - context_lines_before)
context_end = min(total_lines, end_line_num + context_lines_after)
context_start = max(0, start_line_num - context_lines_before)
context_end = min(total_lines - 1, end_line_num + context_lines_after)
# Create TextLine objects for the context
context_lines = []
for i in range(context_start - 1, context_end):
line_num = i + 1
for line_num in range(context_start, context_end + 1):
if context_start <= line_num < start_line_num:
match_type = LineType.BEFORE_MATCH
elif end_line_num < line_num <= context_end:
@@ -206,7 +205,7 @@ def search_text(
else:
match_type = LineType.MATCH
context_lines.append(TextLine(line_number=line_num, line_content=lines[i], match_type=match_type))
context_lines.append(TextLine(line_number=line_num, line_content=lines[line_num], match_type=match_type))
matches.append(MatchedConsecutiveLines(lines=context_lines, source_file_path=source_file_path))
else:
@@ -215,7 +214,6 @@ def search_text(
# Search line by line, normal compile without DOTALL
compiled_pattern = re.compile(pattern)
for i, line in enumerate(lines):
line_num = i + 1
if compiled_pattern.search(line):
# Calculate the range of lines to include in the context
context_start = max(0, i - context_lines_before)
@@ -224,7 +222,6 @@ def search_text(
# Create TextLine objects for the context
context_lines = []
for j in range(context_start, context_end + 1):
context_line_num = j + 1
if j < i:
match_type = LineType.BEFORE_MATCH
elif j > i:
@@ -232,7 +229,7 @@ def search_text(
else:
match_type = LineType.MATCH
context_lines.append(TextLine(line_number=context_line_num, line_content=lines[j], match_type=match_type))
context_lines.append(TextLine(line_number=j, line_content=lines[j], match_type=match_type))
matches.append(MatchedConsecutiveLines(lines=context_lines, source_file_path=source_file_path))