1use cli_boilerplate_automation::{bait::TransformExt, broc::EnvVars, env_vars, unwrap};
2
3use crate::{
4 SSS, Selection, Selector,
5 action::ActionExt,
6 event::EventSender,
7 message::{Event, Interrupt},
8 nucleo::{Status, injector::WorkerInjector},
9 ui::{DisplayUI, OverlayUI, PickerUI, PreviewUI, Rect, UI},
10};
11
12#[derive(Default, Debug)]
14pub struct State {
15 last_id: Option<u32>,
16 interrupt: Interrupt,
17 interrupt_payload: String,
18
19 pub(crate) input: String,
21 pub(crate) col: Option<usize>,
22 pub(crate) iterations: u32,
23 pub(crate) preview_visible: bool,
24 pub(crate) layout: [Rect; 4], pub(crate) overlay_index: Option<usize>,
26 pub(crate) synced: [bool; 2], pub(crate) events: Event,
29
30 pub preview_set_payload: Option<String>,
32 pub preview_payload: String,
34 stashed_preview_visibility: Option<bool>,
36 pub should_quit: bool,
40 pub should_quit_nomatch: bool,
42 pub filtering: bool,
43}
44
45impl State {
46 pub fn new() -> Self {
47 Self {
49 last_id: None,
50 interrupt: Interrupt::None,
51 interrupt_payload: String::new(),
52
53 preview_payload: String::new(),
54 preview_set_payload: None,
55 preview_visible: false,
56 stashed_preview_visibility: None,
57 layout: [Rect::default(); 4],
58 overlay_index: None,
59 col: None,
60
61 input: String::new(),
62 iterations: 0,
63 synced: [false; 2],
64
65 events: Event::empty(),
66 should_quit: false,
67 should_quit_nomatch: false,
68 filtering: true,
69 }
70 }
71 pub fn contains(&self, event: Event) -> bool {
74 self.events.contains(event)
75 }
76
77 pub fn payload(&self) -> &String {
78 &self.interrupt_payload
79 }
80
81 pub fn interrupt(&self) -> Interrupt {
82 self.interrupt
83 }
84
85 pub fn set_interrupt(&mut self, interrupt: Interrupt, payload: String) {
86 self.interrupt = interrupt;
87 self.interrupt_payload = payload;
88 }
89
90 pub fn clear_interrupt(&mut self) {
91 self.interrupt = Interrupt::None;
92 self.interrupt_payload.clear();
93 }
94
95 pub fn insert(&mut self, event: Event) {
96 self.events.insert(event);
97 }
98
99 pub fn overlay_index(&self) -> Option<usize> {
100 self.overlay_index
101 }
102 pub fn preview_set_payload(&self) -> Option<String> {
103 self.preview_set_payload.clone()
104 }
105 pub fn preview_payload(&self) -> &String {
106 &self.preview_payload
107 }
108 pub fn stashed_preview_visibility(&self) -> Option<bool> {
109 self.stashed_preview_visibility
110 }
111
112 pub(crate) fn update_input(&mut self, new_input: &str) -> bool {
114 let changed = self.input.cmp_replace(new_input.to_string());
115 if changed {
116 self.insert(Event::QueryChange);
117 }
118 changed
119 }
120
121 pub(crate) fn update_preview(&mut self, context: &str) -> bool {
122 let changed = self.preview_payload.cmp_replace(context.into());
123 if changed {
124 self.insert(Event::PreviewChange);
125 }
126 changed
127 }
128
129 pub(crate) fn update_preview_set(&mut self, context: String) -> bool {
130 let next = Some(context);
131 let changed = self.preview_set_payload.cmp_replace(next);
132 if changed {
133 self.insert(Event::PreviewSet);
134 }
135 changed
136 }
137
138 pub(crate) fn update_preview_unset(&mut self) {
139 let changed = self.preview_set_payload.cmp_replace(None);
140 if changed {
141 self.insert(Event::PreviewSet);
142 }
143 }
144
145 pub(crate) fn update_layout(&mut self, new_layout: [Rect; 4]) -> bool {
146 let changed = self.layout.cmp_replace(new_layout);
147 if changed {
148 self.insert(Event::Resize);
149 }
150 changed
151 }
152
153 pub(crate) fn update_preview_visible(&mut self, preview_ui: &PreviewUI) -> bool {
155 let visible = preview_ui.visible();
156 let changed = self.preview_visible.cmp_replace(visible);
157 if changed && visible {
158 self.insert(Event::PreviewChange);
159 }
160 changed
161 }
162
163 pub(crate) fn update<'a, T: SSS, S: Selection, A: ActionExt>(
164 &'a mut self,
165 picker_ui: &'a PickerUI<T, S>,
166 overlay_ui: &'a Option<OverlayUI<A>>,
167 ) {
168 if self.iterations == 0 {
169 self.insert(Event::Start);
170 }
171 self.iterations += 1;
172
173 self.update_input(&picker_ui.input.input);
174 self.col = picker_ui.results.col();
175
176 let status = &picker_ui.results.status;
177 self.synced[1] |= status.running;
178 if status.changed {
179 if !picker_ui.results.status.running {
181 if !self.synced[0] {
182 if picker_ui.results.status.item_count > 0 {
184 self.insert(Event::Synced);
185 self.synced[0] = true;
186 }
187 } else {
188 log::trace!("resynced on iteration {}", self.iterations);
191 self.insert(Event::Resynced);
192 }
193 }
194 }
195
196 if let Some(o) = overlay_ui {
197 if self.overlay_index != o.index() {
198 self.insert(Event::OverlayChange);
199 self.overlay_index = o.index()
200 }
201 self.overlay_index = o.index()
202 }
203
204 let new_id = get_current(picker_ui).map(|x| x.0);
205 let changed = self.last_id != get_current(picker_ui).map(|x| x.0);
206 if changed {
207 self.last_id = new_id;
208 self.insert(Event::CursorChange);
209 }
210 }
212
213 pub(crate) fn dispatcher<'a, 'b: 'a, T: SSS, S: Selection>(
215 &'a mut self,
216 ui: &'a mut UI,
217 picker_ui: &'a mut PickerUI<'b, T, S>,
218 footer_ui: &'a mut DisplayUI,
219 preview_ui: &'a mut Option<PreviewUI>,
220 event_controller: &'a EventSender,
221 ) -> MMState<'a, 'b, T, S> {
222 MMState {
223 state: self,
224 ui,
225 picker_ui,
226 footer_ui,
227 preview_ui,
228 event_controller,
229 }
230 }
231
232 fn reset(&mut self) {
233 }
235
236 pub(crate) fn events(&mut self) -> Event {
237 self.reset();
238 std::mem::take(&mut self.events)
239 }
240}
241
242pub struct MMState<'a, 'b: 'a, T: SSS, S: Selection> {
244 pub(crate) state: &'a mut State,
246
247 pub ui: &'a mut UI,
248 pub picker_ui: &'a mut PickerUI<'b, T, S>,
249 pub footer_ui: &'a mut DisplayUI,
250 pub preview_ui: &'a mut Option<PreviewUI>,
251 pub event_controller: &'a EventSender,
252}
253
254impl<'a, 'b: 'a, T: SSS, S: Selection> MMState<'a, 'b, T, S> {
255 pub fn previewer_area(&self) -> Option<&Rect> {
256 self.preview_ui.as_ref().map(|ui| &ui.area)
257 }
258
259 pub fn ui_area(&self) -> &Rect {
260 &self.ui.area
261 }
262 pub fn ui_size(&self) -> [u16; 2] {
263 let q = &self.ui.area;
264 [
265 q.width.saturating_sub(self.ui.config.border.width()),
266 q.height.saturating_sub(self.ui.config.border.width()),
267 ]
268 }
269
270 pub fn current_item(&self) -> Option<S> {
271 get_current(self.picker_ui).map(|s| s.1)
272 }
273
274 pub fn current_raw(&self) -> Option<&T> {
276 self.picker_ui
277 .worker
278 .get_nth(self.picker_ui.results.index())
279 }
280 pub fn map_selected_to_vec<U>(&self, mut f: impl FnMut(&S) -> U) -> Vec<U> {
282 if !self.picker_ui.selector.is_empty() {
283 self.picker_ui.selector.map_to_vec(f)
284 } else {
285 get_current(self.picker_ui)
286 .iter()
287 .map(|s| f(&s.1))
288 .collect()
289 }
290 }
291
292 pub fn injector(&self) -> WorkerInjector<T> {
293 self.picker_ui.worker.injector()
294 }
295
296 pub fn widths(&self) -> &Vec<u16> {
297 self.picker_ui.results.widths()
298 }
299
300 pub fn status(&self) -> &Status {
301 &self.picker_ui.results.status
303 }
304
305 pub fn selections(&self) -> &Selector<T, S> {
306 &self.picker_ui.selector
307 }
308
309 pub fn preview_visible(&self) -> bool {
310 self.preview_ui.as_ref().is_some_and(|s| s.visible())
311 }
312
313 pub fn get_content_and_index(&self) -> (String, u32) {
314 (
315 self.picker_ui.input.input.clone(),
316 self.picker_ui.results.index(),
317 )
318 }
319
320 pub fn restart_worker(&mut self) {
321 self.picker_ui.worker.restart(false);
322 self.state.synced = [false; 2];
323 }
324
325 pub fn make_env_vars(&self) -> EnvVars {
326 env_vars! {
327 "FZF_LINES" => self.ui_area().height.to_string(),
328 "FZF_COLUMNS" => self.ui_area().width.to_string(),
329 "FZF_TOTAL_COUNT" => self.status().item_count.to_string(),
330 "FZF_MATCH_COUNT" => self.status().matched_count.to_string(),
331 "FZF_SELECT_COUNT" => self.selections().len().to_string(),
332 "FZF_POS" => get_current(self.picker_ui).map_or("".to_string(), |x| format!("{}", x.0)),
333 "FZF_QUERY" => self.input.clone(),
334 }
335 }
336
337 pub fn stash_preview_visibility(&mut self, show: Option<bool>) {
342 log::trace!("Called stash_preview_visibility with {show:?}");
343 let p = unwrap!(self.preview_ui);
344 if let Some(s) = show {
345 self.state.stashed_preview_visibility = Some(p.visible());
346 p.show(s);
347 } else if let Some(s) = self.state.stashed_preview_visibility.take() {
348 p.show(s);
349 }
350 }
351}
352
353pub(crate) fn get_current<T: SSS, S: Selection>(picker_ui: &PickerUI<T, S>) -> Option<(u32, S)> {
354 let current_raw = picker_ui.worker.get_nth(picker_ui.results.index());
355 current_raw.map(picker_ui.selector.identifier)
356}
357
358impl<'a, 'b: 'a, T: SSS, S: Selection> std::ops::Deref for MMState<'a, 'b, T, S> {
360 type Target = State;
361
362 fn deref(&self) -> &Self::Target {
363 self.state
364 }
365}
366
367impl<'a, 'b: 'a, T: SSS, S: Selection> std::ops::DerefMut for MMState<'a, 'b, T, S> {
368 fn deref_mut(&mut self) -> &mut Self::Target {
369 self.state
370 }
371}