From 85515f35a61d271c056992b761b5bad9781e978a Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 15 May 2026 23:15:29 +0200 Subject: [PATCH] fix(coding-agent): added gap separators between noncontiguous search matches - SearchTool now tracked the last emitted line and inserted ellipsis markers when noncontiguous match blocks were output. - Display output gap markers were padded to align with code-frame gutters. - Added a regression test that verified a no-context search emits an ellipsis between separated matches in the same file. --- packages/coding-agent/src/tools/search.ts | 7 +++++++ packages/coding-agent/test/tools.test.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/coding-agent/src/tools/search.ts b/packages/coding-agent/src/tools/search.ts index 4a90a242d..b8cde2a9e 100644 --- a/packages/coding-agent/src/tools/search.ts +++ b/packages/coding-agent/src/tools/search.ts @@ -328,11 +328,18 @@ export class SearchTool implements AgentTool = []; + let lastEmittedLine: number | undefined; + const gutterPad = " ".repeat(lineNumberWidth + 1); for (const match of fileMatches) { const pushLine = (lineNumber: number, line: string, isMatch: boolean, recordable: boolean) => { + if (lastEmittedLine !== undefined && lineNumber > lastEmittedLine + 1) { + modelOut.push("..."); + displayOut.push(`${gutterPad}│...`); + } modelOut.push(formatMatchLine(lineNumber, line, isMatch, { useHashLines })); displayOut.push(formatCodeFrameLine(isMatch ? "*" : " ", lineNumber, line, lineNumberWidth)); if (recordable) cacheEntries.push([lineNumber, line] as const); + lastEmittedLine = lineNumber; }; if (match.contextBefore) { for (const ctx of match.contextBefore) { diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index ea641d1fc..33957fdc6 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -1419,6 +1419,24 @@ function b() { expect(output).toMatch(/\*5\|match two/); }); + it("inserts a gap separator between non-contiguous match blocks", async () => { + const testFile = path.join(testDir, "gaps.txt"); + const lines = Array.from({ length: 10 }, (_, idx) => (idx === 0 || idx === 5 ? "match" : `filler ${idx}`)); + fs.writeFileSync(testFile, lines.join("\n")); + + const noContextSettings = Settings.isolated({ "search.contextBefore": 0, "search.contextAfter": 0 }); + const noContextSearchTool = wrapToolWithMetaNotice( + new SearchTool(createTestToolSession(testDir, noContextSettings)), + ); + const result = await noContextSearchTool.execute("test-call-12-gap", { + pattern: "match", + paths: [testFile], + }); + + const output = getTextOutput(result); + expect(output).toMatch(/\*1\|match\n\.\.\.\n\*6\|match/); + }); + it("should paginate files via the skip parameter", async () => { const skipDir = path.join(testDir, "skip-dir"); fs.mkdirSync(skipDir, { recursive: true });