matchmaker/render/
state.rs

1use bitflags::bitflags;
2use crossterm::event::{self};
3use log::{debug, error, warn};
4use ratatui::{
5    layout::{Constraint, Direction, Layout, Position, Rect},
6    style::{Style, Stylize},
7    text::Text,
8    widgets::{Block, Borders, Paragraph, Row, Table, Wrap},
9};
10use rustc_hash::FxHashSet;
11use std::{
12    cell::{RefCell, RefMut},
13    collections::HashSet,
14    fmt::{Formatter, Write},
15    ops::Deref,
16    sync::Arc,
17};
18use unicode_segmentation::UnicodeSegmentation;
19use unicode_width::UnicodeWidthStr;
20
21use crate::{
22    PickerItem, Selection, SelectionSet,
23    action::Action,
24    config::{
25        InputConfig, PreviewConfig, PreviewSetting, RenderConfig, ResultsConfig,
26        TerminalLayoutSettings,
27    },
28    env_vars,
29    message::Event,
30    nucleo::{injector::WorkerInjector, worker::{Status, Worker}},
31    spawn::{EnvVars, preview::PreviewerView},
32    tui::Tui,
33    ui::{PickerUI, PreviewUI, UI},
34    utils::text::{
35        clip_text_lines, fit_width, grapheme_index_to_byte_index, prefix_text, substitute_escaped,
36    },
37};
38
39// --------------------------------------------------------------------
40// todo: use bitflag for more efficient hashmap
41
42pub struct State<S: Selection, C> {
43    pub current: Option<(u32, S)>,
44    pub input: String,
45    pub col: Option<usize>,
46    
47    preview_payload: String,
48    // pub execute_payload: Option<String>,
49    // pub become_payload: Option<String>,
50    pub context: Arc<C>,
51    pub iterations: u32,
52    pub preview_show: bool,
53    pub layout: [Rect; 4],
54    
55    events: HashSet<Event>,
56}
57
58pub struct EphemeralState<'a, T: PickerItem, S: Selection, C> {
59    state: &'a State<S, C>,
60    
61    pub picker_ui: &'a PickerUI<'a, T, S, C>,
62    pub area: &'a Rect,
63    pub previewer_area: Option<Rect>,
64    pub effects: Effects,
65}
66
67impl<'a, T: PickerItem, S: Selection, C> EphemeralState<'a, T, S, C> {    
68    pub fn new(
69        state: &'a State<S, C>,
70        picker_ui: &'a PickerUI<T, S, C>,
71        area: &'a Rect,
72        previewer_area: Option<Rect>,
73    ) -> Self {
74        Self {
75            state,
76            picker_ui,
77            area,
78            previewer_area,
79            effects: Effects::empty(),
80        }
81    }
82    
83    pub fn current_raw(&self) -> Option<&T> {
84        self.picker_ui.worker.get_nth(self.picker_ui.results.index())
85    }
86
87    pub fn injector(&self) -> WorkerInjector<T, C> {
88        self.picker_ui.worker.injector()
89    }
90    
91    pub fn widths(&self) -> &Vec<u16> {
92        self.picker_ui.results.widths()
93    }
94    
95    pub fn status(&self) -> &Status { // replace StatusType with the actual type
96        &self.picker_ui.results.status
97    }
98    
99    pub fn selections(&self) -> &SelectionSet<T, S> {
100        &self.picker_ui.selections
101    }
102    pub fn make_env_vars(&self) -> EnvVars {
103        env_vars! {
104            "FZF_LINES" => self.area.height.to_string(),
105            "FZF_COLUMNS" => self.area.width.to_string(),
106            "FZF_TOTAL_COUNT" => self.status().item_count.to_string(),
107            "FZF_MATCH_COUNT" => self.status().matched_count.to_string(),
108            "FZF_SELECT_COUNT" => self.selections().len().to_string(),
109            "FZF_POS" => self.current.as_ref().map_or("".to_string(), |x| format!("{}", x.0)),
110            "FZF_QUERY" => self.input.clone(),
111        }
112    }
113}
114
115impl<S: Selection, C> State<S, C> {
116    pub fn new(context: Arc<C>) -> Self {
117        Self {
118            current: None,
119            
120            preview_payload: String::new(),
121            preview_show: false,
122            layout: [Rect::default(); 4],
123            col: None,
124            
125            context,
126            input: String::new(),
127            iterations: 0,
128            
129            events: HashSet::new(),
130        }
131    }
132    
133    pub fn current(&mut self) -> Option<S> {
134        self.current.take().map(|x| x.1)
135    }
136    
137    pub fn preview_payload(&self) -> &String {
138        &self.preview_payload
139    }
140    
141    pub fn contains(&self, event: &Event) -> bool {
142        self.events.contains(event)
143    }
144    
145    pub fn insert(&mut self, event: Event) -> bool {
146        self.events.insert(event)
147    }
148    
149    fn reset(&mut self) {
150        self.iterations += 1;
151    }
152    
153    pub fn update_current(&mut self, new_current: Option<(u32, S)>) -> bool {
154        let changed = self.current != new_current;
155        if changed {
156            self.current = new_current;
157            self.insert(Event::CursorChange);
158        }
159        changed
160    }
161    
162    pub fn update_input(&mut self, new_input: &str) -> bool {
163        let changed = self.input != new_input;
164        if changed {
165            self.input = new_input.to_string();
166            self.insert(Event::QueryChange);
167        }
168        changed
169    }
170    
171    pub fn update_preview(&mut self, context: &str) -> bool {
172        let next = context;
173        let changed = self.preview_payload != next;
174        if changed {
175            self.preview_payload = next.into();
176            self.insert(Event::PreviewChange);
177        }
178        changed
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    
202    
203    pub fn update<'a, T: PickerItem>(&'a mut self, picker_ui: &'a PickerUI<T, S, C>){
204        self.update_input(&picker_ui.input.input);
205        self.col = picker_ui.results.col();
206        
207        let current_raw = picker_ui.worker.get_nth(picker_ui.results.index());
208        self.update_current(current_raw.map(picker_ui.selections.identifier));
209    }
210    
211    pub fn dispatcher<'a, T: PickerItem>(&'a self, ui: &'a UI, picker_ui: &'a PickerUI<T, S, C>, preview_ui: Option<&PreviewUI>) -> EphemeralState<'a, T, S, C> {
212        EphemeralState::new(&self, 
213            picker_ui,
214            &ui.area,
215            preview_ui.map(|p| p.area),
216        )
217    }
218    
219    pub fn events(
220        &mut self,
221    ) -> HashSet<Event> {
222        let mut ret = std::mem::take(&mut self.events);
223        self.reset();
224        ret
225    }
226}
227
228bitflags! {
229    #[derive(Clone, Copy)]
230    pub struct Effects: u32 {
231        // const CREATE_WIDGET ?
232    }
233}
234
235
236
237// ----- BOILERPLATE -----------
238impl<S: Selection, C> std::fmt::Debug for State<S, C> {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        f.debug_struct("State")
241        .field("input", &self.input)
242        .field("preview_payload", &self.preview_payload)
243        .field("iterations", &self.iterations)
244        .field("preview_show", &self.preview_show)
245        .field("layout", &self.layout)
246        .field("events", &self.events)
247        .finish_non_exhaustive()
248    }
249}
250
251impl<'a, T: PickerItem, S: Selection, C> Deref for EphemeralState<'a, T, S, C> {
252    type Target = State<S, C>;
253    
254    fn deref(&self) -> &Self::Target {
255        self.state
256    }
257}
258
259impl<'a, T: PickerItem, S: Selection, L> Clone for EphemeralState<'a, T, S, L> {
260    fn clone(&self) -> Self {
261        Self {
262            state: self.state,
263            area: self.area,
264            picker_ui: self.picker_ui,
265            previewer_area: self.previewer_area,
266            effects: self.effects,
267        }
268    }
269}