From 1d95a18a1163c738c2f575b3e85e22a37b319dcd Mon Sep 17 00:00:00 2001 From: R Aadarsh Date: Sun, 19 Oct 2025 14:48:37 +0530 Subject: [PATCH] Create a new crate `encodings` that will have all that is not related to UI. The `encodings_ui` crate will only have UI related components in the future. --- Cargo.lock | 10 +- Cargo.toml | 3 + crates/encodings/Cargo.toml | 15 +- crates/encodings/src/lib.rs | 429 +++++------------- crates/encodings_ui/Cargo.toml | 25 + .../{encodings => encodings_ui}/LICENSE-GPL | 0 crates/encodings_ui/src/lib.rs | 343 ++++++++++++++ .../src/selectors.rs | 0 crates/project/src/invalid_item_view.rs | 16 +- crates/zed/Cargo.toml | 2 +- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 2 +- crates/zed_actions/src/lib.rs | 2 +- 13 files changed, 514 insertions(+), 335 deletions(-) create mode 100644 crates/encodings_ui/Cargo.toml rename crates/{encodings => encodings_ui}/LICENSE-GPL (100%) create mode 100644 crates/encodings_ui/src/lib.rs rename crates/{encodings => encodings_ui}/src/selectors.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 119769b58ad3c64d5b1eae27a46574813325f8a3..c4bf18ec7e4b27d7ca94fd524514dc60853aa90f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,14 @@ dependencies = [ [[package]] name = "encodings" version = "0.1.0" +dependencies = [ + "anyhow", + "encoding_rs", +] + +[[package]] +name = "encodings_ui" +version = "0.1.0" dependencies = [ "editor", "encoding_rs", @@ -21208,7 +21216,7 @@ dependencies = [ "edit_prediction_button", "editor", "encoding_rs", - "encodings", + "encodings_ui", "env_logger 0.11.8", "extension", "extension_host", diff --git a/Cargo.toml b/Cargo.toml index 80c8452838d9db5fcd41ba8133e0466011bc5f02..8e5bc5a79cfc178252e065718b7de2033219102e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "crates/editor", "crates/eval", "crates/encodings", + "crates/encodings_ui", "crates/explorer_command_injector", "crates/extension", "crates/extension_api", @@ -223,6 +224,7 @@ members = [ "tooling/perf", "tooling/xtask", "crates/encodings", + "crates/encodings_ui", ] default-members = ["crates/zed"] @@ -316,6 +318,7 @@ edit_prediction_button = { path = "crates/edit_prediction_button" } edit_prediction_context = { path = "crates/edit_prediction_context" } zeta2_tools = { path = "crates/zeta2_tools" } encodings = {path = "crates/encodings"} +encodings_ui = {path = "crates/encodings_ui"} inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } journal = { path = "crates/journal" } diff --git a/crates/encodings/Cargo.toml b/crates/encodings/Cargo.toml index 341c8cfa8f078287eed1e521a1a8a628dd4bf7c3..50b932ab8649f2fb03d4d34c82c6144068bff4a0 100644 --- a/crates/encodings/Cargo.toml +++ b/crates/encodings/Cargo.toml @@ -5,21 +5,8 @@ publish.workspace = true edition.workspace = true [dependencies] -editor.workspace = true encoding_rs.workspace = true -fs.workspace = true -futures.workspace = true -fuzzy.workspace = true -gpui.workspace = true -language.workspace = true -picker.workspace = true -settings.workspace = true -ui.workspace = true -util.workspace = true -workspace.workspace = true -workspace-hack.workspace = true -zed_actions.workspace = true - +anyhow.workspace = true [lints] workspace = true diff --git a/crates/encodings/src/lib.rs b/crates/encodings/src/lib.rs index 86576212ec47cfaa477c9e4e1dd318fa2ad4f0d4..e3baad901670373ff0fb0586fbdd73ee7ef2aafb 100644 --- a/crates/encodings/src/lib.rs +++ b/crates/encodings/src/lib.rs @@ -1,343 +1,154 @@ -//! A crate for handling file encodings in the text editor. - -use crate::selectors::encoding::Action; -use editor::Editor; -use encoding_rs::Encoding; -use gpui::{ClickEvent, Entity, Subscription, WeakEntity}; -use language::Buffer; -use ui::{App, Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div}; -use ui::{Clickable, ParentElement}; -use util::ResultExt; -use workspace::{ - CloseActiveItem, ItemHandle, OpenOptions, StatusItemView, Workspace, - with_active_or_new_workspace, +use encoding_rs; +use std::{ + fmt::Debug, + sync::{Arc, Mutex, atomic::AtomicBool}, }; -use zed_actions::encodings::{ForceOpen, Toggle}; -use crate::selectors::encoding::EncodingSelector; -use crate::selectors::save_or_reopen::EncodingSaveOrReopenSelector; +pub use encoding_rs::{ + BIG5, EUC_JP, EUC_KR, GB18030, GBK, IBM866, ISO_2022_JP, ISO_8859_2, ISO_8859_3, ISO_8859_4, + ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, ISO_8859_8_I, ISO_8859_10, ISO_8859_13, + ISO_8859_14, ISO_8859_15, ISO_8859_16, KOI8_R, KOI8_U, MACINTOSH, SHIFT_JIS, UTF_8, UTF_16BE, + UTF_16LE, WINDOWS_874, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, WINDOWS_1254, + WINDOWS_1255, WINDOWS_1256, WINDOWS_1257, WINDOWS_1258, X_MAC_CYRILLIC, +}; -/// A status bar item that shows the current file encoding and allows changing it. -pub struct EncodingIndicator { - pub encoding: Option<&'static Encoding>, - pub workspace: WeakEntity, +pub struct Encoding(Mutex<&'static encoding_rs::Encoding>); - /// Subscription to observe changes in the active editor - observe_editor: Option, +impl Debug for Encoding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple(&format!("Encoding{:?}", self.0)) + .field(&self.get().name()) + .finish() + } +} - /// Subscription to observe changes in the `encoding` field of the `Buffer` struct - observe_buffer_encoding: Option, +impl Default for Encoding { + fn default() -> Self { + Encoding(Mutex::new(UTF_8)) + } +} - /// Whether to show the indicator or not, based on whether an editor is active - show: bool, +unsafe impl Send for Encoding {} +unsafe impl Sync for Encoding {} - /// Whether to show `EncodingSaveOrReopenSelector`. It will be shown only when - /// the current buffer is associated with a file. - show_save_or_reopen_selector: bool, -} +impl Encoding { + pub fn new(encoding: &'static encoding_rs::Encoding) -> Self { + Self(Mutex::new(encoding)) + } -pub mod selectors; + pub fn set(&self, encoding: &'static encoding_rs::Encoding) { + *self.0.lock().unwrap() = encoding; + } -impl Render for EncodingIndicator { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { - let status_element = div(); - let show_save_or_reopen_selector = self.show_save_or_reopen_selector; + pub fn get(&self) -> &'static encoding_rs::Encoding { + *self.0.lock().unwrap() + } - if !self.show { - return status_element; + pub async fn decode( + &self, + input: Vec, + force: bool, + detect_utf16: bool, + buffer_encoding: Option>, + ) -> anyhow::Result { + // Check if the input starts with a BOM for UTF-16 encodings only if detect_utf16 is true. + if detect_utf16 { + if let Some(encoding) = match input.get(..2) { + Some([0xFF, 0xFE]) => Some(UTF_16LE), + Some([0xFE, 0xFF]) => Some(UTF_16BE), + _ => None, + } { + self.set(encoding); + + if let Some(v) = buffer_encoding { + v.set(encoding) + } + } } - status_element.child( - Button::new( - "encoding", - encoding_name(self.encoding.unwrap_or(encoding_rs::UTF_8)), - ) - .label_size(LabelSize::Small) - .tooltip(Tooltip::text("Select Encoding")) - .on_click(cx.listener(move |indicator, _: &ClickEvent, window, cx| { - if let Some(workspace) = indicator.workspace.upgrade() { - workspace.update(cx, move |workspace, cx| { - // Open the `EncodingSaveOrReopenSelector` if the buffer is associated with a file, - if show_save_or_reopen_selector { - EncodingSaveOrReopenSelector::toggle(workspace, window, cx) - } - // otherwise, open the `EncodingSelector` directly. - else { - let (_, buffer, _) = workspace - .active_item(cx) - .unwrap() - .act_as::(cx) - .unwrap() - .read(cx) - .active_excerpt(cx) - .unwrap(); - - let weak_workspace = workspace.weak_handle(); + let (cow, had_errors) = self.get().decode_with_bom_removal(&input); - workspace.toggle_modal(window, cx, |window, cx| { - let selector = EncodingSelector::new( - window, - cx, - Action::Save, - Some(buffer.downgrade()), - weak_workspace, - None, - ); - selector - }) - } - }) - } - })), - ) - } -} + if force { + return Ok(cow.to_string()); + } -impl EncodingIndicator { - pub fn new( - encoding: Option<&'static Encoding>, - workspace: WeakEntity, - observe_editor: Option, - observe_buffer_encoding: Option, - ) -> EncodingIndicator { - EncodingIndicator { - encoding, - workspace, - observe_editor, - show: false, - observe_buffer_encoding, - show_save_or_reopen_selector: false, + if !had_errors { + Ok(cow.to_string()) + } else { + Err(anyhow::anyhow!( + "The file contains invalid bytes for the specified encoding: {}.\nThis usually means that the file is not a regular text file, or is encoded in a different encoding.\nContinuing to open it may result in data loss if saved.", + self.get().name() + )) } } - /// Update the encoding when the active editor is switched. - pub fn update_when_editor_is_switched( - &mut self, - editor: Entity, - _: &mut Window, - cx: &mut Context, - ) { - let editor = editor.read(cx); - if let Some((_, buffer, _)) = editor.active_excerpt(cx) { - let encoding = buffer.read(cx).encoding.clone(); - self.encoding = Some(&*encoding.lock().unwrap()); + pub async fn encode(&self, input: String) -> anyhow::Result> { + if self.get() == UTF_16BE { + let mut data = Vec::::with_capacity(input.len() * 2); - if let Some(_) = buffer.read(cx).file() { - self.show_save_or_reopen_selector = true; - } else { - self.show_save_or_reopen_selector = false; - } - } + // Convert the input string to UTF-16BE bytes + let utf16be_bytes = input.encode_utf16().flat_map(|u| u.to_be_bytes()); - cx.notify(); - } + data.extend(utf16be_bytes); + return Ok(data); + } else if self.get() == UTF_16LE { + let mut data = Vec::::with_capacity(input.len() * 2); - /// Update the encoding when the `encoding` field of the `Buffer` struct changes. - pub fn update_when_buffer_encoding_changes( - &mut self, - buffer: Entity, - _: &mut Window, - cx: &mut Context, - ) { - let encoding = buffer.read(cx).encoding.clone(); - self.encoding = Some(&*encoding.lock().unwrap()); - cx.notify(); - } -} + // Convert the input string to UTF-16LE bytes + let utf16le_bytes = input.encode_utf16().flat_map(|u| u.to_le_bytes()); -impl StatusItemView for EncodingIndicator { - fn set_active_pane_item( - &mut self, - active_pane_item: Option<&dyn ItemHandle>, - window: &mut Window, - cx: &mut Context, - ) { - match active_pane_item.and_then(|item| item.downcast::()) { - Some(editor) => { - self.observe_editor = - Some(cx.observe_in(&editor, window, Self::update_when_editor_is_switched)); - if let Some((_, buffer, _)) = &editor.read(cx).active_excerpt(cx) { - self.observe_buffer_encoding = Some(cx.observe_in( - buffer, - window, - Self::update_when_buffer_encoding_changes, - )); - } - self.update_when_editor_is_switched(editor, window, cx); - self.show = true; - } - None => { - self.encoding = None; - self.observe_editor = None; - self.show = false; - } + data.extend(utf16le_bytes); + return Ok(data); + } else { + let (cow, _encoding_used, _had_errors) = self.get().encode(&input); + + Ok(cow.into_owned()) } } -} - -/// Get a human-readable name for the given encoding. -pub fn encoding_name(encoding: &'static Encoding) -> String { - let name = encoding.name(); - match name { - "UTF-8" => "UTF-8", - "UTF-16LE" => "UTF-16 LE", - "UTF-16BE" => "UTF-16 BE", - "windows-1252" => "Windows-1252", - "windows-1251" => "Windows-1251", - "windows-1250" => "Windows-1250", - "ISO-8859-2" => "ISO 8859-2", - "ISO-8859-3" => "ISO 8859-3", - "ISO-8859-4" => "ISO 8859-4", - "ISO-8859-5" => "ISO 8859-5", - "ISO-8859-6" => "ISO 8859-6", - "ISO-8859-7" => "ISO 8859-7", - "ISO-8859-8" => "ISO 8859-8", - "ISO-8859-13" => "ISO 8859-13", - "ISO-8859-15" => "ISO 8859-15", - "KOI8-R" => "KOI8-R", - "KOI8-U" => "KOI8-U", - "macintosh" => "MacRoman", - "x-mac-cyrillic" => "Mac Cyrillic", - "windows-874" => "Windows-874", - "windows-1253" => "Windows-1253", - "windows-1254" => "Windows-1254", - "windows-1255" => "Windows-1255", - "windows-1256" => "Windows-1256", - "windows-1257" => "Windows-1257", - "windows-1258" => "Windows-1258", - "EUC-KR" => "Windows-949", - "EUC-JP" => "EUC-JP", - "ISO-2022-JP" => "ISO 2022-JP", - "GBK" => "GBK", - "gb18030" => "GB18030", - "Big5" => "Big5", - _ => name, + pub fn reset(&self) { + self.set(UTF_8); } - .to_string() } -/// Get an encoding from its index in the predefined list. -/// If the index is out of range, UTF-8 is returned as a default. -pub fn encoding_from_index(index: usize) -> &'static Encoding { - match index { - 0 => encoding_rs::UTF_8, - 1 => encoding_rs::UTF_16LE, - 2 => encoding_rs::UTF_16BE, - 3 => encoding_rs::WINDOWS_1252, - 4 => encoding_rs::WINDOWS_1251, - 5 => encoding_rs::WINDOWS_1250, - 6 => encoding_rs::ISO_8859_2, - 7 => encoding_rs::ISO_8859_3, - 8 => encoding_rs::ISO_8859_4, - 9 => encoding_rs::ISO_8859_5, - 10 => encoding_rs::ISO_8859_6, - 11 => encoding_rs::ISO_8859_7, - 12 => encoding_rs::ISO_8859_8, - 13 => encoding_rs::ISO_8859_13, - 14 => encoding_rs::ISO_8859_15, - 15 => encoding_rs::KOI8_R, - 16 => encoding_rs::KOI8_U, - 17 => encoding_rs::MACINTOSH, - 18 => encoding_rs::X_MAC_CYRILLIC, - 19 => encoding_rs::WINDOWS_874, - 20 => encoding_rs::WINDOWS_1253, - 21 => encoding_rs::WINDOWS_1254, - 22 => encoding_rs::WINDOWS_1255, - 23 => encoding_rs::WINDOWS_1256, - 24 => encoding_rs::WINDOWS_1257, - 25 => encoding_rs::WINDOWS_1258, - 26 => encoding_rs::EUC_KR, - 27 => encoding_rs::EUC_JP, - 28 => encoding_rs::ISO_2022_JP, - 29 => encoding_rs::GBK, - 30 => encoding_rs::GB18030, - 31 => encoding_rs::BIG5, - _ => encoding_rs::UTF_8, - } +/// Convert a byte vector from a specified encoding to a UTF-8 string. +pub async fn to_utf8( + input: Vec, + encoding: Encoding, + force: bool, + detect_utf16: bool, + buffer_encoding: Option>, +) -> anyhow::Result { + encoding + .decode(input, force, detect_utf16, buffer_encoding) + .await } -/// Get an encoding from its name. -pub fn encoding_from_name(name: &str) -> &'static Encoding { - match name { - "UTF-8" => encoding_rs::UTF_8, - "UTF-16 LE" => encoding_rs::UTF_16LE, - "UTF-16 BE" => encoding_rs::UTF_16BE, - "Windows-1252" => encoding_rs::WINDOWS_1252, - "Windows-1251" => encoding_rs::WINDOWS_1251, - "Windows-1250" => encoding_rs::WINDOWS_1250, - "ISO 8859-2" => encoding_rs::ISO_8859_2, - "ISO 8859-3" => encoding_rs::ISO_8859_3, - "ISO 8859-4" => encoding_rs::ISO_8859_4, - "ISO 8859-5" => encoding_rs::ISO_8859_5, - "ISO 8859-6" => encoding_rs::ISO_8859_6, - "ISO 8859-7" => encoding_rs::ISO_8859_7, - "ISO 8859-8" => encoding_rs::ISO_8859_8, - "ISO 8859-13" => encoding_rs::ISO_8859_13, - "ISO 8859-15" => encoding_rs::ISO_8859_15, - "KOI8-R" => encoding_rs::KOI8_R, - "KOI8-U" => encoding_rs::KOI8_U, - "MacRoman" => encoding_rs::MACINTOSH, - "Mac Cyrillic" => encoding_rs::X_MAC_CYRILLIC, - "Windows-874" => encoding_rs::WINDOWS_874, - "Windows-1253" => encoding_rs::WINDOWS_1253, - "Windows-1254" => encoding_rs::WINDOWS_1254, - "Windows-1255" => encoding_rs::WINDOWS_1255, - "Windows-1256" => encoding_rs::WINDOWS_1256, - "Windows-1257" => encoding_rs::WINDOWS_1257, - "Windows-1258" => encoding_rs::WINDOWS_1258, - "Windows-949" => encoding_rs::EUC_KR, - "EUC-JP" => encoding_rs::EUC_JP, - "ISO 2022-JP" => encoding_rs::ISO_2022_JP, - "GBK" => encoding_rs::GBK, - "GB18030" => encoding_rs::GB18030, - "Big5" => encoding_rs::BIG5, - _ => encoding_rs::UTF_8, // Default to UTF-8 for unknown names - } +/// Convert a UTF-8 string to a byte vector in a specified encoding. +pub async fn from_utf8(input: String, target: Encoding) -> anyhow::Result> { + target.encode(input).await } -pub fn init(cx: &mut App) { - cx.on_action(|action: &Toggle, cx: &mut App| { - let Toggle(path) = action.clone(); - let path = path.to_path_buf(); - - with_active_or_new_workspace(cx, |workspace, window, cx| { - let weak_workspace = workspace.weak_handle(); - workspace.toggle_modal(window, cx, |window, cx| { - EncodingSelector::new(window, cx, Action::Reopen, None, weak_workspace, Some(path)) - }); - }); - }); - - cx.on_action(|action: &ForceOpen, cx: &mut App| { - let ForceOpen(path) = action.clone(); - let path = path.to_path_buf(); - - with_active_or_new_workspace(cx, |workspace, window, cx| { - workspace.active_pane().update(cx, |pane, cx| { - pane.close_active_item(&CloseActiveItem::default(), window, cx) - .detach(); - }); - - { - let force = workspace.encoding_options.force.get_mut(); - - *force = true; - } +pub struct EncodingOptions { + pub encoding: Arc>, + pub force: AtomicBool, + pub detect_utf16: AtomicBool, +} - let open_task = workspace.open_abs_path(path, OpenOptions::default(), window, cx); - let weak_workspace = workspace.weak_handle(); +impl EncodingOptions { + pub fn reset(&mut self) { + self.encoding.lock().unwrap().reset(); + *self.force.get_mut() = false; + *self.detect_utf16.get_mut() = true; + } +} - cx.spawn(async move |_, cx| { - let workspace = weak_workspace.upgrade().unwrap(); - open_task.await.log_err(); - workspace - .update(cx, |workspace: &mut Workspace, _| { - *workspace.encoding_options.force.get_mut() = false; - }) - .log_err(); - }) - .detach(); - }); - }); +impl Default for EncodingOptions { + fn default() -> Self { + EncodingOptions { + encoding: Arc::new(Mutex::new(Encoding::default())), + force: AtomicBool::new(false), + detect_utf16: AtomicBool::new(true), + } + } } diff --git a/crates/encodings_ui/Cargo.toml b/crates/encodings_ui/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bc83885841456f1ba7fd63ff97dc6d1e40f5ffad --- /dev/null +++ b/crates/encodings_ui/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "encodings_ui" +version = "0.1.0" +publish.workspace = true +edition.workspace = true + +[dependencies] +editor.workspace = true +encoding_rs.workspace = true +fs.workspace = true +futures.workspace = true +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true +picker.workspace = true +settings.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true +workspace-hack.workspace = true +zed_actions.workspace = true + + +[lints] +workspace = true diff --git a/crates/encodings/LICENSE-GPL b/crates/encodings_ui/LICENSE-GPL similarity index 100% rename from crates/encodings/LICENSE-GPL rename to crates/encodings_ui/LICENSE-GPL diff --git a/crates/encodings_ui/src/lib.rs b/crates/encodings_ui/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..591d6e5349350b2b13aab4dd58623fdba6f23c90 --- /dev/null +++ b/crates/encodings_ui/src/lib.rs @@ -0,0 +1,343 @@ +//! A crate for handling file encodings in the text editor. + +use crate::selectors::encoding::Action; +use editor::Editor; +use encoding_rs::Encoding; +use gpui::{ClickEvent, Entity, Subscription, WeakEntity}; +use language::Buffer; +use ui::{App, Button, ButtonCommon, Context, LabelSize, Render, Tooltip, Window, div}; +use ui::{Clickable, ParentElement}; +use util::ResultExt; +use workspace::{ + CloseActiveItem, ItemHandle, OpenOptions, StatusItemView, Workspace, + with_active_or_new_workspace, +}; +use zed_actions::encodings_ui::{ForceOpen, Toggle}; + +use crate::selectors::encoding::EncodingSelector; +use crate::selectors::save_or_reopen::EncodingSaveOrReopenSelector; + +/// A status bar item that shows the current file encoding and allows changing it. +pub struct EncodingIndicator { + pub encoding: Option<&'static Encoding>, + pub workspace: WeakEntity, + + /// Subscription to observe changes in the active editor + observe_editor: Option, + + /// Subscription to observe changes in the `encoding` field of the `Buffer` struct + observe_buffer_encoding: Option, + + /// Whether to show the indicator or not, based on whether an editor is active + show: bool, + + /// Whether to show `EncodingSaveOrReopenSelector`. It will be shown only when + /// the current buffer is associated with a file. + show_save_or_reopen_selector: bool, +} + +pub mod selectors; + +impl Render for EncodingIndicator { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { + let status_element = div(); + let show_save_or_reopen_selector = self.show_save_or_reopen_selector; + + if !self.show { + return status_element; + } + + status_element.child( + Button::new( + "encoding", + encoding_name(self.encoding.unwrap_or(encoding_rs::UTF_8)), + ) + .label_size(LabelSize::Small) + .tooltip(Tooltip::text("Select Encoding")) + .on_click(cx.listener(move |indicator, _: &ClickEvent, window, cx| { + if let Some(workspace) = indicator.workspace.upgrade() { + workspace.update(cx, move |workspace, cx| { + // Open the `EncodingSaveOrReopenSelector` if the buffer is associated with a file, + if show_save_or_reopen_selector { + EncodingSaveOrReopenSelector::toggle(workspace, window, cx) + } + // otherwise, open the `EncodingSelector` directly. + else { + let (_, buffer, _) = workspace + .active_item(cx) + .unwrap() + .act_as::(cx) + .unwrap() + .read(cx) + .active_excerpt(cx) + .unwrap(); + + let weak_workspace = workspace.weak_handle(); + + workspace.toggle_modal(window, cx, |window, cx| { + let selector = EncodingSelector::new( + window, + cx, + Action::Save, + Some(buffer.downgrade()), + weak_workspace, + None, + ); + selector + }) + } + }) + } + })), + ) + } +} + +impl EncodingIndicator { + pub fn new( + encoding: Option<&'static Encoding>, + workspace: WeakEntity, + observe_editor: Option, + observe_buffer_encoding: Option, + ) -> EncodingIndicator { + EncodingIndicator { + encoding, + workspace, + observe_editor, + show: false, + observe_buffer_encoding, + show_save_or_reopen_selector: false, + } + } + + /// Update the encoding when the active editor is switched. + pub fn update_when_editor_is_switched( + &mut self, + editor: Entity, + _: &mut Window, + cx: &mut Context, + ) { + let editor = editor.read(cx); + if let Some((_, buffer, _)) = editor.active_excerpt(cx) { + let encoding = buffer.read(cx).encoding.clone(); + self.encoding = Some(&*encoding.lock().unwrap()); + + if let Some(_) = buffer.read(cx).file() { + self.show_save_or_reopen_selector = true; + } else { + self.show_save_or_reopen_selector = false; + } + } + + cx.notify(); + } + + /// Update the encoding when the `encoding` field of the `Buffer` struct changes. + pub fn update_when_buffer_encoding_changes( + &mut self, + buffer: Entity, + _: &mut Window, + cx: &mut Context, + ) { + let encoding = buffer.read(cx).encoding.clone(); + self.encoding = Some(&*encoding.lock().unwrap()); + cx.notify(); + } +} + +impl StatusItemView for EncodingIndicator { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + window: &mut Window, + cx: &mut Context, + ) { + match active_pane_item.and_then(|item| item.downcast::()) { + Some(editor) => { + self.observe_editor = + Some(cx.observe_in(&editor, window, Self::update_when_editor_is_switched)); + if let Some((_, buffer, _)) = &editor.read(cx).active_excerpt(cx) { + self.observe_buffer_encoding = Some(cx.observe_in( + buffer, + window, + Self::update_when_buffer_encoding_changes, + )); + } + self.update_when_editor_is_switched(editor, window, cx); + self.show = true; + } + None => { + self.encoding = None; + self.observe_editor = None; + self.show = false; + } + } + } +} + +/// Get a human-readable name for the given encoding. +pub fn encoding_name(encoding: &'static Encoding) -> String { + let name = encoding.name(); + + match name { + "UTF-8" => "UTF-8", + "UTF-16LE" => "UTF-16 LE", + "UTF-16BE" => "UTF-16 BE", + "windows-1252" => "Windows-1252", + "windows-1251" => "Windows-1251", + "windows-1250" => "Windows-1250", + "ISO-8859-2" => "ISO 8859-2", + "ISO-8859-3" => "ISO 8859-3", + "ISO-8859-4" => "ISO 8859-4", + "ISO-8859-5" => "ISO 8859-5", + "ISO-8859-6" => "ISO 8859-6", + "ISO-8859-7" => "ISO 8859-7", + "ISO-8859-8" => "ISO 8859-8", + "ISO-8859-13" => "ISO 8859-13", + "ISO-8859-15" => "ISO 8859-15", + "KOI8-R" => "KOI8-R", + "KOI8-U" => "KOI8-U", + "macintosh" => "MacRoman", + "x-mac-cyrillic" => "Mac Cyrillic", + "windows-874" => "Windows-874", + "windows-1253" => "Windows-1253", + "windows-1254" => "Windows-1254", + "windows-1255" => "Windows-1255", + "windows-1256" => "Windows-1256", + "windows-1257" => "Windows-1257", + "windows-1258" => "Windows-1258", + "EUC-KR" => "Windows-949", + "EUC-JP" => "EUC-JP", + "ISO-2022-JP" => "ISO 2022-JP", + "GBK" => "GBK", + "gb18030" => "GB18030", + "Big5" => "Big5", + _ => name, + } + .to_string() +} + +/// Get an encoding from its index in the predefined list. +/// If the index is out of range, UTF-8 is returned as a default. +pub fn encoding_from_index(index: usize) -> &'static Encoding { + match index { + 0 => encoding_rs::UTF_8, + 1 => encoding_rs::UTF_16LE, + 2 => encoding_rs::UTF_16BE, + 3 => encoding_rs::WINDOWS_1252, + 4 => encoding_rs::WINDOWS_1251, + 5 => encoding_rs::WINDOWS_1250, + 6 => encoding_rs::ISO_8859_2, + 7 => encoding_rs::ISO_8859_3, + 8 => encoding_rs::ISO_8859_4, + 9 => encoding_rs::ISO_8859_5, + 10 => encoding_rs::ISO_8859_6, + 11 => encoding_rs::ISO_8859_7, + 12 => encoding_rs::ISO_8859_8, + 13 => encoding_rs::ISO_8859_13, + 14 => encoding_rs::ISO_8859_15, + 15 => encoding_rs::KOI8_R, + 16 => encoding_rs::KOI8_U, + 17 => encoding_rs::MACINTOSH, + 18 => encoding_rs::X_MAC_CYRILLIC, + 19 => encoding_rs::WINDOWS_874, + 20 => encoding_rs::WINDOWS_1253, + 21 => encoding_rs::WINDOWS_1254, + 22 => encoding_rs::WINDOWS_1255, + 23 => encoding_rs::WINDOWS_1256, + 24 => encoding_rs::WINDOWS_1257, + 25 => encoding_rs::WINDOWS_1258, + 26 => encoding_rs::EUC_KR, + 27 => encoding_rs::EUC_JP, + 28 => encoding_rs::ISO_2022_JP, + 29 => encoding_rs::GBK, + 30 => encoding_rs::GB18030, + 31 => encoding_rs::BIG5, + _ => encoding_rs::UTF_8, + } +} + +/// Get an encoding from its name. +pub fn encoding_from_name(name: &str) -> &'static Encoding { + match name { + "UTF-8" => encoding_rs::UTF_8, + "UTF-16 LE" => encoding_rs::UTF_16LE, + "UTF-16 BE" => encoding_rs::UTF_16BE, + "Windows-1252" => encoding_rs::WINDOWS_1252, + "Windows-1251" => encoding_rs::WINDOWS_1251, + "Windows-1250" => encoding_rs::WINDOWS_1250, + "ISO 8859-2" => encoding_rs::ISO_8859_2, + "ISO 8859-3" => encoding_rs::ISO_8859_3, + "ISO 8859-4" => encoding_rs::ISO_8859_4, + "ISO 8859-5" => encoding_rs::ISO_8859_5, + "ISO 8859-6" => encoding_rs::ISO_8859_6, + "ISO 8859-7" => encoding_rs::ISO_8859_7, + "ISO 8859-8" => encoding_rs::ISO_8859_8, + "ISO 8859-13" => encoding_rs::ISO_8859_13, + "ISO 8859-15" => encoding_rs::ISO_8859_15, + "KOI8-R" => encoding_rs::KOI8_R, + "KOI8-U" => encoding_rs::KOI8_U, + "MacRoman" => encoding_rs::MACINTOSH, + "Mac Cyrillic" => encoding_rs::X_MAC_CYRILLIC, + "Windows-874" => encoding_rs::WINDOWS_874, + "Windows-1253" => encoding_rs::WINDOWS_1253, + "Windows-1254" => encoding_rs::WINDOWS_1254, + "Windows-1255" => encoding_rs::WINDOWS_1255, + "Windows-1256" => encoding_rs::WINDOWS_1256, + "Windows-1257" => encoding_rs::WINDOWS_1257, + "Windows-1258" => encoding_rs::WINDOWS_1258, + "Windows-949" => encoding_rs::EUC_KR, + "EUC-JP" => encoding_rs::EUC_JP, + "ISO 2022-JP" => encoding_rs::ISO_2022_JP, + "GBK" => encoding_rs::GBK, + "GB18030" => encoding_rs::GB18030, + "Big5" => encoding_rs::BIG5, + _ => encoding_rs::UTF_8, // Default to UTF-8 for unknown names + } +} + +pub fn init(cx: &mut App) { + cx.on_action(|action: &Toggle, cx: &mut App| { + let Toggle(path) = action.clone(); + let path = path.to_path_buf(); + + with_active_or_new_workspace(cx, |workspace, window, cx| { + let weak_workspace = workspace.weak_handle(); + workspace.toggle_modal(window, cx, |window, cx| { + EncodingSelector::new(window, cx, Action::Reopen, None, weak_workspace, Some(path)) + }); + }); + }); + + cx.on_action(|action: &ForceOpen, cx: &mut App| { + let ForceOpen(path) = action.clone(); + let path = path.to_path_buf(); + + with_active_or_new_workspace(cx, |workspace, window, cx| { + workspace.active_pane().update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem::default(), window, cx) + .detach(); + }); + + { + let force = workspace.encoding_options.force.get_mut(); + + *force = true; + } + + let open_task = workspace.open_abs_path(path, OpenOptions::default(), window, cx); + let weak_workspace = workspace.weak_handle(); + + cx.spawn(async move |_, cx| { + let workspace = weak_workspace.upgrade().unwrap(); + open_task.await.log_err(); + workspace + .update(cx, |workspace: &mut Workspace, _| { + *workspace.encoding_options.force.get_mut() = false; + }) + .log_err(); + }) + .detach(); + }); + }); +} diff --git a/crates/encodings/src/selectors.rs b/crates/encodings_ui/src/selectors.rs similarity index 100% rename from crates/encodings/src/selectors.rs rename to crates/encodings_ui/src/selectors.rs diff --git a/crates/project/src/invalid_item_view.rs b/crates/project/src/invalid_item_view.rs index 252ea9673244e055c48c20a11dab02179bc7f694..7436c941d56f2512d239fa17b6a918c9c6383636 100644 --- a/crates/project/src/invalid_item_view.rs +++ b/crates/project/src/invalid_item_view.rs @@ -2,9 +2,9 @@ use std::{path::Path, sync::Arc}; use gpui::{EventEmitter, FocusHandle, Focusable}; use ui::{ - App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, InteractiveElement, - KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render, SharedString, Styled as _, - TintColor, Window, h_flex, v_flex, + h_flex, v_flex, App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, + InteractiveElement, KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render, + SharedString, Styled as _, TintColor, Window, }; use zed_actions::workspace::OpenWithSystem; @@ -127,9 +127,11 @@ impl Render for InvalidItemView { .on_click( move |_, window, cx| { window.dispatch_action( - Box::new(zed_actions::encodings::Toggle( - path0.clone(), - )), + Box::new( + zed_actions::encodings_ui::Toggle( + path0.clone(), + ), + ), cx, ) }, @@ -145,7 +147,7 @@ impl Render for InvalidItemView { move |_, window, cx| { window.dispatch_action( Box::new( - zed_actions::encodings::ForceOpen( + zed_actions::encodings_ui::ForceOpen( path1.clone(), ), ), diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 25dc614bfd63c04e14ef78bed851207d0d68ecb7..4bf7f2824e9d20f85c6cc9c74817fecfec9d63cd 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -53,7 +53,7 @@ diagnostics.workspace = true editor.workspace = true zeta2_tools.workspace = true encoding.workspace = true -encodings.workspace = true +encodings_ui.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f826399e4599b12f5aca11790cbb043971f2996f..576e8cd41a587da4cc97780a1a5c10f091930f56 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -630,7 +630,7 @@ pub fn main() { zeta::init(cx); inspector_ui::init(app_state.clone(), cx); json_schema_store::init(cx); - encodings::init(cx); + encodings_ui::init(cx); cx.observe_global::({ let http = app_state.client.http_client(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2bbdb428ee12c7c9ac95f62bfa06cb26f32a934c..2c5479f5b90316ebc4a8deff6d769ab3ff3050dd 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -444,7 +444,7 @@ pub fn initialize_workspace( }); let encoding_indicator = cx.new(|_cx| { - encodings::EncodingIndicator::new(None, workspace.weak_handle(), None, None) + encodings_ui::EncodingIndicator::new(None, workspace.weak_handle(), None, None) }); let cursor_position = diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index bcf46d5087693bbef8d805fbbea1b8c4fc3f0f13..5c60efd96f6d9f335c29d399fcfbea07db5a2028 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -299,7 +299,7 @@ pub mod settings_profile_selector { pub struct Toggle; } -pub mod encodings { +pub mod encodings_ui { use std::sync::Arc; use gpui::Action;