selectors.rs

  1use anyhow::Result;
  2use editor::Editor;
  3use encodings::Encoding;
  4use encodings::EncodingOptions;
  5use futures::channel::oneshot;
  6use gpui::ParentElement;
  7use gpui::Task;
  8use language::Buffer;
  9use picker::Picker;
 10use picker::PickerDelegate;
 11use std::path::Path;
 12use std::sync::Arc;
 13use std::sync::atomic::AtomicBool;
 14use ui::Label;
 15use ui::ListItemSpacing;
 16use ui::rems;
 17use util::ResultExt;
 18
 19use fuzzy::{StringMatch, StringMatchCandidate};
 20use gpui::{DismissEvent, Entity, WeakEntity};
 21
 22use ui::{Context, HighlightedLabel, ListItem, Window};
 23use workspace::Workspace;
 24
 25pub fn save_or_reopen(
 26    buffer: Entity<Buffer>,
 27    workspace: &mut Workspace,
 28    window: &mut Window,
 29    cx: &mut Context<Workspace>,
 30) {
 31    let weak_workspace = cx.weak_entity();
 32    workspace.toggle_modal(window, cx, |window, cx| {
 33        let delegate = EncodingSaveOrReopenDelegate::new(buffer, weak_workspace);
 34        Picker::nonsearchable_uniform_list(delegate, window, cx)
 35            .modal(true)
 36            .width(rems(34.0))
 37    })
 38}
 39
 40pub fn open_with_encoding(
 41    path: Arc<Path>,
 42    workspace: &mut Workspace,
 43    window: &mut Window,
 44    cx: &mut Context<Workspace>,
 45) -> Task<Result<()>> {
 46    let (tx, rx) = oneshot::channel();
 47    workspace.toggle_modal(window, cx, |window, cx| {
 48        let delegate = EncodingSelectorDelegate::new(None, tx);
 49        Picker::uniform_list(delegate, window, cx)
 50    });
 51    let project = workspace.project().clone();
 52    cx.spawn_in(window, async move |workspace, cx| {
 53        let encoding = rx.await.unwrap();
 54
 55        let (worktree, rel_path) = project
 56            .update(cx, |project, cx| {
 57                project.find_or_create_worktree(path, false, cx)
 58            })?
 59            .await?;
 60
 61        let project_path = (worktree.update(cx, |worktree, _| worktree.id())?, rel_path).into();
 62
 63        let buffer = project
 64            .update(cx, |project, cx| {
 65                project.buffer_store().update(cx, |buffer_store, cx| {
 66                    buffer_store.open_buffer(
 67                        project_path,
 68                        &EncodingOptions {
 69                            expected: encoding,
 70                            auto_detect: true,
 71                        },
 72                        cx,
 73                    )
 74                })
 75            })?
 76            .await?;
 77        workspace.update_in(cx, |workspace, window, cx| {
 78            workspace.open_project_item::<Editor>(
 79                workspace.active_pane().clone(),
 80                buffer,
 81                true,
 82                true,
 83                window,
 84                cx,
 85            )
 86        })?;
 87
 88        Ok(())
 89    })
 90}
 91
 92pub fn reopen_with_encoding(
 93    buffer: Entity<Buffer>,
 94    workspace: &mut Workspace,
 95    window: &mut Window,
 96    cx: &mut Context<Workspace>,
 97) {
 98    let encoding = buffer.read(cx).encoding();
 99    let (tx, rx) = oneshot::channel();
100    workspace.toggle_modal(window, cx, |window, cx| {
101        let delegate = EncodingSelectorDelegate::new(Some(encoding), tx);
102        Picker::uniform_list(delegate, window, cx)
103    });
104    cx.spawn(async move |_, cx| {
105        let encoding = rx.await.unwrap();
106
107        let (task, prev) = buffer.update(cx, |buffer, cx| {
108            let prev = buffer.encoding();
109            buffer.set_encoding(encoding, cx);
110            (buffer.reload(cx), prev)
111        })?;
112
113        if task.await.is_err() {
114            buffer.update(cx, |buffer, cx| {
115                buffer.set_encoding(prev, cx);
116            })?;
117        }
118
119        anyhow::Ok(())
120    })
121    .detach();
122}
123
124pub fn save_with_encoding(
125    buffer: Entity<Buffer>,
126    workspace: &mut Workspace,
127    window: &mut Window,
128    cx: &mut Context<Workspace>,
129) {
130    let encoding = buffer.read(cx).encoding();
131    let (tx, rx) = oneshot::channel();
132    workspace.toggle_modal(window, cx, |window, cx| {
133        let delegate = EncodingSelectorDelegate::new(Some(encoding), tx);
134        Picker::uniform_list(delegate, window, cx)
135    });
136    cx.spawn(async move |workspace, cx| {
137        let encoding = rx.await.unwrap();
138        workspace
139            .update(cx, |workspace, cx| {
140                buffer.update(cx, |buffer, cx| {
141                    buffer.set_encoding(encoding, cx);
142                });
143                workspace
144                    .project()
145                    .update(cx, |project, cx| project.save_buffer(buffer, cx))
146            })
147            .ok();
148    })
149    .detach();
150}
151
152pub enum SaveOrReopen {
153    Save,
154    Reopen,
155}
156
157pub struct EncodingSaveOrReopenDelegate {
158    current_selection: usize,
159    actions: Vec<SaveOrReopen>,
160    workspace: WeakEntity<Workspace>,
161    buffer: Entity<Buffer>,
162}
163
164impl EncodingSaveOrReopenDelegate {
165    pub fn new(buffer: Entity<Buffer>, workspace: WeakEntity<Workspace>) -> Self {
166        Self {
167            current_selection: 0,
168            actions: vec![SaveOrReopen::Save, SaveOrReopen::Reopen],
169            workspace,
170            buffer,
171        }
172    }
173}
174
175impl PickerDelegate for EncodingSaveOrReopenDelegate {
176    type ListItem = ListItem;
177
178    fn match_count(&self) -> usize {
179        self.actions.len()
180    }
181
182    fn selected_index(&self) -> usize {
183        self.current_selection
184    }
185
186    fn set_selected_index(
187        &mut self,
188        ix: usize,
189        _window: &mut Window,
190        _cx: &mut Context<Picker<Self>>,
191    ) {
192        self.current_selection = ix;
193    }
194
195    fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
196        "Select an action...".into()
197    }
198
199    fn update_matches(
200        &mut self,
201        _query: String,
202        _window: &mut Window,
203        _cx: &mut Context<Picker<Self>>,
204    ) -> Task<()> {
205        return Task::ready(());
206    }
207
208    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
209        self.dismissed(window, cx);
210        cx.defer_in(window, |this, window, cx| {
211            let this = &this.delegate;
212            this.workspace
213                .update(cx, |workspace, cx| {
214                    match this.actions[this.current_selection] {
215                        SaveOrReopen::Reopen => {
216                            reopen_with_encoding(this.buffer.clone(), workspace, window, cx);
217                        }
218                        SaveOrReopen::Save => {
219                            save_with_encoding(this.buffer.clone(), workspace, window, cx);
220                        }
221                    }
222                })
223                .ok();
224        })
225    }
226
227    fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
228        cx.emit(DismissEvent)
229    }
230
231    fn render_match(
232        &self,
233        ix: usize,
234        _: bool,
235        _: &mut Window,
236        _: &mut Context<Picker<Self>>,
237    ) -> Option<Self::ListItem> {
238        Some(
239            ListItem::new(ix)
240                .child(match self.actions[ix] {
241                    SaveOrReopen::Save => Label::new("Save with encoding"),
242                    SaveOrReopen::Reopen => Label::new("Reopen with encoding"),
243                })
244                .spacing(ui::ListItemSpacing::Sparse),
245        )
246    }
247}
248
249pub struct EncodingSelectorDelegate {
250    current_selection: usize,
251    encodings: Vec<StringMatchCandidate>,
252    matches: Vec<StringMatch>,
253    tx: Option<oneshot::Sender<Encoding>>,
254}
255
256impl EncodingSelectorDelegate {
257    pub fn new(
258        encoding: Option<Encoding>,
259        tx: oneshot::Sender<Encoding>,
260    ) -> EncodingSelectorDelegate {
261        let encodings = vec![
262            StringMatchCandidate::new(0, "UTF-8"),
263            StringMatchCandidate::new(1, "UTF-16 LE"),
264            StringMatchCandidate::new(2, "UTF-16 BE"),
265            StringMatchCandidate::new(3, "Windows-1252"),
266            StringMatchCandidate::new(4, "Windows-1251"),
267            StringMatchCandidate::new(5, "Windows-1250"),
268            StringMatchCandidate::new(6, "ISO 8859-2"),
269            StringMatchCandidate::new(7, "ISO 8859-3"),
270            StringMatchCandidate::new(8, "ISO 8859-4"),
271            StringMatchCandidate::new(9, "ISO 8859-5"),
272            StringMatchCandidate::new(10, "ISO 8859-6"),
273            StringMatchCandidate::new(11, "ISO 8859-7"),
274            StringMatchCandidate::new(12, "ISO 8859-8"),
275            StringMatchCandidate::new(13, "ISO 8859-13"),
276            StringMatchCandidate::new(14, "ISO 8859-15"),
277            StringMatchCandidate::new(15, "KOI8-R"),
278            StringMatchCandidate::new(16, "KOI8-U"),
279            StringMatchCandidate::new(17, "MacRoman"),
280            StringMatchCandidate::new(18, "Mac Cyrillic"),
281            StringMatchCandidate::new(19, "Windows-874"),
282            StringMatchCandidate::new(20, "Windows-1253"),
283            StringMatchCandidate::new(21, "Windows-1254"),
284            StringMatchCandidate::new(22, "Windows-1255"),
285            StringMatchCandidate::new(23, "Windows-1256"),
286            StringMatchCandidate::new(24, "Windows-1257"),
287            StringMatchCandidate::new(25, "Windows-1258"),
288            StringMatchCandidate::new(26, "Windows-949"),
289            StringMatchCandidate::new(27, "EUC-JP"),
290            StringMatchCandidate::new(28, "ISO 2022-JP"),
291            StringMatchCandidate::new(29, "GBK"),
292            StringMatchCandidate::new(30, "GB18030"),
293            StringMatchCandidate::new(31, "Big5"),
294        ];
295        let current_selection = if let Some(encoding) = encoding {
296            encodings
297                .iter()
298                .position(|e| encoding.name() == e.string)
299                .unwrap_or_default()
300        } else {
301            0
302        };
303
304        EncodingSelectorDelegate {
305            current_selection,
306            encodings,
307            matches: Vec::new(),
308            tx: Some(tx),
309        }
310    }
311}
312
313impl PickerDelegate for EncodingSelectorDelegate {
314    type ListItem = ListItem;
315
316    fn match_count(&self) -> usize {
317        self.matches.len()
318    }
319
320    fn selected_index(&self) -> usize {
321        self.current_selection
322    }
323
324    fn set_selected_index(&mut self, ix: usize, _: &mut Window, _: &mut Context<Picker<Self>>) {
325        self.current_selection = ix;
326    }
327
328    fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
329        "Select an encoding...".into()
330    }
331
332    fn update_matches(
333        &mut self,
334        query: String,
335        window: &mut Window,
336        cx: &mut Context<Picker<Self>>,
337    ) -> Task<()> {
338        let executor = cx.background_executor().clone();
339        let encodings = self.encodings.clone();
340
341        cx.spawn_in(window, async move |picker, cx| {
342            let matches: Vec<StringMatch>;
343
344            if query.is_empty() {
345                matches = encodings
346                    .into_iter()
347                    .enumerate()
348                    .map(|(index, value)| StringMatch {
349                        candidate_id: index,
350                        score: 0.0,
351                        positions: Vec::new(),
352                        string: value.string,
353                    })
354                    .collect();
355            } else {
356                matches = fuzzy::match_strings(
357                    &encodings,
358                    &query,
359                    true,
360                    false,
361                    30,
362                    &AtomicBool::new(false),
363                    executor,
364                )
365                .await
366            }
367            picker
368                .update(cx, |picker, cx| {
369                    let delegate = &mut picker.delegate;
370                    delegate.matches = matches;
371                    delegate.current_selection = delegate
372                        .current_selection
373                        .min(delegate.matches.len().saturating_sub(1));
374                    cx.notify();
375                })
376                .log_err();
377        })
378    }
379
380    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
381        let current_selection = self.matches[self.current_selection].string.clone();
382        let encoding = Encoding::from_name(&current_selection);
383        if let Some(tx) = self.tx.take() {
384            tx.send(encoding).log_err();
385        }
386        self.dismissed(window, cx);
387    }
388
389    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
390        cx.emit(DismissEvent);
391    }
392
393    fn render_match(
394        &self,
395        ix: usize,
396        _: bool,
397        _: &mut Window,
398        _: &mut Context<Picker<Self>>,
399    ) -> Option<Self::ListItem> {
400        Some(
401            ListItem::new(ix)
402                .child(HighlightedLabel::new(
403                    &self.matches[ix].string,
404                    self.matches[ix].positions.clone(),
405                ))
406                .spacing(ListItemSpacing::Sparse),
407        )
408    }
409}