@@ -18,37 +18,37 @@ data class GrepParams(
1818 * The regular expression pattern to search for in file contents
1919 */
2020 val pattern : String ,
21-
21+
2222 /* *
2323 * The directory to search in (optional, defaults to project root)
2424 */
2525 val path : String? = null ,
26-
26+
2727 /* *
2828 * File pattern to include in the search (e.g. "*.kt", "*.{ts,js}")
2929 */
3030 val include : String? = null ,
31-
31+
3232 /* *
3333 * File pattern to exclude from the search
3434 */
3535 val exclude : String? = null ,
36-
36+
3737 /* *
3838 * Whether the search should be case-sensitive
3939 */
4040 val caseSensitive : Boolean = false ,
41-
41+
4242 /* *
4343 * Maximum number of matches to return
4444 */
4545 val maxMatches : Int = 100 ,
46-
46+
4747 /* *
4848 * Number of context lines to show before and after each match
4949 */
5050 val contextLines : Int = 0 ,
51-
51+
5252 /* *
5353 * Whether to search recursively in subdirectories
5454 */
@@ -133,29 +133,27 @@ class GrepInvocation(
133133 tool : GrepTool ,
134134 private val fileSystem : ToolFileSystem
135135) : BaseToolInvocation<GrepParams, ToolResult>(params, tool) {
136-
136+
137137 override fun getDescription (): String {
138138 val searchPath = params.path ? : " project root"
139139 val includeDesc = params.include?.let { " (include: $it )" } ? : " "
140140 val excludeDesc = params.exclude?.let { " (exclude: $it )" } ? : " "
141141 return " Search for pattern '${params.pattern} ' in $searchPath$includeDesc$excludeDesc "
142142 }
143-
143+
144144 override fun getToolLocations (): List <ToolLocation > {
145145 val searchPath = params.path ? : fileSystem.getProjectPath() ? : " ."
146146 return listOf (ToolLocation (searchPath, LocationType .DIRECTORY ))
147147 }
148-
148+
149149 override suspend fun execute (context : ToolExecutionContext ): ToolResult {
150150 return ToolErrorUtils .safeExecute(ToolErrorType .INVALID_PATTERN ) {
151151 val searchPath = params.path ? : fileSystem.getProjectPath() ? : " ."
152-
153- // Validate search path exists
152+
154153 if (! fileSystem.exists(searchPath)) {
155154 throw ToolException (" Search path not found: $searchPath " , ToolErrorType .DIRECTORY_NOT_FOUND )
156155 }
157-
158- // Create regex pattern
156+
159157 val regex = try {
160158 if (params.caseSensitive) {
161159 Regex (params.pattern)
@@ -165,25 +163,23 @@ class GrepInvocation(
165163 } catch (e: Exception ) {
166164 throw ToolException (" Invalid regex pattern: ${params.pattern} " , ToolErrorType .INVALID_PATTERN )
167165 }
168-
169- // Find files to search
166+
170167 val filesToSearch = findFilesToSearch(searchPath)
171-
172- // Search for matches
168+
173169 val matches = mutableListOf<GrepMatch >()
174170 var totalMatches = 0
175-
171+
176172 for (file in filesToSearch) {
177173 if (totalMatches >= params.maxMatches) break
178-
174+
179175 val fileMatches = searchInFile(file, regex)
180176 matches.addAll(fileMatches.take(params.maxMatches - totalMatches))
181177 totalMatches + = fileMatches.size
182178 }
183-
179+
184180 // Format results
185181 val resultText = formatResults(matches, filesToSearch.size)
186-
182+
187183 val metadata = mapOf (
188184 " pattern" to params.pattern,
189185 " search_path" to searchPath,
@@ -194,66 +190,66 @@ class GrepInvocation(
194190 " include_pattern" to (params.include ? : " " ),
195191 " exclude_pattern" to (params.exclude ? : " " )
196192 )
197-
193+
198194 ToolResult .Success (resultText, metadata)
199195 }
200196 }
201-
197+
202198 private fun findFilesToSearch (searchPath : String ): List <String > {
203199 val files = mutableListOf<String >()
204-
200+
205201 fun collectFiles (path : String ) {
206202 val pathFiles = fileSystem.listFiles(path)
207-
203+
208204 for (file in pathFiles) {
209205 val fileInfo = fileSystem.getFileInfo(file)
210-
206+
211207 if (fileInfo?.isDirectory == true && params.recursive) {
212208 collectFiles(file)
213209 } else if (fileInfo?.isDirectory == false ) {
214210 // Check include/exclude patterns
215211 val fileName = file.substringAfterLast(' /' )
216-
212+
217213 val shouldInclude = params.include?.let { pattern ->
218214 matchesGlobPattern(fileName, pattern)
219215 } ? : true
220-
216+
221217 val shouldExclude = params.exclude?.let { pattern ->
222218 matchesGlobPattern(fileName, pattern)
223219 } ? : false
224-
220+
225221 if (shouldInclude && ! shouldExclude) {
226222 files.add(file)
227223 }
228224 }
229225 }
230226 }
231-
227+
232228 collectFiles(searchPath)
233229 return files.sorted()
234230 }
235-
231+
236232 private suspend fun searchInFile (filePath : String , regex : Regex ): List <GrepMatch > {
237233 val matches = mutableListOf<GrepMatch >()
238-
234+
239235 try {
240236 val content = fileSystem.readFile(filePath) ? : return emptyList()
241237 val lines = content.lines()
242-
238+
243239 lines.forEachIndexed { index, line ->
244240 val matchResults = regex.findAll(line)
245-
241+
246242 for (matchResult in matchResults) {
247243 val contextBefore = if (params.contextLines > 0 ) {
248244 val startIndex = (index - params.contextLines).coerceAtLeast(0 )
249245 lines.subList(startIndex, index)
250246 } else emptyList()
251-
247+
252248 val contextAfter = if (params.contextLines > 0 ) {
253249 val endIndex = (index + params.contextLines + 1 ).coerceAtMost(lines.size)
254250 lines.subList(index + 1 , endIndex)
255251 } else emptyList()
256-
252+
257253 matches.add(
258254 GrepMatch (
259255 file = filePath,
@@ -270,54 +266,49 @@ class GrepInvocation(
270266 } catch (e: Exception ) {
271267 // Skip files that can't be read
272268 }
273-
269+
274270 return matches
275271 }
276-
272+
277273 private fun formatResults (matches : List <GrepMatch >, filesSearched : Int ): String {
278274 if (matches.isEmpty()) {
279275 return " No matches found for pattern '${params.pattern} ' in $filesSearched files."
280276 }
281-
282- val result = StringBuilder ()
283- result .appendLine(" Found ${matches.size} matches for pattern '${params.pattern} ' in $filesSearched files:" )
284- result .appendLine()
285-
277+
278+ val sb = StringBuilder ()
279+ sb .appendLine(" Found ${matches.size} matches for pattern '${params.pattern} ' in $filesSearched files:" )
280+ sb .appendLine()
281+
286282 var currentFile = " "
287-
283+ val maxChars = 2000
284+
288285 for (match in matches) {
286+ if (sb.length > maxChars) {
287+ sb.appendLine()
288+ sb.appendLine(" ... (results truncated to $maxChars characters)" )
289+ break
290+ }
291+
289292 if (match.file != currentFile) {
290- if (currentFile.isNotEmpty()) result .appendLine()
291- result .appendLine(" File: ${match.file} " )
293+ if (currentFile.isNotEmpty()) sb .appendLine()
294+ sb .appendLine(" ### ${match.file} " )
292295 currentFile = match.file
293296 }
294-
295- // Show context before
296- match.contextBefore.forEachIndexed { index, contextLine ->
297- val lineNum = match.lineNumber - match.contextBefore.size + index
298- result.appendLine(" $lineNum : $contextLine " )
297+
298+ match.contextBefore.forEach { contextLine ->
299+ sb.appendLine(contextLine)
299300 }
300-
301- // Show the match line with highlighting
302- val line = match.line
303- val beforeMatch = line.substring(0 , match.matchStart)
304- val matchText = line.substring(match.matchStart, match.matchEnd)
305- val afterMatch = line.substring(match.matchEnd)
306-
307- result.appendLine(" → ${match.lineNumber} : $beforeMatch **$matchText **$afterMatch " )
308-
309- // Show context after
310- match.contextAfter.forEachIndexed { index, contextLine ->
311- val lineNum = match.lineNumber + index + 1
312- result.appendLine(" $lineNum : $contextLine " )
301+
302+ sb.appendLine(match.line)
303+
304+ match.contextAfter.forEach { contextLine ->
305+ sb.appendLine(contextLine)
313306 }
314-
315- if (params.contextLines > 0 ) result.appendLine()
316307 }
317-
318- return result .toString().trim ()
308+
309+ return sb .toString()
319310 }
320-
311+
321312 private fun matchesGlobPattern (fileName : String , pattern : String ): Boolean {
322313 // Simple glob pattern matching
323314 val regexPattern = pattern
@@ -327,7 +318,7 @@ class GrepInvocation(
327318 .replace(" {" , " (" )
328319 .replace(" }" , " )" )
329320 .replace(" ," , " |" )
330-
321+
331322 return fileName.matches(Regex (regexPattern))
332323 }
333324}
@@ -338,25 +329,26 @@ class GrepInvocation(
338329class GrepTool (
339330 private val fileSystem : ToolFileSystem
340331) : BaseExecutableTool<GrepParams, ToolResult>() {
341-
332+
342333 override val name: String = " grep"
343- override val description: String = """ Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.""" .trimIndent()
344-
334+ override val description: String =
335+ """ Searches for a regular expression pattern within the content of files in a specified directory (or current working directory). Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.""" .trimIndent()
336+
345337 override val metadata: ToolMetadata = ToolMetadata (
346338 displayName = " Search Content" ,
347339 tuiEmoji = " 🔍" ,
348340 composeIcon = " search" ,
349341 category = ToolCategory .Search ,
350342 schema = GrepSchema
351343 )
352-
344+
353345 override fun getParameterClass (): String = GrepParams ::class .simpleName ? : " GrepParams"
354-
355- override fun createToolInvocation (params : GrepParams ): ToolInvocation <GrepParams , ToolResult > {
346+
347+ public override fun createToolInvocation (params : GrepParams ): ToolInvocation <GrepParams , ToolResult > {
356348 validateParameters(params)
357349 return GrepInvocation (params, this , fileSystem)
358350 }
359-
351+
360352 private fun validateParameters (params : GrepParams ) {
361353 if (params.pattern.isBlank()) {
362354 throw ToolException (" Search pattern cannot be empty" , ToolErrorType .MISSING_REQUIRED_PARAMETER )
0 commit comments