diff --git a/package-lock.json b/package-lock.json index f98a1303b..36864aff4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4466,6 +4466,18 @@ "node": ">= 20" } }, + "node_modules/marked-cjk-friendly": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/marked-cjk-friendly/-/marked-cjk-friendly-0.1.2.tgz", + "integrity": "sha512-GUj8HjiEbur9yhgUo52FBgKizggF1QtT6gVdrYdWW6ZB4lVVjgS3EyyXPyfyRD0V4ghGRPlO7MO80rgSWbm0eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "marked": ">=15.0.0" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -6422,7 +6434,8 @@ "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "18.0.11" + "marked": "18.0.11", + "marked-cjk-friendly": "0.1.2" }, "devDependencies": { "@xterm/headless": "5.5.0", diff --git a/packages/coding-agent/install-lock/package-lock.json b/packages/coding-agent/install-lock/package-lock.json index dae984deb..e43090842 100644 --- a/packages/coding-agent/install-lock/package-lock.json +++ b/packages/coding-agent/install-lock/package-lock.json @@ -551,7 +551,8 @@ "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "18.0.11" + "marked": "18.0.11", + "marked-cjk-friendly": "0.1.2" }, "engines": { "node": ">=22.19.0" @@ -1160,6 +1161,18 @@ "node": ">= 20" } }, + "node_modules/marked-cjk-friendly": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/marked-cjk-friendly/-/marked-cjk-friendly-0.1.2.tgz", + "integrity": "sha512-GUj8HjiEbur9yhgUo52FBgKizggF1QtT6gVdrYdWW6ZB4lVVjgS3EyyXPyfyRD0V4ghGRPlO7MO80rgSWbm0eA==", + "license": "MIT", + "peerDependencies": { + "marked": ">=15.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", diff --git a/packages/tui/package.json b/packages/tui/package.json index 4b8807694..6ae4a20f4 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -53,7 +53,8 @@ "types": "./dist/index.d.ts", "dependencies": { "get-east-asian-width": "1.6.0", - "marked": "18.0.11" + "marked": "18.0.11", + "marked-cjk-friendly": "0.1.2" }, "devDependencies": { "@xterm/headless": "5.5.0", diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 2fe05aad4..8782741f3 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -1,4 +1,5 @@ import { Marked, type Token, Tokenizer, type TokenizerExtension, type Tokens } from "marked"; +import markedCjkFriendly from "marked-cjk-friendly"; import { renderLatex } from "../latex.ts"; import { getCapabilities, hyperlink, isImageLine } from "../terminal-image.ts"; import type { Component } from "../tui.ts"; @@ -172,7 +173,10 @@ const markdownParser = new Marked(); markdownParser.setOptions({ tokenizer: new StrictStrikethroughTokenizer(), }); -markdownParser.use({ extensions: [...LATEX_MARKDOWN_EXTENSIONS] }); +markdownParser.use({ + tokenizer: { emStrong: markedCjkFriendly().tokenizer?.emStrong }, + extensions: [...LATEX_MARKDOWN_EXTENSIONS], +}); /** * Default text styling for markdown content. diff --git a/packages/tui/test/markdown.test.ts b/packages/tui/test/markdown.test.ts index 785c312b5..96fbd958d 100644 --- a/packages/tui/test/markdown.test.ts +++ b/packages/tui/test/markdown.test.ts @@ -1585,6 +1585,28 @@ bar`, }); }); + // #10154 + describe("CJK emphasis", () => { + const tagTheme: MarkdownTheme = { + ...defaultMarkdownTheme, + bold: (text) => `${text}`, + italic: (text) => `${text}`, + }; + const render = (text: string) => new Markdown(text, 0, 0, tagTheme).render(80).map((line) => line.trimEnd()); + + it("renders emphasis delimited next to CJK punctuation", () => { + assert.deepStrictEqual(render("**这是测试。**后面"), ["这是测试。后面"]); + assert.deepStrictEqual(render("**一句话说清:**一台机器"), ["一句话说清:一台机器"]); + assert.deepStrictEqual(render("**要点到此为止。**20 小时"), ["要点到此为止。20 小时"]); + assert.deepStrictEqual(render("字**「引用」**字"), ["字「引用」字"]); + assert.deepStrictEqual(render("*斜体。*后面"), ["斜体。后面"]); + }); + + it("keeps CommonMark flanking rules for non-CJK text", () => { + assert.deepStrictEqual(render("**end.**Then next"), ["**end.**Then next"]); + }); + }); + describe("Links", () => { afterEach(() => { resetCapabilitiesCache();