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>
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user