vtcode_tui/core_tui/widgets/
transcript.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Style},
5 widgets::{Block, Clear, Paragraph, Widget},
6};
7
8use crate::config::constants::ui;
9use crate::ui::tui::session::terminal_capabilities;
10use crate::ui::tui::session::{
11 Session, render::apply_transcript_rows, render::apply_transcript_width,
12};
13
14pub struct TranscriptWidget<'a> {
30 session: &'a mut Session,
31 show_scrollbar: bool,
32 custom_style: Option<Style>,
33}
34
35impl<'a> TranscriptWidget<'a> {
36 pub fn new(session: &'a mut Session) -> Self {
38 Self {
39 session,
40 show_scrollbar: false,
41 custom_style: None,
42 }
43 }
44
45 #[must_use]
47 pub fn show_scrollbar(mut self, show: bool) -> Self {
48 self.show_scrollbar = show;
49 self
50 }
51
52 #[must_use]
54 pub fn custom_style(mut self, style: Style) -> Self {
55 self.custom_style = Some(style);
56 self
57 }
58}
59
60impl<'a> Widget for TranscriptWidget<'a> {
61 fn render(self, area: Rect, buf: &mut Buffer) {
62 if area.height == 0 || area.width == 0 {
63 self.session.set_transcript_area(None);
64 return;
65 }
66
67 let block = Block::new()
68 .border_type(terminal_capabilities::get_border_type())
69 .style(self.session.styles.default_style())
70 .border_style(self.session.styles.border_style());
71
72 let inner = block.inner(area);
73 block.render(area, buf);
74
75 if inner.height == 0 || inner.width == 0 {
76 self.session.set_transcript_area(None);
77 return;
78 }
79 self.session.set_transcript_area(Some(inner));
80
81 let effective_height = inner.height.min(ui::TUI_MAX_VIEWPORT_HEIGHT);
84 let effective_width = inner.width.min(ui::TUI_MAX_VIEWPORT_WIDTH);
85
86 apply_transcript_rows(self.session, effective_height);
87
88 let content_width = effective_width;
89 if content_width == 0 {
90 return;
91 }
92 apply_transcript_width(self.session, content_width);
93
94 let viewport_rows = effective_height as usize;
95 let padding = usize::from(ui::INLINE_TRANSCRIPT_BOTTOM_PADDING);
96 let effective_padding = padding.min(viewport_rows.saturating_sub(1));
97 let total_rows = self.session.total_transcript_rows(content_width) + effective_padding;
98 let (top_offset, _clamped_total_rows) = self
99 .session
100 .prepare_transcript_scroll(total_rows, viewport_rows);
101 let vertical_offset = top_offset.min(self.session.scroll_manager.max_offset());
102 self.session.transcript_view_top = vertical_offset;
103
104 let visible_start = vertical_offset;
105 let scroll_area = inner;
106
107 let cached_lines = self.session.collect_transcript_window_cached(
109 content_width,
110 visible_start,
111 viewport_rows,
112 );
113
114 let fill_count = viewport_rows.saturating_sub(cached_lines.len());
116 let needs_mutation = fill_count > 0 || !self.session.queued_inputs.is_empty();
117
118 let visible_lines = if needs_mutation {
121 let mut lines = cached_lines.to_vec();
123 if fill_count > 0 {
124 let target_len = lines.len() + fill_count;
125 lines.resize_with(target_len, ratatui::text::Line::default);
126 }
127 self.session.overlay_queue_lines(&mut lines, content_width);
128 lines
129 } else {
130 cached_lines.to_vec()
132 };
133
134 if self.session.transcript_content_changed {
137 Clear.render(scroll_area, buf);
138 self.session.transcript_content_changed = false;
139 }
140 let paragraph =
141 Paragraph::new(visible_lines.clone()).style(self.session.styles.default_style());
142 paragraph.render(scroll_area, buf);
143 apply_full_width_line_backgrounds(buf, scroll_area, &visible_lines);
144 }
145}
146
147fn line_background(line: &ratatui::text::Line<'_>) -> Option<Color> {
148 line.spans.iter().find_map(|span| span.style.bg)
149}
150
151fn apply_full_width_line_backgrounds(
152 buf: &mut Buffer,
153 area: Rect,
154 lines: &[ratatui::text::Line<'_>],
155) {
156 if area.width == 0 || area.height == 0 {
157 return;
158 }
159
160 let max_rows = usize::from(area.height).min(lines.len());
161 for (row, line) in lines.iter().take(max_rows).enumerate() {
162 if let Some(bg) = line_background(line) {
163 let row_rect = Rect::new(area.x, area.y + row as u16, area.width, 1);
164 buf.set_style(row_rect, Style::default().bg(bg));
165 }
166 }
167}