Merge pull request #1278 from zed-industries/sync-line-ending

Antonio Scandurra created

Transmit new line ending when buffer is reloaded

Change summary

crates/collab/src/integration_tests.rs | 18 ++++++++++--------
crates/language/src/buffer.rs          |  9 +++++++--
crates/project/src/project.rs          | 13 +++++++++----
crates/project/src/worktree.rs         |  2 ++
crates/rpc/proto/zed.proto             |  1 +
5 files changed, 29 insertions(+), 14 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -1244,7 +1244,7 @@ async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
         .insert_tree(
             "/dir",
             json!({
-                "a.txt": "a-contents",
+                "a.txt": "a\nb\nc",
             }),
         )
         .await;
@@ -1259,23 +1259,25 @@ async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
     buffer_b.read_with(cx_b, |buf, _| {
         assert!(!buf.is_dirty());
         assert!(!buf.has_conflict());
+        assert_eq!(buf.line_ending(), LineEnding::Unix);
     });
 
+    let new_contents = Rope::from("d\ne\nf");
     client_a
         .fs
-        .save(
-            "/dir/a.txt".as_ref(),
-            &"new contents".into(),
-            LineEnding::Unix,
-        )
+        .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
         .await
         .unwrap();
     buffer_b
         .condition(&cx_b, |buf, _| {
-            buf.text() == "new contents" && !buf.is_dirty()
+            buf.text() == new_contents.to_string() && !buf.is_dirty()
         })
         .await;
-    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.has_conflict()));
+    buffer_b.read_with(cx_b, |buf, _| {
+        assert!(!buf.is_dirty());
+        assert!(!buf.has_conflict());
+        assert_eq!(buf.line_ending(), LineEnding::Windows);
+    });
 }
 
 #[gpui::test(iterations = 10)]

crates/language/src/buffer.rs 🔗

@@ -221,6 +221,7 @@ pub trait LocalFile: File {
         buffer_id: u64,
         version: &clock::Global,
         fingerprint: String,
+        line_ending: LineEnding,
         mtime: SystemTime,
         cx: &mut MutableAppContext,
     );
@@ -562,6 +563,7 @@ impl Buffer {
                         this.did_reload(
                             this.version(),
                             this.as_rope().fingerprint(),
+                            this.line_ending,
                             new_mtime,
                             cx,
                         );
@@ -580,17 +582,20 @@ impl Buffer {
         &mut self,
         version: clock::Global,
         fingerprint: String,
+        line_ending: LineEnding,
         mtime: SystemTime,
         cx: &mut ModelContext<Self>,
     ) {
         self.saved_version = version;
         self.saved_version_fingerprint = fingerprint;
+        self.line_ending = line_ending;
         self.saved_mtime = mtime;
         if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
             file.buffer_reloaded(
                 self.remote_id(),
                 &self.saved_version,
                 self.saved_version_fingerprint.clone(),
+                self.line_ending,
                 self.saved_mtime,
                 cx,
             );
@@ -2538,7 +2543,7 @@ impl std::ops::SubAssign for IndentSize {
 }
 
 impl LineEnding {
-    fn from_proto(style: proto::LineEnding) -> Self {
+    pub fn from_proto(style: proto::LineEnding) -> Self {
         match style {
             proto::LineEnding::Unix => Self::Unix,
             proto::LineEnding::Windows => Self::Windows,
@@ -2565,7 +2570,7 @@ impl LineEnding {
         }
     }
 
-    fn to_proto(self) -> proto::LineEnding {
+    pub fn to_proto(self) -> proto::LineEnding {
         match self {
             LineEnding::Unix => proto::LineEnding::Unix,
             LineEnding::Windows => proto::LineEnding::Windows,

crates/project/src/project.rs 🔗

@@ -23,8 +23,9 @@ use language::{
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CharKind, CodeAction, CodeLabel,
     Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _,
-    Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter, OffsetRangeExt,
-    Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
+    Language, LanguageRegistry, LanguageServerName, LineEnding, LocalFile, LspAdapter,
+    OffsetRangeExt, Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16,
+    Transaction,
 };
 use lsp::{
     DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
@@ -5539,8 +5540,12 @@ impl Project {
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
-        let payload = envelope.payload.clone();
+        let payload = envelope.payload;
         let version = deserialize_version(payload.version);
+        let line_ending = LineEnding::from_proto(
+            proto::LineEnding::from_i32(payload.line_ending)
+                .ok_or_else(|| anyhow!("missing line ending"))?,
+        );
         let mtime = payload
             .mtime
             .ok_or_else(|| anyhow!("missing mtime"))?
@@ -5552,7 +5557,7 @@ impl Project {
                 .and_then(|buffer| buffer.upgrade(cx));
             if let Some(buffer) = buffer {
                 buffer.update(cx, |buffer, cx| {
-                    buffer.did_reload(version, payload.fingerprint, mtime, cx);
+                    buffer.did_reload(version, payload.fingerprint, line_ending, mtime, cx);
                 });
             }
             Ok(())

crates/project/src/worktree.rs 🔗

@@ -1736,6 +1736,7 @@ impl language::LocalFile for File {
         buffer_id: u64,
         version: &clock::Global,
         fingerprint: String,
+        line_ending: LineEnding,
         mtime: SystemTime,
         cx: &mut MutableAppContext,
     ) {
@@ -1749,6 +1750,7 @@ impl language::LocalFile for File {
                     version: serialize_version(&version),
                     mtime: Some(mtime.into()),
                     fingerprint,
+                    line_ending: line_ending.to_proto() as i32,
                 })
                 .log_err();
         }

crates/rpc/proto/zed.proto 🔗

@@ -390,6 +390,7 @@ message BufferReloaded {
     repeated VectorClockEntry version = 3;
     Timestamp mtime = 4;
     string fingerprint = 5;
+    LineEnding line_ending = 6;
 }
 
 message ReloadBuffers {