Skip to content

Commit 0f0ce67

Browse files
committed
Merge branch 'limit-ctx-line-length'
2 parents 89163a3 + 61bd3e1 commit 0f0ce67

File tree

9 files changed

+221
-21
lines changed

9 files changed

+221
-21
lines changed

src/code_index_mcp/search/ag.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using The Silver Searcher (ag).
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable word boundary matching (not true fuzzy search)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
# ag prints line numbers and groups by file by default, which is good.
4547
# --noheading is used to be consistent with other tools' output format.
@@ -116,10 +118,10 @@ def search(
116118
if process.returncode > 1:
117119
raise RuntimeError(f"ag failed with exit code {process.returncode}: {process.stderr}")
118120

119-
return parse_search_output(process.stdout, base_path)
121+
return parse_search_output(process.stdout, base_path, max_line_length)
120122

121123
except FileNotFoundError:
122124
raise RuntimeError("'ag' (The Silver Searcher) not found. Please install it and ensure it's in your PATH.")
123125
except Exception as e:
124126
# Re-raise other potential exceptions like permission errors
125-
raise RuntimeError(f"An error occurred while running ag: {e}")
127+
raise RuntimeError(f"An error occurred while running ag: {e}")

src/code_index_mcp/search/base.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414

1515
from ..indexing.qualified_names import normalize_file_path
1616

17-
def parse_search_output(output: str, base_path: str) -> Dict[str, List[Tuple[int, str]]]:
17+
def parse_search_output(
18+
output: str,
19+
base_path: str,
20+
max_line_length: Optional[int] = None
21+
) -> Dict[str, List[Tuple[int, str]]]:
1822
"""
1923
Parse the output of command-line search tools (grep, ag, rg).
2024
2125
Args:
2226
output: The raw output from the command-line tool.
2327
base_path: The base path of the project to make file paths relative.
28+
max_line_length: Optional maximum line length to truncate long lines.
2429
2530
Returns:
2631
A dictionary where keys are file paths and values are lists of (line_number, line_content) tuples.
@@ -71,6 +76,10 @@ def parse_search_output(output: str, base_path: str) -> Dict[str, List[Tuple[int
7176
# Normalize path separators for consistency
7277
relative_path = normalize_file_path(relative_path)
7378

79+
# Truncate content if it exceeds max_line_length
80+
if max_line_length and len(content) > max_line_length:
81+
content = content[:max_line_length] + '... (truncated)'
82+
7483
if relative_path not in results:
7584
results[relative_path] = []
7685
results[relative_path].append((line_number, content))
@@ -193,7 +202,8 @@ def search(
193202
context_lines: int = 0,
194203
file_pattern: Optional[str] = None,
195204
fuzzy: bool = False,
196-
regex: bool = False
205+
regex: bool = False,
206+
max_line_length: Optional[int] = None
197207
) -> Dict[str, List[Tuple[int, str]]]:
198208
"""
199209
Execute a search using the specific strategy.
@@ -211,4 +221,3 @@ def search(
211221
A dictionary mapping filenames to lists of (line_number, line_content) tuples.
212222
"""
213223
pass
214-

src/code_index_mcp/search/basic.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def search(
4646
context_lines: int = 0,
4747
file_pattern: Optional[str] = None,
4848
fuzzy: bool = False,
49-
regex: bool = False
49+
regex: bool = False,
50+
max_line_length: Optional[int] = None
5051
) -> Dict[str, List[Tuple[int, str]]]:
5152
"""
5253
Execute a basic, line-by-line search.
@@ -60,6 +61,7 @@ def search(
6061
file_pattern: File pattern to filter
6162
fuzzy: Enable word boundary matching
6263
regex: Enable regex pattern matching
64+
max_line_length: Optional. Limit the length of lines when context_lines is used
6365
"""
6466
results: Dict[str, List[Tuple[int, str]]] = {}
6567

@@ -94,15 +96,20 @@ def search(
9496
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
9597
for line_num, line in enumerate(f, 1):
9698
if search_regex.search(line):
99+
content = line.rstrip('\n')
100+
# Truncate content if it exceeds max_line_length
101+
if max_line_length and len(content) > max_line_length:
102+
content = content[:max_line_length] + '... (truncated)'
103+
97104
if rel_path not in results:
98105
results[rel_path] = []
99106
# Strip newline for consistent output
100-
results[rel_path].append((line_num, line.rstrip('\n')))
107+
results[rel_path].append((line_num, content))
101108
except (UnicodeDecodeError, PermissionError, OSError):
102109
# Ignore files that can't be opened or read due to encoding/permission issues
103110
continue
104111
except Exception:
105112
# Ignore any other unexpected exceptions to maintain robustness
106113
continue
107114

108-
return results
115+
return results

src/code_index_mcp/search/grep.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def search(
3232
context_lines: int = 0,
3333
file_pattern: Optional[str] = None,
3434
fuzzy: bool = False,
35-
regex: bool = False
35+
regex: bool = False,
36+
max_line_length: Optional[int] = None
3637
) -> Dict[str, List[Tuple[int, str]]]:
3738
"""
3839
Execute a search using standard grep.
@@ -45,6 +46,7 @@ def search(
4546
file_pattern: File pattern to filter
4647
fuzzy: Enable word boundary matching
4748
regex: Enable regex pattern matching
49+
max_line_length: Optional. Limit the length of lines when context_lines is used
4850
"""
4951
# -r: recursive, -n: line number
5052
cmd = ['grep', '-r', '-n']
@@ -102,9 +104,9 @@ def search(
102104
if process.returncode > 1:
103105
raise RuntimeError(f"grep failed with exit code {process.returncode}: {process.stderr}")
104106

105-
return parse_search_output(process.stdout, base_path)
107+
return parse_search_output(process.stdout, base_path, max_line_length)
106108

107109
except FileNotFoundError:
108110
raise RuntimeError("'grep' not found. Please install it and ensure it's in your PATH.")
109111
except Exception as e:
110-
raise RuntimeError(f"An error occurred while running grep: {e}")
112+
raise RuntimeError(f"An error occurred while running grep: {e}")

src/code_index_mcp/search/ripgrep.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using ripgrep.
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable word boundary matching (not true fuzzy search)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
cmd = ['rg', '--line-number', '--no-heading', '--color=never', '--no-ignore']
4547

@@ -87,10 +89,10 @@ def search(
8789
if process.returncode > 1:
8890
raise RuntimeError(f"ripgrep failed with exit code {process.returncode}: {process.stderr}")
8991

90-
return parse_search_output(process.stdout, base_path)
92+
return parse_search_output(process.stdout, base_path, max_line_length)
9193

9294
except FileNotFoundError:
9395
raise RuntimeError("ripgrep (rg) not found. Please install it and ensure it's in your PATH.")
9496
except Exception as e:
9597
# Re-raise other potential exceptions like permission errors
96-
raise RuntimeError(f"An error occurred while running ripgrep: {e}")
98+
raise RuntimeError(f"An error occurred while running ripgrep: {e}")

src/code_index_mcp/search/ugrep.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def search(
2727
context_lines: int = 0,
2828
file_pattern: Optional[str] = None,
2929
fuzzy: bool = False,
30-
regex: bool = False
30+
regex: bool = False,
31+
max_line_length: Optional[int] = None
3132
) -> Dict[str, List[Tuple[int, str]]]:
3233
"""
3334
Execute a search using the 'ug' command-line tool.
@@ -40,6 +41,7 @@ def search(
4041
file_pattern: File pattern to filter
4142
fuzzy: Enable true fuzzy search (ugrep native support)
4243
regex: Enable regex pattern matching
44+
max_line_length: Optional. Limit the length of lines when context_lines is used
4345
"""
4446
if not self.is_available():
4547
return {"error": "ugrep (ug) command not found."}
@@ -89,7 +91,7 @@ def search(
8991
error_output = process.stderr.strip()
9092
return {"error": f"ugrep execution failed with code {process.returncode}", "details": error_output}
9193

92-
return parse_search_output(process.stdout, base_path)
94+
return parse_search_output(process.stdout, base_path, max_line_length)
9395

9496
except FileNotFoundError:
9597
return {"error": "ugrep (ug) command not found. Please ensure it's installed and in your PATH."}

src/code_index_mcp/server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ def search_code_advanced(
138138
context_lines: int = 0,
139139
file_pattern: str = None,
140140
fuzzy: bool = False,
141-
regex: bool = None
141+
regex: bool = None,
142+
max_line_length: int = None
142143
) -> Dict[str, Any]:
143144
"""
144145
Search for a code pattern in the project using an advanced, fast tool.
@@ -152,6 +153,7 @@ def search_code_advanced(
152153
context_lines: Number of lines to show before and after the match.
153154
file_pattern: A glob pattern to filter files to search in
154155
(e.g., "*.py", "*.js", "test_*.py").
156+
max_line_length: Optional. Default None (no limit). Limits the length of lines when context_lines is used.
155157
All search tools now handle glob patterns consistently:
156158
- ugrep: Uses glob patterns (*.py, *.{js,ts})
157159
- ripgrep: Uses glob patterns (*.py, *.{js,ts})
@@ -180,7 +182,8 @@ def search_code_advanced(
180182
context_lines=context_lines,
181183
file_pattern=file_pattern,
182184
fuzzy=fuzzy,
183-
regex=regex
185+
regex=regex,
186+
max_line_length=max_line_length
184187
)
185188

186189
@mcp.tool()

src/code_index_mcp/services/search_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def search_code( # pylint: disable=too-many-arguments
3131
context_lines: int = 0,
3232
file_pattern: Optional[str] = None,
3333
fuzzy: bool = False,
34-
regex: Optional[bool] = None
34+
regex: Optional[bool] = None,
35+
max_line_length: Optional[int] = None
3536
) -> Dict[str, Any]:
3637
"""
3738
Search for code patterns in the project.
@@ -45,6 +46,7 @@ def search_code( # pylint: disable=too-many-arguments
4546
file_pattern: Glob pattern to filter files
4647
fuzzy: Whether to enable fuzzy matching
4748
regex: Regex mode - True/False to force, None for auto-detection
49+
max_line_length: Optional. Default None (no limit). Limits the length of lines when context_lines is used.
4850
4951
Returns:
5052
Dictionary with search results or error information
@@ -89,7 +91,8 @@ def search_code( # pylint: disable=too-many-arguments
8991
context_lines=context_lines,
9092
file_pattern=file_pattern,
9193
fuzzy=fuzzy,
92-
regex=regex
94+
regex=regex,
95+
max_line_length=max_line_length
9396
)
9497
return ResponseFormatter.search_results_response(results)
9598
except Exception as e:

0 commit comments

Comments
 (0)