1use std::fmt::Write;
2
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5use crate::Line;
6use crate::Style;
7use crate::display_width_text;
8use crate::rendering::frame::Cursor;
9use crate::rendering::frame::Frame;
10use crate::rendering::renderer::Renderer;
11use crate::{SelectOption, ViewContext};
12
13use super::TestTerminal;
14
15fn frame_from_lines(lines: &[crate::line::Line], _width: u16, _rows: u16) -> Frame {
16 Frame::new(lines.to_vec())
17 .with_cursor(Cursor { row: lines.len().saturating_sub(1), col: 0, is_visible: true })
18 .clamp_cursor()
19}
20
21pub fn render_component(mut render: impl FnMut(&ViewContext) -> Frame, width: u16, rows: u16) -> TestTerminal {
22 let ctx = ViewContext::new((width, rows));
23 let frame = render(&ctx);
24 let terminal = TestTerminal::new(width, rows);
25 let mut renderer = Renderer::new(terminal, crate::theme::Theme::default(), (width, rows));
26 renderer.render_frame(|_| frame).unwrap();
27 renderer.writer().clone()
28}
29
30pub fn render_component_with_renderer(
31 mut render: impl FnMut(&ViewContext) -> Frame,
32 renderer: &mut Renderer<TestTerminal>,
33 width: u16,
34 rows: u16,
35) {
36 let ctx = ViewContext::new((width, rows));
37 let frame = render(&ctx);
38 renderer.render_frame(|_| frame).unwrap();
39}
40
41pub fn render_lines(lines: &[crate::line::Line], width: u16, rows: u16) -> TestTerminal {
42 render_component(|_| frame_from_lines(lines, width, rows), width, rows)
43}
44
45pub fn key(code: KeyCode) -> KeyEvent {
46 KeyEvent::new(code, KeyModifiers::NONE)
47}
48
49pub fn pad(text: &str, width: usize) -> String {
50 format!("{text:<width$}")
51}
52
53pub fn cols(parts: &[(&str, usize)]) -> String {
54 let mut out = String::new();
55 for (text, width) in parts {
56 let _ = write!(out, "{text:<width$}");
57 }
58 out.trim_end().to_string()
59}
60
61pub fn sample_options() -> Vec<SelectOption> {
62 vec![
63 SelectOption { value: "a".into(), title: "Alpha".into(), description: None },
64 SelectOption { value: "b".into(), title: "Beta".into(), description: None },
65 SelectOption { value: "c".into(), title: "Gamma".into(), description: None },
66 ]
67}
68
69pub fn style_at_column(line: &Line, col: usize) -> Style {
72 let mut current = 0;
73 for span in line.spans() {
74 let width = display_width_text(span.text());
75 if col < current + width {
76 return span.style();
77 }
78 current += width;
79 }
80 Style::default()
81}