- Move the functionality in `fs::encodings` to a seperate crate

R Aadarsh created

`encodings`

- `EncodingWrapper` is replaced with `encodings::Encoding`

Change summary

Cargo.lock                                                    |  24 
crates/agent/src/tools/edit_file_tool.rs                      |   8 
crates/agent2/Cargo.toml                                      |   2 
crates/assistant_tools/Cargo.toml                             |   2 
crates/assistant_tools/src/edit_file_tool.rs                  |   9 
crates/collab/Cargo.toml                                      |   2 
crates/collab/src/tests/integration_tests.rs                  |   8 
crates/collab/src/tests/random_project_collaboration_tests.rs |   6 
crates/copilot/Cargo.toml                                     |   2 
crates/copilot/src/copilot.rs                                 |  12 
crates/encodings/src/lib.rs                                   |   6 
crates/encodings_ui/src/lib.rs                                |   4 
crates/encodings_ui/src/selectors.rs                          |  16 
crates/extension_host/Cargo.toml                              |   2 
crates/extension_host/src/extension_host.rs                   |   7 
crates/fs/Cargo.toml                                          |   2 
crates/fs/src/encodings.rs                                    | 152 -----
crates/fs/src/fs.rs                                           |  29 
crates/git_ui/Cargo.toml                                      |   2 
crates/git_ui/src/file_diff_view.rs                           |   6 
crates/language/Cargo.toml                                    |   2 
crates/language/src/buffer.rs                                 |  27 
crates/project/Cargo.toml                                     |   2 
crates/project/src/buffer_store.rs                            |  36 
crates/project/src/prettier_store.rs                          |   8 
crates/project/src/project.rs                                 |  25 
crates/project/src/project_tests.rs                           |  34 
crates/remote_server/Cargo.toml                               |   2 
crates/remote_server/src/remote_editing_tests.rs              |  12 
crates/workspace/Cargo.toml                                   |   2 
crates/workspace/src/workspace.rs                             |  25 
crates/worktree/Cargo.toml                                    |   2 
crates/worktree/src/worktree.rs                               |  54 -
crates/worktree/src/worktree_tests.rs                         |  14 
crates/zed/Cargo.toml                                         |   3 
crates/zed/src/zed.rs                                         |  20 
36 files changed, 201 insertions(+), 368 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -153,7 +153,7 @@ dependencies = [
  "db",
  "derive_more 0.99.20",
  "editor",
- "encoding_rs",
+ "encodings",
  "env_logger 0.11.8",
  "fs",
  "futures 0.3.31",
@@ -3355,7 +3355,7 @@ dependencies = [
  "dashmap 6.1.0",
  "debugger_ui",
  "editor",
- "encoding_rs",
+ "encodings",
  "envy",
  "extension",
  "file_finder",
@@ -3722,7 +3722,7 @@ dependencies = [
  "dirs 4.0.0",
  "edit_prediction",
  "editor",
- "encoding_rs",
+ "encodings",
  "fs",
  "futures 0.3.31",
  "gpui",
@@ -5928,7 +5928,7 @@ dependencies = [
  "criterion",
  "ctor",
  "dap",
- "encoding_rs",
+ "encodings",
  "extension",
  "fs",
  "futures 0.3.31",
@@ -6428,7 +6428,7 @@ dependencies = [
  "async-trait",
  "cocoa 0.26.0",
  "collections",
- "encoding_rs",
+ "encodings",
  "fsevent",
  "futures 0.3.31",
  "git",
@@ -7130,7 +7130,7 @@ dependencies = [
  "ctor",
  "db",
  "editor",
- "encoding_rs",
+ "encodings",
  "futures 0.3.31",
  "fuzzy",
  "git",
@@ -8805,7 +8805,7 @@ dependencies = [
  "ctor",
  "diffy",
  "ec4rs",
- "encoding_rs",
+ "encodings",
  "fs",
  "futures 0.3.31",
  "fuzzy",
@@ -13021,7 +13021,7 @@ dependencies = [
  "context_server",
  "dap",
  "dap_adapters",
- "encoding_rs",
+ "encodings",
  "extension",
  "fancy-regex 0.14.0",
  "fs",
@@ -13997,7 +13997,7 @@ dependencies = [
  "dap_adapters",
  "debug_adapter_extension",
  "editor",
- "encoding_rs",
+ "encodings",
  "env_logger 0.11.8",
  "extension",
  "extension_host",
@@ -20762,7 +20762,7 @@ dependencies = [
  "component",
  "dap",
  "db",
- "encoding_rs",
+ "encodings",
  "fs",
  "futures 0.3.31",
  "gpui",
@@ -20805,7 +20805,7 @@ dependencies = [
  "async-lock 2.8.0",
  "clock",
  "collections",
- "encoding_rs",
+ "encodings",
  "fs",
  "futures 0.3.31",
  "fuzzy",
@@ -21215,7 +21215,7 @@ dependencies = [
  "diagnostics",
  "edit_prediction_button",
  "editor",
- "encoding_rs",
+ "encodings",
  "encodings_ui",
  "env_logger 0.11.8",
  "extension",

crates/agent/src/tools/edit_file_tool.rs πŸ”—

@@ -563,8 +563,8 @@ mod tests {
     use super::*;
     use crate::{ContextServerRegistry, Templates};
     use client::TelemetrySettings;
-    use encoding_rs::UTF_8;
-    use fs::{Fs, encodings::EncodingWrapper};
+    use encodings::{Encoding, UTF_8};
+    use fs::Fs;
     use gpui::{TestAppContext, UpdateGlobal};
     use language_model::fake_provider::FakeLanguageModel;
     use prompt_store::ProjectContext;
@@ -745,7 +745,7 @@ mod tests {
             path!("/root/src/main.rs").as_ref(),
             &Rope::from_str_small("initial content"),
             language::LineEnding::Unix,
-            EncodingWrapper::new(UTF_8),
+            Encoding::new(UTF_8),
         )
         .await
         .unwrap();
@@ -913,7 +913,7 @@ mod tests {
             path!("/root/src/main.rs").as_ref(),
             &Rope::from_str_small("initial content"),
             language::LineEnding::Unix,
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();

crates/agent2/Cargo.toml πŸ”—

@@ -32,7 +32,7 @@ cloud_llm_client.workspace = true
 collections.workspace = true
 context_server.workspace = true
 db.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 fs.workspace = true
 futures.workspace = true
 git.workspace = true

crates/assistant_tools/Cargo.toml πŸ”—

@@ -28,7 +28,7 @@ component.workspace = true
 derive_more.workspace = true
 diffy = "0.4.2"
 editor.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 feature_flags.workspace = true
 futures.workspace = true
 gpui.workspace = true

crates/assistant_tools/src/edit_file_tool.rs πŸ”—

@@ -1229,9 +1229,10 @@ async fn build_buffer_diff(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use ::fs::{Fs, encodings::EncodingWrapper};
+    use ::fs::Fs;
     use client::TelemetrySettings;
-    use encoding_rs::UTF_8;
+    use encodings::Encoding;
+    use encodings::UTF_8;
     use gpui::{TestAppContext, UpdateGlobal};
     use language_model::fake_provider::FakeLanguageModel;
     use serde_json::json;
@@ -1500,7 +1501,7 @@ mod tests {
             path!("/root/src/main.rs").as_ref(),
             &"initial content".into(),
             language::LineEnding::Unix,
-            EncodingWrapper::new(UTF_8),
+            Encoding::new(UTF_8),
         )
         .await
         .unwrap();
@@ -1670,7 +1671,7 @@ mod tests {
             path!("/root/src/main.rs").as_ref(),
             &"initial content".into(),
             language::LineEnding::Unix,
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();

crates/collab/Cargo.toml πŸ”—

@@ -31,7 +31,7 @@ chrono.workspace = true
 clock.workspace = true
 collections.workspace = true
 dashmap.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 envy = "0.4.2"
 futures.workspace = true
 gpui.workspace = true

crates/collab/src/tests/integration_tests.rs πŸ”—

@@ -12,8 +12,8 @@ use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
 use call::{ActiveCall, ParticipantLocation, Room, room};
 use client::{RECEIVE_TIMEOUT, User};
 use collections::{HashMap, HashSet};
-use encoding_rs::UTF_8;
-use fs::{FakeFs, Fs as _, RemoveOptions, encodings::EncodingWrapper};
+use encodings::Encoding;
+use fs::{FakeFs, Fs as _, RemoveOptions};
 use futures::{StreamExt as _, channel::mpsc};
 use git::{
     repository::repo_path,
@@ -3702,7 +3702,7 @@ async fn test_buffer_reloading(
             path!("/dir/a.txt").as_ref(),
             &new_contents,
             LineEnding::Windows,
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();
@@ -4483,7 +4483,7 @@ async fn test_reloading_buffer_manually(
             path!("/a/a.rs").as_ref(),
             &Rope::from_str_small("let seven = 7;"),
             LineEnding::Unix,
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();

crates/collab/src/tests/random_project_collaboration_tests.rs πŸ”—

@@ -5,8 +5,8 @@ use async_trait::async_trait;
 use call::ActiveCall;
 use collections::{BTreeMap, HashMap};
 use editor::Bias;
-use encoding_rs::UTF_8;
-use fs::{FakeFs, Fs as _, encodings::EncodingWrapper};
+use encodings::Encoding;
+use fs::{FakeFs, Fs as _};
 use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
 use gpui::{BackgroundExecutor, Entity, TestAppContext};
 use language::{
@@ -944,7 +944,7 @@ impl RandomizedTest for ProjectCollaborationTest {
                             &path,
                             &Rope::from_str_small(content.as_str()),
                             text::LineEnding::Unix,
-                            EncodingWrapper::new(UTF_8),
+                            Encoding::default(),
                         )
                         .await
                         .unwrap();

crates/copilot/Cargo.toml πŸ”—

@@ -30,7 +30,7 @@ client.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
 dirs.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true

crates/copilot/src/copilot.rs πŸ”—

@@ -1241,8 +1241,7 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
 #[cfg(test)]
 mod tests {
     use super::*;
-    use fs::encodings::EncodingWrapper;
-    use encoding_rs::Encoding;
+    use encodings::Encoding;
     use gpui::TestAppContext;
     use util::{path, paths::PathStyle, rel_path::rel_path};
 
@@ -1453,7 +1452,14 @@ mod tests {
             self.abs_path.clone()
         }
 
-        fn load(&self, _: &App, _: EncodingWrapper, _: bool, _: bool, _: Option<Arc<std::sync::Mutex<&'static Encoding>>>) -> Task<Result<String>> {
+        fn load(
+            &self,
+            _: &App,
+            _: Encoding,
+            _: bool,
+            _: bool,
+            _: Option<Arc<Encoding>>,
+        ) -> Task<Result<String>> {
             unimplemented!()
         }
 

crates/encodings/src/lib.rs πŸ”—

@@ -22,6 +22,12 @@ impl Debug for Encoding {
     }
 }
 
+impl Clone for Encoding {
+    fn clone(&self) -> Self {
+        Encoding(Mutex::new(self.get()))
+    }
+}
+
 impl Default for Encoding {
     fn default() -> Self {
         Encoding(Mutex::new(UTF_8))

crates/encodings_ui/src/lib.rs πŸ”—

@@ -120,7 +120,7 @@ impl EncodingIndicator {
         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());
+            self.encoding = Some(encoding.get());
 
             if let Some(_) = buffer.read(cx).file() {
                 self.show_save_or_reopen_selector = true;
@@ -140,7 +140,7 @@ impl EncodingIndicator {
         cx: &mut Context<EncodingIndicator>,
     ) {
         let encoding = buffer.read(cx).encoding.clone();
-        self.encoding = Some(&*encoding.lock().unwrap());
+        self.encoding = Some(encoding.get());
         cx.notify();
     }
 }

crates/encodings_ui/src/selectors.rs πŸ”—

@@ -278,7 +278,6 @@ pub mod save_or_reopen {
 /// This module contains the encoding selector for choosing an encoding to save or reopen a file with.
 pub mod encoding {
     use editor::Editor;
-    use fs::encodings::EncodingWrapper;
     use std::{path::PathBuf, sync::atomic::AtomicBool};
 
     use fuzzy::{StringMatch, StringMatchCandidate};
@@ -449,8 +448,9 @@ pub mod encoding {
                 // By limiting the scope, we ensure that it is released
                 {
                     let buffer_encoding = buffer.encoding.clone();
-                    *buffer_encoding.lock().unwrap() =
-                        encoding_from_name(self.matches[self.current_selection].string.as_str());
+                    buffer_encoding.set(encoding_from_name(
+                        self.matches[self.current_selection].string.as_str(),
+                    ));
                 }
 
                 self.dismissed(window, cx);
@@ -481,8 +481,12 @@ pub mod encoding {
                         encoding_from_name(self.matches[self.current_selection].string.as_str());
 
                     let open_task = workspace.update(cx, |workspace, cx| {
-                        *workspace.encoding_options.encoding.lock().unwrap() =
-                            EncodingWrapper::new(encoding);
+                        workspace
+                            .encoding_options
+                            .encoding
+                            .lock()
+                            .unwrap()
+                            .set(encoding);
 
                         workspace.open_abs_path(path, OpenOptions::default(), window, cx)
                     });
@@ -510,7 +514,7 @@ pub mod encoding {
                         {
                             buffer
                                 .read_with(cx, |buffer, _| {
-                                    *buffer.encoding.lock().unwrap() = encoding;
+                                    buffer.encoding.set(encoding);
                                 })
                                 .log_err();
                         }

crates/extension_host/Cargo.toml πŸ”—

@@ -23,7 +23,7 @@ async-trait.workspace = true
 client.workspace = true
 collections.workspace = true
 dap.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 extension.workspace = true
 fs.workspace = true
 futures.workspace = true

crates/extension_host/src/extension_host.rs πŸ”—

@@ -12,7 +12,7 @@ use async_tar::Archive;
 use client::ExtensionProvides;
 use client::{Client, ExtensionMetadata, GetExtensionsResponse, proto, telemetry::Telemetry};
 use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};
-use encoding_rs::UTF_8;
+use encodings::Encoding;
 pub use extension::ExtensionManifest;
 use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
 use extension::{
@@ -21,7 +21,6 @@ use extension::{
     ExtensionLanguageServerProxy, ExtensionSlashCommandProxy, ExtensionSnippetProxy,
     ExtensionThemeProxy,
 };
-use fs::encodings::EncodingWrapper;
 use fs::{Fs, RemoveOptions};
 use futures::future::join_all;
 use futures::{
@@ -1508,7 +1507,7 @@ impl ExtensionStore {
                     &index_path,
                     &Rope::from_str(&index_json, &executor),
                     Default::default(),
-                    EncodingWrapper::new(UTF_8),
+                    Encoding::default(),
                 )
                 .await
                 .context("failed to save extension index")
@@ -1681,7 +1680,7 @@ impl ExtensionStore {
                     &tmp_dir.join(EXTENSION_TOML),
                     &Rope::from_str_small(&manifest_toml),
                     language::LineEnding::Unix,
-                    EncodingWrapper::new(UTF_8),
+                    Encoding::default(),
                 )
                 .await?;
             } else {

crates/fs/Cargo.toml πŸ”—

@@ -16,7 +16,7 @@ anyhow.workspace = true
 async-tar.workspace = true
 async-trait.workspace = true
 collections.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 futures.workspace = true
 git.workspace = true
 gpui.workspace = true

crates/fs/src/encodings.rs πŸ”—

@@ -1,152 +0,0 @@
-//! Encoding and decoding utilities using the `encoding_rs` crate.
-use std::{
-    fmt::Debug,
-    sync::{Arc, Mutex},
-};
-
-use std::sync::atomic::AtomicBool;
-
-use anyhow::Result;
-use encoding_rs::Encoding;
-
-/// A wrapper around `encoding_rs::Encoding` to implement `Send` and `Sync`.
-/// Since the reference is static, it is safe to send it across threads.
-#[derive(Copy)]
-pub struct EncodingWrapper(pub &'static Encoding);
-
-impl Debug for EncodingWrapper {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_tuple(&format!("EncodingWrapper{:?}", self.0))
-            .field(&self.0.name())
-            .finish()
-    }
-}
-
-impl Default for EncodingWrapper {
-    fn default() -> Self {
-        EncodingWrapper(encoding_rs::UTF_8)
-    }
-}
-
-impl PartialEq for EncodingWrapper {
-    fn eq(&self, other: &Self) -> bool {
-        self.0.name() == other.0.name()
-    }
-}
-
-unsafe impl Send for EncodingWrapper {}
-unsafe impl Sync for EncodingWrapper {}
-
-impl Clone for EncodingWrapper {
-    fn clone(&self) -> Self {
-        EncodingWrapper(self.0)
-    }
-}
-
-impl EncodingWrapper {
-    pub fn new(encoding: &'static Encoding) -> EncodingWrapper {
-        EncodingWrapper(encoding)
-    }
-
-    pub fn get_encoding(&self) -> &'static Encoding {
-        self.0
-    }
-
-    pub async fn decode(
-        &mut self,
-        input: Vec<u8>,
-        force: bool,
-        detect_utf16: bool,
-        buffer_encoding: Option<Arc<Mutex<&'static Encoding>>>,
-    ) -> Result<String> {
-        // 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(encoding_rs::UTF_16LE),
-                Some([0xFE, 0xFF]) => Some(encoding_rs::UTF_16BE),
-                _ => None,
-            } {
-                self.0 = encoding;
-
-                if let Some(v) = buffer_encoding
-                    && let Ok(mut v) = v.lock()
-                {
-                    *v = encoding;
-                }
-            }
-        }
-
-        let (cow, had_errors) = self.0.decode_with_bom_removal(&input);
-
-        if force {
-            return Ok(cow.to_string());
-        }
-
-        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.0.name()
-            ))
-        }
-    }
-
-    pub async fn encode(&self, input: String) -> Result<Vec<u8>> {
-        if self.0 == encoding_rs::UTF_16BE {
-            let mut data = Vec::<u8>::with_capacity(input.len() * 2);
-
-            // Convert the input string to UTF-16BE bytes
-            let utf16be_bytes = input.encode_utf16().flat_map(|u| u.to_be_bytes());
-
-            data.extend(utf16be_bytes);
-            return Ok(data);
-        } else if self.0 == encoding_rs::UTF_16LE {
-            let mut data = Vec::<u8>::with_capacity(input.len() * 2);
-
-            // Convert the input string to UTF-16LE bytes
-            let utf16le_bytes = input.encode_utf16().flat_map(|u| u.to_le_bytes());
-
-            data.extend(utf16le_bytes);
-            return Ok(data);
-        } else {
-            let (cow, _encoding_used, _had_errors) = self.0.encode(&input);
-
-            Ok(cow.into_owned())
-        }
-    }
-}
-
-/// Convert a byte vector from a specified encoding to a UTF-8 string.
-pub async fn to_utf8(
-    input: Vec<u8>,
-    mut encoding: EncodingWrapper,
-    force: bool,
-    detect_utf16: bool,
-    buffer_encoding: Option<Arc<Mutex<&'static Encoding>>>,
-) -> Result<String> {
-    encoding
-        .decode(input, force, detect_utf16, buffer_encoding)
-        .await
-}
-
-/// Convert a UTF-8 string to a byte vector in a specified encoding.
-pub async fn from_utf8(input: String, target: EncodingWrapper) -> Result<Vec<u8>> {
-    target.encode(input).await
-}
-
-pub struct EncodingOptions {
-    pub encoding: Arc<Mutex<EncodingWrapper>>,
-    pub force: AtomicBool,
-    pub detect_utf16: AtomicBool,
-}
-
-impl Default for EncodingOptions {
-    fn default() -> Self {
-        EncodingOptions {
-            encoding: Arc::new(Mutex::new(EncodingWrapper::default())),
-            force: AtomicBool::new(false),
-            detect_utf16: AtomicBool::new(true),
-        }
-    }
-}

crates/fs/src/fs.rs πŸ”—

@@ -1,7 +1,6 @@
 #[cfg(target_os = "macos")]
 mod mac_watcher;
 
-pub mod encodings;
 #[cfg(not(target_os = "macos"))]
 pub mod fs_watcher;
 
@@ -9,7 +8,7 @@ use anyhow::{Context as _, Result, anyhow};
 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 use ashpd::desktop::trash;
 use futures::stream::iter;
-use encoding_rs::Encoding;
+use encodings::Encoding;
 use gpui::App;
 use gpui::BackgroundExecutor;
 use gpui::Global;
@@ -62,9 +61,9 @@ use std::ffi::OsStr;
 
 #[cfg(any(test, feature = "test-support"))]
 pub use fake_git_repo::{LOAD_HEAD_TEXT_TASK, LOAD_INDEX_TEXT_TASK};
-use crate::encodings::EncodingWrapper;
-use crate::encodings::from_utf8;
-use crate::encodings::to_utf8;
+use encodings::Encoding;
+use encodings::from_utf8;
+use encodings::to_utf8;
 
 pub trait Watcher: Send + Sync {
     fn add(&self, path: &Path) -> Result<()>;
@@ -124,10 +123,10 @@ pub trait Fs: Send + Sync {
     async fn load_with_encoding(
         &self,
         path: &Path,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
         force: bool,
         detect_utf16: bool,
-        buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        buffer_encoding: Option<Arc<Encoding>>,
     ) -> Result<String> {
         Ok(to_utf8(
             self.load_bytes(path).await?,
@@ -146,7 +145,7 @@ pub trait Fs: Send + Sync {
         path: &Path,
         text: &Rope,
         line_ending: LineEnding,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
     ) -> Result<()>;
     async fn write(&self, path: &Path, content: &[u8]) -> Result<()>;
     async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
@@ -693,7 +692,7 @@ impl Fs for RealFs {
         path: &Path,
         text: &Rope,
         line_ending: LineEnding,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
     ) -> Result<()> {
         let buffer_size = text.summary().len.min(10 * 1024);
         if let Some(path) = path.parent() {
@@ -704,18 +703,18 @@ impl Fs for RealFs {
 
         // BOM for UTF-16 is written at the start of the file here because
         // if BOM is written in the `encode` function of `fs::encodings`, it would be written
-        // for every chunk, resulting in multiple BOMs in the file.
-        if encoding.get_encoding() == encoding_rs::UTF_16BE {
+        // twice. Hence, it is written only here.
+        if encoding.get() == encodings::UTF_16BE {
             // Write BOM for UTF-16BE
             writer.write_all(&[0xFE, 0xFF]).await?;
-        } else if encoding.get_encoding() == encoding_rs::UTF_16LE {
+        } else if encoding.get() == encodings::UTF_16LE {
             // Write BOM for UTF-16LE
             writer.write_all(&[0xFF, 0xFE]).await?;
         }
 
         for chunk in chunks(text, line_ending) {
             writer
-                .write_all(&from_utf8(chunk.to_string(), encoding.clone()).await?)
+                .write_all(&from_utf8(chunk.to_string(), Encoding::new(encoding.get())).await?)
                 .await?
         }
 
@@ -2435,9 +2434,9 @@ impl Fs for FakeFs {
         path: &Path,
         text: &Rope,
         line_ending: LineEnding,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
     ) -> Result<()> {
-        use crate::encodings::from_utf8;
+        use encodings::from_utf8;
 
         self.simulate_random_delay().await;
         let path = normalize_path(path);

crates/git_ui/Cargo.toml πŸ”—

@@ -29,7 +29,7 @@ command_palette_hooks.workspace = true
 component.workspace = true
 db.workspace = true
 editor.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
 git.workspace = true

crates/git_ui/src/file_diff_view.rs πŸ”—

@@ -358,7 +358,7 @@ impl Render for FileDiffView {
 mod tests {
     use super::*;
     use editor::test::editor_test_context::assert_state_with_diff;
-    use encoding_rs::UTF_8;
+    use encodings::Encoding;
     use gpui::TestAppContext;
     use language::Rope;
     use project::{FakeFs, Fs, Project};
@@ -442,7 +442,7 @@ mod tests {
                 ",
             )),
             Default::default(),
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();
@@ -477,7 +477,7 @@ mod tests {
                 ",
             )),
             Default::default(),
-            EncodingWrapper::new(UTF_8),
+            Encoding::default(),
         )
         .await
         .unwrap();

crates/language/Cargo.toml πŸ”—

@@ -32,7 +32,7 @@ clock.workspace = true
 collections.workspace = true
 diffy = "0.4.2"
 ec4rs.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 fs.workspace = true
 futures.workspace = true
 fuzzy.workspace = true

crates/language/src/buffer.rs πŸ”—

@@ -21,8 +21,8 @@ use anyhow::{Context as _, Result};
 use clock::Lamport;
 pub use clock::ReplicaId;
 use collections::HashMap;
-use encoding_rs::Encoding;
-use fs::{MTime, encodings::EncodingWrapper};
+use encodings::Encoding;
+use fs::MTime;
 use futures::channel::oneshot;
 use gpui::{
     App, AppContext as _, BackgroundExecutor, Context, Entity, EventEmitter, HighlightStyle,
@@ -127,7 +127,7 @@ pub struct Buffer {
     has_unsaved_edits: Cell<(clock::Global, bool)>,
     change_bits: Vec<rc::Weak<Cell<bool>>>,
     _subscriptions: Vec<gpui::Subscription>,
-    pub encoding: Arc<std::sync::Mutex<&'static Encoding>>,
+    pub encoding: Arc<Encoding>,
     pub observe_file_encoding: Option<gpui::Subscription>,
 }
 
@@ -375,7 +375,7 @@ pub trait File: Send + Sync + Any {
     /// Return whether Zed considers this to be a private file.
     fn is_private(&self) -> bool;
 
-    fn encoding(&self) -> Option<Arc<std::sync::Mutex<&'static Encoding>>> {
+    fn encoding(&self) -> Option<Arc<Encoding>> {
         unimplemented!()
     }
 }
@@ -422,10 +422,10 @@ pub trait LocalFile: File {
     fn load(
         &self,
         cx: &App,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
         force: bool,
         detect_utf16: bool,
-        buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        buffer_encoding: Option<Arc<Encoding>>,
     ) -> Task<Result<String>>;
 
     /// Loads the file's contents from disk.
@@ -1032,7 +1032,7 @@ impl Buffer {
             has_conflict: false,
             change_bits: Default::default(),
             _subscriptions: Vec::new(),
-            encoding: Arc::new(std::sync::Mutex::new(encoding_rs::UTF_8)),
+            encoding: Arc::new(Encoding::new(encodings::UTF_8)),
             observe_file_encoding: None,
         }
     }
@@ -1369,7 +1369,8 @@ impl Buffer {
     /// Reloads the contents of the buffer from disk.
     pub fn reload(&mut self, cx: &Context<Self>) -> oneshot::Receiver<Option<Transaction>> {
         let (tx, rx) = futures::channel::oneshot::channel();
-        let encoding = EncodingWrapper::new(*(self.encoding.lock().unwrap()));
+        let encoding = self.encoding.clone();
+
         let buffer_encoding = self.encoding.clone();
 
         let prev_version = self.text.version();
@@ -1377,7 +1378,7 @@ impl Buffer {
             let Some((new_mtime, new_text)) = this.update(cx, |this, cx| {
                 let file = this.file.as_ref()?.as_local()?;
                 Some((file.disk_state().mtime(), {
-                    file.load(cx, encoding, false, true, Some(buffer_encoding))
+                    file.load(cx, (*encoding).clone(), false, true, Some(buffer_encoding))
                 }))
             })?
             else {
@@ -2939,9 +2940,9 @@ impl Buffer {
     pub fn update_encoding(&mut self) {
         if let Some(file) = self.file() {
             if let Some(encoding) = file.encoding() {
-                *self.encoding.lock().unwrap() = *encoding.lock().unwrap();
+                self.encoding.set(encoding.get());
             } else {
-                *self.encoding.lock().unwrap() = encoding_rs::UTF_8;
+                self.encoding.set(encodings::UTF_8);
             };
         }
     }
@@ -5269,10 +5270,10 @@ impl LocalFile for TestFile {
     fn load(
         &self,
         _cx: &App,
-        _encoding: EncodingWrapper,
+        _encoding: Encoding,
         _force: bool,
         _detect_utf16: bool,
-        _buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        _buffer_encoding: Option<Arc<Encoding>>,
     ) -> Task<Result<String>> {
         unimplemented!()
     }

crates/project/Cargo.toml πŸ”—

@@ -39,7 +39,7 @@ clock.workspace = true
 collections.workspace = true
 context_server.workspace = true
 dap.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 extension.workspace = true
 fancy-regex.workspace = true
 fs.workspace = true

crates/project/src/buffer_store.rs πŸ”—

@@ -7,9 +7,9 @@ use crate::{
 use anyhow::{Context as _, Result, anyhow};
 use client::Client;
 use collections::{HashMap, HashSet, hash_map};
+use encodings::Encoding;
 use fs::Fs;
 use futures::{Future, FutureExt as _, StreamExt, channel::oneshot, future::Shared};
-use fs::{Fs, encodings::EncodingWrapper};
 use gpui::{
     App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
 };
@@ -398,13 +398,7 @@ impl LocalBufferStore {
         }
 
         let save = worktree.update(cx, |worktree, cx| {
-            worktree.write_file(
-                path.as_ref(),
-                text,
-                line_ending,
-                cx,
-                &encoding.lock().unwrap(),
-            )
+            worktree.write_file(path.as_ref(), text, line_ending, cx, (*encoding).clone())
         });
 
         cx.spawn(async move |this, cx| {
@@ -634,7 +628,7 @@ impl LocalBufferStore {
         &self,
         path: Arc<RelPath>,
         worktree: Entity<Worktree>,
-        encoding: Option<EncodingWrapper>,
+        encoding: Option<Encoding>,
         force: bool,
         detect_utf16: bool,
         cx: &mut Context<BufferStore>,
@@ -643,8 +637,14 @@ impl LocalBufferStore {
             let reservation = cx.reserve_entity();
             let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
 
-            let load_file_task =
-                worktree.load_file(path.as_ref(), encoding, force, detect_utf16, None, cx);
+            let load_file_task = worktree.load_file(
+                path.as_ref(),
+                encoding.clone(),
+                force,
+                detect_utf16,
+                None,
+                cx,
+            );
 
             cx.spawn(async move |_, cx| {
                 let loaded_file = load_file_task.await?;
@@ -681,13 +681,11 @@ impl LocalBufferStore {
                             entry_id: None,
                             is_local: true,
                             is_private: false,
-                            encoding: Some(Arc::new(std::sync::Mutex::new(
-                                if let Some(encoding) = encoding {
-                                    encoding.0
-                                } else {
-                                    encoding_rs::UTF_8
-                                },
-                            ))),
+                            encoding: Some(Arc::new(if let Some(encoding) = encoding {
+                                encoding
+                            } else {
+                                Encoding::default()
+                            })),
                         })),
                         Capability::ReadWrite,
                     )
@@ -845,7 +843,7 @@ impl BufferStore {
     pub fn open_buffer(
         &mut self,
         project_path: ProjectPath,
-        encoding: Option<EncodingWrapper>,
+        encoding: Option<Encoding>,
         force: bool,
         detect_utf16: bool,
         cx: &mut Context<Self>,

crates/project/src/prettier_store.rs πŸ”—

@@ -7,8 +7,8 @@ use std::{
 
 use anyhow::{Context as _, Result, anyhow};
 use collections::{HashMap, HashSet};
-use encoding_rs::UTF_8;
-use fs::{Fs, encodings::EncodingWrapper};
+use encodings::Encoding;
+use fs::Fs;
 use futures::{
     FutureExt,
     future::{self, Shared},
@@ -982,12 +982,12 @@ async fn save_prettier_server_file(
     executor: &BackgroundExecutor,
 ) -> anyhow::Result<()> {
     let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::default();
     fs.save(
         &prettier_wrapper_path,
         &text::Rope::from_str(prettier::PRETTIER_SERVER_JS, executor),
         text::LineEnding::Unix,
-        encoding_wrapper,
+        encoding,
     )
     .await
     .with_context(|| {

crates/project/src/project.rs πŸ”—

@@ -26,13 +26,11 @@ mod project_tests;
 mod environment;
 use buffer_diff::BufferDiff;
 use context_server_store::ContextServerStore;
-use encoding_rs::Encoding;
+
+use encodings::EncodingOptions;
 pub use environment::ProjectEnvironmentEvent;
-use fs::encodings::EncodingOptions;
-use fs::encodings::EncodingWrapper;
 use git::repository::get_git_committer;
 use git_store::{Repository, RepositoryId};
-use std::sync::atomic::AtomicBool;
 
 pub mod search_history;
 mod yarn;
@@ -1232,11 +1230,7 @@ impl Project {
                 toolchain_store: Some(toolchain_store),
 
                 agent_location: None,
-                encoding_options: EncodingOptions {
-                    encoding: Arc::new(std::sync::Mutex::new(EncodingWrapper::default())),
-                    force: AtomicBool::new(false),
-                    detect_utf16: AtomicBool::new(true),
-                },
+                encoding_options: EncodingOptions::default(),
             }
         })
     }
@@ -1422,11 +1416,7 @@ impl Project {
 
                 toolchain_store: Some(toolchain_store),
                 agent_location: None,
-                encoding_options: EncodingOptions {
-                    encoding: Arc::new(std::sync::Mutex::new(EncodingWrapper::default())),
-                    force: AtomicBool::new(false),
-                    detect_utf16: AtomicBool::new(false),
-                },
+                encoding_options: EncodingOptions::default(),
             };
 
             // remote server -> local machine handlers
@@ -1680,12 +1670,7 @@ impl Project {
                 remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
                 toolchain_store: None,
                 agent_location: None,
-                encoding_options: EncodingOptions {
-                    encoding: Arc::new(std::sync::Mutex::new(EncodingWrapper::default())),
-
-                    force: AtomicBool::new(false),
-                    detect_utf16: AtomicBool::new(false),
-                },
+                encoding_options: EncodingOptions::default(),
             };
 
             project.set_role(role, cx);

crates/project/src/project_tests.rs πŸ”—

@@ -12,8 +12,8 @@ use buffer_diff::{
     BufferDiffEvent, CALCULATE_DIFF_TASK, DiffHunkSecondaryStatus, DiffHunkStatus,
     DiffHunkStatusKind, assert_hunks,
 };
-use encoding_rs::UTF_8;
-use fs::{FakeFs, encodings::EncodingWrapper};
+use encodings::{Encoding, UTF_8};
+use fs::FakeFs;
 use futures::{StreamExt, future};
 use git::{
     GitHostingProviderRegistry,
@@ -1461,13 +1461,13 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
     .await
     .unwrap();
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::default();
 
     fs.save(
         path!("/the-root/Cargo.lock").as_ref(),
         &Rope::default(),
         Default::default(),
-        encoding_wrapper.clone(),
+        encoding.clone(),
     )
     .await
     .unwrap();
@@ -1475,7 +1475,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         path!("/the-stdlib/LICENSE").as_ref(),
         &Rope::default(),
         Default::default(),
-        encoding_wrapper.clone(),
+        encoding.clone(),
     )
     .await
     .unwrap();
@@ -1483,7 +1483,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
         path!("/the/stdlib/src/string.rs").as_ref(),
         &Rope::default(),
         Default::default(),
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();
@@ -4075,7 +4075,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
     // the next file change occurs.
     cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::default();
 
     // Change the buffer's file on disk, and then wait for the file change
     // to be detected by the worktree, so that the buffer starts reloading.
@@ -4083,7 +4083,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
         path!("/dir/file1").as_ref(),
         &Rope::from_str("the first contents", cx.background_executor()),
         Default::default(),
-        encoding_wrapper.clone(),
+        encoding.clone(),
     )
     .await
     .unwrap();
@@ -4095,7 +4095,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
         path!("/dir/file1").as_ref(),
         &Rope::from_str("the second contents", cx.background_executor()),
         Default::default(),
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();
@@ -4134,7 +4134,7 @@ async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
     // the next file change occurs.
     cx.executor().deprioritize(*language::BUFFER_DIFF_TASK);
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::new(UTF_8);
 
     // Change the buffer's file on disk, and then wait for the file change
     // to be detected by the worktree, so that the buffer starts reloading.
@@ -4142,7 +4142,7 @@ async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
         path!("/dir/file1").as_ref(),
         &Rope::from_str("the first contents", cx.background_executor()),
         Default::default(),
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();
@@ -4818,13 +4818,13 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
     let (new_contents, new_offsets) =
         marked_text_offsets("oneˇ\nthree ˇFOURˇ five\nsixtyˇ seven\n");
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::new(UTF_8);
 
     fs.save(
         path!("/dir/the-file").as_ref(),
         &Rope::from_str(new_contents.as_str(), cx.background_executor()),
         LineEnding::Unix,
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();
@@ -4852,14 +4852,14 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
         assert!(!buffer.has_conflict());
     });
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::new(UTF_8);
 
     // Change the file on disk again, adding blank lines to the beginning.
     fs.save(
         path!("/dir/the-file").as_ref(),
         &Rope::from_str("\n\n\nAAAA\naaa\nBB\nbbbbb\n", cx.background_executor()),
         LineEnding::Unix,
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();
@@ -4906,7 +4906,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
         assert_eq!(buffer.line_ending(), LineEnding::Windows);
     });
 
-    let encoding_wrapper = EncodingWrapper::new(UTF_8);
+    let encoding = Encoding::new(UTF_8);
 
     // Change a file's line endings on disk from unix to windows. The buffer's
     // state updates correctly.
@@ -4914,7 +4914,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
         path!("/dir/file1").as_ref(),
         &Rope::from_str("aaa\nb\nc\n", cx.background_executor()),
         LineEnding::Windows,
-        encoding_wrapper,
+        encoding,
     )
     .await
     .unwrap();

crates/remote_server/Cargo.toml πŸ”—

@@ -28,7 +28,7 @@ clap.workspace = true
 client.workspace = true
 dap_adapters.workspace = true
 debug_adapter_extension.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 env_logger.workspace = true
 extension.workspace = true
 extension_host.workspace = true

crates/remote_server/src/remote_editing_tests.rs πŸ”—

@@ -6,12 +6,12 @@ use agent::{AgentTool, ReadFileTool, ReadFileToolInput, ToolCallEventStream};
 use client::{Client, UserStore};
 use clock::FakeSystemClock;
 use collections::{HashMap, HashSet};
-use encoding_rs::UTF_8;
 use language_model::{LanguageModelRequest, fake_provider::FakeLanguageModel};
 
+use encodings::Encoding;
 use extension::ExtensionHostProxy;
-use fs::{FakeFs, Fs, encodings::EncodingWrapper};
-use gpui::{AppContext as _, Entity, SemanticVersion, SharedString, TestAppContext};
+use fs::{FakeFs, Fs};
+use gpui::{AppContext as _, Entity, SemanticVersion, TestAppContext};
 use http_client::{BlockedHttpClient, FakeHttpClient};
 use language::{
     Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, Rope,
@@ -123,7 +123,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
         path!("/code/project1/src/main.rs").as_ref(),
         &Rope::from_str_small("fn main() {}"),
         Default::default(),
-        EncodingWrapper::new(UTF_8),
+        Encoding::default(),
     )
     .await
     .unwrap();
@@ -770,7 +770,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
         &PathBuf::from(path!("/code/project1/src/lib.rs")),
         &Rope::from_str_small("bangles"),
         LineEnding::Unix,
-        EncodingWrapper::new(UTF_8),
+        Encoding::default(),
     )
     .await
     .unwrap();
@@ -786,7 +786,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
         &PathBuf::from(path!("/code/project1/src/lib.rs")),
         &Rope::from_str_small("bloop"),
         LineEnding::Unix,
-        EncodingWrapper::new(UTF_8),
+        Encoding::default(),
     )
     .await
     .unwrap();

crates/workspace/Cargo.toml πŸ”—

@@ -35,7 +35,7 @@ clock.workspace = true
 collections.workspace = true
 component.workspace = true
 db.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true

crates/workspace/src/workspace.rs πŸ”—

@@ -19,8 +19,8 @@ mod workspace_settings;
 
 pub use crate::notifications::NotificationFrame;
 pub use dock::Panel;
-use encoding_rs::UTF_8;
-use fs::encodings::EncodingWrapper;
+use encodings::Encoding;
+
 pub use path_list::PathList;
 pub use toast_layer::{ToastAction, ToastLayer, ToastView};
 
@@ -32,7 +32,7 @@ use client::{
 };
 use collections::{HashMap, HashSet, hash_map};
 use dock::{Dock, DockPosition, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};
-use fs::encodings::EncodingOptions;
+use encodings::EncodingOptions;
 
 use futures::{
     Future, FutureExt, StreamExt,
@@ -625,7 +625,7 @@ type BuildProjectItemForPathFn =
     fn(
         &Entity<Project>,
         &ProjectPath,
-        Option<EncodingWrapper>,
+        Option<Encoding>,
         &mut Window,
         &mut App,
     ) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
@@ -649,9 +649,9 @@ impl ProjectItemRegistry {
         self.build_project_item_for_path_fns
             .push(|project, project_path, encoding, window, cx| {
                 let project_path = project_path.clone();
-                let EncodingWrapper(encoding) = encoding.unwrap_or_default();
+                let encoding = encoding.unwrap_or_default();
 
-                project.update(cx, |project, _| {*project.encoding_options.encoding.lock().unwrap() = EncodingWrapper::new(encoding)});
+                project.update(cx, |project, _| {project.encoding_options.encoding.lock().unwrap().set(encoding.get())});
 
                 let is_file = project
                     .read(cx)
@@ -721,7 +721,7 @@ impl ProjectItemRegistry {
         &self,
         project: &Entity<Project>,
         path: &ProjectPath,
-        encoding: Option<EncodingWrapper>,
+        encoding: Option<Encoding>,
         window: &mut Window,
         cx: &mut App,
     ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
@@ -1945,9 +1945,7 @@ impl Workspace {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     ) -> Task<Result<()>> {
-        *self.encoding_options.force.get_mut() = false;
-        *self.encoding_options.detect_utf16.get_mut() = true;
-        *self.encoding_options.encoding.lock().unwrap() = EncodingWrapper::new(encoding_rs::UTF_8);
+        self.encoding_options.reset();
 
         let to_load = if let Some(pane) = pane.upgrade() {
             pane.update(cx, |pane, cx| {
@@ -3593,9 +3591,7 @@ impl Workspace {
         registry.open_path(
             project,
             &path,
-            Some(EncodingWrapper::new(
-                (self.encoding_options.encoding.lock().unwrap()).0,
-            )),
+            Some(self.encoding_options.encoding.lock().unwrap().clone()),
             window,
             cx,
         )
@@ -7622,12 +7618,11 @@ pub fn create_and_open_local_file(
         let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
         if !fs.is_file(path).await {
             fs.create_file(path, Default::default()).await?;
-            let encoding_wrapper = EncodingWrapper::new(UTF_8);
             fs.save(
                 path,
                 &default_content(),
                 Default::default(),
-                encoding_wrapper,
+                Default::default(),
             )
             .await?;
         }

crates/worktree/Cargo.toml πŸ”—

@@ -27,7 +27,7 @@ anyhow.workspace = true
 async-lock.workspace = true
 clock.workspace = true
 collections.workspace = true
-encoding_rs.workspace = true
+encodings.workspace = true
 fs.workspace = true
 futures.workspace = true
 fuzzy.workspace = true

crates/worktree/src/worktree.rs πŸ”—

@@ -7,11 +7,8 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{Context as _, Result, anyhow};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet, VecDeque};
-use encoding_rs::Encoding;
-use fs::{
-    Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive, encodings::EncodingWrapper,
-    read_dir_items,
-};
+use encodings::Encoding;
+use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive, read_dir_items};
 use futures::{
     FutureExt as _, Stream, StreamExt,
     channel::{
@@ -710,10 +707,10 @@ impl Worktree {
     pub fn load_file(
         &self,
         path: &Path,
-        encoding: Option<EncodingWrapper>,
+        encoding: Option<Encoding>,
         force: bool,
         detect_utf16: bool,
-        buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        buffer_encoding: Option<Arc<Encoding>>,
         cx: &Context<Worktree>,
     ) -> Task<Result<LoadedFile>> {
         match self {
@@ -745,7 +742,7 @@ impl Worktree {
         text: Rope,
         line_ending: LineEnding,
         cx: &Context<Worktree>,
-        encoding: &'static Encoding,
+        encoding: Encoding,
     ) -> Task<Result<Arc<File>>> {
         match self {
             Worktree::Local(this) => this.write_file(path, text, line_ending, cx, encoding),
@@ -1330,10 +1327,10 @@ impl LocalWorktree {
     fn load_file(
         &self,
         path: &Path,
-        encoding: Option<EncodingWrapper>,
+        encoding: Option<Encoding>,
         force: bool,
         detect_utf16: bool,
-        buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        buffer_encoding: Option<Arc<Encoding>>,
         cx: &Context<Worktree>,
     ) -> Task<Result<LoadedFile>> {
         let path = Arc::from(path);
@@ -1361,14 +1358,14 @@ impl LocalWorktree {
             let text = fs
                 .load_with_encoding(
                     &abs_path,
-                    if let Some(encoding) = encoding {
-                        encoding
+                    if let Some(ref encoding) = encoding {
+                        Encoding::new(encoding.get())
                     } else {
-                        EncodingWrapper::new(encoding_rs::UTF_8)
+                        Encoding::new(encodings::UTF_8)
                     },
                     force,
                     detect_utf16,
-                    buffer_encoding,
+                    buffer_encoding.clone(),
                 )
                 .await?;
 
@@ -1394,11 +1391,7 @@ impl LocalWorktree {
                         },
                         is_local: true,
                         is_private,
-                        encoding: if let Some(encoding) = encoding {
-                            Some(Arc::new(std::sync::Mutex::new(encoding.0)))
-                        } else {
-                            None
-                        },
+                        encoding: encoding.map(|e| Arc::new(Encoding::new(e.get()))),
                     })
                 }
             };
@@ -1487,20 +1480,18 @@ impl LocalWorktree {
         text: Rope,
         line_ending: LineEnding,
         cx: &Context<Worktree>,
-        encoding: &'static Encoding,
+        encoding: Encoding,
     ) -> Task<Result<Arc<File>>> {
         let fs = self.fs.clone();
         let is_private = self.is_path_private(&path);
         let abs_path = self.absolutize(&path);
 
-        let encoding_wrapper = EncodingWrapper::new(encoding);
-
         let write = cx.background_spawn({
             let fs = fs.clone();
             let abs_path = abs_path.clone();
-            async move {
-                fs.save(&abs_path, &text, line_ending, encoding_wrapper)
-                    .await
+            {
+                let encoding = encoding.clone();
+                async move { fs.save(&abs_path, &text, line_ending, encoding).await }
             }
         });
 
@@ -1535,7 +1526,7 @@ impl LocalWorktree {
                     entry_id: None,
                     is_local: true,
                     is_private,
-                    encoding: Some(Arc::new(std::sync::Mutex::new(encoding))),
+                    encoding: Some(Arc::new(encoding)),
                 }))
             }
         })
@@ -3097,7 +3088,7 @@ pub struct File {
     pub entry_id: Option<ProjectEntryId>,
     pub is_local: bool,
     pub is_private: bool,
-    pub encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+    pub encoding: Option<Arc<Encoding>>,
 }
 
 impl PartialEq for File {
@@ -3111,7 +3102,7 @@ impl PartialEq for File {
             && if let Some(encoding) = &self.encoding
                 && let Some(other_encoding) = &other.encoding
             {
-                if *encoding.lock().unwrap() != *other_encoding.lock().unwrap() {
+                if encoding.get() != other_encoding.get() {
                     false
                 } else {
                     true
@@ -3171,7 +3162,8 @@ impl language::File for File {
 
     fn path_style(&self, cx: &App) -> PathStyle {
         self.worktree.read(cx).path_style()
-    fn encoding(&self) -> Option<Arc<std::sync::Mutex<&'static Encoding>>> {
+    }
+    fn encoding(&self) -> Option<Arc<Encoding>> {
         if let Some(encoding) = &self.encoding {
             Some(encoding.clone())
         } else {
@@ -3188,10 +3180,10 @@ impl language::LocalFile for File {
     fn load(
         &self,
         cx: &App,
-        encoding: EncodingWrapper,
+        encoding: Encoding,
         force: bool,
         detect_utf16: bool,
-        buffer_encoding: Option<Arc<std::sync::Mutex<&'static Encoding>>>,
+        buffer_encoding: Option<Arc<Encoding>>,
     ) -> Task<Result<String>> {
         let worktree = self.worktree.read(cx).as_local().unwrap();
         let abs_path = worktree.absolutize(&self.path);

crates/worktree/src/worktree_tests.rs πŸ”—

@@ -3,8 +3,7 @@ use crate::{
     worktree_settings::WorktreeSettings,
 };
 use anyhow::Result;
-use encoding_rs::UTF_8;
-use fs::{FakeFs, Fs, RealFs, RemoveOptions, encodings::EncodingWrapper};
+use fs::{FakeFs, Fs, RealFs, RemoveOptions};
 use git::GITIGNORE;
 use gpui::{AppContext as _, BackgroundExecutor, BorrowAppContext, Context, Task, TestAppContext};
 use parking_lot::Mutex;
@@ -666,7 +665,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
         "/root/.gitignore".as_ref(),
         &Rope::from_str("e", cx.background_executor()),
         Default::default(),
-        encoding_wrapper,
+        Default::default(),
     )
     .await
     .unwrap();
@@ -740,7 +739,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
                 Rope::from_str("hello", cx.background_executor()),
                 Default::default(),
                 cx,
-                UTF_8,
+                Default::default(),
             )
         })
         .await
@@ -752,7 +751,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
                 Rope::from_str("world", cx.background_executor()),
                 Default::default(),
                 cx,
-                UTF_8,
+                Default::default(),
             )
         })
         .await
@@ -1787,7 +1786,7 @@ fn randomly_mutate_worktree(
                     Rope::default(),
                     Default::default(),
                     cx,
-                    UTF_8,
+                    Default::default(),
                 );
                 cx.background_spawn(async move {
                     task.await?;
@@ -1876,12 +1875,11 @@ async fn randomly_mutate_fs(
             ignore_path.strip_prefix(root_path).unwrap(),
             ignore_contents
         );
-        let encoding_wrapper = EncodingWrapper::new(UTF_8);
         fs.save(
             &ignore_path,
             &Rope::from_str(ignore_contents.as_str(), executor),
             Default::default(),
-            encoding_wrapper,
+            Default::default(),
         )
         .await
         .unwrap();

crates/zed/Cargo.toml πŸ”—

@@ -52,7 +52,8 @@ debugger_ui.workspace = true
 diagnostics.workspace = true
 editor.workspace = true
 zeta2_tools.workspace = true
-encoding.workspace = true
+edit_prediction_tools.workspace = true
+encodings.workspace = true
 encodings_ui.workspace = true
 env_logger.workspace = true
 extension.workspace = true

crates/zed/src/zed.rs πŸ”—

@@ -2177,8 +2177,8 @@ mod tests {
     use assets::Assets;
     use collections::HashSet;
     use editor::{DisplayPoint, Editor, SelectionEffects, display_map::DisplayRow};
-    use encoding_rs::UTF_8;
-    use fs::encodings::EncodingWrapper;
+    use encodings::Encoding;
+
     use gpui::{
         Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion,
         TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions,
@@ -4384,7 +4384,7 @@ mod tests {
                 "/settings.json".as_ref(),
                 &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4395,7 +4395,7 @@ mod tests {
                 "/keymap.json".as_ref(),
                 &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4444,7 +4444,7 @@ mod tests {
                 "/keymap.json".as_ref(),
                 &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4465,7 +4465,7 @@ mod tests {
                 "/settings.json".as_ref(),
                 &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4506,7 +4506,7 @@ mod tests {
                 "/settings.json".as_ref(),
                 &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4516,7 +4516,7 @@ mod tests {
                 "/keymap.json".as_ref(),
                 &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4560,7 +4560,7 @@ mod tests {
                 "/keymap.json".as_ref(),
                 &Rope::from_str_small(r#"[{"bindings": {"backspace": null}}]"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();
@@ -4581,7 +4581,7 @@ mod tests {
                 "/settings.json".as_ref(),
                 &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#),
                 Default::default(),
-                EncodingWrapper::new(UTF_8),
+                Encoding::default(),
             )
             .await
             .unwrap();