lib.rs

  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}