Handle a file's line endings changing on disk

This commit is contained in:
Max Brunsfeld 2022-07-04 12:28:55 -07:00
parent b0efa4f5c1
commit 0ba12eab22
2 changed files with 72 additions and 12 deletions

View file

@ -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<str>,
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);

View file

@ -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]