Calculate match score for a TestMatch, considering both name and path. Path-based queries get exact match bonus if they match the path structure. Args: query: Search query string match: TestMatch to score Returns: Tuple of (match_score, length) where score is 0
(query: str, match: TestMatch)
| 187 | |
| 188 | |
| 189 | def _score_match(query: str, match: TestMatch) -> tuple[float, int]: |
| 190 | """Calculate match score for a TestMatch, considering both name and path. |
| 191 | |
| 192 | Path-based queries get exact match bonus if they match the path structure. |
| 193 | |
| 194 | Args: |
| 195 | query: Search query string |
| 196 | match: TestMatch to score |
| 197 | |
| 198 | Returns: |
| 199 | Tuple of (match_score, length) where score is 0.0-1.0 and length is for tie-breaking |
| 200 | """ |
| 201 | # Normalize path separators in query for cross-platform support |
| 202 | normalized_query = query.replace("\\", "/") |
| 203 | |
| 204 | # Try matching against the path if query contains path separators |
| 205 | if "/" in normalized_query or "\\" in query: |
| 206 | # Path-based query - try exact path match first |
| 207 | if match.path == normalized_query: |
| 208 | return (1.0, len(match.name)) # Exact path match |
| 209 | |
| 210 | # Try matching path without extension |
| 211 | path_no_ext = match.path.replace(".cpp", "").replace(".ino", "") |
| 212 | if path_no_ext == normalized_query: |
| 213 | return (1.0, len(match.name)) # Exact path match (without extension) |
| 214 | |
| 215 | # Try fuzzy match on path |
| 216 | path_score, path_len = _fuzzy_score(normalized_query, match.path) |
| 217 | if path_score > 0.0: |
| 218 | return (path_score, path_len) |
| 219 | |
| 220 | # Also try treating spaces as path separators for queries like "fl async" -> "fl/async" |
| 221 | # This allows natural language queries to match directory structures |
| 222 | if " " in query and "/" not in query: |
| 223 | space_to_slash = query.replace(" ", "/") |
| 224 | |
| 225 | # Try exact path match with space-to-slash conversion |
| 226 | path_no_ext = match.path.replace(".cpp", "").replace(".ino", "") |
| 227 | if space_to_slash in path_no_ext: |
| 228 | # Substring match in path - check if it's a component match |
| 229 | # e.g., "fl async" matches "tests/fl/async.cpp" |
| 230 | parts = path_no_ext.split("/") |
| 231 | query_parts = space_to_slash.split("/") |
| 232 | |
| 233 | # Check if query parts appear consecutively in path parts |
| 234 | for i in range(len(parts) - len(query_parts) + 1): |
| 235 | if parts[i : i + len(query_parts)] == query_parts: |
| 236 | # Exact component match - use name length for tie-breaking |
| 237 | return (0.95, len(match.name)) |
| 238 | |
| 239 | # Try fuzzy match with space-to-slash conversion |
| 240 | path_score, path_len = _fuzzy_score(space_to_slash, path_no_ext) |
| 241 | if path_score > 0.0: |
| 242 | # Boost score slightly since this is likely what the user meant |
| 243 | return (min(1.0, path_score + 0.05), path_len) |
| 244 | |
| 245 | # Try matching against the Meson target name derived from the path |
| 246 | # e.g., "tests/fl/stl/string.cpp" -> "fl_stl_string" |
no test coverage detected