matchmaker/render/
state.rs

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