(params)
| 231 | }, |
| 232 | |
| 233 | async searchContent(params) { |
| 234 | const record = asRecord(params) |
| 235 | const dir = stringValue(record, "dir") |
| 236 | const query = typeof record.query === "string" ? record.query : "" |
| 237 | const limit = positiveInt(record.limit, DEFAULT_SEARCH_LIMIT) |
| 238 | const caseSensitive = boolValue(record.caseSensitive) |
| 239 | const files = await walk(dir) |
| 240 | const matches: Array<{ file: string; line: number; content: string; matchStart: number; matchEnd: number }> = [] |
| 241 | const matcher = boolValue(record.regex) |
| 242 | ? new RegExp(query, caseSensitive ? "u" : "iu") |
| 243 | : null |
| 244 | const needle = caseSensitive ? query : query.toLowerCase() |
| 245 | |
| 246 | for (const file of files) { |
| 247 | if (file.isDir || matches.length >= limit) break |
| 248 | const stat = await fs.stat(file.fullPath).catch(() => null) |
| 249 | if (!stat || stat.size > MAX_SEARCH_FILE_SIZE) continue |
| 250 | const content = await fs.readFile(file.fullPath, "utf8").catch(() => null) |
| 251 | if (content === null) continue |
| 252 | const lines = content.split(/\r?\n/) |
| 253 | for (let index = 0; index < lines.length && matches.length < limit; index++) { |
| 254 | const line = lines[index] |
| 255 | const match = matcher?.exec(line) |
| 256 | const matchStart = matcher ? (match?.index ?? -1) : (caseSensitive ? line : line.toLowerCase()).indexOf(needle) |
| 257 | if (matchStart < 0) continue |
| 258 | matches.push({ |
| 259 | file: file.relativePath, |
| 260 | line: index + 1, |
| 261 | content: line, |
| 262 | matchStart, |
| 263 | matchEnd: matchStart + (matcher ? (match?.[0].length ?? 0) : query.length), |
| 264 | }) |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | return { matches, truncated: matches.length >= limit } |
| 269 | }, |
| 270 | } |
| 271 | } |
nothing calls this directly
no test coverage detected