toon_format/tui/components/
repl_panel.rs1use ratatui::{
2 layout::{
3 Constraint,
4 Direction,
5 Layout,
6 Margin,
7 Rect,
8 },
9 style::{
10 Color,
11 Modifier,
12 Style,
13 },
14 text::{
15 Line,
16 Span,
17 },
18 widgets::{
19 Block,
20 Borders,
21 Paragraph,
22 Scrollbar,
23 ScrollbarOrientation,
24 ScrollbarState,
25 Wrap,
26 },
27 Frame,
28};
29
30use crate::tui::state::{
31 AppState,
32 ReplLineKind,
33};
34
35pub struct ReplPanel;
36
37impl ReplPanel {
38 pub fn render(f: &mut Frame, area: Rect, app: &mut AppState) {
39 let chunks = Layout::default()
40 .direction(Direction::Vertical)
41 .constraints([Constraint::Min(10), Constraint::Length(3)])
42 .split(area);
43
44 Self::render_output(f, chunks[0], app);
45 Self::render_input(f, chunks[1], app);
46 }
47
48 fn render_output(f: &mut Frame, area: Rect, app: &AppState) {
49 let lines: Vec<Line> = app
50 .repl
51 .output
52 .iter()
53 .skip(app.repl.scroll_offset)
54 .map(|line| {
55 let style = match line.kind {
56 ReplLineKind::Prompt => Style::default().fg(Color::Cyan),
57 ReplLineKind::Success => Style::default().fg(Color::Green),
58 ReplLineKind::Error => Style::default().fg(Color::Red),
59 ReplLineKind::Info => Style::default().fg(Color::Yellow),
60 };
61 Line::from(Span::styled(&line.content, style))
62 })
63 .collect();
64
65 let block = Block::default()
66 .borders(Borders::ALL)
67 .border_style(Style::default().fg(Color::Cyan))
68 .title(" REPL Session (Ctrl+R to toggle, Esc to close) ");
69
70 let paragraph = Paragraph::new(lines)
71 .block(block)
72 .wrap(Wrap { trim: false });
73
74 f.render_widget(paragraph, area);
75
76 if app.repl.output.len() > (area.height as usize - 2) {
77 let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
78 .begin_symbol(Some("↑"))
79 .end_symbol(Some("↓"));
80
81 let mut scrollbar_state =
82 ScrollbarState::new(app.repl.output.len()).position(app.repl.scroll_offset);
83
84 f.render_stateful_widget(
85 scrollbar,
86 area.inner(Margin {
87 vertical: 1,
88 horizontal: 0,
89 }),
90 &mut scrollbar_state,
91 );
92 }
93 }
94
95 fn render_input(f: &mut Frame, area: Rect, app: &AppState) {
96 let prompt = Span::styled(
97 "> ",
98 Style::default()
99 .fg(Color::Cyan)
100 .add_modifier(Modifier::BOLD),
101 );
102
103 let input_text = Span::raw(&app.repl.input);
104 let cursor = Span::styled("█", Style::default().fg(Color::White));
105
106 let line = Line::from(vec![prompt, input_text, cursor]);
107
108 let block = Block::default()
109 .borders(Borders::ALL)
110 .border_style(Style::default().fg(Color::Cyan));
111
112 let paragraph = Paragraph::new(line).block(block);
113
114 f.render_widget(paragraph, area);
115 }
116}