返回 CodeWhale
transcript_legacy.rs
根目录 / crates / tui / src / tui / agent_focus / transcript_legacy.rs
1 //! Frozen focused-child transcript painter; exact base function bytes.
2 use super::*;
3 use ratatui::widgets::{Paragraph, Widget};
4
5 pub(crate) fn render_focus(app: &mut App, area: Rect, buf: &mut Buffer) {
6 let Some(focus) = app.agent_focus.as_ref() else {
7 return;
8 };
9 let theme = app.ui_theme;
10 let background = Style::default().bg(theme.surface_bg);
11 buf.set_style(area, background);
12 if area.height == 0 || area.width == 0 {
13 return;
14 }
15 let (status_glyph, status_word) = focused_status(app).unwrap_or(('○', "unknown".to_string()));
16 let banner = app
17 .tr(MessageId::AgentFocusBanner)
18 .replace("{agent}", &focus.label)
19 .replace("{status}", &status_word);
20 let mut banner_spans = vec![
21 Span::styled(
22 format!("{status_glyph} "),
23 Style::default().fg(theme.accent_action),
24 ),
25 Span::styled(
26 banner,
27 Style::default()
28 .fg(theme.accent_action)
29 .add_modifier(Modifier::BOLD),
30 ),
31 ];
32 // The worker's effective posture, in the same dot chain: what it may do
33 // is stated where its conversation is read, not hidden in a role name.
34 if let Some(posture) = focused_posture(app) {
35 banner_spans.push(Span::styled(
36 format!(" · {posture}"),
37 Style::default().fg(theme.text_muted),
38 ));
39 }
40 let banner_line = Line::from(banner_spans);
41 let width = area.width.max(1);
42 let mut lines: Vec<Line<'static>> = Vec::new();
43 if focus.omitted_messages > 0 {
44 lines.push(Line::from(Span::styled(
45 app.tr(MessageId::AgentFocusOmitted)
46 .replace("{count}", &focus.omitted_messages.to_string()),
47 Style::default().fg(theme.text_muted),
48 )));
49 }
50 let result_cells = result_cells(app, focus.result.as_ref());
51 if focus.cells.is_empty() && result_cells.is_empty() && focus.local_cells.is_empty() {
52 lines.push(Line::from(Span::styled(
53 app.tr(MessageId::AgentFocusNoTranscript)
54 .replace("{agent}", &focus.label),
55 Style::default().fg(theme.text_muted),
56 )));
57 }
58 for cell in focus
59 .cells
60 .iter()
61 .chain(result_cells.iter())
62 .chain(focus.local_cells.iter())
63 {
64 lines.extend(cell.transcript_lines(width));
65 lines.push(Line::default());
66 }
67 let visible = usize::from(area.height.saturating_sub(1)).max(1);
68 let total = lines.len();
69 let max_top = total.saturating_sub(visible);
70 let delta = app.viewport.pending_scroll_delta;
71 app.viewport.pending_scroll_delta = 0;
72 let Some(focus) = app.agent_focus.as_mut() else {
73 return;
74 };
75 let current = focus.scroll_top.unwrap_or(max_top);
76 let next = if delta < 0 {
77 current.saturating_sub(delta.unsigned_abs() as usize)
78 } else {
79 current.saturating_add(delta as usize)
80 }
81 .min(max_top);
82 focus.scroll_top = if next >= max_top { None } else { Some(next) };
83 focus.last_visible = visible;
84 focus.last_total = total;
85 let top = focus.scroll_top.unwrap_or(max_top);
86
87 let banner_area = Rect::new(area.x, area.y, area.width, 1);
88 Paragraph::new(banner_line)
89 .style(background)
90 .render(banner_area, buf);
91 let body_area = Rect::new(
92 area.x,
93 area.y.saturating_add(1),
94 area.width,
95 area.height.saturating_sub(1),
96 );
97 let shown: Vec<Line<'static>> = lines.into_iter().skip(top).take(visible).collect();
98 Paragraph::new(shown)
99 .style(background)
100 .render(body_area, buf);
101 // The focused view owns the transcript geometry for paging keys.
102 app.viewport.last_transcript_area = Some(body_area);
103 app.viewport.last_transcript_visible = visible;
104 app.viewport.last_transcript_total = total;
105 app.viewport.last_transcript_top = top;
106 }
107
108 // Frozen source boundary.
109
109 lines RUST