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