selectors.rs

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