Skip to content

Commit 9fadd3d

Browse files
KJ7LNWEric Wheeler
andauthored
fix: ripgrep search result handling and formatting (#1831)
Problem: - Previous implementation incorrectly grouped context lines with matches - Context lines were split into beforeContext/afterContext arrays, making it difficult to maintain proper line order - Output format was inconsistent with read_file output - Non-contiguous search results were sometimes broken or incorrectly grouped - Example of incorrect output: # bench/bundle-test/rollup.config.mjs 16 | file: 'dist/index-wasm.min.mjs', 17 | format: 'es', 24 | output: { ---- 25 | file: 'dist/index-lite.min.mjs', 26 | format: 'es', ---- # packages/rehype/package.json ---- 10 | it('run', async () => { 27 | "files": [ 28 | "dist" ---- Solution: - Replace separate context arrays with a single 'lines' array containing both matches and context - Add isMatch flag to distinguish between match and context lines - Improve contiguity detection by checking if line numbers are sequential - Standardize output format to match read_file output - Properly handle non-contiguous search results by creating new result groups - Example of correct output: # bench/bundle-test/rollup.config.mjs 16 | file: 'dist/index-wasm.min.mjs', 17 | format: 'es', ---- 24 | output: { 25 | file: 'dist/index-lite.min.mjs', 26 | format: 'es', ---- This change ensures search results are properly grouped and displayed with the correct context. Signed-off-by: Eric Wheeler <[email protected]> Co-authored-by: Eric Wheeler <[email protected]>
1 parent 4a0c0b1 commit 9fadd3d

File tree

1 file changed

+63
-54
lines changed

1 file changed

+63
-54
lines changed

src/services/ripgrep/index.ts

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,21 @@ rel/path/to/helper.ts
5050
const isWindows = /^win/.test(process.platform)
5151
const binName = isWindows ? "rg.exe" : "rg"
5252

53-
interface ContextResult {
54-
line: number
55-
text: string
53+
interface SearchFileResult {
54+
file: string
55+
searchResults: SearchResult[]
5656
}
5757

5858
interface SearchResult {
59-
file: string
59+
lines: SearchLineResult[]
60+
}
61+
62+
interface SearchLineResult {
6063
line: number
61-
column: number
6264
text: string
63-
beforeContext: ContextResult[]
64-
afterContext: ContextResult[]
65+
isMatch: boolean
66+
column?: number
6567
}
66-
6768
// Constants
6869
const MAX_RESULTS = 300
6970
const MAX_LINE_LENGTH = 500
@@ -157,43 +158,50 @@ export async function regexSearchFiles(
157158
console.error("Error executing ripgrep:", error)
158159
return "No results found"
159160
}
160-
const results: SearchResult[] = []
161+
162+
const results: SearchFileResult[] = []
161163
let currentResult: Partial<SearchResult> | null = null
164+
let currentFile: SearchFileResult | null = null
162165

163166
output.split("\n").forEach((line) => {
164167
if (line) {
165168
try {
166169
const parsed = JSON.parse(line)
167-
if (parsed.type === "match") {
168-
if (currentResult) {
169-
results.push(currentResult as SearchResult)
170+
if (parsed.type === "begin") {
171+
currentFile = {
172+
file: parsed.data.path.text.toString(),
173+
searchResults: [],
170174
}
171-
172-
// Safety check: truncate extremely long lines to prevent excessive output
173-
const matchText = parsed.data.lines.text
174-
const truncatedMatch = truncateLine(matchText)
175-
176-
currentResult = {
177-
file: parsed.data.path.text,
178-
line: parsed.data.line_number,
179-
column: parsed.data.submatches[0].start,
180-
text: truncatedMatch,
181-
beforeContext: [],
182-
afterContext: [],
183-
}
184-
} else if (parsed.type === "context" && currentResult) {
185-
// Apply the same truncation logic to context lines
186-
const contextText = parsed.data.lines.text
187-
const truncatedContext = truncateLine(contextText)
188-
let contextResult: ContextResult = {
175+
} else if (parsed.type === "end") {
176+
// Reset the current result when a new file is encountered
177+
results.push(currentFile as SearchFileResult)
178+
currentFile = null
179+
} else if ((parsed.type === "match" || parsed.type === "context") && currentFile) {
180+
const line = {
189181
line: parsed.data.line_number,
190-
text: truncatedContext,
182+
text: truncateLine(parsed.data.lines.text),
183+
isMatch: parsed.type === "match",
184+
...(parsed.type === "match" && { column: parsed.data.absolute_offset }),
191185
}
192186

193-
if (parsed.data.line_number < currentResult.line!) {
194-
currentResult.beforeContext!.push(contextResult)
187+
const lastResult = currentFile.searchResults[currentFile.searchResults.length - 1]
188+
if (lastResult?.lines.length > 0) {
189+
const lastLine = lastResult.lines[lastResult.lines.length - 1]
190+
191+
// If this line is contiguous with the last result, add to it
192+
if (parsed.data.line_number <= lastLine.line + 1) {
193+
lastResult.lines.push(line)
194+
} else {
195+
// Otherwise create a new result
196+
currentFile.searchResults.push({
197+
lines: [line],
198+
})
199+
}
195200
} else {
196-
currentResult.afterContext!.push(contextResult)
201+
// First line in file
202+
currentFile.searchResults.push({
203+
lines: [line],
204+
})
197205
}
198206
}
199207
} catch (error) {
@@ -202,9 +210,7 @@ export async function regexSearchFiles(
202210
}
203211
})
204212

205-
if (currentResult) {
206-
results.push(currentResult as SearchResult)
207-
}
213+
// console.log(results)
208214

209215
// Filter results using RooIgnoreController if provided
210216
const filteredResults = rooIgnoreController
@@ -214,40 +220,43 @@ export async function regexSearchFiles(
214220
return formatResults(filteredResults, cwd)
215221
}
216222

217-
function formatResults(results: SearchResult[], cwd: string): string {
223+
function formatResults(fileResults: SearchFileResult[], cwd: string): string {
218224
const groupedResults: { [key: string]: SearchResult[] } = {}
219225

226+
let totalResults = fileResults.reduce((sum, file) => sum + file.searchResults.length, 0)
220227
let output = ""
221-
if (results.length >= MAX_RESULTS) {
228+
if (totalResults >= MAX_RESULTS) {
222229
output += `Showing first ${MAX_RESULTS} of ${MAX_RESULTS}+ results. Use a more specific search if necessary.\n\n`
223230
} else {
224-
output += `Found ${results.length === 1 ? "1 result" : `${results.length.toLocaleString()} results`}.\n\n`
231+
output += `Found ${totalResults === 1 ? "1 result" : `${totalResults.toLocaleString()} results`}.\n\n`
225232
}
226233

227234
// Group results by file name
228-
results.slice(0, MAX_RESULTS).forEach((result) => {
229-
const relativeFilePath = path.relative(cwd, result.file)
235+
fileResults.slice(0, MAX_RESULTS).forEach((file) => {
236+
const relativeFilePath = path.relative(cwd, file.file)
230237
if (!groupedResults[relativeFilePath]) {
231238
groupedResults[relativeFilePath] = []
239+
240+
groupedResults[relativeFilePath].push(...file.searchResults)
232241
}
233-
groupedResults[relativeFilePath].push(result)
234242
})
235243

236244
for (const [filePath, fileResults] of Object.entries(groupedResults)) {
237-
output += `${filePath.toPosix()}\n││ ││----\n`
238-
239-
fileResults.forEach((result, index) => {
240-
const allLines = [...result.beforeContext, result, ...result.afterContext]
241-
allLines.forEach((line) => {
242-
output += `││ ${line.line} ││${line.text?.trimEnd() ?? ""}\n`
243-
})
244-
245-
if (index < fileResults.length - 1) {
246-
output += "││ ││----\n"
245+
output += `# ${filePath.toPosix()}\n`
246+
247+
fileResults.forEach((result) => {
248+
// Only show results with at least one line
249+
if (result.lines.length > 0) {
250+
// Show all lines in the result
251+
result.lines.forEach((line) => {
252+
const lineNumber = String(line.line).padStart(3, " ")
253+
output += `${lineNumber} | ${line.text.trimEnd()}\n`
254+
})
255+
output += "----\n"
247256
}
248257
})
249258

250-
output += "││ ││----\n\n"
259+
output += "\n"
251260
}
252261

253262
return output.trim()

0 commit comments

Comments
 (0)