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}