From a05a58ba185573b3fa261b983433dda2b38528ff Mon Sep 17 00:00:00 2001 From: lixueyi Date: Tue, 4 Aug 2026 11:33:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(chat=20ui):=20=E4=BF=AE=E5=A4=8D=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=95=8C=E9=9D=A2=E6=BB=9A=E5=8A=A8=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E5=92=8C=E6=BB=9A=E5=8A=A8=E6=8C=87=E7=A4=BA=E5=99=A8=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 基于可视区域高度动态计算最大滚动偏移量,避免滚动超出合理范围 2. 在渲染时校验并修正滚动偏移,防止因布局变化导致视图异常 3. 修正滚动进度百分比的计算逻辑,使其符合实际滚动位置 4. 优化代码格式与可读性,调整条件分支的换行排版 --- src/apps/cli/src/ui/chat/render.rs | 55 ++++++++++++++++++------------ src/apps/cli/src/ui/chat/scroll.rs | 16 ++++++--- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/apps/cli/src/ui/chat/render.rs b/src/apps/cli/src/ui/chat/render.rs index f0d6190d12..93fcf5d45d 100644 --- a/src/apps/cli/src/ui/chat/render.rs +++ b/src/apps/cli/src/ui/chat/render.rs @@ -295,6 +295,24 @@ impl ChatView { return; } + // Reconcile scroll_offset against the freshly computed total_lines. + // Presentation toggles (e.g. /thinking) invalidate the render cache + // and shrink total_lines, but leave scroll_offset stale — clamping + // here prevents the view from being pinned to the top with a frozen + // scrollbar. + if self.browse_mode { + if total_lines <= visible_lines { + self.browse_mode = false; + self.auto_scroll = true; + self.scroll_offset = 0; + } else { + let max_scroll = total_lines.saturating_sub(visible_lines); + if self.scroll_offset > max_scroll { + self.scroll_offset = max_scroll; + } + } + } + // prefix_sum[i] = total lines of messages 0..i (exclusive end). let prefix_sum = layout.prefix_sum; @@ -397,12 +415,12 @@ impl ChatView { // ── Scroll indicator ── if self.browse_mode { - let progress_pct = if self.scroll_offset == 0 { + let max_scroll = total_lines.saturating_sub(visible_lines); + let progress_pct = if max_scroll == 0 { 100 - } else if self.scroll_offset >= total_lines { - 0 } else { - ((total_lines - self.scroll_offset) * 100 / total_lines).min(100) + let effective = self.scroll_offset.min(max_scroll); + ((max_scroll - effective) * 100 / max_scroll).min(100) }; let scroll_indicator = format!("{}%", progress_pct); @@ -671,23 +689,21 @@ impl ChatView { // round-trip instead of being inverted by XOR. let collapsed = match self.presentation.thinking { crate::config::ThinkingMode::Show => false, - crate::config::ThinkingMode::Hide => { - self.thinking_disclosures - .is_collapsed(&thinking_block_id, true) - } + crate::config::ThinkingMode::Hide => self + .thinking_disclosures + .is_collapsed(&thinking_block_id, true), }; // In Show mode the header is non-interactive: hide the // caret so users don't expect to click-collapse an // already fully-expanded block. - let caret = if self.presentation.thinking - == crate::config::ThinkingMode::Show - { - "" - } else if collapsed { - "\u{25b8}" - } else { - "\u{25be}" - }; + let caret = + if self.presentation.thinking == crate::config::ThinkingMode::Show { + "" + } else if collapsed { + "\u{25b8}" + } else { + "\u{25be}" + }; let header_y = items.len().min(u16::MAX as usize) as u16; thinking_regions.push((thinking_block_id.clone(), header_y, header_y)); @@ -795,10 +811,7 @@ impl ChatView { }; plain_lines.push(format!("{line_prefix}{wrapped}")); items.push(ListItem::new(Line::from(vec![ - Span::styled( - line_prefix, - self.theme.style(StyleKind::Primary), - ), + Span::styled(line_prefix, self.theme.style(StyleKind::Primary)), Span::raw(wrapped), ]))); } diff --git a/src/apps/cli/src/ui/chat/scroll.rs b/src/apps/cli/src/ui/chat/scroll.rs index 4c428eb925..3e07dc5663 100644 --- a/src/apps/cli/src/ui/chat/scroll.rs +++ b/src/apps/cli/src/ui/chat/scroll.rs @@ -81,13 +81,17 @@ impl ChatView { pub(crate) fn scroll_up(&mut self, lines: usize, total_message_lines: usize) { self.committed_message_anchor = None; + let visible = self + .messages_area + .map(|a| a.height.max(1) as usize) + .unwrap_or(20); + let max_offset = total_message_lines.saturating_sub(visible); if self.browse_mode { - self.scroll_offset = - (self.scroll_offset + lines).min(total_message_lines.saturating_sub(1)); + self.scroll_offset = (self.scroll_offset + lines).min(max_offset); } else { self.browse_mode = true; self.auto_scroll = false; - self.scroll_offset = lines; + self.scroll_offset = lines.min(max_offset); } } @@ -107,7 +111,11 @@ impl ChatView { self.committed_message_anchor = None; self.browse_mode = true; self.auto_scroll = false; - self.scroll_offset = total_message_lines.saturating_sub(1); + let visible = self + .messages_area + .map(|a| a.height.max(1) as usize) + .unwrap_or(20); + self.scroll_offset = total_message_lines.saturating_sub(visible); } pub(crate) fn scroll_to_bottom(&mut self) {