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