fix: header scrolls with messages, auto-scroll pauses on scroll-up
- Header/logo moved from fixed layout to messagesArea first items (matches official LogoHeader behavior: scrolls with messages, not pinned) - addMessageInternal preserves scrollOffset when user has scrolled up (matches official sticky-scroll: only auto-scrolls when at bottom) - Terminal title uses ConsolePatcher.getOriginalOut() to bypass interception Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -211,21 +211,12 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
setCursorPosition(cursorRow, cursorCol);
|
||||
}
|
||||
|
||||
int headerHeight = 8; // 6 content rows + 2 border lines
|
||||
int bottomHeight = 4 + inputLineCount;
|
||||
int messagePaddingTop = 1;
|
||||
int maxMessageLines = Math.max(1, h - headerHeight - bottomHeight - messagePaddingTop);
|
||||
|
||||
// 终端高度太小时,隐藏标题框以腾出消息空间
|
||||
boolean showHeader = h >= 20;
|
||||
if (!showHeader) {
|
||||
maxMessageLines = Math.max(1, h - bottomHeight - messagePaddingTop);
|
||||
}
|
||||
// 标题框现在是消息区的一部分,会随消息一起滚动(匹配官方 LogoHeader 行为)
|
||||
int maxMessageLines = Math.max(1, h - bottomHeight - messagePaddingTop);
|
||||
|
||||
List<Renderable> layout = new ArrayList<>();
|
||||
if (showHeader) {
|
||||
layout.add(headerBox(w));
|
||||
}
|
||||
layout.add(messagesArea(s, maxMessageLines));
|
||||
layout.add(Spacer.create());
|
||||
layout.add(statusBar(w, h));
|
||||
@@ -238,8 +229,8 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
.flexDirection(FlexDirection.COLUMN).width(w).height(h);
|
||||
}
|
||||
|
||||
/** 标题框 — 保留原始 ASCII Logo 样式(双列布局) */
|
||||
private Renderable headerBox(int w) {
|
||||
/** 标题框行列表 — ASCII Logo + 信息(作为消息区首部随消息滚动) */
|
||||
private List<Renderable> headerLines() {
|
||||
// ASCII 冒烟咖啡杯
|
||||
String[] logo = {
|
||||
" ) ) ) ",
|
||||
@@ -250,8 +241,6 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
" ╰─┬────┬─╯ "
|
||||
};
|
||||
int logoWidth = 19;
|
||||
int sepWidth = 3; // " │ "
|
||||
int rightWidth = Math.max(0, w - 4 - logoWidth - sepWidth - 2);
|
||||
|
||||
// 构建右侧信息行(带颜色高亮)
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -293,19 +282,17 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
rightPart
|
||||
));
|
||||
}
|
||||
|
||||
return Box.of(rows.toArray(new Renderable[0]))
|
||||
.flexDirection(FlexDirection.COLUMN)
|
||||
.borderStyle(BorderStyle.ROUND)
|
||||
.borderColor(Color.BRIGHT_MAGENTA)
|
||||
.paddingX(1)
|
||||
.width(w);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 消息列表(带虚拟滚动) */
|
||||
private Renderable messagesArea(TuiState s, int maxLines) {
|
||||
List<Renderable> allItems = new ArrayList<>();
|
||||
|
||||
// 标题框内容(作为消息区首部,随消息一起滚动 — 匹配官方 LogoHeader 行为)
|
||||
allItems.addAll(headerLines());
|
||||
allItems.add(Text.of(" ")); // 空行分隔
|
||||
|
||||
// 初始提示消息
|
||||
allItems.add(Text.of(
|
||||
Text.of("● ").color(Color.BRIGHT_BLUE),
|
||||
@@ -1219,8 +1206,12 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
private void addMessageInternal(UIMessage msg, TuiState s) {
|
||||
List<UIMessage> newMsgs = new ArrayList<>(s.messages);
|
||||
newMsgs.add(msg);
|
||||
// 匹配官方 sticky-scroll 行为:
|
||||
// - 用户在底部(scrollOffset=0)→ 新消息保持在底部
|
||||
// - 用户已上滚(scrollOffset>0)→ 保持当前位置,不自动跳到底部
|
||||
int newOffset = s.scrollOffset;
|
||||
setState(new TuiState(s.inputText, Collections.unmodifiableList(newMsgs),
|
||||
0, s.thinking, s.thinkingText));
|
||||
newOffset, s.thinking, s.thinkingText));
|
||||
}
|
||||
|
||||
/** 追加 token 到当前流式助手消息 */
|
||||
@@ -1503,15 +1494,18 @@ public class ClaudeCodeComponent extends Component<ClaudeCodeComponent.TuiState>
|
||||
/**
|
||||
* 设置终端标题(OSC 0 escape sequence)。
|
||||
* 匹配官方 Claude Code 的 useTerminalTitle hook 行为。
|
||||
* Windows 使用 ANSI OSC 0,兼容 Windows Terminal / ConEmu 等现代终端。
|
||||
* 必须绕过 jink 的 ConsolePatcher(它会拦截 System.out/err),
|
||||
* 直接写入原始输出流。
|
||||
*/
|
||||
private static void setTerminalTitle(String title) {
|
||||
if (title == null || title.isBlank()) return;
|
||||
try {
|
||||
// 绕过 ConsolePatcher 拦截,直接写入终端
|
||||
PrintStream raw = io.mybatis.jink.util.ConsolePatcher.getOriginalOut();
|
||||
// OSC 0: Set window title and icon name
|
||||
// Format: ESC ] 0 ; <title> BEL
|
||||
System.err.print("\033]0;" + title + "\007");
|
||||
System.err.flush();
|
||||
raw.print("\033]0;" + title + "\007");
|
||||
raw.flush();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user