diff --git a/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java b/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java index ff27e5d..e10ebc8 100644 --- a/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java +++ b/src/main/java/com/claudecode/tui/ClaudeCodeComponent.java @@ -67,7 +67,6 @@ public class ClaudeCodeComponent extends Component private final String model; private final String baseUrl; private final int toolCount; - private final int cmdCount; private final TokenTracker tokenTracker; private final Runnable onExit; @@ -97,6 +96,10 @@ public class ClaudeCodeComponent extends Component private volatile int lastRenderedItemCount = 0; private volatile int lastMaxVisibleLines = 20; + /** Ctrl+C 双击退出:上次按下时间 */ + private volatile long lastCtrlCTime = 0; + private static final long CTRL_C_EXIT_WINDOW_MS = 2000; // 2秒内再按一次退出 + /** 首次用户输入回调(用于 conversation summary) */ private Consumer onFirstUserInput; @@ -104,7 +107,7 @@ public class ClaudeCodeComponent extends Component CommandRegistry commandRegistry, ToolRegistry toolRegistry, String provider, String model, String baseUrl, - int toolCount, int cmdCount, + int toolCount, TokenTracker tokenTracker, Runnable onExit) { super(TuiState.empty()); @@ -115,7 +118,6 @@ public class ClaudeCodeComponent extends Component this.model = model; this.baseUrl = baseUrl; this.toolCount = toolCount; - this.cmdCount = cmdCount; this.tokenTracker = tokenTracker; this.onExit = onExit; } @@ -539,8 +541,14 @@ public class ClaudeCodeComponent extends Component } } + // Ctrl+C 双击退出提示 + boolean ctrlCPending = (System.currentTimeMillis() - lastCtrlCTime) < CTRL_C_EXIT_WINDOW_MS; + Renderable leftText = ctrlCPending + ? Text.of("Press Ctrl-C again to exit").color(Color.BRIGHT_YELLOW) + : Text.of("↑↓ history Esc interrupt").dimmed(); + return Box.of( - Text.of("↑↓ history Esc interrupt Ctrl+D exit").dimmed(), + leftText, Spacer.create(), Text.of(tokenInfo).color(Color.BRIGHT_GREEN) ).paddingX(1).height(1); @@ -565,13 +573,34 @@ public class ClaudeCodeComponent extends Component return; } - // Ctrl+C: 取消当前输入或中断 Agent + // Ctrl+C: 中断 Agent 或双击退出 if (key.ctrl() && "c".equals(input)) { if (agentRunning.get()) { + // Agent 运行中 → 取消任务 agentLoop.cancel(); addMessageInternal(new SystemMsg("^C (interrupt)", Color.BRIGHT_YELLOW), s); + lastCtrlCTime = System.currentTimeMillis(); } else { - setState(new TuiState("", s.messages, s.scrollOffset, false, "")); + long now = System.currentTimeMillis(); + if (now - lastCtrlCTime < CTRL_C_EXIT_WINDOW_MS) { + // 第二次 Ctrl+C → 退出 + if (onExit != null) onExit.run(); + } else { + // 第一次 Ctrl+C → 清空输入 + 提示再按一次退出 + lastCtrlCTime = now; + setState(new TuiState("", s.messages, s.scrollOffset, false, "")); + // 启动定时器,超时后清除提示 + Thread.startVirtualThread(() -> { + try { Thread.sleep(CTRL_C_EXIT_WINDOW_MS); } catch (InterruptedException ignored) {} + // 超时后刷新显示(清除 "Press Ctrl-C again to exit" 提示) + synchronized (stateLock) { + if (System.currentTimeMillis() - lastCtrlCTime >= CTRL_C_EXIT_WINDOW_MS) { + TuiState cur = getState(); + setState(new TuiState(cur.inputText, cur.messages, cur.scrollOffset, cur.thinking, cur.thinkingText)); + } + } + }); + } } return; } diff --git a/src/main/java/com/claudecode/tui/JinkReplSession.java b/src/main/java/com/claudecode/tui/JinkReplSession.java index 9309af0..f0d143c 100644 --- a/src/main/java/com/claudecode/tui/JinkReplSession.java +++ b/src/main/java/com/claudecode/tui/JinkReplSession.java @@ -79,7 +79,6 @@ public class JinkReplSession { providerInfo.model(), providerInfo.baseUrl(), toolRegistry.size(), - commandRegistry.getCommands().size(), tokenTracker, this::exit );