fix: terminal title uses jink writeRaw instead of raw PrintStream

Terminal title now writes OSC 0 through Ink.Instance.writeRaw()
which goes directly to the JLine terminal. This eliminates garbled
characters caused by writing through intercepted System.out/err or
to streams that bypass the terminal's ANSI processing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
abel533 1 month ago
parent ecb88b25a0
commit f05732a60c
  1. 22
      src/main/java/com/claudecode/tui/ClaudeCodeComponent.java
  2. 3
      src/main/java/com/claudecode/tui/JinkReplSession.java

@ -77,6 +77,8 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
private int historyIndex = -1; private int historyIndex = -1;
private String savedInput = ""; private String savedInput = "";
private final AtomicBoolean agentRunning = new AtomicBoolean(false); private final AtomicBoolean agentRunning = new AtomicBoolean(false);
/** jink 渲染实例引用(用于 writeRaw 设置终端标题等) */
private volatile io.mybatis.jink.Ink.Instance inkApp;
/** 思考动画帧 */ /** 思考动画帧 */
private static final String[] SPINNER_FRAMES = {"◐", "◓", "◑", "◒"}; private static final String[] SPINNER_FRAMES = {"◐", "◓", "◑", "◒"};
@ -1476,6 +1478,11 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
return agentRunning.get(); return agentRunning.get();
} }
/** 设置 jink Ink.Instance 引用(用于 writeRaw 终端标题等) */
public void setInkApp(io.mybatis.jink.Ink.Instance app) {
this.inkApp = app;
}
/** 从 JSON 工具参数中提取人类可读的摘要 */ /** 从 JSON 工具参数中提取人类可读的摘要 */
private static String extractToolSummary(String toolName, String args) { private static String extractToolSummary(String toolName, String args) {
if (args == null || args.isBlank()) return null; if (args == null || args.isBlank()) return null;
@ -1506,19 +1513,18 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
/** /**
* 设置终端标题OSC 0 escape sequence * 设置终端标题OSC 0 escape sequence
* 匹配官方 Claude Code useTerminalTitle hook 行为 * 匹配官方 Claude Code useTerminalTitle hook / TerminalWriteContext 行为
* 必须绕过 jink ConsolePatcher它会拦截 System.out/err * 通过 jink Ink.Instance.writeRaw() 直接写入 JLine 终端
* 直接写入原始输出流
*/ */
private static void setTerminalTitle(String title) { private void setTerminalTitle(String title) {
if (title == null || title.isBlank()) return; if (title == null || title.isBlank()) return;
try { try {
// 绕过 ConsolePatcher 拦截,直接写入终端 var app = this.inkApp;
PrintStream raw = io.mybatis.jink.util.ConsolePatcher.getOriginalOut(); if (app != null) {
// OSC 0: Set window title and icon name // OSC 0: Set window title and icon name
// Format: ESC ] 0 ; <title> BEL // Format: ESC ] 0 ; <title> BEL
raw.print("\033]0;" + title + "\007"); app.writeRaw("\033]0;" + title + "\007");
raw.flush(); }
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }

@ -95,6 +95,9 @@ public class JinkReplSession {
// 启动 jink 渲染(exitOnCtrlC=false,让组件处理 Ctrl+C) // 启动 jink 渲染(exitOnCtrlC=false,让组件处理 Ctrl+C)
inkApp = Ink.render(component, false); inkApp = Ink.render(component, false);
// 设置 inkApp 引用,使组件可以通过 writeRaw 设置终端标题
component.setInkApp(inkApp);
// 拦截 System.out/err,防止日志干扰 TUI // 拦截 System.out/err,防止日志干扰 TUI
inkApp.patchConsole(); inkApp.patchConsole();

Loading…
Cancel
Save