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(¤t_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}