matchmaker/render/
state.rs

1use cli_boilerplate_automation::{broc::EnvVars, env_vars};
2use std::{collections::HashSet, ops::Deref};
3
4use crate::{
5    SSS, Selection, Selector,
6    action::ActionExt,
7    message::Event,
8    nucleo::{Status, injector::WorkerInjector},
9    ui::{OverlayUI, PickerUI, PreviewUI, Rect, UI},
10};
11
12// --------------------------------------------------------------------
13// todo: use bitflag for more efficient hashmap
14
15#[derive(Default)]
16pub struct State<S: Selection> {
17    pub current: Option<(u32, S)>,
18    pub input: String,
19    pub col: Option<usize>,
20
21    pub(crate) preview_run: String,
22    pub(crate) preview_set: Option<String>,
23    pub iterations: u32,
24    pub preview_show: bool,
25    pub layout: [Rect; 4], //preview, input, status, results
26    pub overlay_index: Option<usize>,
27
28    pub(crate) matcher_running: bool,
29    pub(crate) events: HashSet<Event>,
30}
31
32pub struct MMState<'a, T: SSS, S: Selection> {
33    pub(crate) state: &'a State<S>,
34
35    pub picker_ui: &'a PickerUI<'a, T, S>,
36    pub ui: &'a UI,
37    pub preview_ui: Option<&'a PreviewUI>,
38}
39
40impl<'a, T: SSS, S: Selection> MMState<'a, T, S> {
41    pub fn previewer_area(&self) -> Option<&Rect> {
42        self.preview_ui.map(|ui| &ui.area)
43    }
44
45    pub fn ui_area(&self) -> &Rect {
46        &self.ui.area
47    }
48
49    pub fn current_raw(&self) -> Option<&T> {
50        self.picker_ui
51            .worker
52            .get_nth(self.picker_ui.results.index())
53    }
54    /// Runs f on selections if nonempty, otherwise, the current item
55    pub fn map_selected_to_vec<U>(&self, mut f: impl FnMut(&S) -> U) -> Vec<U> {
56        if !self.picker_ui.selections.is_empty() {
57            self.picker_ui.selections.map_to_vec(f)
58        } else {
59            self.current.iter().map(|s| f(&s.1)).collect()
60        }
61    }
62
63    pub fn injector(&self) -> WorkerInjector<T> {
64        self.picker_ui.worker.injector()
65    }
66
67    pub fn widths(&self) -> &Vec<u16> {
68        self.picker_ui.results.widths()
69    }
70
71    pub fn status(&self) -> &Status {
72        // replace StatusType with the actual type
73        &self.picker_ui.results.status
74    }
75
76    pub fn selections(&self) -> &Selector<T, S> {
77        &self.picker_ui.selections
78    }
79
80    pub fn make_env_vars(&self) -> EnvVars {
81        env_vars! {
82            "FZF_LINES" => self.ui_area().height.to_string(),
83            "FZF_COLUMNS" => self.ui_area().width.to_string(),
84            "FZF_TOTAL_COUNT" => self.status().item_count.to_string(),
85            "FZF_MATCH_COUNT" => self.status().matched_count.to_string(),
86            "FZF_SELECT_COUNT" => self.selections().len().to_string(),
87            "FZF_POS" => self.current.as_ref().map_or("".to_string(), |x| format!("{}", x.0)),
88            "FZF_QUERY" => self.input.clone(),
89        }
90    }
91
92    // pub fn dispatch<E>(&mut self, handler: &DynamicMethod<T, S, E>, event: &E) {
93    //     (handler)(self, event);
94    // }
95}
96
97impl<S: Selection> State<S> {
98    pub fn new() -> Self {
99        // this is the same as default
100        Self {
101            current: None,
102
103            preview_run: String::new(),
104            preview_set: None,
105            preview_show: false,
106            layout: [Rect::default(); 4],
107            overlay_index: None,
108            col: None,
109
110            input: String::new(),
111            iterations: 0,
112            matcher_running: true,
113
114            events: HashSet::new(),
115        }
116    }
117    // ------ properties -----------
118    pub(crate) fn take_current(&mut self) -> Option<S> {
119        self.current.take().map(|x| x.1)
120    }
121
122    pub fn preview_payload(&self) -> &String {
123        &self.preview_run
124    }
125
126    pub fn contains(&self, event: &Event) -> bool {
127        self.events.contains(event)
128    }
129
130    pub fn insert(&mut self, event: Event) -> bool {
131        self.events.insert(event)
132    }
133
134    pub fn preview_set_payload(&self) -> &Option<String> {
135        &self.preview_set
136    }
137
138    // ------- updates --------------
139    pub(crate) fn update_current<T: SSS>(
140        &mut self,
141        picker_ui: &PickerUI<T, S>,
142        // new_current: Option<(u32, S)>
143    ) {
144        let current_raw = picker_ui.worker.get_nth(picker_ui.results.index());
145        let new_current = current_raw.map(picker_ui.selections.identifier);
146
147        let changed = self.current.as_ref().map(|x| x.0) != new_current.as_ref().map(|x| x.0);
148        if changed {
149            self.current = new_current;
150            self.insert(Event::CursorChange);
151        }
152    }
153
154    pub(crate) fn update_input(&mut self, new_input: &str) -> bool {
155        let changed = self.input != new_input;
156        if changed {
157            self.input = new_input.to_string();
158            self.insert(Event::QueryChange);
159        }
160        changed
161    }
162
163    pub(crate) fn update_preview(&mut self, context: &str) -> bool {
164        let changed = self.preview_run != context;
165        if changed {
166            self.preview_run = context.into();
167            self.insert(Event::PreviewChange);
168        }
169        changed
170    }
171
172    pub(crate) fn update_preview_set(&mut self, context: String) -> bool {
173        let next = Some(context);
174        let changed = self.preview_set != next;
175        if changed {
176            self.preview_set = next;
177            self.insert(Event::PreviewSet);
178        }
179        changed
180    }
181
182    pub(crate) fn update_preview_unset(&mut self) {
183        self.preview_set = None;
184        self.insert(Event::PreviewSet);
185    }
186
187    pub(crate) fn update_layout(&mut self, new_layout: [Rect; 4]) -> bool {
188        let changed = self.layout != new_layout;
189        if changed {
190            self.insert(Event::Resize);
191            self.layout = new_layout;
192        }
193        changed
194    }
195
196    /// Emit PreviewChange event on visibility change
197    pub(crate) fn update_preview_ui(&mut self, preview_ui: &PreviewUI) -> bool {
198        let next = preview_ui.is_show();
199        let changed = self.preview_show != next;
200        self.preview_show = next;
201        // todo: cache to make up for this
202        if changed && next {
203            self.insert(Event::PreviewChange);
204        };
205        changed
206    }
207
208    pub(crate) fn update<'a, T: SSS, A: ActionExt>(
209        &'a mut self,
210        picker_ui: &'a PickerUI<T, S>,
211        overlay_ui: &'a Option<OverlayUI<A>>,
212    ) {
213        if self.iterations == 0 {
214            self.insert(Event::Start);
215        }
216        self.iterations += 1;
217
218        self.update_input(&picker_ui.input.input);
219        self.col = picker_ui.results.col();
220
221        if self.matcher_running != picker_ui.results.status.running {
222            if !picker_ui.results.status.running {
223                self.insert(Event::Synced);
224            }
225            self.matcher_running = picker_ui.results.status.running;
226        };
227
228        if let Some(o) = overlay_ui {
229            if self.overlay_index != o.index() {
230                self.insert(Event::OverlayChange);
231                self.overlay_index = o.index()
232            }
233            self.overlay_index = o.index()
234        }
235        self.update_current(picker_ui);
236    }
237
238    // ---------- flush -----------
239    pub(crate) fn dispatcher<'a, T: SSS>(
240        &'a self,
241        ui: &'a UI,
242        picker_ui: &'a PickerUI<T, S>,
243        preview_ui: Option<&'a PreviewUI>,
244    ) -> MMState<'a, T, S> {
245        MMState {
246            state: self,
247            picker_ui,
248            ui,
249            preview_ui,
250        }
251    }
252
253    fn reset(&mut self) {
254        // nothing
255    }
256
257    pub(crate) fn events(&mut self) -> HashSet<Event> {
258        self.reset();
259        // this rules out persistent preview_set, todo: impl effects to trigger this instead
260        std::mem::take(&mut self.events) // maybe copy is faster dunno
261    }
262}
263
264// ----- BOILERPLATE -----------
265impl<S: Selection> std::fmt::Debug for State<S> {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        f.debug_struct("State")
268            .field("input", &self.input)
269            .field("preview_payload", &self.preview_run)
270            .field("iterations", &self.iterations)
271            .field("preview_show", &self.preview_show)
272            .field("layout", &self.layout)
273            .field("events", &self.events)
274            .finish_non_exhaustive()
275    }
276}
277
278impl<'a, T: SSS, S: Selection> Deref for MMState<'a, T, S> {
279    type Target = State<S>;
280
281    fn deref(&self) -> &Self::Target {
282        self.state
283    }
284}