Possibility to restrict paths to search for

The LLM also tries passing the relative_path param to the tool
This commit is contained in:
Michael Panchenko committed 2025-06-30 21:16:49 +02:00
1 parent 993df0c25c
commit 81e0a324a5
4 files changed
+63 -37

No files matched your search

+9 -3
View File
@@ -20,10 +20,16 @@ class InMemorySerenaConfig(SerenaConfigBase):
if __name__ == "__main__":
agent = SerenaAgent(project=REPO_ROOT)
agent = SerenaAgent(project=REPO_ROOT, serena_config=InMemorySerenaConfig())
# apply a tool
find_refs_tool = agent.get_tool(FindReferencingSymbolsTool)
print("Finding the symbol 'SyncLanguageServer'\n")
result = agent.execute_task(lambda: find_refs_tool.apply(name_path="SolidLanguageServer", relative_path="src/solidlsp/ls.py"))
find_file_tool = agent.get_tool(FindFileTool)
search_pattern_tool = agent.get_tool(SearchForPatternTool)
result = agent.execute_task(
lambda: search_pattern_tool.apply(
".*Pyright.*|.*Omnisharp.*", restrict_search_to_code_files=False, relative_path="src/solidlsp/ls.py"
)
)
pprint(json.loads(result))
+19 -14
View File
@@ -2178,6 +2178,7 @@ class SearchForPatternTool(Tool):
context_lines_after: int = 0,
paths_include_glob: str | None = None,
paths_exclude_glob: str | None = None,
relative_path: str = "",
restrict_search_to_code_files: bool = False,
max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH,
) -> str:
@@ -2191,6 +2192,8 @@ class SearchForPatternTool(Tool):
: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; if not provided, search globally.
:param paths_exclude_glob: optional glob pattern specifying files to exclude from the search (takes precedence over paths_include_glob).
: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 will be raised.
:param max_answer_chars: if the output is longer than this number of characters,
no content will be returned. 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
@@ -2203,34 +2206,36 @@ class SearchForPatternTool(Tool):
which is why it is the default.
:return: A JSON object mapping file paths to lists of matched consecutive lines (with context, if requested).
"""
# this was previously a kwarg and was true by default
# However, the LLM doesn't really know which files are taken into account by the language server
# and which onees
abs_path = os.path.join(self.get_project_root(), relative_path)
if not os.path.exists(abs_path):
raise FileNotFoundError(f"Relative path {relative_path} does not exist.")
if restrict_search_to_code_files:
matches = self.language_server.search_files_for_pattern(
pattern=pattern,
relative_path=relative_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,
)
else:
# we walk through all files in the project starting from the root
project_root = self.get_project_root()
rel_paths_to_search = []
for root, dirs, files in os.walk(project_root):
# don't explore ignored dirs
dirs[:] = [d for d in dirs if not self.agent.path_is_gitignored(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
if not self.agent.path_is_gitignored(file_path):
relative_path = os.path.relpath(file_path, project_root)
rel_paths_to_search.append(relative_path)
if os.path.isfile(abs_path):
rel_paths_to_search = [relative_path]
else:
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,
relative_to=self.get_project_root(),
)
# TODO (maybe): not super efficient to walk through the files again and filter if glob patterns are provided
# but it probably never matters and this version required no further refactoring
matches = search_files(
rel_paths_to_search,
pattern,
root_path=self.get_project_root(),
paths_include_glob=paths_include_glob,
paths_exclude_glob=paths_exclude_glob,
)
@@ -10,6 +10,7 @@ import pathlib
import stat
import subprocess
import threading
from overrides import override
from solidlsp.ls import SolidLanguageServer
+34 -20
View File
@@ -5,7 +5,6 @@ import logging
import os
import pathlib
import pickle
import re
import subprocess
import threading
from abc import ABC, abstractmethod
@@ -20,6 +19,7 @@ import pathspec
import tqdm
from serena.text_utils import MatchedConsecutiveLines, search_files
from serena.util.file_system import match_path
from solidlsp import ls_types
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_exceptions import LanguageServerException
@@ -697,11 +697,13 @@ class SolidLanguageServer(ABC):
for ref in references
]
def retrieve_full_file_content(self, relative_file_path: str) -> str:
def retrieve_full_file_content(self, file_path: str) -> str:
"""
Retrieve the full content of the given file.
"""
with self.open_file(relative_file_path) as file_data:
if os.path.isabs(file_path):
file_path = os.path.relpath(file_path, self.repository_root_path)
with self.open_file(file_path) as file_data:
return file_data.contents
def retrieve_content_around_line(
@@ -1188,8 +1190,11 @@ class SolidLanguageServer(ABC):
symbol_body = symbol_body[symbol_start_column:]
return symbol_body
def request_parsed_files(self) -> list[str]:
"""Retrieves relative paths of all files analyzed by the Language Server."""
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",
@@ -1197,23 +1202,30 @@ class SolidLanguageServer(ABC):
)
raise LanguageServerException("Language Server not started")
rel_file_paths = []
for root, dirs, files in os.walk(self.repository_root_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
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: re.Pattern | str,
pattern: str,
relative_path: str = "",
context_lines_before: int = 0,
context_lines_after: int = 0,
paths_include_glob: str | None = None,
@@ -1223,6 +1235,7 @@ class SolidLanguageServer(ABC):
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
@@ -1232,11 +1245,12 @@ class SolidLanguageServer(ABC):
if isinstance(pattern, str):
pattern = re.compile(pattern)
relative_file_paths = self.request_parsed_files()
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,