fix: Banner边框上下对齐 - 加入ANSI可见宽度计算+右侧闭合│+精确padding
- 顶部边框公式修正: innerWidth - titleVisibleLen - 每行body加右侧闭合│边框 - visibleLength()去除ANSI转义计算真实宽度 - 右侧信息区域自动pad到固定宽度 - Logo宽度调整为20字符 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
// 输出行
|
||||
out.println(" " + V + " "
|
||||
// 右侧信息需要计算可见长度,补齐空格到 rightAreaWidth
|
||||
int rightVisible = visibleLength(rightPart);
|
||||
int rightPadding = rightAreaWidth - rightVisible;
|
||||
if (rightPadding < 0) rightPadding = 0;
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user