This PR uses [`pnpm patch`](https://www.petermekhaeil.com/til/pnpm-patch/) to pull in the following proposed fixes for `marked-terminal`: * https://github.com/mikaelbr/marked-terminal/pull/366 * https://github.com/mikaelbr/marked-terminal/pull/367 This adds a substantial test to `codex-cli/tests/markdown.test.tsx` to verify the new behavior. Note that one of the tests shows two citations being split across a line even though the rendered version would fit comfortably on one line. Changing this likely requires a subtle fix to `marked-terminal` to account for "rendered length" when determining line breaks.
This commit is contained in:
@@ -6,6 +6,17 @@ import React from "react";
|
|||||||
import { describe, afterEach, beforeEach, it, expect, vi } from "vitest";
|
import { describe, afterEach, beforeEach, it, expect, vi } from "vitest";
|
||||||
import chalk from "chalk";
|
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.
|
/** Simple sanity check that the Markdown component renders bold/italic text.
|
||||||
* We strip ANSI codes, so the output should contain the raw words. */
|
* We strip ANSI codes, so the output should contain the raw words. */
|
||||||
it("renders basic markdown", () => {
|
it("renders basic markdown", () => {
|
||||||
@@ -44,13 +55,98 @@ describe("ensure <Markdown> produces content with correct ANSI escape codes", ()
|
|||||||
);
|
);
|
||||||
|
|
||||||
const frame = lastFrame();
|
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}`);
|
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(
|
||||||
|
<Markdown fileOpener={undefined}>* **bold** text</Markdown>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<Markdown fileOpener={undefined}>{nestedList}</Markdown>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<Markdown fileOpener={"vscode"} cwd="/home/user/codex">
|
||||||
|
{nestedList}
|
||||||
|
</Markdown>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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", () => {
|
it("citations should get converted to hyperlinks when stdout supports them", () => {
|
||||||
const { lastFrame } = renderTui(
|
const { lastFrame } = renderTui(
|
||||||
<Markdown fileOpener={"vscode"} cwd="/foo/bar">
|
<Markdown fileOpener={"vscode"} cwd="/foo/bar">
|
||||||
@@ -58,11 +154,6 @@ describe("ensure <Markdown> produces content with correct ANSI escape codes", ()
|
|||||||
</Markdown>,
|
</Markdown>,
|
||||||
);
|
);
|
||||||
|
|
||||||
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 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();
|
const outputWithAnsi = lastFrame();
|
||||||
expect(outputWithAnsi).toBe(expected);
|
expect(outputWithAnsi).toBe(expected);
|
||||||
|
|||||||
@@ -29,6 +29,11 @@
|
|||||||
"overrides": {
|
"overrides": {
|
||||||
"punycode": "^2.3.1"
|
"punycode": "^2.3.1"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"patchedDependencies": {
|
||||||
|
"marked-terminal@7.3.0": "patches/marked-terminal@7.3.0.patch"
|
||||||
|
}
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22",
|
"node": ">=22",
|
||||||
"pnpm": ">=9.0.0"
|
"pnpm": ">=9.0.0"
|
||||||
|
|||||||
26
patches/marked-terminal@7.3.0.patch
Normal file
26
patches/marked-terminal@7.3.0.patch
Normal file
@@ -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) {
|
||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -9,6 +9,11 @@ overrides:
|
|||||||
micromatch: ^4.0.8
|
micromatch: ^4.0.8
|
||||||
semver: ^7.7.1
|
semver: ^7.7.1
|
||||||
|
|
||||||
|
patchedDependencies:
|
||||||
|
marked-terminal@7.3.0:
|
||||||
|
hash: 536fe9685e91d559cf29a033191aa39da45729949e9d1c69989255091c8618fb
|
||||||
|
path: patches/marked-terminal@7.3.0.patch
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
@@ -66,7 +71,7 @@ importers:
|
|||||||
version: 15.0.8
|
version: 15.0.8
|
||||||
marked-terminal:
|
marked-terminal:
|
||||||
specifier: ^7.3.0
|
specifier: ^7.3.0
|
||||||
version: 7.3.0(marked@15.0.8)
|
version: 7.3.0(patch_hash=536fe9685e91d559cf29a033191aa39da45729949e9d1c69989255091c8618fb)(marked@15.0.8)
|
||||||
meow:
|
meow:
|
||||||
specifier: ^13.2.0
|
specifier: ^13.2.0
|
||||||
version: 13.2.0
|
version: 13.2.0
|
||||||
@@ -4105,7 +4110,7 @@ snapshots:
|
|||||||
|
|
||||||
make-error@1.3.6: {}
|
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:
|
dependencies:
|
||||||
ansi-escapes: 7.0.0
|
ansi-escapes: 7.0.0
|
||||||
ansi-regex: 6.1.0
|
ansi-regex: 6.1.0
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ packages:
|
|||||||
|
|
||||||
ignoredBuiltDependencies:
|
ignoredBuiltDependencies:
|
||||||
- esbuild
|
- esbuild
|
||||||
|
|
||||||
|
patchedDependencies:
|
||||||
|
marked-terminal@7.3.0: patches/marked-terminal@7.3.0.patch
|
||||||
|
|||||||
Reference in New Issue
Block a user