fix: Banner边框上下对齐 - 加入ANSI可见宽度计算+右侧闭合│+精确padding

- 顶部边框公式修正: innerWidth - titleVisibleLen
- 每行body加右侧闭合│边框
- visibleLength()去除ANSI转义计算真实宽度
- 右侧信息区域自动pad到固定宽度
- Logo宽度调整为20字符

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
liuzh 1 month ago
parent e7eb5e33b1
commit d8a7171325
  1. 92
      src/main/java/com/claudecode/console/BannerPrinter.java

@ -1,6 +1,7 @@
package com.claudecode.console;
import java.io.PrintStream;
import java.util.regex.Pattern;
/**
* Banner 打印器 对应 claude-code/src/components/Banner.tsx
@ -12,54 +13,30 @@ public class BannerPrinter {
private static final String VERSION = "0.1.0-SNAPSHOT";
// 边框字符
private static final String TL = "╭", TR = "╮", BL = "╰", BR = "╯";
private static final String H = "─", V = "│";
// 匹配 ANSI 转义序列的正则
private static final Pattern ANSI_PATTERN = Pattern.compile("\033\\[[0-9;]*m");
/**
* 打印带边框的启动 Banner
*
* @param out 输出流
* @param provider API 提供者名称
* @param model 模型名称
* @param baseUrl API URL
* @param workDir 工作目录
* @param toolCount 工具数量
* @param cmdCount 命令数量
* @param termInfo 终端信息
*/
public static void printBoxed(PrintStream out, String provider, String model,
String baseUrl, String workDir,
int toolCount, int cmdCount, String termInfo) {
int boxWidth = 90;
String hr = H.repeat(boxWidth - 2);
// 内容宽度(不含左右边框 │)
int innerWidth = 88;
// Logo(ASCII 冒烟咖啡杯 — 每行精确 16 字符宽
// Logo(ASCII 冒烟咖啡杯 — 每行精确 20 字符宽,含左右空格)
String[] logo = {
" ) ) ) ",
" ╭────────╮ ",
" │ ~~~~~~ │ ",
" │ CLAUDE │ ",
" │ CODE │ ",
" │ ~~~~~~ │─╮ ",
" │ CLAUDE │ ",
" │ CODE │─╯ ",
" ╰─┬────┬─╯ "
};
int logoVisualWidth = 20;
// 右侧信息
String titleLine = "Claude Code Java v" + VERSION;
String descLine = "Describe a task to get started.";
String tipLine = "Tip: /help for commands, Tab to complete";
// 打印顶部边框
out.println();
out.println(" " + TL + "─── " + AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN
+ "Claude Code Java" + AnsiStyle.RESET + AnsiStyle.DIM + " v" + VERSION
+ AnsiStyle.RESET + " " + H.repeat(boxWidth - 28 - VERSION.length()) + TR);
// Logo + 右侧信息(双列布局)
int logoWidth = 16; // logo 视觉宽度
int rightStart = logoWidth + 4;
int contentWidth = boxWidth - 4; // 边框内可用宽度
// 右侧信息(纯可见文本 + ANSI 颜色)
String[] rightInfo = {
"",
AnsiStyle.BOLD + "Welcome!" + AnsiStyle.RESET,
@ -70,23 +47,50 @@ public class BannerPrinter {
AnsiStyle.DIM + "Tools: " + toolCount + " | Commands: " + cmdCount + " | " + termInfo + AnsiStyle.RESET,
};
// ── 顶部边框 ──
// 格式: ╭─── Claude Code Java v0.1.0-SNAPSHOT ───...───╮
String title = "Claude Code Java";
String versionStr = " v" + VERSION + " ";
String prefix = "─── ";
// 可见宽度: prefix + title + versionStr + 补充的 ─
int titleVisibleLen = prefix.length() + title.length() + versionStr.length();
int trailingDashes = innerWidth - titleVisibleLen;
if (trailingDashes < 1) trailingDashes = 1;
out.println();
out.println(" ╭" + prefix
+ AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN + title + AnsiStyle.RESET
+ AnsiStyle.DIM + versionStr + AnsiStyle.RESET
+ "─".repeat(trailingDashes) + "╮");
// ── 内容行(双列布局) ──
// 每行格式: │ [logo] │ [rightInfo] ... padding ... │
// 中间分隔符 " │ " 占 3 个可见字符
int separatorWidth = 3;
int rightAreaWidth = innerWidth - logoVisualWidth - separatorWidth;
int maxRows = Math.max(logo.length, rightInfo.length);
for (int i = 0; i < maxRows; i++) {
String leftPart = i < logo.length ? logo[i] : "";
String rightPart = i < rightInfo.length ? rightInfo[i] : "";
// 左侧 logo 部分(固定宽度,无 ANSI 所以直接 pad)
String paddedLeft = padRight(leftPart, logoWidth);
// 左侧 logo(纯文本,直接 pad)
String paddedLeft = padRight(leftPart, logoVisualWidth);
// 右侧信息需要计算可见长度,补齐空格到 rightAreaWidth
int rightVisible = visibleLength(rightPart);
int rightPadding = rightAreaWidth - rightVisible;
if (rightPadding < 0) rightPadding = 0;
// 输出行
out.println(" " + V + " "
out.println(" │"
+ AnsiStyle.BRIGHT_CYAN + paddedLeft + AnsiStyle.RESET
+ AnsiStyle.DIM + " │ " + AnsiStyle.RESET
+ rightPart);
+ rightPart + " ".repeat(rightPadding)
+ "│");
}
// 底部边框
out.println(" " + BL + hr + BR);
// ── 底部边框 ──
out.println(" " + "─".repeat(innerWidth) + "╯");
}
/**
@ -100,6 +104,12 @@ public class BannerPrinter {
out.println();
}
/** 计算去除 ANSI 转义后的可见字符宽度 */
private static int visibleLength(String s) {
if (s == null || s.isEmpty()) return 0;
return ANSI_PATTERN.matcher(s).replaceAll("").length();
}
/** 右侧补空格到指定视觉宽度 */
private static String padRight(String s, int width) {
int len = s.length();

Loading…
Cancel
Save