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}