Detailed changes
  
  
    
    @@ -14311,7 +14311,6 @@ dependencies = [
  "log",
  "rand 0.9.2",
  "rayon",
- "regex",
  "sum_tree",
  "unicode-segmentation",
  "util",
@@ -17147,6 +17146,7 @@ dependencies = [
  "parking_lot",
  "postage",
  "rand 0.9.2",
+ "regex",
  "rope",
  "smallvec",
  "sum_tree",
  
  
  
    
    @@ -12629,12 +12629,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
                 );
             }
         });
-
-    #[cfg(target_os = "windows")]
-    let line_ending = "\r\n";
-    #[cfg(not(target_os = "windows"))]
-    let line_ending = "\n";
-
     // Handle formatting requests to the language server.
     cx.lsp
         .set_request_handler::<lsp::request::Formatting, _, _>({
@@ -12658,7 +12652,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
                             ),
                             (
                                 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
-                                line_ending.into()
+                                "\n".into()
                             ),
                         ]
                     );
@@ -12669,14 +12663,14 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
                                 lsp::Position::new(1, 0),
                                 lsp::Position::new(1, 0),
                             ),
-                            new_text: line_ending.into(),
+                            new_text: "\n".into(),
                         },
                         lsp::TextEdit {
                             range: lsp::Range::new(
                                 lsp::Position::new(2, 0),
                                 lsp::Position::new(2, 0),
                             ),
-                            new_text: line_ending.into(),
+                            new_text: "\n".into(),
                         },
                     ]))
                 }
@@ -26662,83 +26656,6 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
     ));
 }
 
-#[gpui::test]
-async fn test_non_linux_line_endings_registration(cx: &mut TestAppContext) {
-    init_test(cx, |_| {});
-
-    let unix_newlines_file_text = "fn main() {
-        let a = 5;
-    }";
-    let clrf_file_text = unix_newlines_file_text.lines().join("\r\n");
-
-    let fs = FakeFs::new(cx.executor());
-    fs.insert_tree(
-        path!("/a"),
-        json!({
-            "first.rs": &clrf_file_text,
-        }),
-    )
-    .await;
-
-    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
-    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
-    let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let registered_text = Arc::new(Mutex::new(Vec::new()));
-    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
-    language_registry.add(rust_lang());
-    let mut fake_servers = language_registry.register_fake_lsp(
-        "Rust",
-        FakeLspAdapter {
-            capabilities: lsp::ServerCapabilities {
-                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
-                ..lsp::ServerCapabilities::default()
-            },
-            name: "rust-analyzer",
-            initializer: Some({
-                let registered_text = registered_text.clone();
-                Box::new(move |fake_server| {
-                    fake_server.handle_notification::<lsp::notification::DidOpenTextDocument, _>({
-                        let registered_text = registered_text.clone();
-                        move |params, _| {
-                            registered_text.lock().push(params.text_document.text);
-                        }
-                    });
-                })
-            }),
-            ..FakeLspAdapter::default()
-        },
-    );
-
-    let editor = workspace
-        .update(cx, |workspace, window, cx| {
-            workspace.open_abs_path(
-                PathBuf::from(path!("/a/first.rs")),
-                OpenOptions::default(),
-                window,
-                cx,
-            )
-        })
-        .unwrap()
-        .await
-        .unwrap()
-        .downcast::<Editor>()
-        .unwrap();
-    let _fake_language_server = fake_servers.next().await.unwrap();
-    cx.executor().run_until_parked();
-
-    assert_eq!(
-        editor.update(cx, |editor, cx| editor.text(cx)),
-        unix_newlines_file_text,
-        "Default text API returns \n-separated text",
-    );
-    assert_eq!(
-        vec![clrf_file_text],
-        registered_text.lock().drain(..).collect::<Vec<_>>(),
-        "Expected the language server to receive the exact same text from the FS",
-    );
-}
-
 #[gpui::test]
 async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
  
  
  
    
    @@ -2492,7 +2492,7 @@ impl LocalLspStore {
                         uri.clone(),
                         adapter.language_id(&language.name()),
                         0,
-                        initial_snapshot.text_with_original_line_endings(),
+                        initial_snapshot.text(),
                     );
 
                     vec![snapshot]
