diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index aa3b86be67..18c38678a6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -98,7 +98,7 @@ pub enum IndentKind { Tab, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Debug, Clone, PartialEq, Eq)] pub enum NewlineStyle { Unix, Windows, @@ -283,6 +283,7 @@ pub(crate) struct Diff { base_version: clock::Global, new_text: Arc, changes: Vec<(ChangeTag, usize)>, + newline_style: NewlineStyle, start_offset: usize, } @@ -973,6 +974,7 @@ impl Buffer { let base_version = self.version(); cx.background().spawn(async move { let old_text = old_text.to_string(); + let newline_style = NewlineStyle::detect(&new_text); let new_text = new_text.replace("\r\n", "\n").replace('\r', "\n"); let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_str()) .iter_all_changes() @@ -982,6 +984,7 @@ impl Buffer { base_version, new_text: new_text.into(), changes, + newline_style, start_offset: 0, } }) @@ -995,6 +998,7 @@ impl Buffer { if self.version == diff.base_version { self.finalize_last_transaction(); self.start_transaction(); + self.newline_style = diff.newline_style; let mut offset = diff.start_offset; for (tag, len) in diff.changes { let range = offset..(offset + len); diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 921c7553a6..0a0d9c195d 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2363,7 +2363,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { fs.remove_file("/dir/file2".as_ref(), Default::default()) .await .unwrap(); - buffer2.condition(&cx, |b, _| b.is_dirty()).await; + cx.foreground().run_until_parked(); assert_eq!( *events.borrow(), &[ @@ -2393,9 +2393,7 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { fs.remove_file("/dir/file3".as_ref(), Default::default()) .await .unwrap(); - buffer3 - .condition(&cx, |_, _| !events.borrow().is_empty()) - .await; + cx.foreground().run_until_parked(); assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]); cx.read(|cx| assert!(buffer3.read(cx).is_dirty())); } @@ -2439,10 +2437,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { // Because the buffer was not modified, it is reloaded from disk. Its // contents are edited according to the diff between the old and new // file contents. - buffer - .condition(&cx, |buffer, _| buffer.text() == new_contents) - .await; - + cx.foreground().run_until_parked(); buffer.update(cx, |buffer, _| { assert_eq!(buffer.text(), new_contents); assert!(!buffer.is_dirty()); @@ -2476,9 +2471,70 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { // Because the buffer is modified, it doesn't reload from disk, but is // marked as having a conflict. - buffer - .condition(&cx, |buffer, _| buffer.has_conflict()) - .await; + cx.foreground().run_until_parked(); + buffer.read_with(cx, |buffer, _| { + assert!(buffer.has_conflict()); + }); +} + +#[gpui::test] +async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "file1": "a\nb\nc\n", + "file2": "one\r\ntwo\r\nthree\r\n", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let buffer1 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx)) + .await + .unwrap(); + let buffer2 = project + .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx)) + .await + .unwrap(); + + buffer1.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "a\nb\nc\n"); + assert_eq!(buffer.newline_style(), NewlineStyle::Unix); + }); + buffer2.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "one\ntwo\nthree\n"); + assert_eq!(buffer.newline_style(), NewlineStyle::Windows); + }); + + // Change a file's line endings on disk from unix to windows. The buffer's + // state updates correctly. + fs.save( + "/dir/file1".as_ref(), + &"aaa\nb\nc\n".into(), + NewlineStyle::Windows, + ) + .await + .unwrap(); + cx.foreground().run_until_parked(); + buffer1.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "aaa\nb\nc\n"); + assert_eq!(buffer.newline_style(), NewlineStyle::Windows); + }); + + // Save a file with windows line endings. The file is written correctly. + buffer2 + .update(cx, |buffer, cx| { + buffer.set_text("one\ntwo\nthree\nfour\n", cx); + buffer.save(cx) + }) + .await + .unwrap(); + assert_eq!( + fs.load("/dir/file2".as_ref()).await.unwrap(), + "one\r\ntwo\r\nthree\r\nfour\r\n", + ); } #[gpui::test]