Add JetBrainsFindDeclarationTool

This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-03-31 01:12:06 +02:00
1 parent e74acfffec
commit 6ef9158ff5
4 files changed
+104

No files matched your search

+10
View File
@@ -57,6 +57,16 @@ class CodeEditor(Generic[TSymbol], ABC):
"""
raise NotImplementedError("This method must be overridden for each subclass")
def read_file(self, relative_path: str) -> str:
"""
Reads the content of a file.
:param relative_path: the relative path of the file to read
:return: the content of the file
"""
with self._open_file_context(relative_path) as file:
return file.get_contents()
@contextmanager
def edited_file_context(self, relative_path: str) -> Iterator["CodeEditor.EditedFile"]:
"""
@@ -587,6 +587,29 @@ class JetBrainsPluginClient(ToStringMixin):
}
self._make_request("POST", "/refreshFile", request_data)
def find_declaration(
self, relative_path: str, line: int, col: int, include_body: bool, include_quick_info: bool
) -> jb.SymbolCollectionResponse:
"""
Finds the declaration of the symbol at the given location.
:param relative_path: the relative path to the file
:param line: the line number (0-based)
:param col: the column number (0-based)
:param include_body: whether to include the symbol body
:param include_quick_info: whether to include quick info about the symbol
"""
request_data = {
"relativePath": relative_path,
"line": line,
"col": col,
"includeBody": include_body,
"includeQuickInfo": include_quick_info,
}
symbol_collection = cast(jb.SymbolCollectionResponse, self._make_request("POST", "/findDeclaration", request_data))
self._postprocess_symbol_collection_response(symbol_collection)
return symbol_collection
def close(self) -> None:
self._session.close()
+30
View File
@@ -7,6 +7,7 @@ from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient
from serena.jetbrains.jetbrains_types import SymbolDTO
from serena.symbol import JetBrainsSymbolDictGrouper
from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead
from serena.util.text_utils import find_text_coordinates
log = logging.getLogger(__name__)
@@ -416,3 +417,32 @@ class JetBrainsTypeHierarchyTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptiona
result = self._to_json(result_dict)
return self._limit_length(result, max_answer_chars)
class JetBrainsFindDeclarationTool(Tool, ToolMarkerSymbolicRead):
"""
Finds the declaration of a symbol using the JetBrains backend
"""
def apply(self, relative_path: str, regex: str, include_body: bool = False) -> str:
r"""
Finds the declaration of a symbol.
:param relative_path: the relative path to the source file containing the symbol for which to find the declaration.
:param regex: a regular expression with one group, where the group matches the symbol for which to perform the lookup.
For example, to find the declaration of the `process` method in a call like `obj.process()`,
pass an expression like "obj\.(process)\(\)".
Provide as much context as necessary to render the regex unambiguous.
Uses Python syntax with MULTILINE and DOTALL flags enabled.
:param include_body: whether to include the symbol's body in the result. Default False.
"""
editor = self.create_code_editor()
content = editor.read_file(relative_path)
coords = find_text_coordinates(content, regex, require_unique=True)
assert coords is not None
with JetBrainsPluginClient.from_project(self.project) as client:
symbol_collection = client.find_declaration(
relative_path=relative_path, line=coords.line, col=coords.col, include_quick_info=False, include_body=include_body
)
result = self._to_json(symbol_collection)
return result
+41
View File
@@ -11,6 +11,7 @@ from bs4 import BeautifulSoup
from joblib import Parallel, delayed
from serena.constants import DEFAULT_SOURCE_FILE_ENCODING
from solidlsp.ls_utils import TextUtils
log = logging.getLogger(__name__)
@@ -523,3 +524,43 @@ class ContentReplacer:
"Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected."
)
return updated_content
@dataclass
class TextCoords:
line: int
"""
0-based line number
"""
col: int
"""
0-based column number
"""
def find_text_coordinates(content: str, regex: str, require_unique: bool = False) -> TextCoords | None:
"""
Finds the line and column number of the first match of a regex pattern in the given content.
:param content: the text content to search through
:param regex: the regular expression pattern to search for; it must match part of a single line,
and contain exactly one group that captures the position of interest (e.g., the exact variable name to find the coordinates of)
:param require_unique: if True, raises an error if not exactly one match is found;
if False, returns None if no match is found, and returns the coordinates of the first match if multiple matches are found
:return: the coordinates of the match or None
"""
pattern = re.compile(regex, flags=re.MULTILINE | re.DOTALL)
matches = list(pattern.finditer(content))
if len(matches) == 0:
if require_unique:
raise ValueError(f"No match found for regex: {regex}")
return None
else:
if require_unique and len(matches) > 1:
raise ValueError(f"Match must be unique; found {len(matches)} matches for regex: {regex}")
match = matches[0]
if len(match.groups()) != 1:
raise ValueError(f"Regex must contain exactly one group to capture the position, but found {len(match.groups())} groups.")
index_in_content = match.start(1)
line, col = TextUtils.get_line_col_from_index(content, index_in_content)
return TextCoords(line, col)