feat: Ctrl+C 双击退出(匹配原版 Claude Code 行为)

- 第一次 Ctrl+C: Agent 运行中则取消任务,空闲时清空输入
- 第二次 Ctrl+C (2秒内): 退出应用
- 快捷键栏显示 'Press Ctrl-C again to exit' 黄色提示
- 2秒超时后自动清除提示(虚拟线程定时)
- 移除未使用的 cmdCount 字段
- 保留 Esc 中断和 Ctrl+D 退出作为备选

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

@ -67,7 +67,6 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
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<ClaudeCodeComponent.TuiState>
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<String> onFirstUserInput;
@ -104,7 +107,7 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
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<ClaudeCodeComponent.TuiState>
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<ClaudeCodeComponent.TuiState>
}
}
// 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<ClaudeCodeComponent.TuiState>
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 {
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;
}

@ -79,7 +79,6 @@ public class JinkReplSession {
providerInfo.model(),
providerInfo.baseUrl(),
toolRegistry.size(),
commandRegistry.getCommands().size(),
tokenTracker,
this::exit
);

Loading…
Cancel
Save