1//! A crate for handling file encodings in the text editor.
2
3use crate::selectors::encoding::Action;
4use editor::Editor;
5use encoding_rs::Encoding;
6use gpui::{ClickEvent, Entity, Subscription, WeakEntity};
7use language::Buffer;
8use ui::{App, Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div};
9use ui::{Clickable, ParentElement};
10use workspace::{ItemHandle, StatusItemView, Workspace, with_active_or_new_workspace};
11
12use crate::selectors::encoding::EncodingSelector;
13use crate::selectors::save_or_reopen::EncodingSaveOrReopenSelector;
14
15/// A status bar item that shows the current file encoding and allows changing it.
16pub struct EncodingIndicator {
17 pub encoding: Option<&'static Encoding>,
18 pub workspace: WeakEntity<Workspace>,
19
20 /// Subscription to observe changes in the active editor
21 observe_editor: Option<Subscription>,
22
23 /// Subscription to observe changes in the `encoding` field of the `Buffer` struct
24 observe_buffer_encoding: Option<Subscription>,
25
26 /// Whether to show the indicator or not, based on whether an editor is active
27 show: bool,
28
29 /// Whether to show `EncodingSaveOrReopenSelector`. It will be shown only when
30 /// the current buffer is associated with a file.
31 show_save_or_reopen_selector: bool,
32}
33
34pub mod selectors;
35
36impl Render for EncodingIndicator {
37 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
38 let status_element = div();
39 let show_save_or_reopen_selector = self.show_save_or_reopen_selector;
40
41 if !self.show {
42 return status_element;
43 }
44
45 status_element.child(
46 Button::new(
47 "encoding",
48 encoding_name(self.encoding.unwrap_or(encoding_rs::UTF_8)),
49 )
50 .label_size(LabelSize::Small)
51 .tooltip(Tooltip::text("Select Encoding"))
52 .on_click(cx.listener(move |indicator, _: &ClickEvent, window, cx| {
53 if let Some(workspace) = indicator.workspace.upgrade() {
54 workspace.update(cx, move |workspace, cx| {
55 // Open the `EncodingSaveOrReopenSelector` if the buffer is associated with a file,
56 if show_save_or_reopen_selector {
57 EncodingSaveOrReopenSelector::toggle(workspace, window, cx)
58 }
59 // otherwise, open the `EncodingSelector` directly.
60 else {
61 let (_, buffer, _) = workspace
62 .active_item(cx)
63 .unwrap()
64 .act_as::<Editor>(cx)
65 .unwrap()
66 .read(cx)
67 .active_excerpt(cx)
68 .unwrap();
69
70 let weak_workspace = workspace.weak_handle();
71
72 workspace.toggle_modal(window, cx, |window, cx| {
73 let selector = EncodingSelector::new(
74 window,
75 cx,
76 Action::Save,
77 Some(buffer.downgrade()),
78 weak_workspace,
79 );
80 selector
81 })
82 }
83 })
84 }
85 })),
86 )
87 }
88}
89
90impl EncodingIndicator {
91 pub fn new(
92 encoding: Option<&'static Encoding>,
93 workspace: WeakEntity<Workspace>,
94 observe_editor: Option<Subscription>,
95 observe_buffer_encoding: Option<Subscription>,
96 ) -> EncodingIndicator {
97 EncodingIndicator {
98 encoding,
99 workspace,
100 observe_editor,
101 show: false,
102 observe_buffer_encoding,
103 show_save_or_reopen_selector: false,
104 }
105 }
106
107 /// Update the encoding when the active editor is switched.
108 pub fn update_when_editor_is_switched(
109 &mut self,
110 editor: Entity<Editor>,
111 _: &mut Window,
112 cx: &mut Context<EncodingIndicator>,
113 ) {
114 let editor = editor.read(cx);
115 if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
116 let encoding = buffer.read(cx).encoding.clone();
117 self.encoding = Some(&*encoding.lock().unwrap());
118
119 if let Some(_) = buffer.read(cx).file() {
120 self.show_save_or_reopen_selector = true;
121 } else {
122 self.show_save_or_reopen_selector = false;
123 }
124 }
125
126 cx.notify();
127 }
128
129 /// Update the encoding when the `encoding` field of the `Buffer` struct changes.
130 pub fn update_when_buffer_encoding_changes(
131 &mut self,
132 buffer: Entity<Buffer>,
133 _: &mut Window,
134 cx: &mut Context<EncodingIndicator>,
135 ) {
136 let encoding = buffer.read(cx).encoding.clone();
137 self.encoding = Some(&*encoding.lock().unwrap());
138 cx.notify();
139 }
140}
141
142impl StatusItemView for EncodingIndicator {
143 fn set_active_pane_item(
144 &mut self,
145 active_pane_item: Option<&dyn ItemHandle>,
146 window: &mut Window,
147 cx: &mut Context<Self>,
148 ) {
149 match active_pane_item.and_then(|item| item.downcast::<Editor>()) {
150 Some(editor) => {
151 self.observe_editor =
152 Some(cx.observe_in(&editor, window, Self::update_when_editor_is_switched));
153 if let Some((_, buffer, _)) = &editor.read(cx).active_excerpt(cx) {
154 self.observe_buffer_encoding = Some(cx.observe_in(
155 buffer,
156 window,
157 Self::update_when_buffer_encoding_changes,
158 ));
159 }
160 self.update_when_editor_is_switched(editor, window, cx);
161 self.show = true;
162 }
163 None => {
164 self.encoding = None;
165 self.observe_editor = None;
166 self.show = false;
167 }
168 }
169 }
170}
171
172/// Get a human-readable name for the given encoding.
173pub fn encoding_name(encoding: &'static Encoding) -> String {
174 let name = encoding.name();
175
176 match name {
177 "UTF-8" => "UTF-8",
178 "UTF-16LE" => "UTF-16 LE",
179 "UTF-16BE" => "UTF-16 BE",
180 "windows-1252" => "Windows-1252",
181 "windows-1251" => "Windows-1251",
182 "windows-1250" => "Windows-1250",
183 "ISO-8859-2" => "ISO 8859-2",
184 "ISO-8859-3" => "ISO 8859-3",
185 "ISO-8859-4" => "ISO 8859-4",
186 "ISO-8859-5" => "ISO 8859-5",
187 "ISO-8859-6" => "ISO 8859-6",
188 "ISO-8859-7" => "ISO 8859-7",
189 "ISO-8859-8" => "ISO 8859-8",
190 "ISO-8859-13" => "ISO 8859-13",
191 "ISO-8859-15" => "ISO 8859-15",
192 "KOI8-R" => "KOI8-R",
193 "KOI8-U" => "KOI8-U",
194 "macintosh" => "MacRoman",
195 "x-mac-cyrillic" => "Mac Cyrillic",
196 "windows-874" => "Windows-874",
197 "windows-1253" => "Windows-1253",
198 "windows-1254" => "Windows-1254",
199 "windows-1255" => "Windows-1255",
200 "windows-1256" => "Windows-1256",
201 "windows-1257" => "Windows-1257",
202 "windows-1258" => "Windows-1258",
203 "EUC-KR" => "Windows-949",
204 "EUC-JP" => "EUC-JP",
205 "ISO-2022-JP" => "ISO 2022-JP",
206 "GBK" => "GBK",
207 "gb18030" => "GB18030",
208 "Big5" => "Big5",
209 _ => name,
210 }
211 .to_string()
212}
213
214/// Get an encoding from its index in the predefined list.
215/// If the index is out of range, UTF-8 is returned as a default.
216pub fn encoding_from_index(index: usize) -> &'static Encoding {
217 match index {
218 0 => encoding_rs::UTF_8,
219 1 => encoding_rs::UTF_16LE,
220 2 => encoding_rs::UTF_16BE,
221 3 => encoding_rs::WINDOWS_1252,
222 4 => encoding_rs::WINDOWS_1251,
223 5 => encoding_rs::WINDOWS_1250,
224 6 => encoding_rs::ISO_8859_2,
225 7 => encoding_rs::ISO_8859_3,
226 8 => encoding_rs::ISO_8859_4,
227 9 => encoding_rs::ISO_8859_5,
228 10 => encoding_rs::ISO_8859_6,
229 11 => encoding_rs::ISO_8859_7,
230 12 => encoding_rs::ISO_8859_8,
231 13 => encoding_rs::ISO_8859_13,
232 14 => encoding_rs::ISO_8859_15,
233 15 => encoding_rs::KOI8_R,
234 16 => encoding_rs::KOI8_U,
235 17 => encoding_rs::MACINTOSH,
236 18 => encoding_rs::X_MAC_CYRILLIC,
237 19 => encoding_rs::WINDOWS_874,
238 20 => encoding_rs::WINDOWS_1253,
239 21 => encoding_rs::WINDOWS_1254,
240 22 => encoding_rs::WINDOWS_1255,
241 23 => encoding_rs::WINDOWS_1256,
242 24 => encoding_rs::WINDOWS_1257,
243 25 => encoding_rs::WINDOWS_1258,
244 26 => encoding_rs::EUC_KR,
245 27 => encoding_rs::EUC_JP,
246 28 => encoding_rs::ISO_2022_JP,
247 29 => encoding_rs::GBK,
248 30 => encoding_rs::GB18030,
249 31 => encoding_rs::BIG5,
250 _ => encoding_rs::UTF_8,
251 }
252}
253
254/// Get an encoding from its name.
255pub fn encoding_from_name(name: &str) -> &'static Encoding {
256 match name {
257 "UTF-8" => encoding_rs::UTF_8,
258 "UTF-16 LE" => encoding_rs::UTF_16LE,
259 "UTF-16 BE" => encoding_rs::UTF_16BE,
260 "Windows-1252" => encoding_rs::WINDOWS_1252,
261 "Windows-1251" => encoding_rs::WINDOWS_1251,
262 "Windows-1250" => encoding_rs::WINDOWS_1250,
263 "ISO 8859-2" => encoding_rs::ISO_8859_2,
264 "ISO 8859-3" => encoding_rs::ISO_8859_3,
265 "ISO 8859-4" => encoding_rs::ISO_8859_4,
266 "ISO 8859-5" => encoding_rs::ISO_8859_5,
267 "ISO 8859-6" => encoding_rs::ISO_8859_6,
268 "ISO 8859-7" => encoding_rs::ISO_8859_7,
269 "ISO 8859-8" => encoding_rs::ISO_8859_8,
270 "ISO 8859-13" => encoding_rs::ISO_8859_13,
271 "ISO 8859-15" => encoding_rs::ISO_8859_15,
272 "KOI8-R" => encoding_rs::KOI8_R,
273 "KOI8-U" => encoding_rs::KOI8_U,
274 "MacRoman" => encoding_rs::MACINTOSH,
275 "Mac Cyrillic" => encoding_rs::X_MAC_CYRILLIC,
276 "Windows-874" => encoding_rs::WINDOWS_874,
277 "Windows-1253" => encoding_rs::WINDOWS_1253,
278 "Windows-1254" => encoding_rs::WINDOWS_1254,
279 "Windows-1255" => encoding_rs::WINDOWS_1255,
280 "Windows-1256" => encoding_rs::WINDOWS_1256,
281 "Windows-1257" => encoding_rs::WINDOWS_1257,
282 "Windows-1258" => encoding_rs::WINDOWS_1258,
283 "Windows-949" => encoding_rs::EUC_KR,
284 "EUC-JP" => encoding_rs::EUC_JP,
285 "ISO 2022-JP" => encoding_rs::ISO_2022_JP,
286 "GBK" => encoding_rs::GBK,
287 "GB18030" => encoding_rs::GB18030,
288 "Big5" => encoding_rs::BIG5,
289 _ => encoding_rs::UTF_8, // Default to UTF-8 for unknown names
290 }
291}
292
293pub fn init(cx: &mut App) {
294 cx.on_action(|_: &zed_actions::encodings::Toggle, cx: &mut App| {
295 with_active_or_new_workspace(cx, |workspace, window, cx| {
296 let weak_workspace = workspace.weak_handle();
297 workspace.toggle_modal(window, cx, |window, cx| {
298 EncodingSelector::new(window, cx, Action::Reopen, None, weak_workspace)
299 });
300 });
301 });
302}