diff --git a/codex-cli/src/components/chat/terminal-chat-response-item.tsx b/codex-cli/src/components/chat/terminal-chat-response-item.tsx index 888547a6..a81d5414 100644 --- a/codex-cli/src/components/chat/terminal-chat-response-item.tsx +++ b/codex-cli/src/components/chat/terminal-chat-response-item.tsx @@ -334,6 +334,11 @@ function rewriteFileCitations( return markdown.replace(citationRegex, (_match, file, start, _end) => { const absPath = path.resolve(cwd, file); const uri = `${fileOpener}://file${absPath}:${start}`; - return `[${file}](${uri})`; + const label = `${file}:${start}`; + // In practice, sometimes multiple citations for the same file, but with a + // different line number, are shown sequentially, so we: + // - include the line number in the label to disambiguate them + // - add a space after the link to make it easier to read + return `[${label}](${uri}) `; }); } diff --git a/codex-cli/tests/markdown.test.tsx b/codex-cli/tests/markdown.test.tsx index 79c12bb9..f4c7fb42 100644 --- a/codex-cli/tests/markdown.test.tsx +++ b/codex-cli/tests/markdown.test.tsx @@ -137,14 +137,16 @@ orchestration is: ${GREEN}${BOLD}### ${YELLOW}src/cli.tsx${COLOR_OFF}${BOLD_OFF} * Uses ${BOLD}meow${BOLD_OFF} for flags/subcommands and prints the built-in help/usage: - ${BLUE}src/cli.tsx (${LINK_ON}vscode://file/home/user/codex/src/cli.tsx:49${LINK_OFF})src/cli.tsx ${COLOR_OFF} + ${BLUE}src/cli.tsx:49 (${LINK_ON}vscode://file/home/user/codex/src/cli.tsx:49${LINK_OFF})${COLOR_OFF} ${BLUE}src/cli.tsx:55 ${COLOR_OFF} ${BLUE}(${LINK_ON}vscode://file/home/user/codex/src/cli.tsx:55${LINK_OFF})${COLOR_OFF} * Handles special subcommands (e.g. ${YELLOW}codex completion …${COLOR_OFF}), ${YELLOW}--config${COLOR_OFF}, API-key validation, then either: * Spawns the ${BOLD}AgentLoop${BOLD_OFF} for the normal multi-step prompting/edits flow, or * Runs ${BOLD}single-pass${BOLD_OFF} mode if ${YELLOW}--full-context${COLOR_OFF} is set.`; - expect(outputWithAnsi).toBe(expectedNestedList); + expect(toDiffableString(outputWithAnsi)).toBe( + toDiffableString(expectedNestedList), + ); }); it("citations should get converted to hyperlinks when stdout supports them", () => { @@ -154,8 +156,17 @@ either: , ); - const expected = `File with TODO: ${BLUE}src/approvals.ts (${LINK_ON}vscode://file/foo/bar/src/approvals.ts:40${LINK_OFF})${COLOR_OFF}`; + const expected = `File with TODO: ${BLUE}src/approvals.ts:40 (${LINK_ON}vscode://file/foo/bar/src/approvals.ts:40${LINK_OFF})${COLOR_OFF}`; const outputWithAnsi = lastFrame(); expect(outputWithAnsi).toBe(expected); }); }); + +function toDiffableString(str: string) { + // The test harness is not able to handle ANSI codes, so we need to escape + // them, but still give it line-based input so that it can diff the output. + return str + .split("\n") + .map((line) => JSON.stringify(line)) + .join("\n"); +}