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