diff --git a/codex-cli/tests/markdown.test.tsx b/codex-cli/tests/markdown.test.tsx index dd18b66d..79c12bb9 100644 --- a/codex-cli/tests/markdown.test.tsx +++ b/codex-cli/tests/markdown.test.tsx @@ -6,6 +6,17 @@ import React from "react"; import { describe, afterEach, beforeEach, it, expect, vi } from "vitest"; import chalk from "chalk"; +const BOLD = "\x1B[1m"; +const BOLD_OFF = "\x1B[22m"; +const ITALIC = "\x1B[3m"; +const ITALIC_OFF = "\x1B[23m"; +const LINK_ON = "\x1B[4m"; +const LINK_OFF = "\x1B[24m"; +const BLUE = "\x1B[34m"; +const GREEN = "\x1B[32m"; +const YELLOW = "\x1B[33m"; +const COLOR_OFF = "\x1B[39m"; + /** Simple sanity check that the Markdown component renders bold/italic text. * We strip ANSI codes, so the output should contain the raw words. */ it("renders basic markdown", () => { @@ -44,13 +55,98 @@ describe("ensure produces content with correct ANSI escape codes", () ); const frame = lastFrame(); - const BOLD = "\x1B[1m"; - const BOLD_OFF = "\x1B[22m"; - const ITALIC = "\x1B[3m"; - const ITALIC_OFF = "\x1B[23m"; expect(frame).toBe(`${BOLD}bold${BOLD_OFF} ${ITALIC}italic${ITALIC_OFF}`); }); + // We had to patch in https://github.com/mikaelbr/marked-terminal/pull/366 to + // make this work. + it("bold test in a bullet should be rendered correctly", () => { + const { lastFrame } = renderTui( + * **bold** text, + ); + + const outputWithAnsi = lastFrame(); + expect(outputWithAnsi).toBe(`* ${BOLD}bold${BOLD_OFF} text`); + }); + + it("ensure simple nested list works as expected", () => { + // Empirically, if there is no text at all before the first list item, + // it gets indented. + const nestedList = `\ +Paragraph before bulleted list. + +* item 1 + * subitem 1 + * subitem 2 +* item 2 +`; + const { lastFrame } = renderTui( + {nestedList}, + ); + + const outputWithAnsi = lastFrame(); + const i4 = " ".repeat(4); + const expectedNestedList = `\ +Paragraph before bulleted list. + +${i4}* item 1 +${i4}${i4}* subitem 1 +${i4}${i4}* subitem 2 +${i4}* item 2`; + expect(outputWithAnsi).toBe(expectedNestedList); + }); + + // We had to patch in https://github.com/mikaelbr/marked-terminal/pull/367 to + // make this work. + it("ensure sequential subitems with styling to do not get extra newlines", () => { + // This is a real-world example that exhibits many of the Markdown features + // we care about. Though the original issue fix this was intended to verify + // was that even though there is a single newline between the two subitems, + // the stock version of marked-terminal@7.3.0 was adding an extra newline + // in the output. + const nestedList = `\ +## 🛠 Core CLI Logic + +All of the TypeScript/React code lives under \`src/\`. The main entrypoint for argument parsing and orchestration is: + +### \`src/cli.tsx\` +- Uses **meow** for flags/subcommands and prints the built-in help/usage: + 【F:src/cli.tsx†L49-L53】【F:src/cli.tsx†L55-L60】 +- Handles special subcommands (e.g. \`codex completion …\`), \`--config\`, API-key validation, then either: + - Spawns the **AgentLoop** for the normal multi-step prompting/edits flow, or + - Runs **single-pass** mode if \`--full-context\` is set. + +`; + const { lastFrame } = renderTui( + + {nestedList} + , + ); + + const outputWithAnsi = lastFrame(); + + // Note that the line with two citations gets split across two lines. + // While the underlying ANSI content is long such that the split appears to + // be merited, the rendered output is considerably shorter and ideally it + // would be a single line. + const expectedNestedList = `${GREEN}${BOLD}## 🛠 Core CLI Logic${BOLD_OFF}${COLOR_OFF} + +All of the TypeScript/React code lives under ${YELLOW}src/${COLOR_OFF}. The main entrypoint for argument parsing and +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}(${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); + }); + it("citations should get converted to hyperlinks when stdout supports them", () => { const { lastFrame } = renderTui( @@ -58,11 +154,6 @@ describe("ensure produces content with correct ANSI escape codes", () , ); - const BLUE = "\x1B[34m"; - const LINK_ON = "\x1B[4m"; - const LINK_OFF = "\x1B[24m"; - const COLOR_OFF = "\x1B[39m"; - const expected = `File with TODO: ${BLUE}src/approvals.ts (${LINK_ON}vscode://file/foo/bar/src/approvals.ts:40${LINK_OFF})${COLOR_OFF}`; const outputWithAnsi = lastFrame(); expect(outputWithAnsi).toBe(expected); diff --git a/package.json b/package.json index 7bdb5f3e..9d45c6e3 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,11 @@ "overrides": { "punycode": "^2.3.1" }, + "pnpm": { + "patchedDependencies": { + "marked-terminal@7.3.0": "patches/marked-terminal@7.3.0.patch" + } + }, "engines": { "node": ">=22", "pnpm": ">=9.0.0" diff --git a/patches/marked-terminal@7.3.0.patch b/patches/marked-terminal@7.3.0.patch new file mode 100644 index 00000000..bce52c34 --- /dev/null +++ b/patches/marked-terminal@7.3.0.patch @@ -0,0 +1,26 @@ +diff --git a/index.js b/index.js +index 5e2d4b4f212a7c614ebcd5cba8c4928fa3e0d2d0..24dba3560bee4f88dac9106911ef204f37babebe 100644 +--- a/index.js ++++ b/index.js +@@ -83,7 +83,7 @@ Renderer.prototype.space = function () { + + Renderer.prototype.text = function (text) { + if (typeof text === 'object') { +- text = text.text; ++ text = text.tokens ? this.parser.parseInline(text.tokens) : text.text; + } + return this.o.text(text); + }; +@@ -185,10 +185,10 @@ Renderer.prototype.listitem = function (text) { + } + var transform = compose(this.o.listitem, this.transform); + var isNested = text.indexOf('\n') !== -1; +- if (isNested) text = text.trim(); ++ if (!isNested) text = transform(text); + + // Use BULLET_POINT as a marker for ordered or unordered list item +- return '\n' + BULLET_POINT + transform(text); ++ return '\n' + BULLET_POINT + text; + }; + + Renderer.prototype.checkbox = function (checked) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0efcba7..00b8f63e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,11 @@ overrides: micromatch: ^4.0.8 semver: ^7.7.1 +patchedDependencies: + marked-terminal@7.3.0: + hash: 536fe9685e91d559cf29a033191aa39da45729949e9d1c69989255091c8618fb + path: patches/marked-terminal@7.3.0.patch + importers: .: @@ -66,7 +71,7 @@ importers: version: 15.0.8 marked-terminal: specifier: ^7.3.0 - version: 7.3.0(marked@15.0.8) + version: 7.3.0(patch_hash=536fe9685e91d559cf29a033191aa39da45729949e9d1c69989255091c8618fb)(marked@15.0.8) meow: specifier: ^13.2.0 version: 13.2.0 @@ -4105,7 +4110,7 @@ snapshots: make-error@1.3.6: {} - marked-terminal@7.3.0(marked@15.0.8): + marked-terminal@7.3.0(patch_hash=536fe9685e91d559cf29a033191aa39da45729949e9d1c69989255091c8618fb)(marked@15.0.8): dependencies: ansi-escapes: 7.0.0 ansi-regex: 6.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d3ac8560..edb77fe2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,6 @@ packages: ignoredBuiltDependencies: - esbuild + +patchedDependencies: + marked-terminal@7.3.0: patches/marked-terminal@7.3.0.patch