Просмотр исходного кода

fix(tui): disable ANSI markdown rendering on Terminal.app

Terminal.app's CoreGraphics pipeline crashes (SIGSEGV in
color_space_icc_md5_equal) when ANSI escape codes + CJK text
are rendered together — UTF-8 bytes get misinterpreted as
color space pointers.

On Terminal.app, renderMarkdown now uses a plain-text fallback
that strips markdown syntax without emitting any ANSI codes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 7 месяцев назад
Родитель
Сommit
7769c3b34b
2 измененных файлов с 30 добавлено и 5 удалено
  1. 6 1
      packages/cli/src/tui/__tests__/markdown.test.ts
  2. 24 4
      packages/cli/src/tui/markdown.ts

+ 6 - 1
packages/cli/src/tui/__tests__/markdown.test.ts

@@ -1,7 +1,12 @@
-import { describe, expect, it } from "vitest";
+import { afterEach, describe, expect, it } from "vitest";
 import { renderMarkdown } from "../markdown.js";
 import { renderMarkdown } from "../markdown.js";
 
 
 describe("renderMarkdown", () => {
 describe("renderMarkdown", () => {
+  const origTermProgram = process.env.TERM_PROGRAM;
+  afterEach(() => {
+    if (origTermProgram === undefined) delete process.env.TERM_PROGRAM;
+    else process.env.TERM_PROGRAM = origTermProgram;
+  });
   it("converts **text** to ANSI bold", () => {
   it("converts **text** to ANSI bold", () => {
     const result = renderMarkdown("这是 **加粗** 文本");
     const result = renderMarkdown("这是 **加粗** 文本");
     // Should contain ANSI bold on/off around 加粗
     // Should contain ANSI bold on/off around 加粗

+ 24 - 4
packages/cli/src/tui/markdown.ts

@@ -1,5 +1,6 @@
 import { Marked } from "marked";
 import { Marked } from "marked";
 import { markedTerminal } from "marked-terminal";
 import { markedTerminal } from "marked-terminal";
+import { isAppleTerminal } from "./theme.js";
 
 
 const marked = new Marked();
 const marked = new Marked();
 marked.use(markedTerminal({
 marked.use(markedTerminal({
@@ -20,19 +21,38 @@ const BOLD_OFF = "\x1b[22m";
 
 
 /**
 /**
  * Post-process marked-terminal output:
  * Post-process marked-terminal output:
- * 1. Replace `* ` bullets with `· `
- * 2. Convert `**text**` markers to ANSI bold (applied after reflow to avoid code splitting)
+ * 1. Strip \x1b[0m (full reset) that overrides Ink's <Text color>
+ * 2. Replace `* ` bullets with `· `
+ * 3. Convert `**text**` markers to ANSI bold
  */
  */
 function postProcess(text: string): string {
 function postProcess(text: string): string {
   return text
   return text
-    // Strip \x1b[0m (full reset) — it overrides the color set by Ink's <Text color>.
-    // Table/block-level ANSI (e.g. [90m for borders, [39m for fg reset) is preserved.
     .replace(/\x1b\[0m/g, "")
     .replace(/\x1b\[0m/g, "")
     .replace(/^(\s*)\* /gm, "$1· ")
     .replace(/^(\s*)\* /gm, "$1· ")
     .replace(/\*\*(.+?)\*\*/g, `${BOLD_ON}$1${BOLD_OFF}`);
     .replace(/\*\*(.+?)\*\*/g, `${BOLD_ON}$1${BOLD_OFF}`);
 }
 }
 
 
+/**
+ * Lightweight plain-text markdown for Terminal.app.
+ * Strips all ANSI codes to avoid triggering CoreGraphics crashes
+ * (Terminal.app misinterprets UTF-8 bytes as color space pointers).
+ */
+function renderPlain(text: string): string {
+  return text
+    .replace(/^#{1,6}\s+/gm, "")
+    .replace(/\*\*(.+?)\*\*/g, "$1")
+    .replace(/\*(.+?)\*/g, "$1")
+    .replace(/`([^`]+)`/g, "$1")
+    .replace(/^(\s*)[-*]\s/gm, "$1· ");
+}
+
 export function renderMarkdown(text: string): string {
 export function renderMarkdown(text: string): string {
+  // Terminal.app crashes when ANSI escape codes + CJK text hit its
+  // CoreGraphics rendering pipeline. Use plain text as a workaround.
+  if (isAppleTerminal) {
+    return renderPlain(text);
+  }
+
   try {
   try {
     const rendered = marked.parse(text);
     const rendered = marked.parse(text);
     if (typeof rendered !== "string") {
     if (typeof rendered !== "string") {