mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 20:15:56 +00:00
Add JetBrainsFindDeclarationTool
This commit is contained in:
1 parent
e74acfffec
commit
6ef9158ff5
4 files changed
+104
No files matched your search
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user