lib.rs

  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}