matchmaker/render/
state.rs

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