1mod active_buffer_encoding;
2pub use active_buffer_encoding::ActiveBufferEncoding;
3
4use editor::Editor;
5use encoding_rs::Encoding;
6use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
7use gpui::{
8 App, AppContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
9 InteractiveElement, ParentElement, Render, Styled, Task, WeakEntity, Window, actions,
10};
11use language::Buffer;
12use picker::{Picker, PickerDelegate};
13use std::sync::Arc;
14use ui::{HighlightedLabel, ListItem, ListItemSpacing, Toggleable, v_flex};
15use util::ResultExt;
16use workspace::{ModalView, Toast, Workspace, notifications::NotificationId};
17
18actions!(
19 encoding_selector,
20 [
21 /// Toggles the encoding selector modal.
22 Toggle
23 ]
24);
25
26pub fn init(cx: &mut App) {
27 cx.observe_new(EncodingSelector::register).detach();
28}
29
30pub struct EncodingSelector {
31 picker: Entity<Picker<EncodingSelectorDelegate>>,
32}
33
34impl EncodingSelector {
35 fn register(
36 workspace: &mut Workspace,
37 _window: Option<&mut Window>,
38 _: &mut Context<Workspace>,
39 ) {
40 workspace.register_action(move |workspace, _: &Toggle, window, cx| {
41 Self::toggle(workspace, window, cx);
42 });
43 }
44
45 pub fn toggle(
46 workspace: &mut Workspace,
47 window: &mut Window,
48 cx: &mut Context<Workspace>,
49 ) -> Option<()> {
50 let (_, buffer, _) = workspace
51 .active_item(cx)?
52 .act_as::<Editor>(cx)?
53 .read(cx)
54 .active_excerpt(cx)?;
55
56 let buffer_handle = buffer.read(cx);
57 let project = workspace.project().read(cx);
58
59 if buffer_handle.is_dirty() {
60 workspace.show_toast(
61 Toast::new(
62 NotificationId::unique::<EncodingSelector>(),
63 "Save file to change encoding",
64 ),
65 cx,
66 );
67 return Some(());
68 }
69 if project.is_shared() {
70 workspace.show_toast(
71 Toast::new(
72 NotificationId::unique::<EncodingSelector>(),
73 "Cannot change encoding during collaboration",
74 ),
75 cx,
76 );
77 return Some(());
78 }
79 if project.is_via_remote_server() {
80 workspace.show_toast(
81 Toast::new(
82 NotificationId::unique::<EncodingSelector>(),
83 "Cannot change encoding of remote server file",
84 ),
85 cx,
86 );
87 return Some(());
88 }
89
90 workspace.toggle_modal(window, cx, move |window, cx| {
91 EncodingSelector::new(buffer, window, cx)
92 });
93 Some(())
94 }
95
96 fn new(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>) -> Self {
97 let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade(), buffer);
98 let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
99 Self { picker }
100 }
101}
102
103impl Render for EncodingSelector {
104 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl gpui::IntoElement {
105 v_flex()
106 .key_context("EncodingSelector")
107 .w(gpui::rems(34.))
108 .child(self.picker.clone())
109 }
110}
111
112impl Focusable for EncodingSelector {
113 fn focus_handle(&self, cx: &App) -> FocusHandle {
114 self.picker.focus_handle(cx)
115 }
116}
117
118impl EventEmitter<DismissEvent> for EncodingSelector {}
119impl ModalView for EncodingSelector {}
120
121pub struct EncodingSelectorDelegate {
122 encoding_selector: WeakEntity<EncodingSelector>,
123 buffer: Entity<Buffer>,
124 encodings: Vec<&'static Encoding>,
125 match_candidates: Arc<Vec<StringMatchCandidate>>,
126 matches: Vec<StringMatch>,
127 selected_index: usize,
128}
129
130impl EncodingSelectorDelegate {
131 fn new(encoding_selector: WeakEntity<EncodingSelector>, buffer: Entity<Buffer>) -> Self {
132 let encodings = available_encodings();
133 let match_candidates = encodings
134 .iter()
135 .enumerate()
136 .map(|(id, enc)| StringMatchCandidate::new(id, enc.name()))
137 .collect::<Vec<_>>();
138 Self {
139 encoding_selector,
140 buffer,
141 encodings,
142 match_candidates: Arc::new(match_candidates),
143 matches: vec![],
144 selected_index: 0,
145 }
146 }
147
148 fn render_data_for_match(&self, mat: &StringMatch, cx: &App) -> String {
149 let candidate_encoding = self.encodings[mat.candidate_id];
150 let current_encoding = self.buffer.read(cx).encoding();
151
152 if candidate_encoding.name() == current_encoding.name() {
153 format!("{} (current)", candidate_encoding.name())
154 } else {
155 candidate_encoding.name().to_string()
156 }
157 }
158}
159
160fn available_encodings() -> Vec<&'static Encoding> {
161 let mut encodings = vec![
162 // Unicode
163 encoding_rs::UTF_8,
164 encoding_rs::UTF_16LE,
165 encoding_rs::UTF_16BE,
166 // Japanese
167 encoding_rs::SHIFT_JIS,
168 encoding_rs::EUC_JP,
169 encoding_rs::ISO_2022_JP,
170 // Chinese
171 encoding_rs::GBK,
172 encoding_rs::GB18030,
173 encoding_rs::BIG5,
174 // Korean
175 encoding_rs::EUC_KR,
176 // Windows / Single Byte Series
177 encoding_rs::WINDOWS_1252, // Western (ISO-8859-1 unified)
178 encoding_rs::WINDOWS_1250, // Central European
179 encoding_rs::WINDOWS_1251, // Cyrillic
180 encoding_rs::WINDOWS_1253, // Greek
181 encoding_rs::WINDOWS_1254, // Turkish (ISO-8859-9 unified)
182 encoding_rs::WINDOWS_1255, // Hebrew
183 encoding_rs::WINDOWS_1256, // Arabic
184 encoding_rs::WINDOWS_1257, // Baltic
185 encoding_rs::WINDOWS_1258, // Vietnamese
186 encoding_rs::WINDOWS_874, // Thai
187 // ISO-8859 Series (others)
188 encoding_rs::ISO_8859_2,
189 encoding_rs::ISO_8859_3,
190 encoding_rs::ISO_8859_4,
191 encoding_rs::ISO_8859_5,
192 encoding_rs::ISO_8859_6,
193 encoding_rs::ISO_8859_7,
194 encoding_rs::ISO_8859_8,
195 encoding_rs::ISO_8859_8_I, // Logical Hebrew
196 encoding_rs::ISO_8859_10,
197 encoding_rs::ISO_8859_13,
198 encoding_rs::ISO_8859_14,
199 encoding_rs::ISO_8859_15,
200 encoding_rs::ISO_8859_16,
201 // Cyrillic / Legacy Misc
202 encoding_rs::KOI8_R,
203 encoding_rs::KOI8_U,
204 encoding_rs::IBM866,
205 encoding_rs::MACINTOSH,
206 encoding_rs::X_MAC_CYRILLIC,
207 // NOTE: The following encodings are intentionally excluded from the list:
208 //
209 // 1. encoding_rs::REPLACEMENT
210 // Used internally for decoding errors. Not suitable for user selection.
211 //
212 // 2. encoding_rs::X_USER_DEFINED
213 // Used for binary data emulation (legacy web behavior). Not for general text editing.
214 ];
215
216 encodings.sort_by_key(|enc| enc.name());
217
218 encodings
219}
220
221impl PickerDelegate for EncodingSelectorDelegate {
222 type ListItem = ListItem;
223
224 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
225 "Reopen with encoding...".into()
226 }
227
228 fn match_count(&self) -> usize {
229 self.matches.len()
230 }
231
232 fn selected_index(&self) -> usize {
233 self.selected_index
234 }
235
236 fn set_selected_index(
237 &mut self,
238 ix: usize,
239 _window: &mut Window,
240 _: &mut Context<Picker<Self>>,
241 ) {
242 self.selected_index = ix;
243 }
244
245 fn update_matches(
246 &mut self,
247 query: String,
248 window: &mut Window,
249 cx: &mut Context<Picker<Self>>,
250 ) -> Task<()> {
251 let background = cx.background_executor().clone();
252 let candidates = self.match_candidates.clone();
253
254 cx.spawn_in(window, async move |this, cx| {
255 let matches = if query.is_empty() {
256 candidates
257 .iter()
258 .enumerate()
259 .map(|(index, candidate)| StringMatch {
260 candidate_id: index,
261 string: candidate.string.clone(),
262 positions: Vec::new(),
263 score: 0.0,
264 })
265 .collect()
266 } else {
267 match_strings(
268 &candidates,
269 &query,
270 false,
271 true,
272 100,
273 &Default::default(),
274 background,
275 )
276 .await
277 };
278
279 this.update(cx, |this, cx| {
280 let delegate = &mut this.delegate;
281 delegate.matches = matches;
282 delegate.selected_index = delegate
283 .selected_index
284 .min(delegate.matches.len().saturating_sub(1));
285 cx.notify();
286 })
287 .log_err();
288 })
289 }
290
291 fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
292 if let Some(mat) = self.matches.get(self.selected_index) {
293 let selected_encoding = self.encodings[mat.candidate_id];
294
295 self.buffer.update(cx, |buffer, cx| {
296 let _ = buffer.reload_with_encoding(selected_encoding, cx);
297 });
298 }
299 self.dismissed(window, cx);
300 }
301
302 fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
303 self.encoding_selector
304 .update(cx, |_, cx| cx.emit(DismissEvent))
305 .log_err();
306 }
307
308 fn render_match(
309 &self,
310 ix: usize,
311 selected: bool,
312 _: &mut Window,
313 cx: &mut Context<Picker<Self>>,
314 ) -> Option<Self::ListItem> {
315 let mat = &self.matches.get(ix)?;
316
317 let label = self.render_data_for_match(mat, cx);
318
319 Some(
320 ListItem::new(ix)
321 .inset(true)
322 .spacing(ListItemSpacing::Sparse)
323 .toggle_state(selected)
324 .child(HighlightedLabel::new(label, mat.positions.clone())),
325 )
326 }
327}