datui_lib/
pivot_melt_keys.rs1use crate::form::{FormKey, PickerKey};
5use crate::jobs::{Answer, Job};
6use crate::pivot_melt_modal::{
7 PREVIEW_INPUT_ROWS, PivotMeltFocus, PivotMeltTab, PreviewFrame, PreviewInput,
8};
9use crate::{App, AppEvent, InputMode};
10use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
11
12impl App {
13 pub(crate) fn open_pivot_builder(&mut self) {
15 let Some(state) = &self.data_table_state else {
16 return;
17 };
18 let modal = &mut self.pivot_melt_modal;
19 modal.available_columns = state.schema().iter_names().map(|s| s.to_string()).collect();
20 modal.column_dtypes = state
21 .schema()
22 .iter()
23 .map(|(n, d)| (n.to_string(), d.clone()))
24 .collect();
25 let view_rows = state.num_rows_if_valid();
26 let sorted = state.is_sorted();
27 modal.open(self.history_limit, &self.theme);
28 modal.preview.view_rows = view_rows;
29 modal.preview.sorted = sorted;
30 self.input_mode = InputMode::PivotMelt;
31 self.request_reshape_preview();
32 }
33
34 pub(crate) fn pivot_melt_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
37 let out = self.pivot_melt_form_key(event);
38 if self.pivot_melt_modal.active {
39 self.request_reshape_preview();
40 }
41 out
42 }
43
44 pub fn request_reshape_preview(&mut self) {
48 let modal = &mut self.pivot_melt_modal;
49 let wanted = modal.staged_spec();
50 let preview = &mut modal.preview;
51 if wanted == preview.wanted {
52 return;
53 }
54 preview.token = preview.token.wrapping_add(1);
55 preview.wanted = wanted;
56 if preview.running.is_none() {
57 self.spawn_reshape_preview();
58 }
59 }
60
61 fn spawn_reshape_preview(&mut self) {
64 let preview = &self.pivot_melt_modal.preview;
65 let Some(spec) = preview.wanted.clone() else {
66 return;
67 };
68 let (epoch, token) = (preview.epoch, preview.token);
69 let input = preview.input.clone();
70 let read = match &input {
71 Some(_) => None,
72 None => {
73 let Some(state) = self.data_table_state.as_ref() else {
74 self.pivot_melt_modal.preview.shown =
76 Some((spec, Err("Nothing to preview".to_string())));
77 return;
78 };
79 let small = state
82 .num_rows_if_valid()
83 .is_some_and(|rows| rows <= PREVIEW_INPUT_ROWS);
84 let sorted = (state.is_sorted() && !small).then(|| state.visible_lf());
85 let lf = if small {
86 state.visible_lf()
87 } else {
88 state.preview_lf()
89 };
90 Some((lf, sorted, state.polars_streaming()))
91 }
92 };
93 self.pivot_melt_modal.preview.running = Some(token);
94 self.spawn_job(Job::ReshapePreview { epoch, token }, None, move |_| {
95 let (input, read) = match (input, read) {
96 (Some(input), _) => (input, None),
97 (None, Some((lf, sorted, streaming))) => {
98 let message = |e: polars::prelude::PolarsError| {
99 crate::error_display::user_message_from_polars(&e)
100 };
101 let head_of = |lf: polars::prelude::LazyFrame| {
103 crate::statistics::collect_lazy(
104 lf.slice(0, PREVIEW_INPUT_ROWS as u32 + 1),
105 streaming,
106 )
107 };
108 let mut head = head_of(lf).map_err(message)?;
109 if head.height() <= PREVIEW_INPUT_ROWS
112 && let Some(sorted) = sorted
113 {
114 head = head_of(sorted).map_err(message)?;
115 }
116 let input = PreviewInput {
117 whole: head.height() <= PREVIEW_INPUT_ROWS,
118 rows: std::sync::Arc::new(head.head(Some(PREVIEW_INPUT_ROWS))),
119 };
120 (input.clone(), Some(input))
121 }
122 (None, None) => return Err("Nothing to preview".to_string()),
123 };
124 let result = crate::pivot_melt_modal::run_preview(&input.rows, &spec);
125 Ok(Answer::ReshapePreviewed {
126 input: read,
127 result,
128 })
129 });
130 }
131
132 pub(crate) fn reshape_preview_ended(
135 &mut self,
136 epoch: u64,
137 token: u64,
138 input: Option<PreviewInput>,
139 result: Result<PreviewFrame, String>,
140 ) {
141 let preview = &mut self.pivot_melt_modal.preview;
142 if !self.pivot_melt_modal.active || preview.epoch != epoch {
143 return;
144 }
145 if preview.running == Some(token) {
146 preview.running = None;
147 }
148 if preview.input.is_none() {
149 preview.input = input;
150 }
151 if token == preview.token {
152 if let Some(spec) = preview.wanted.clone() {
153 preview.shown = Some((spec, result));
154 }
155 } else if preview.running.is_none() && preview.stale() {
156 self.spawn_reshape_preview();
158 }
159 }
160
161 pub fn reshape_preview_pending(&self) -> bool {
163 self.pivot_melt_modal.active && self.pivot_melt_modal.preview.running.is_some()
164 }
165
166 fn pivot_melt_form_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
168 if event.code == KeyCode::Esc && self.pivot_computing() {
171 self.cancel_pivot();
172 return None;
173 }
174 if event.code == KeyCode::Char('?') && event.modifiers.contains(KeyModifiers::CONTROL) {
175 self.open_help_overlay();
176 return None;
177 }
178
179 let multi = self
180 .pivot_melt_modal
181 .is_multi_row(self.pivot_melt_modal.focus);
182 if let Some(picker) = self.pivot_melt_modal.picker.as_mut() {
183 match crate::form::picker_key(picker, multi, event) {
184 PickerKey::Close => self.pivot_melt_modal.picker_cancel(),
185 PickerKey::Choose => self.pivot_melt_modal.picker_choose(),
186 PickerKey::Toggle => self.pivot_melt_modal.picker_toggle(),
187 PickerKey::ChooseAndMove(forward) => {
188 self.pivot_melt_modal.picker_choose();
189 crate::form::Form::move_focus(
190 &mut self.pivot_melt_modal,
191 if forward { 1 } else { -1 },
192 );
193 }
194 PickerKey::Handled | PickerKey::Other => {}
195 }
196 return None;
197 }
198
199 self.pivot_melt_modal.attention = false;
202
203 match crate::form::key(&mut self.pivot_melt_modal, event) {
204 FormKey::Cancel => {
205 self.pivot_melt_modal.close();
206 self.input_mode = InputMode::Normal;
207 }
208 FormKey::Submit => return self.submit_pivot_melt(),
211 FormKey::Step(PivotMeltFocus::TabBar, _) => self.pivot_melt_modal.switch_tab(),
212 FormKey::Step(row, delta) => {
213 if self.pivot_melt_modal.is_choice_row(row) {
214 self.pivot_melt_modal.step_choice(delta);
215 } else {
216 self.pivot_melt_modal.step_picker_row(delta);
217 }
218 }
219 FormKey::Act(_) => self.pivot_melt_modal.open_picker(),
221 FormKey::Text(_) => {
223 if let Some(input) = self.pivot_melt_modal.focused_text_input_mut() {
224 let _ = input.handle_key(event, None);
225 }
226 }
227 FormKey::Other if event.code == KeyCode::Char('?') => self.open_help_overlay(),
228 FormKey::Moved | FormKey::Other => {}
229 }
230 None
231 }
232
233 fn submit_pivot_melt(&mut self) -> Option<AppEvent> {
236 let modal = &mut self.pivot_melt_modal;
237 match modal.active_tab {
238 PivotMeltTab::Pivot => {
239 if modal.pivot_validation_error().is_some() {
240 modal.attention = true;
241 None
242 } else {
243 modal.build_pivot_spec().map(AppEvent::Pivot)
244 }
245 }
246 PivotMeltTab::Melt => {
247 if modal.melt_validation_error().is_some() {
248 modal.attention = true;
249 None
250 } else {
251 modal.build_melt_spec().map(AppEvent::Melt)
252 }
253 }
254 }
255 }
256}