@@ -7574,7 +7574,6 @@ impl LspStore {
             let previous_snapshot = buffer_snapshots.last()?;
 
             let build_incremental_change = || {
-                let line_ending = next_snapshot.line_ending();
                 buffer
                     .edits_since::<Dimensions<PointUtf16, usize>>(
                         previous_snapshot.snapshot.version(),
@@ -7582,18 +7581,16 @@ impl LspStore {
                     .map(|edit| {
                         let edit_start = edit.new.start.0;
                         let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
+                        let new_text = next_snapshot
+                            .text_for_range(edit.new.start.1..edit.new.end.1)
+                            .collect();
                         lsp::TextDocumentContentChangeEvent {
                             range: Some(lsp::Range::new(
                                 point_to_lsp(edit_start),
                                 point_to_lsp(edit_end),
                             )),
                             range_length: None,
-                            // Collect changed text and preserve line endings.
-                            // text_for_range returns chunks with normalized \n, so we need to
-                            // convert to the buffer's actual line ending for LSP.
-                            text: line_ending.into_string(
-                                next_snapshot.text_for_range(edit.new.start.1..edit.new.end.1),
-                            ),
+                            text: new_text,
                         }
                     })
                     .collect()
@@ -7613,7 +7610,7 @@ impl LspStore {
                     vec![lsp::TextDocumentContentChangeEvent {
                         range: None,
                         range_length: None,
-                        text: next_snapshot.text_with_original_line_endings(),
+                        text: next_snapshot.text(),
                     }]
                 }
                 Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(),
@@ -10998,12 +10995,13 @@ impl LspStore {
 
                     let snapshot = versions.last().unwrap();
                     let version = snapshot.version;
+                    let initial_snapshot = &snapshot.snapshot;
                     let uri = lsp::Uri::from_file_path(file.abs_path(cx)).unwrap();
                     language_server.register_buffer(
                         uri,
                         adapter.language_id(&language.name()),
                         version,
-                        buffer_handle.read(cx).text_with_original_line_endings(),
+                        initial_snapshot.text(),
                     );
                     buffer_paths_registered.push((buffer_id, file.abs_path(cx)));
                     local
  
  
  
    
    @@ -15,7 +15,6 @@ path = "src/rope.rs"
 arrayvec = "0.7.1"
 log.workspace = true
 rayon.workspace = true
-regex.workspace = true
 sum_tree.workspace = true
 unicode-segmentation.workspace = true
 util.workspace = true
  
  
  
    
    @@ -6,13 +6,10 @@ mod unclipped;
 
 use arrayvec::ArrayVec;
 use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
-use regex::Regex;
 use std::{
-    borrow::Cow,
     cmp, fmt, io, mem,
     ops::{self, AddAssign, Range},
     str,
-    sync::{Arc, LazyLock},
 };
 use sum_tree::{Bias, Dimension, Dimensions, SumTree};
 
@@ -24,95 +21,6 @@ pub use unclipped::Unclipped;
 
 use crate::chunk::Bitmap;
 
-static LINE_SEPARATORS_REGEX: LazyLock<Regex> =
-    LazyLock::new(|| Regex::new(r"\r\n|\r").expect("Failed to create LINE_SEPARATORS_REGEX"));
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum LineEnding {
-    Unix,
-    Windows,
-}
-
-impl Default for LineEnding {
-    fn default() -> Self {
-        #[cfg(unix)]
-        return Self::Unix;
-
-        #[cfg(not(unix))]
-        return Self::Windows;
-    }
-}
-
-impl LineEnding {
-    pub fn as_str(&self) -> &'static str {
-        match self {
-            LineEnding::Unix => "\n",
-            LineEnding::Windows => "\r\n",
-        }
-    }
-
-    pub fn label(&self) -> &'static str {
-        match self {
-            LineEnding::Unix => "LF",
-            LineEnding::Windows => "CRLF",
-        }
-    }
-
-    pub fn detect(text: &str) -> Self {
-        let mut max_ix = cmp::min(text.len(), 1000);
-        while !text.is_char_boundary(max_ix) {
-            max_ix -= 1;
-        }
-
-        if let Some(ix) = text[..max_ix].find(['\n']) {
-            if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
-                Self::Windows
-            } else {
-                Self::Unix
-            }
-        } else {
-            Self::default()
-        }
-    }
-
-    pub fn normalize(text: &mut String) {
-        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") {
-            *text = replaced;
-        }
-    }
-
-    pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
-        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
-            replaced.into()
-        } else {
-            text
-        }
-    }
-
-    pub fn normalize_cow(text: Cow<str>) -> Cow<str> {
-        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
-            replaced.into()
-        } else {
-            text
-        }
-    }
-
-    /// Converts text chunks into a [`String`] using the current line ending.
-    pub fn into_string(&self, chunks: Chunks<'_>) -> String {
-        match self {
-            LineEnding::Unix => chunks.collect(),
-            LineEnding::Windows => {
-                let line_ending = self.as_str();
-                let mut result = String::new();
-                for chunk in chunks {
-                    result.push_str(&chunk.replace('\n', line_ending));
-                }
-                result
-            }
-        }
-    }
-}
-
 #[derive(Clone, Default)]
 pub struct Rope {
     chunks: SumTree<Chunk>,
@@ -460,16 +368,6 @@ impl Rope {
         Chunks::new(self, range, true)
     }
 
-    /// Formats the rope's text with the specified line ending string.
-    /// This replaces all `\n` characters with the provided line ending.
-    ///
-    /// The rope internally stores all line breaks as `\n` (see `Display` impl).
-    /// Use this method to convert to different line endings for file operations,
-    /// LSP communication, or other scenarios requiring specific line ending formats.
-    pub fn to_string_with_line_ending(&self, line_ending: LineEnding) -> String {
-        line_ending.into_string(self.chunks())
-    }
-
     pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
         if offset >= self.summary().len {
             return self.summary().len_utf16;
@@ -711,16 +609,10 @@ impl From<&String> for Rope {
     }
 }
 
-/// Display implementation for Rope.
-///
-/// Note: This always uses `\n` as the line separator, regardless of the original
-/// file's line endings. The rope internally normalizes all line breaks to `\n`.
-/// If you need to preserve original line endings (e.g., for LSP communication),
-/// use `to_string_with_line_ending` instead.
 impl fmt::Display for Rope {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         for chunk in self.chunks() {
-            write!(f, "{chunk}")?;
+            write!(f, "{}", chunk)?;
         }
         Ok(())
     }
@@ -2370,53 +2262,6 @@ mod tests {
         }
     }
 
