datui_lib/widgets/
copy.rs1use crate::app::modals::copy_modal::{CopyFocus, CopyModal};
4use crate::render::context::RenderContext;
5use crate::widgets::ui::{FormLine, FormValue, FormView, HintBar};
6use datui_cli::keys::Context;
7use ratatui::buffer::Buffer;
8use ratatui::layout::Rect;
9use ratatui::style::Style;
10
11const LABEL_WIDTH: u16 = 9;
13
14fn row_label(focus: CopyFocus) -> &'static str {
15 match focus {
16 CopyFocus::Scope => "Scope:",
17 CopyFocus::Column => "Column:",
18 CopyFocus::Format => "Format:",
19 CopyFocus::Header => "Header:",
20 }
21}
22
23pub fn render_copy_modal(area: Rect, buf: &mut Buffer, modal: &mut CopyModal, ctx: &RenderContext) {
24 let footer = HintBar::from_ctx(ctx)
25 .screen(Context::Copy)
26 .group("Form")
27 .key("Enter")
28 .weight(3);
29 let footer = match modal.focus {
30 CopyFocus::Header => footer.key_as("Space", "Toggle"),
31 CopyFocus::Column => footer.key_as("Space", "Pick"),
32 CopyFocus::Scope | CopyFocus::Format => footer.key("← / →"),
33 };
34 let footer = footer.weight(2).key("Tab").weight(1).key("Esc").weight(4);
35 let rows = modal
36 .row_order()
37 .into_iter()
38 .map(|row| {
39 let value = match row {
40 CopyFocus::Scope => FormValue::Choice(modal.scope.as_str()),
41 CopyFocus::Format => FormValue::Choice(modal.format.as_str()),
42 CopyFocus::Header => FormValue::Toggle(modal.header()),
43 CopyFocus::Column => match modal.column.as_deref() {
44 Some(column) => FormValue::Choice(column),
45 None => FormValue::Placeholder("none"),
46 },
47 };
48 FormLine::Field(row, row_label(row), value)
49 })
50 .collect();
51 let status = match modal.spec_line() {
53 Ok(line) => (line, Style::default().fg(ctx.text_primary)),
54 Err(gap) if modal.attention => (gap, Style::default().fg(ctx.warning)),
55 Err(gap) => (gap, Style::default().fg(ctx.dimmed)),
56 };
57 FormView {
58 title: "Copy",
59 screen: Context::Copy,
60 footer: Some(footer),
61 label_width: LABEL_WIDTH,
62 rows,
63 focused: Some(modal.focus),
64 picker: modal.picker.as_ref(),
65 status: Some(status),
66 shields: true,
67 }
68 .render::<CopyModal>(area, buf, ctx);
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::app::modals::copy_modal::{CopyContext, CopyScope};
75
76 fn render_to_text(modal: &mut CopyModal) -> String {
77 let area = Rect::new(0, 0, 46, 14);
78 let mut buf = Buffer::empty(area);
79 let ctx = RenderContext::for_test();
80 render_copy_modal(area, &mut buf, modal, &ctx);
81 crate::tests::buffer_text(&buf)
82 }
83
84 #[test]
85 fn the_dialog_echoes_every_choice_and_the_spec() {
86 let mut modal = CopyModal::new();
87 modal.open(
88 vec!["city".into(), "pop".into()],
89 None,
90 CopyContext {
91 row_number: 1235,
92 view_rows: 42,
93 view_cols: 8,
94 total_rows: Some(1_000_000),
95 },
96 );
97 let text = render_to_text(&mut modal);
98 assert!(text.contains("Scope:"), "{text}");
99 assert!(text.contains("Row"), "{text}");
100 assert!(text.contains("Format:"), "{text}");
101 assert!(text.contains("Copy row 1,235 as TSV"), "{text}");
102 }
103
104 #[test]
105 fn the_python_scope_shows_only_the_scope_row() {
106 let mut modal = CopyModal::new();
107 modal.open(vec!["city".into()], None, CopyContext::default());
108 modal.scope = CopyScope::Python;
109 let text = render_to_text(&mut modal);
110 assert!(text.contains("Python (Polars)"), "{text}");
111 assert!(!text.contains("Format:"), "{text}");
112 assert!(!text.contains("Header:"), "{text}");
113 assert!(
114 text.contains("Copy the view as a Python (Polars) script"),
115 "{text}"
116 );
117 }
118
119 #[test]
120 fn the_open_picker_lists_and_narrows() {
121 let mut modal = CopyModal::new();
122 modal.open(
123 vec!["city".into(), "population".into()],
124 None,
125 CopyContext::default(),
126 );
127 modal.scope = CopyScope::Cell;
128 modal.focus = CopyFocus::Column;
129 modal.open_picker();
130 let text = render_to_text(&mut modal);
131 assert!(text.contains("city"), "{text}");
132 assert!(text.contains("population"), "{text}");
133 modal
134 .picker
135 .as_mut()
136 .unwrap()
137 .filter_key('p', crossterm::event::KeyModifiers::NONE);
138 let text = render_to_text(&mut modal);
139 assert!(text.contains("population"), "{text}");
140 assert!(!text.contains("\n city"), "{text}");
141 }
142}