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

fix(tui): improve Terminal.app compatibility with CJK input

- Detect Terminal.app via $TERM_PROGRAM and lower Ink render FPS from
  30 to 15 to reduce ANSI redraw pressure during CJK IME composition
- Use Intl.Segmenter for grapheme-aware backspace handling (correctly
  deletes CJK characters, emoji with ZWJ, surrogate pairs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 7 месяцев назад
Родитель
Сommit
2b6ddf277e
3 измененных файлов с 16 добавлено и 2 удалено
  1. 7 1
      packages/cli/src/tui/app.ts
  2. 6 1
      packages/cli/src/tui/dashboard.tsx
  3. 3 0
      packages/cli/src/tui/theme.ts

+ 7 - 1
packages/cli/src/tui/app.ts

@@ -12,6 +12,7 @@ import { formatModeLabel, getTuiCopy, normalizeStageLabel, resolveTuiLocale, typ
 import { formatTuiResult } from "./output.js";
 import { loadProjectSession, persistProjectSession } from "./session-store.js";
 import { detectModelInfo, detectProjectLanguage, ensureProject, interactiveLlmSetup } from "./setup.js";
+import { isAppleTerminal } from "./theme.js";
 import { createInteractionTools } from "./tools.js";
 import { animateStartup } from "./effects.js";
 
@@ -172,7 +173,12 @@ export async function launchTui(
         tools,
         chatStreamBridge,
       }),
-      { exitOnCtrlC: true },
+      {
+        exitOnCtrlC: true,
+        // Terminal.app crashes with rapid ANSI redraws + CJK IME input;
+        // halve the frame rate to reduce rendering pressure.
+        ...(isAppleTerminal && { maxFps: 15 }),
+      },
     );
     await app.waitUntilExit();
   } finally {

+ 6 - 1
packages/cli/src/tui/dashboard.tsx

@@ -230,7 +230,12 @@ export function InkTuiApp(props: InkTuiAppProps): React.JSX.Element {
     }
 
     if (key.backspace || key.delete) {
-      setInputValue((current) => current.slice(0, -1));
+      setInputValue((current) => {
+        // Use Intl.Segmenter for grapheme-aware backspace (handles CJK, emoji, etc.)
+        const segments = [...new Intl.Segmenter().segment(current)];
+        segments.pop();
+        return segments.map((s) => s.segment).join("");
+      });
       setSelectedSlashIndex(0);
       return;
     }

+ 3 - 0
packages/cli/src/tui/theme.ts

@@ -2,6 +2,9 @@ import { execSync } from "node:child_process";
 
 export type TerminalTheme = "dark" | "light";
 
+/** Whether the terminal is macOS Terminal.app (limited ANSI support). */
+export const isAppleTerminal = process.env.TERM_PROGRAM === "Apple_Terminal";
+
 /**
  * Detect terminal background color.
  *