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