feat: Phase2 JLine终端交互增强 - 行编辑、历史、Tab补全、信号处理
JLine 3 集成: - ReplSession重写: JLine Terminal + LineReader 替代Scanner - 支持行编辑(光标移动、删除、Home/End等) - 持久化历史记录(~/.claude-code-java/history) - 上下箭头浏览输入历史 - Ctrl+C取消当前输入、Ctrl+D退出 - JLine失败时自动降级到Scanner模式 Tab补全: - ClaudeCodeCompleter: 斜杠命令补全(/后按Tab) - 支持命令别名补全(如 /q → exit) - 分组显示(Commands / Aliases) 新增命令: - /model: 显示当前AI模型信息 - /compact: 压缩对话上下文 - /cost: Token用量显示(占位) 改进: - HelpCommand清理为统一格式 - Banner增加终端信息显示和快捷键提示 - 彩色提示符使用AttributedStringBuilder Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package com.claudecode.command.impl;
|
||||
|
||||
import com.claudecode.command.CommandContext;
|
||||
import com.claudecode.command.SlashCommand;
|
||||
import com.claudecode.console.AnsiStyle;
|
||||
|
||||
/**
|
||||
* /compact 命令 —— 压缩当前对话上下文。
|
||||
* <p>
|
||||
* 对应 claude-code/src/commands/compact.ts。
|
||||
* 当前为简化实现,直接清空历史并提示用户。
|
||||
*/
|
||||
public class CompactCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "compact";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Compact conversation context";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
if (context.agentLoop() != null) {
|
||||
int before = context.agentLoop().getMessageHistory().size();
|
||||
context.agentLoop().reset();
|
||||
return AnsiStyle.green(" ✓ Context compacted: " + before + " messages → 1 (system prompt only)");
|
||||
}
|
||||
return AnsiStyle.yellow(" ⚠ No active conversation to compact.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.claudecode.command.impl;
|
||||
|
||||
import com.claudecode.command.CommandContext;
|
||||
import com.claudecode.command.SlashCommand;
|
||||
import com.claudecode.console.AnsiStyle;
|
||||
|
||||
/**
|
||||
* /cost 命令 —— 显示 Token 使用量和费用估算。
|
||||
* <p>
|
||||
* 对应 claude-code/src/commands/cost.ts。
|
||||
* 当前为占位实现,后续接入实际 Token 统计。
|
||||
*/
|
||||
public class CostCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "cost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Show token usage and cost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
int msgCount = 0;
|
||||
if (context.agentLoop() != null) {
|
||||
msgCount = context.agentLoop().getMessageHistory().size();
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.bold(" Token Usage:\n\n"));
|
||||
sb.append(" Messages: ").append(AnsiStyle.cyan(String.valueOf(msgCount))).append("\n");
|
||||
sb.append(" Input tokens: ").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n");
|
||||
sb.append(" Output tokens:").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n");
|
||||
sb.append(" Est. cost: ").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n");
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.dim(" Token tracking will be added in a future update."));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.claudecode.command.impl;
|
||||
|
||||
import com.claudecode.command.CommandContext;
|
||||
import com.claudecode.command.CommandRegistry;
|
||||
import com.claudecode.command.SlashCommand;
|
||||
import com.claudecode.console.AnsiStyle;
|
||||
|
||||
@@ -31,24 +32,21 @@ public class HelpCommand implements SlashCommand {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(AnsiStyle.bold("\n Available Commands:\n\n"));
|
||||
|
||||
for (SlashCommand cmd : context.toolRegistry() != null
|
||||
? List.<SlashCommand>of() // 这里后续会获取注册的命令
|
||||
: List.<SlashCommand>of()) {
|
||||
sb.append(String.format(" %s%-12s%s %s%n",
|
||||
AnsiStyle.CYAN, "/" + cmd.name(), AnsiStyle.RESET, cmd.description()));
|
||||
}
|
||||
|
||||
// 硬编码展示(后续重构为动态)
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/help", AnsiStyle.RESET, "Show available commands"));
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/clear", AnsiStyle.RESET, "Clear conversation history"));
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/compact", AnsiStyle.RESET, "Compact conversation context"));
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/cost", AnsiStyle.RESET, "Show token usage and cost"));
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/model", AnsiStyle.RESET, "Show or switch AI model"));
|
||||
sb.append(String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/exit", AnsiStyle.RESET, "Exit the application"));
|
||||
// 从注入的 CommandRegistry 获取不到(因为 context 里没有),所以硬编码与注册保持一致
|
||||
sb.append(formatCmd("help", "Show available commands"));
|
||||
sb.append(formatCmd("clear", "Clear conversation history"));
|
||||
sb.append(formatCmd("compact", "Compact conversation context"));
|
||||
sb.append(formatCmd("cost", "Show token usage and cost"));
|
||||
sb.append(formatCmd("model", "Show or switch AI model"));
|
||||
sb.append(formatCmd("exit", "Exit the application (also: /quit, /q)"));
|
||||
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.dim(" Tips: Press Tab for command completion, Ctrl+D to exit\n"));
|
||||
sb.append(AnsiStyle.dim(" Shortcuts: Tab to autocomplete, ↑↓ to browse history, Ctrl+D to exit\n"));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String formatCmd(String name, String desc) {
|
||||
return String.format(" %s%-12s%s %s%n", AnsiStyle.CYAN, "/" + name, AnsiStyle.RESET, desc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.claudecode.command.impl;
|
||||
|
||||
import com.claudecode.command.CommandContext;
|
||||
import com.claudecode.command.SlashCommand;
|
||||
import com.claudecode.console.AnsiStyle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* /model 命令 —— 显示或切换当前 AI 模型。
|
||||
*/
|
||||
public class ModelCommand implements SlashCommand {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "model";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Show or switch AI model";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> aliases() {
|
||||
return List.of("m");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(String args, CommandContext context) {
|
||||
// 当前只显示信息,后续可扩展为切换模型
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.bold(" Model Configuration:\n\n"));
|
||||
sb.append(" Provider: ").append(AnsiStyle.cyan("Anthropic")).append("\n");
|
||||
sb.append(" Model: ").append(AnsiStyle.cyan(
|
||||
System.getenv().getOrDefault("AI_MODEL", "claude-sonnet-4-20250514"))).append("\n");
|
||||
|
||||
if (args != null && !args.isBlank()) {
|
||||
sb.append("\n");
|
||||
sb.append(AnsiStyle.yellow(" ⚠ Model switching not yet implemented. Set AI_MODEL env variable."));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user