vtcode_tui/core_tui/widgets/
transcript.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::Style,
5 widgets::{Block, Clear, Paragraph, Widget, Wrap},
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 let paragraph = Paragraph::new(visible_lines)
135 .style(self.session.styles.default_style())
136 .wrap(Wrap { trim: true });
137
138 if self.session.transcript_content_changed {
141 Clear.render(scroll_area, buf);
142 self.session.transcript_content_changed = false;
143 }
144 paragraph.render(scroll_area, buf);
145 }
146}