diff --git a/src/serena/util/text_utils.py b/src/serena/util/text_utils.py index e3c63e77..8f10fe0b 100644 --- a/src/serena/util/text_utils.py +++ b/src/serena/util/text_utils.py @@ -523,3 +523,35 @@ 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) -> 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) + :return: a tuple of (line_number, column_number) for the first match found, both 0-based, or None if no match is found + """ + pattern = re.compile(regex) + for line_number, line in enumerate(content.splitlines()): + match = pattern.search(line) + if match: + if len(match.groups()) != 1: + raise ValueError(f"Regex must contain exactly one group to capture the position, but found {len(match.groups())} groups.") + column_number = match.start(1) + return TextCoords(line_number, column_number) + return None diff --git a/test/solidlsp/ruby/test_ruby_symbol_retrieval.py b/test/solidlsp/ruby/test_ruby_symbol_retrieval.py index 5d969094..ef0c50fe 100644 --- a/test/solidlsp/ruby/test_ruby_symbol_retrieval.py +++ b/test/solidlsp/ruby/test_ruby_symbol_retrieval.py @@ -12,6 +12,7 @@ import os import pytest +from serena.util.text_utils import find_text_coordinates from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_types import SymbolKind @@ -583,15 +584,17 @@ class TestRubyLanguageServerSymbols: """Test request_defining_symbol for a method call.""" # Test finding the definition of a method being called file_path = os.path.join("examples", "user_management.rb") - # Position at a method call like create_user - defining_symbol = language_server.request_defining_symbol(file_path, 30, 15) - # Verify that we can find method definitions - if defining_symbol is not None: - assert "name" in defining_symbol - assert "kind" in defining_symbol - # Should be a method or constructor - assert defining_symbol.get("kind") in [SymbolKind.Method.value, SymbolKind.Constructor.value, SymbolKind.Function.value] + # Position at a method call like create_user + with language_server.open_file(file_path, open_in_ls=False) as fb: + pos = find_text_coordinates(fb.contents, r"user = @service\.(create_user)") + + # Verify that we can find the method definition + defining_symbol = language_server.request_defining_symbol(file_path, pos.line, pos.col) + assert "name" in defining_symbol + assert "kind" in defining_symbol + assert defining_symbol.get("name") == "create_user" + assert defining_symbol.get("kind") == SymbolKind.Method.value @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None: