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();