(workspaceDir, input)
| 239 | } |
| 240 | |
| 241 | async function searchText(workspaceDir, input) { |
| 242 | if (typeof input.pattern !== 'string' || !input.pattern) { |
| 243 | throw new Error('search_text requires a pattern') |
| 244 | } |
| 245 | |
| 246 | const requestedPath = typeof input.path === 'string' && input.path ? input.path : '.' |
| 247 | const absolutePath = resolveWorkspacePath(workspaceDir, requestedPath) |
| 248 | const maxResults = |
| 249 | Number.isInteger(input.max_results) && input.max_results > 0 |
| 250 | ? Math.min(input.max_results, 100) |
| 251 | : 20 |
| 252 | const caseSensitive = input.case_sensitive === true |
| 253 | const needle = caseSensitive ? input.pattern : input.pattern.toLowerCase() |
| 254 | const matches = [] |
| 255 | |
| 256 | await walkFiles(absolutePath, async filePath => { |
| 257 | if (matches.length >= maxResults) { |
| 258 | return false |
| 259 | } |
| 260 | |
| 261 | const extension = path.extname(filePath).toLowerCase() |
| 262 | if (SEARCH_SKIP_EXTENSIONS.has(extension)) { |
| 263 | return true |
| 264 | } |
| 265 | |
| 266 | const text = await fs.readFile(filePath, 'utf8').catch(() => null) |
| 267 | if (!text) { |
| 268 | return true |
| 269 | } |
| 270 | |
| 271 | const lines = text.split(/\r?\n/) |
| 272 | for (let index = 0; index < lines.length; index += 1) { |
| 273 | const line = lines[index] |
| 274 | const haystack = caseSensitive ? line : line.toLowerCase() |
| 275 | if (!haystack.includes(needle)) { |
| 276 | continue |
| 277 | } |
| 278 | |
| 279 | matches.push({ |
| 280 | path: toWorkspaceRelative(workspaceDir, filePath), |
| 281 | line: index + 1, |
| 282 | snippet: line.slice(0, 240), |
| 283 | }) |
| 284 | |
| 285 | if (matches.length >= maxResults) { |
| 286 | return false |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | return true |
| 291 | }) |
| 292 | |
| 293 | return { |
| 294 | ok: true, |
| 295 | pattern: input.pattern, |
| 296 | matches, |
| 297 | } |
| 298 | } |
no test coverage detected