selectors.rs

  1pub mod save_or_reopen {
  2    use gpui::Styled;
  3    use gpui::{AppContext, ParentElement};
  4    use picker::Picker;
  5    use picker::PickerDelegate;
  6    use std::cell::RefCell;
  7    use std::ops::{Deref, DerefMut};
  8    use std::rc::Rc;
  9    use std::sync::Arc;
 10    use std::sync::atomic::AtomicBool;
 11    use util::ResultExt;
 12
 13    use fuzzy::{StringMatch, StringMatchCandidate};
 14    use gpui::{DismissEvent, Entity, EventEmitter, Focusable, WeakEntity};
 15
 16    use ui::{Context, HighlightedLabel, Label, ListItem, Render, Window, rems, v_flex};
 17    use workspace::{ModalView, Workspace};
 18
 19    use crate::selectors::encoding::{Action, EncodingSelector, EncodingSelectorDelegate};
 20
 21    pub struct EncodingSaveOrReopenSelector {
 22        picker: Entity<Picker<EncodingSaveOrReopenDelegate>>,
 23        pub current_selection: usize,
 24        workspace: WeakEntity<Workspace>,
 25    }
 26
 27    impl EncodingSaveOrReopenSelector {
 28        pub fn new(
 29            window: &mut Window,
 30            cx: &mut Context<EncodingSaveOrReopenSelector>,
 31            workspace: WeakEntity<Workspace>,
 32        ) -> Self {
 33            let delegate =
 34                EncodingSaveOrReopenDelegate::new(cx.entity().downgrade(), workspace.clone());
 35
 36            let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
 37
 38            Self {
 39                picker,
 40                current_selection: 0,
 41                workspace,
 42            }
 43        }
 44
 45        pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
 46            let weak_workspace = workspace.weak_handle();
 47            workspace.toggle_modal(window, cx, |window, cx| {
 48                EncodingSaveOrReopenSelector::new(window, cx, weak_workspace)
 49            });
 50        }
 51    }
 52
 53    impl Focusable for EncodingSaveOrReopenSelector {
 54        fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
 55            self.picker.focus_handle(cx)
 56        }
 57    }
 58
 59    impl Render for EncodingSaveOrReopenSelector {
 60        fn render(
 61            &mut self,
 62            _window: &mut Window,
 63            _cx: &mut Context<Self>,
 64        ) -> impl ui::IntoElement {
 65            v_flex().w(rems(34.0)).child(self.picker.clone())
 66        }
 67    }
 68
 69    impl ModalView for EncodingSaveOrReopenSelector {}
 70
 71    impl EventEmitter<DismissEvent> for EncodingSaveOrReopenSelector {}
 72
 73    pub struct EncodingSaveOrReopenDelegate {
 74        selector: WeakEntity<EncodingSaveOrReopenSelector>,
 75        current_selection: usize,
 76        matches: Vec<StringMatch>,
 77        pub actions: Vec<StringMatchCandidate>,
 78        workspace: WeakEntity<Workspace>,
 79    }
 80
 81    impl EncodingSaveOrReopenDelegate {
 82        pub fn new(
 83            selector: WeakEntity<EncodingSaveOrReopenSelector>,
 84            workspace: WeakEntity<Workspace>,
 85        ) -> Self {
 86            Self {
 87                selector,
 88                current_selection: 0,
 89                matches: Vec::new(),
 90                actions: vec![
 91                    StringMatchCandidate::new(0, "Save with encoding"),
 92                    StringMatchCandidate::new(1, "Reopen with encoding"),
 93                ],
 94                workspace,
 95            }
 96        }
 97
 98        pub fn get_actions(&self) -> (&str, &str) {
 99            (&self.actions[0].string, &self.actions[1].string)
100        }
101
102        pub fn post_selection(
103            &self,
104            cx: &mut Context<Picker<EncodingSaveOrReopenDelegate>>,
105            window: &mut Window,
106        ) {
107            if self.current_selection == 0 {
108                if let Some(workspace) = self.workspace.upgrade() {
109                    workspace.update(cx, |workspace, cx| {
110                        workspace.toggle_modal(window, cx, |window, cx| {
111                            EncodingSelector::new(window, cx, Action::Save)
112                        })
113                    });
114                }
115            } else if self.current_selection == 1 {
116                if let Some(workspace) = self.workspace.upgrade() {
117                    workspace.update(cx, |workspace, cx| {
118                        workspace.toggle_modal(window, cx, |window, cx| {
119                            EncodingSelector::new(window, cx, Action::Reopen)
120                        })
121                    });
122                }
123            }
124        }
125    }
126
127    impl PickerDelegate for EncodingSaveOrReopenDelegate {
128        type ListItem = ListItem;
129
130        fn match_count(&self) -> usize {
131            self.matches.len()
132        }
133
134        fn selected_index(&self) -> usize {
135            self.current_selection
136        }
137
138        fn set_selected_index(
139            &mut self,
140            ix: usize,
141            _window: &mut Window,
142            cx: &mut Context<Picker<Self>>,
143        ) {
144            self.current_selection = ix;
145            self.selector
146                .update(cx, |selector, cx| {
147                    selector.current_selection = ix;
148                })
149                .log_err();
150        }
151
152        fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
153            "Select an action...".into()
154        }
155
156        fn update_matches(
157            &mut self,
158            query: String,
159            window: &mut Window,
160            cx: &mut Context<Picker<Self>>,
161        ) -> gpui::Task<()> {
162            let executor = cx.background_executor().clone();
163            let actions = self.actions.clone();
164
165            cx.spawn_in(window, async move |this, cx| {
166                let matches = if query.is_empty() {
167                    actions
168                        .into_iter()
169                        .enumerate()
170                        .map(|(index, value)| StringMatch {
171                            candidate_id: index,
172                            score: 0.0,
173                            positions: vec![],
174                            string: value.string,
175                        })
176                        .collect::<Vec<StringMatch>>()
177                } else {
178                    fuzzy::match_strings(
179                        &actions,
180                        &query,
181                        false,
182                        false,
183                        2,
184                        &AtomicBool::new(false),
185                        executor,
186                    )
187                    .await
188                };
189
190                this.update(cx, |picker, cx| {
191                    let delegate = &mut picker.delegate;
192                    delegate.matches = matches;
193                    delegate.current_selection = delegate
194                        .current_selection
195                        .min(delegate.matches.len().saturating_sub(1));
196                    delegate
197                        .selector
198                        .update(cx, |selector, cx| {
199                            selector.current_selection = delegate.current_selection
200                        })
201                        .log_err();
202                    cx.notify();
203                })
204                .log_err();
205            })
206        }
207
208        fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
209            self.dismissed(window, cx);
210            if self.selector.is_upgradable() {
211                self.post_selection(cx, window);
212            }
213        }
214
215        fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
216            self.selector
217                .update(cx, |_, cx| cx.emit(DismissEvent))
218                .log_err();
219        }
220
221        fn render_match(
222            &self,
223            ix: usize,
224            _: bool,
225            _: &mut Window,
226            _: &mut Context<Picker<Self>>,
227        ) -> Option<Self::ListItem> {
228            Some(
229                ListItem::new(ix)
230                    .child(HighlightedLabel::new(
231                        &self.matches[ix].string,
232                        self.matches[ix].positions.clone(),
233                    ))
234                    .spacing(ui::ListItemSpacing::Sparse),
235            )
236        }
237    }
238
239    pub fn get_current_encoding() -> &'static str {
240        "UTF-8"
241    }
242}
243
244pub mod encoding {
245    use std::{
246        ops::DerefMut,
247        rc::{Rc, Weak},
248        sync::{Arc, atomic::AtomicBool},
249    };
250
251    use fuzzy::{StringMatch, StringMatchCandidate};
252    use gpui::{
253        AppContext, BackgroundExecutor, DismissEvent, Entity, EventEmitter, Focusable, Length,
254        WeakEntity, actions,
255    };
256    use picker::{Picker, PickerDelegate};
257    use ui::{
258        Context, DefiniteLength, HighlightedLabel, Label, ListItem, ListItemSpacing, ParentElement,
259        Render, Styled, Window, rems, v_flex,
260    };
261    use util::{ResultExt, TryFutureExt};
262    use workspace::{ModalView, Workspace};
263
264    pub struct EncodingSelector {
265        picker: Entity<Picker<EncodingSelectorDelegate>>,
266        action: Action,
267    }
268
269    pub struct EncodingSelectorDelegate {
270        current_selection: usize,
271        encodings: Vec<StringMatchCandidate>,
272        matches: Vec<StringMatch>,
273        selector: WeakEntity<EncodingSelector>,
274    }
275
276    impl EncodingSelectorDelegate {
277        pub fn new(selector: WeakEntity<EncodingSelector>) -> EncodingSelectorDelegate {
278            EncodingSelectorDelegate {
279                current_selection: 0,
280                encodings: vec![
281                    StringMatchCandidate::new(0, "UTF-8"),
282                    StringMatchCandidate::new(1, "UTF-16 LE"),
283                    StringMatchCandidate::new(2, "UTF-16 BE"),
284                    StringMatchCandidate::new(3, "IBM866"),
285                    StringMatchCandidate::new(4, "ISO 8859-1"),
286                    StringMatchCandidate::new(5, "ISO 8859-2"),
287                    StringMatchCandidate::new(6, "ISO 8859-3"),
288                    StringMatchCandidate::new(7, "ISO 8859-4"),
289                    StringMatchCandidate::new(8, "ISO 8859-5"),
290                    StringMatchCandidate::new(9, "ISO 8859-6"),
291                    StringMatchCandidate::new(10, "ISO 8859-7"),
292                    StringMatchCandidate::new(11, "ISO 8859-8"),
293                    StringMatchCandidate::new(12, "ISO 8859-10"),
294                    StringMatchCandidate::new(13, "ISO 8859-13"),
295                    StringMatchCandidate::new(14, "ISO 8859-14"),
296                    StringMatchCandidate::new(15, "ISO 8859-15"),
297                    StringMatchCandidate::new(16, "ISO 8859-16"),
298                    StringMatchCandidate::new(17, "KOI8-R"),
299                    StringMatchCandidate::new(18, "KOI8-U"),
300                    StringMatchCandidate::new(19, "MacRoman"),
301                    StringMatchCandidate::new(20, "Mac Cyrillic"),
302                    StringMatchCandidate::new(21, "Windows-874"),
303                    StringMatchCandidate::new(22, "Windows-1250"),
304                    StringMatchCandidate::new(23, "Windows-1251"),
305                    StringMatchCandidate::new(24, "Windows-1252"),
306                    StringMatchCandidate::new(25, "Windows-1253"),
307                    StringMatchCandidate::new(26, "Windows-1254"),
308                    StringMatchCandidate::new(27, "Windows-1255"),
309                    StringMatchCandidate::new(28, "Windows-1256"),
310                    StringMatchCandidate::new(29, "Windows-1257"),
311                    StringMatchCandidate::new(30, "Windows-1258"),
312                    StringMatchCandidate::new(31, "EUC-KR"),
313                    StringMatchCandidate::new(32, "EUC-JP"),
314                    StringMatchCandidate::new(33, "Shift_JIS"),
315                    StringMatchCandidate::new(34, "ISO 2022-JP"),
316                    StringMatchCandidate::new(35, "GBK"),
317                    StringMatchCandidate::new(36, "GB18030"),
318                    StringMatchCandidate::new(37, "Big5"),
319                    StringMatchCandidate::new(38, "HZ-GB-2312"),
320                ],
321                matches: Vec::new(),
322                selector,
323            }
324        }
325    }
326
327    impl PickerDelegate for EncodingSelectorDelegate {
328        type ListItem = ListItem;
329
330        fn match_count(&self) -> usize {
331            self.matches.len()
332        }
333
334        fn selected_index(&self) -> usize {
335            self.current_selection
336        }
337
338        fn set_selected_index(
339            &mut self,
340            ix: usize,
341            window: &mut Window,
342            cx: &mut Context<Picker<Self>>,
343        ) {
344            self.current_selection = ix;
345        }
346
347        fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
348            "Select an encoding...".into()
349        }
350
351        fn update_matches(
352            &mut self,
353            query: String,
354            window: &mut Window,
355            cx: &mut Context<Picker<Self>>,
356        ) -> gpui::Task<()> {
357            let executor = cx.background_executor().clone();
358            let encodings = self.encodings.clone();
359
360            cx.spawn_in(window, async move |picker, cx| {
361                let matches: Vec<StringMatch>;
362
363                if query.is_empty() {
364                    matches = encodings
365                        .into_iter()
366                        .enumerate()
367                        .map(|(index, value)| StringMatch {
368                            candidate_id: index,
369                            score: 0.0,
370                            positions: Vec::new(),
371                            string: value.string,
372                        })
373                        .collect();
374                } else {
375                    matches = fuzzy::match_strings(
376                        &encodings,
377                        &query,
378                        true,
379                        false,
380                        38,
381                        &AtomicBool::new(false),
382                        executor,
383                    )
384                    .await
385                }
386
387                picker
388                    .update(cx, |picker, cx| {
389                        let delegate = &mut picker.delegate;
390                        delegate.matches = matches;
391                        delegate.current_selection = delegate
392                            .current_selection
393                            .min(delegate.matches.len().saturating_sub(1));
394                        cx.notify();
395                    })
396                    .log_err();
397            })
398        }
399
400        fn confirm(
401            &mut self,
402            secondary: bool,
403            window: &mut Window,
404            cx: &mut Context<Picker<Self>>,
405        ) {
406        }
407
408        fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>) {
409            self.selector
410                .update(cx, |_, cx| cx.emit(DismissEvent))
411                .log_err();
412        }
413
414        fn render_match(
415            &self,
416            ix: usize,
417            selected: bool,
418            window: &mut Window,
419            cx: &mut Context<Picker<Self>>,
420        ) -> Option<Self::ListItem> {
421            Some(
422                ListItem::new(ix)
423                    .child(HighlightedLabel::new(
424                        &self.matches[ix].string,
425                        self.matches[ix].positions.clone(),
426                    ))
427                    .spacing(ListItemSpacing::Sparse),
428            )
429        }
430    }
431
432    pub enum Action {
433        Save,
434        Reopen,
435    }
436
437    impl EncodingSelector {
438        pub fn new(
439            window: &mut Window,
440            cx: &mut Context<EncodingSelector>,
441            action: Action,
442        ) -> EncodingSelector {
443            let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade());
444            let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
445
446            EncodingSelector { picker, action }
447        }
448    }
449
450    impl EventEmitter<DismissEvent> for EncodingSelector {}
451
452    impl Focusable for EncodingSelector {
453        fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
454            cx.focus_handle()
455        }
456    }
457
458    impl ModalView for EncodingSelector {}
459
460    impl Render for EncodingSelector {
461        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl ui::IntoElement {
462            v_flex().w(rems(34.0)).child(self.picker.clone())
463        }
464    }
465}