-    #[test]
-    fn test_to_string_with_line_ending() {
-        // Test Unix line endings (no conversion)
-        let rope = Rope::from("line1\nline2\nline3");
-        assert_eq!(
-            rope.to_string_with_line_ending(LineEnding::Unix),
-            "line1\nline2\nline3"
-        );
-
-        // Test Windows line endings
-        assert_eq!(
-            rope.to_string_with_line_ending(LineEnding::Windows),
-            "line1\r\nline2\r\nline3"
-        );
-
-        // Test empty rope
-        let empty_rope = Rope::from("");
-        assert_eq!(
-            empty_rope.to_string_with_line_ending(LineEnding::Windows),
-            ""
-        );
-
-        // Test single line (no newlines)
-        let single_line = Rope::from("single line");
-        assert_eq!(
-            single_line.to_string_with_line_ending(LineEnding::Windows),
-            "single line"
-        );
-
-        // Test rope ending with newline
-        let ending_newline = Rope::from("line1\nline2\n");
-        assert_eq!(
-            ending_newline.to_string_with_line_ending(LineEnding::Windows),
-            "line1\r\nline2\r\n"
-        );
-
-        // Test large rope with multiple chunks
-        let mut large_rope = Rope::new();
-        for i in 0..100 {
-            large_rope.push(&format!("line{}\n", i));
-        }
-        let result = large_rope.to_string_with_line_ending(LineEnding::Windows);
-        assert!(result.contains("\r\n"));
-        assert!(!result.contains("\n\n"));
-        assert_eq!(result.matches("\r\n").count(), 100);
-    }
-
     fn clip_offset(text: &str, mut offset: usize, bias: Bias) -> usize {
         while !text.is_char_boundary(offset) {
             match bias {
  
  
  
    
    @@ -23,6 +23,7 @@ log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
 rand = { workspace = true, optional = true }
+regex.workspace = true
 rope.workspace = true
 smallvec.workspace = true
 sum_tree.workspace = true
  
  
  
    
    @@ -20,9 +20,11 @@ use operation_queue::OperationQueue;
 pub use patch::Patch;
 use postage::{oneshot, prelude::*};
 
+use regex::Regex;
 pub use rope::*;
 pub use selection::*;
 use std::{
+    borrow::Cow,
     cmp::{self, Ordering, Reverse},
     fmt::Display,
     future::Future,
@@ -30,7 +32,7 @@ use std::{
     num::NonZeroU64,
     ops::{self, Deref, Range, Sub},
     str,
-    sync::Arc,
+    sync::{Arc, LazyLock},
     time::{Duration, Instant},
 };
 pub use subscription::*;
@@ -41,6 +43,9 @@ use undo_map::UndoMap;
 #[cfg(any(test, feature = "test-support"))]
 use util::RandomCharIter;
 
+static LINE_SEPARATORS_REGEX: LazyLock<Regex> =
+    LazyLock::new(|| Regex::new(r"\r\n|\r").expect("Failed to create LINE_SEPARATORS_REGEX"));
+
 pub type TransactionId = clock::Lamport;
 
 pub struct Buffer {
@@ -2014,24 +2019,10 @@ impl BufferSnapshot {
         start..position
     }
 
-    /// Returns the buffer's text as a String.
-    ///
-    /// Note: This always uses `\n` as the line separator, regardless of the buffer's
-    /// actual line ending setting. For LSP communication or other cases where you need
-    /// to preserve the original line endings, use [`Self::text_with_original_line_endings`] instead.
     pub fn text(&self) -> String {
         self.visible_text.to_string()
     }
 
-    /// Returns the buffer's text with line same endings as in buffer's file.
-    ///
-    /// Unlike [`Self::text`] which always uses `\n`, this method formats the text using
-    /// the buffer's actual line ending setting (Unix `\n` or Windows `\r\n`).
-    pub fn text_with_original_line_endings(&self) -> String {
-        self.visible_text
-            .to_string_with_line_ending(self.line_ending)
-    }
-
     pub fn line_ending(&self) -> LineEnding {
         self.line_ending
     }
@@ -2135,10 +2126,6 @@ impl BufferSnapshot {
         self.visible_text.reversed_bytes_in_range(start..end)
     }
 
-    /// Returns the text in the given range.
-    ///
-    /// Note: This always uses `\n` as the line separator, regardless of the buffer's
-    /// actual line ending setting.
     pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks<'_> {
         let start = range.start.to_offset(self);
         let end = range.end.to_offset(self);
@@ -3265,6 +3252,77 @@ impl FromAnchor for usize {
     }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum LineEnding {
+    Unix,
+    Windows,
+}
+
+impl Default for LineEnding {
+    fn default() -> Self {
+        #[cfg(unix)]
+        return Self::Unix;
+
+        #[cfg(not(unix))]
+        return Self::Windows;
+    }
+}
+
+impl LineEnding {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            LineEnding::Unix => "\n",
+            LineEnding::Windows => "\r\n",
+        }
+    }
+
+    pub fn label(&self) -> &'static str {
+        match self {
+            LineEnding::Unix => "LF",
+            LineEnding::Windows => "CRLF",
+        }
+    }
+
+    pub fn detect(text: &str) -> Self {
+        let mut max_ix = cmp::min(text.len(), 1000);
+        while !text.is_char_boundary(max_ix) {
+            max_ix -= 1;
+        }
+
+        if let Some(ix) = text[..max_ix].find(['\n']) {
+            if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
+                Self::Windows
+            } else {
+                Self::Unix
+            }
+        } else {
+            Self::default()
+        }
+    }
+
+    pub fn normalize(text: &mut String) {
+        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") {
+            *text = replaced;
+        }
+    }
+
+    pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
+        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
+            replaced.into()
+        } else {
+            text
+        }
+    }
+
+    pub fn normalize_cow(text: Cow<str>) -> Cow<str> {
+        if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
+            replaced.into()
+        } else {
+            text
+        }
+    }
+}
+
 #[cfg(debug_assertions)]
 pub mod debug {
     use super::*;