Files
oak-gpui/crates/zeta2/src/udiff.rs
T
Oleksiy Syvokon b2f561165f zeta2: Support qwen3-minimal prompt format (#42902)
This prompt is for a fine-tuned model. It has the following changes,
compared to `minimal`:
- No instructions at all, except for one sentence at the beginning of
the prompt.
- Output is a simplified unified diff -- hunk headers have no line
counts (e.g., `@@ -20 +20 @@`)
- Qwen's FIM tokens are used where possible (`<|file_sep|>`,
`<|fim_prefix|>`, `<|fim_suffix|>`, etc.)

To evaluate this model:
```
ZED_ZETA2_MODEL=zeta2-exp [usual zeta-cli eval params ...]  --prompt-format minimal-qwen
```

This will point to the most recent Baseten deployment of zeta2-exp
(which may change in the future, so the prompt-format may get out of
sync).

Release Notes:

- N/A
2025-11-17 20:36:05 +02:00

1025 lines
30 KiB
Rust

use std::borrow::Cow;
use std::fmt::Display;
use std::sync::Arc;
use std::{
fmt::{Debug, Write},
mem,
ops::Range,
path::Path,
};
use anyhow::Context as _;
use anyhow::Result;
use anyhow::anyhow;
use collections::HashMap;
use gpui::AsyncApp;
use gpui::Entity;
use language::{Anchor, Buffer, BufferSnapshot, OffsetRangeExt as _, TextBufferSnapshot};
use project::Project;
pub async fn parse_diff<'a>(
diff_str: &'a str,
get_buffer: impl Fn(&Path) -> Option<(&'a BufferSnapshot, &'a [Range<Anchor>])> + Send,
) -> Result<(&'a BufferSnapshot, Vec<(Range<Anchor>, Arc<str>)>)> {
let mut diff = DiffParser::new(diff_str);
let mut edited_buffer = None;
let mut edits = Vec::new();
while let Some(event) = diff.next()? {
match event {
DiffEvent::Hunk {
path: file_path,
hunk,
} => {
let (buffer, ranges) = match edited_buffer {
None => {
edited_buffer = get_buffer(&Path::new(file_path.as_ref()));
edited_buffer
.as_ref()
.context("Model tried to edit a file that wasn't included")?
}
Some(ref current) => current,
};
edits.extend(
resolve_hunk_edits_in_buffer(hunk, &buffer.text, ranges)
.with_context(|| format!("Diff:\n{diff_str}"))?,
);
}
DiffEvent::FileEnd { renamed_to } => {
let (buffer, _) = edited_buffer
.take()
.context("Got a FileEnd event before an Hunk event")?;
if renamed_to.is_some() {
anyhow::bail!("edit predictions cannot rename files");
}
if diff.next()?.is_some() {
anyhow::bail!("Edited more than one file");
}
return Ok((buffer, edits));
}
}
}
Err(anyhow::anyhow!("No EOF"))
}
#[derive(Debug)]
pub struct OpenedBuffers<'a>(#[allow(unused)] HashMap<Cow<'a, str>, Entity<Buffer>>);
#[must_use]
pub async fn apply_diff<'a>(
diff_str: &'a str,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<OpenedBuffers<'a>> {
let mut included_files = HashMap::default();
for line in diff_str.lines() {
let diff_line = DiffLine::parse(line);
if let DiffLine::OldPath { path } = diff_line {
let buffer = project
.update(cx, |project, cx| {
let project_path =
project
.find_project_path(path.as_ref(), cx)
.with_context(|| {
format!("Failed to find worktree for new path: {}", path)
})?;
anyhow::Ok(project.open_buffer(project_path, cx))
})??
.await?;
included_files.insert(path, buffer);
}
}
let ranges = [Anchor::MIN..Anchor::MAX];
let mut diff = DiffParser::new(diff_str);
let mut current_file = None;
let mut edits = vec![];
while let Some(event) = diff.next()? {
match event {
DiffEvent::Hunk {
path: file_path,
hunk,
} => {
let (buffer, ranges) = match current_file {
None => {
let buffer = included_files
.get_mut(&file_path)
.expect("Opened all files in diff");
current_file = Some((buffer, ranges.as_slice()));
current_file.as_ref().unwrap()
}
Some(ref current) => current,
};
buffer.read_with(cx, |buffer, _| {
edits.extend(
resolve_hunk_edits_in_buffer(hunk, buffer, ranges)
.with_context(|| format!("Diff:\n{diff_str}"))?,
);
anyhow::Ok(())
})??;
}
DiffEvent::FileEnd { renamed_to } => {
let (buffer, _) = current_file
.take()
.context("Got a FileEnd event before an Hunk event")?;
if let Some(renamed_to) = renamed_to {
project
.update(cx, |project, cx| {
let new_project_path = project
.find_project_path(Path::new(renamed_to.as_ref()), cx)
.with_context(|| {
format!("Failed to find worktree for new path: {}", renamed_to)
})?;
let project_file = project::File::from_dyn(buffer.read(cx).file())
.expect("Wrong file type");
anyhow::Ok(project.rename_entry(
project_file.entry_id.unwrap(),
new_project_path,
cx,
))
})??
.await?;
}
let edits = mem::take(&mut edits);
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
})?;
}
}
}
Ok(OpenedBuffers(included_files))
}
struct PatchFile<'a> {
old_path: Cow<'a, str>,
new_path: Cow<'a, str>,
}
struct DiffParser<'a> {
current_file: Option<PatchFile<'a>>,
current_line: Option<(&'a str, DiffLine<'a>)>,
hunk: Hunk,
diff: std::str::Lines<'a>,
}
#[derive(Debug, PartialEq)]
enum DiffEvent<'a> {
Hunk { path: Cow<'a, str>, hunk: Hunk },
FileEnd { renamed_to: Option<Cow<'a, str>> },
}
#[derive(Debug, Default, PartialEq)]
struct Hunk {
context: String,
edits: Vec<Edit>,
}
impl Hunk {
fn is_empty(&self) -> bool {
self.context.is_empty() && self.edits.is_empty()
}
}
#[derive(Debug, PartialEq)]
struct Edit {
range: Range<usize>,
text: String,
}
impl<'a> DiffParser<'a> {
fn new(diff: &'a str) -> Self {
let mut diff = diff.lines();
let current_line = diff.next().map(|line| (line, DiffLine::parse(line)));
DiffParser {
current_file: None,
hunk: Hunk::default(),
current_line,
diff,
}
}
fn next(&mut self) -> Result<Option<DiffEvent<'a>>> {
loop {
let (hunk_done, file_done) = match self.current_line.as_ref().map(|e| &e.1) {
Some(DiffLine::OldPath { .. }) | Some(DiffLine::Garbage(_)) | None => (true, true),
Some(DiffLine::HunkHeader(_)) => (true, false),
_ => (false, false),
};
if hunk_done {
if let Some(file) = &self.current_file
&& !self.hunk.is_empty()
{
return Ok(Some(DiffEvent::Hunk {
path: file.old_path.clone(),
hunk: mem::take(&mut self.hunk),
}));
}
}
if file_done {
if let Some(PatchFile { old_path, new_path }) = self.current_file.take() {
return Ok(Some(DiffEvent::FileEnd {
renamed_to: if old_path != new_path {
Some(new_path)
} else {
None
},
}));
}
}
let Some((line, parsed_line)) = self.current_line.take() else {
break;
};
util::maybe!({
match parsed_line {
DiffLine::OldPath { path } => {
self.current_file = Some(PatchFile {
old_path: path,
new_path: "".into(),
});
}
DiffLine::NewPath { path } => {
if let Some(current_file) = &mut self.current_file {
current_file.new_path = path
}
}
DiffLine::HunkHeader(_) => {}
DiffLine::Context(ctx) => {
if self.current_file.is_some() {
writeln!(&mut self.hunk.context, "{ctx}")?;
}
}
DiffLine::Deletion(del) => {
if self.current_file.is_some() {
let range = self.hunk.context.len()
..self.hunk.context.len() + del.len() + '\n'.len_utf8();
if let Some(last_edit) = self.hunk.edits.last_mut()
&& last_edit.range.end == range.start
{
last_edit.range.end = range.end;
} else {
self.hunk.edits.push(Edit {
range,
text: String::new(),
});
}
writeln!(&mut self.hunk.context, "{del}")?;
}
}
DiffLine::Addition(add) => {
if self.current_file.is_some() {
let range = self.hunk.context.len()..self.hunk.context.len();
if let Some(last_edit) = self.hunk.edits.last_mut()
&& last_edit.range.end == range.start
{
writeln!(&mut last_edit.text, "{add}").unwrap();
} else {
self.hunk.edits.push(Edit {
range,
text: format!("{add}\n"),
});
}
}
}
DiffLine::Garbage(_) => {}
}
anyhow::Ok(())
})
.with_context(|| format!("on line:\n\n```\n{}```", line))?;
self.current_line = self.diff.next().map(|line| (line, DiffLine::parse(line)));
}
anyhow::Ok(None)
}
}
fn resolve_hunk_edits_in_buffer(
hunk: Hunk,
buffer: &TextBufferSnapshot,
ranges: &[Range<Anchor>],
) -> Result<impl Iterator<Item = (Range<Anchor>, Arc<str>)>, anyhow::Error> {
let context_offset = if hunk.context.is_empty() {
Ok(0)
} else {
let mut offset = None;
for range in ranges {
let range = range.to_offset(buffer);
let text = buffer.text_for_range(range.clone()).collect::<String>();
for (ix, _) in text.match_indices(&hunk.context) {
if offset.is_some() {
anyhow::bail!("Context is not unique enough:\n{}", hunk.context);
}
offset = Some(range.start + ix);
}
}
offset.ok_or_else(|| anyhow!("Failed to match context:\n{}", hunk.context))
}?;
let iter = hunk.edits.into_iter().flat_map(move |edit| {
let old_text = buffer
.text_for_range(context_offset + edit.range.start..context_offset + edit.range.end)
.collect::<String>();
let edits_within_hunk = language::text_diff(&old_text, &edit.text);
edits_within_hunk
.into_iter()
.map(move |(inner_range, inner_text)| {
(
buffer.anchor_after(context_offset + edit.range.start + inner_range.start)
..buffer.anchor_before(context_offset + edit.range.start + inner_range.end),
inner_text,
)
})
});
Ok(iter)
}
#[derive(Debug, PartialEq)]
pub enum DiffLine<'a> {
OldPath { path: Cow<'a, str> },
NewPath { path: Cow<'a, str> },
HunkHeader(Option<HunkLocation>),
Context(&'a str),
Deletion(&'a str),
Addition(&'a str),
Garbage(&'a str),
}
#[derive(Debug, PartialEq)]
pub struct HunkLocation {
start_line_old: u32,
count_old: u32,
start_line_new: u32,
count_new: u32,
}
impl<'a> DiffLine<'a> {
pub fn parse(line: &'a str) -> Self {
Self::try_parse(line).unwrap_or(Self::Garbage(line))
}
fn try_parse(line: &'a str) -> Option<Self> {
if let Some(header) = line.strip_prefix("---").and_then(eat_required_whitespace) {
let path = parse_header_path("a/", header);
Some(Self::OldPath { path })
} else if let Some(header) = line.strip_prefix("+++").and_then(eat_required_whitespace) {
Some(Self::NewPath {
path: parse_header_path("b/", header),
})
} else if let Some(header) = line.strip_prefix("@@").and_then(eat_required_whitespace) {
if header.starts_with("...") {
return Some(Self::HunkHeader(None));
}
let mut tokens = header.split_whitespace();
let old_range = tokens.next()?.strip_prefix('-')?;
let new_range = tokens.next()?.strip_prefix('+')?;
let (start_line_old, count_old) = old_range.split_once(',').unwrap_or((old_range, "1"));
let (start_line_new, count_new) = new_range.split_once(',').unwrap_or((new_range, "1"));
Some(Self::HunkHeader(Some(HunkLocation {
start_line_old: start_line_old.parse::<u32>().ok()?.saturating_sub(1),
count_old: count_old.parse().ok()?,
start_line_new: start_line_new.parse::<u32>().ok()?.saturating_sub(1),
count_new: count_new.parse().ok()?,
})))
} else if let Some(deleted_header) = line.strip_prefix("-") {
Some(Self::Deletion(deleted_header))
} else if line.is_empty() {
Some(Self::Context(""))
} else if let Some(context) = line.strip_prefix(" ") {
Some(Self::Context(context))
} else {
Some(Self::Addition(line.strip_prefix("+")?))
}
}
}
impl<'a> Display for DiffLine<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DiffLine::OldPath { path } => write!(f, "--- {path}"),
DiffLine::NewPath { path } => write!(f, "+++ {path}"),
DiffLine::HunkHeader(Some(hunk_location)) => {
write!(
f,
"@@ -{},{} +{},{} @@",
hunk_location.start_line_old + 1,
hunk_location.count_old,
hunk_location.start_line_new + 1,
hunk_location.count_new
)
}
DiffLine::HunkHeader(None) => write!(f, "@@ ... @@"),
DiffLine::Context(content) => write!(f, " {content}"),
DiffLine::Deletion(content) => write!(f, "-{content}"),
DiffLine::Addition(content) => write!(f, "+{content}"),
DiffLine::Garbage(line) => write!(f, "{line}"),
}
}
}
fn parse_header_path<'a>(strip_prefix: &'static str, header: &'a str) -> Cow<'a, str> {
if !header.contains(['"', '\\']) {
let path = header.split_ascii_whitespace().next().unwrap_or(header);
return Cow::Borrowed(path.strip_prefix(strip_prefix).unwrap_or(path));
}
let mut path = String::with_capacity(header.len());
let mut in_quote = false;
let mut chars = header.chars().peekable();
let mut strip_prefix = Some(strip_prefix);
while let Some(char) = chars.next() {
if char == '"' {
in_quote = !in_quote;
} else if char == '\\' {
let Some(&next_char) = chars.peek() else {
break;
};
chars.next();
path.push(next_char);
} else if char.is_ascii_whitespace() && !in_quote {
break;
} else {
path.push(char);
}
if let Some(prefix) = strip_prefix
&& path == prefix
{
strip_prefix.take();
path.clear();
}
}
Cow::Owned(path)
}
fn eat_required_whitespace(header: &str) -> Option<&str> {
let trimmed = header.trim_ascii_start();
if trimmed.len() == header.len() {
None
} else {
Some(trimmed)
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::TestAppContext;
use indoc::indoc;
use language::Point;
use pretty_assertions::assert_eq;
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use util::path;
#[test]
fn parse_lines_simple() {
let input = indoc! {"
diff --git a/text.txt b/text.txt
index 86c770d..a1fd855 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
context
-deleted
+inserted
garbage
--- b/file.txt
+++ a/file.txt
"};
let lines = input.lines().map(DiffLine::parse).collect::<Vec<_>>();
pretty_assertions::assert_eq!(
lines,
&[
DiffLine::Garbage("diff --git a/text.txt b/text.txt"),
DiffLine::Garbage("index 86c770d..a1fd855 100644"),
DiffLine::OldPath {
path: "file.txt".into()
},
DiffLine::NewPath {
path: "file.txt".into()
},
DiffLine::HunkHeader(Some(HunkLocation {
start_line_old: 0,
count_old: 2,
start_line_new: 0,
count_new: 3
})),
DiffLine::Context("context"),
DiffLine::Deletion("deleted"),
DiffLine::Addition("inserted"),
DiffLine::Garbage("garbage"),
DiffLine::Context(""),
DiffLine::OldPath {
path: "b/file.txt".into()
},
DiffLine::NewPath {
path: "a/file.txt".into()
},
]
);
}
#[test]
fn file_header_extra_space() {
let options = ["--- file", "--- file", "---\tfile"];
for option in options {
pretty_assertions::assert_eq!(
DiffLine::parse(option),
DiffLine::OldPath {
path: "file".into()
},
"{option}",
);
}
}
#[test]
fn hunk_header_extra_space() {
let options = [
"@@ -1,2 +1,3 @@",
"@@ -1,2 +1,3 @@",
"@@\t-1,2\t+1,3\t@@",
"@@ -1,2 +1,3 @@",
"@@ -1,2 +1,3 @@",
"@@ -1,2 +1,3 @@",
"@@ -1,2 +1,3 @@ garbage",
];
for option in options {
pretty_assertions::assert_eq!(
DiffLine::parse(option),
DiffLine::HunkHeader(Some(HunkLocation {
start_line_old: 0,
count_old: 2,
start_line_new: 0,
count_new: 3
})),
"{option}",
);
}
}
#[test]
fn hunk_header_without_location() {
pretty_assertions::assert_eq!(DiffLine::parse("@@ ... @@"), DiffLine::HunkHeader(None));
}
#[test]
fn test_parse_path() {
assert_eq!(parse_header_path("a/", "foo.txt"), "foo.txt");
assert_eq!(
parse_header_path("a/", "foo/bar/baz.txt"),
"foo/bar/baz.txt"
);
assert_eq!(parse_header_path("a/", "a/foo.txt"), "foo.txt");
assert_eq!(
parse_header_path("a/", "a/foo/bar/baz.txt"),
"foo/bar/baz.txt"
);
// Extra
assert_eq!(
parse_header_path("a/", "a/foo/bar/baz.txt 2025"),
"foo/bar/baz.txt"
);
assert_eq!(
parse_header_path("a/", "a/foo/bar/baz.txt\t2025"),
"foo/bar/baz.txt"
);
assert_eq!(
parse_header_path("a/", "a/foo/bar/baz.txt \""),
"foo/bar/baz.txt"
);
// Quoted
assert_eq!(
parse_header_path("a/", "a/foo/bar/\"baz quox.txt\""),
"foo/bar/baz quox.txt"
);
assert_eq!(
parse_header_path("a/", "\"a/foo/bar/baz quox.txt\""),
"foo/bar/baz quox.txt"
);
assert_eq!(
parse_header_path("a/", "\"foo/bar/baz quox.txt\""),
"foo/bar/baz quox.txt"
);
assert_eq!(parse_header_path("a/", "\"whatever 🤷\""), "whatever 🤷");
assert_eq!(
parse_header_path("a/", "\"foo/bar/baz quox.txt\" 2025"),
"foo/bar/baz quox.txt"
);
// unescaped quotes are dropped
assert_eq!(parse_header_path("a/", "foo/\"bar\""), "foo/bar");
// Escaped
assert_eq!(
parse_header_path("a/", "\"foo/\\\"bar\\\"/baz.txt\""),
"foo/\"bar\"/baz.txt"
);
assert_eq!(
parse_header_path("a/", "\"C:\\\\Projects\\\\My App\\\\old file.txt\""),
"C:\\Projects\\My App\\old file.txt"
);
}
#[test]
fn test_parse_diff_with_leading_and_trailing_garbage() {
let diff = indoc! {"
I need to make some changes.
I'll change the following things:
- one
- two
- three
```
--- a/file.txt
+++ b/file.txt
one
+AND
two
```
Summary of what I did:
- one
- two
- three
That's about it.
"};
let mut events = Vec::new();
let mut parser = DiffParser::new(diff);
while let Some(event) = parser.next().unwrap() {
events.push(event);
}
assert_eq!(
events,
&[
DiffEvent::Hunk {
path: "file.txt".into(),
hunk: Hunk {
context: "one\ntwo\n".into(),
edits: vec![Edit {
range: 4..4,
text: "AND\n".into()
}],
}
},
DiffEvent::FileEnd { renamed_to: None }
],
)
}
#[gpui::test]
async fn test_apply_diff_successful(cx: &mut TestAppContext) {
let fs = init_test(cx);
let buffer_1_text = indoc! {r#"
one
two
three
four
five
"# };
let buffer_1_text_final = indoc! {r#"
3
4
5
"# };
let buffer_2_text = indoc! {r#"
six
seven
eight
nine
ten
"# };
let buffer_2_text_final = indoc! {r#"
5
six
seven
7.5
eight
nine
ten
11
"# };
fs.insert_tree(
path!("/root"),
json!({
"file1": buffer_1_text,
"file2": buffer_2_text,
}),
)
.await;
let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
let diff = indoc! {r#"
--- a/root/file1
+++ b/root/file1
one
two
-three
+3
four
five
--- a/root/file1
+++ b/root/file1
3
-four
-five
+4
+5
--- a/root/file1
+++ b/root/file1
-one
-two
3
4
--- a/root/file2
+++ b/root/file2
+5
six
--- a/root/file2
+++ b/root/file2
seven
+7.5
eight
--- a/root/file2
+++ b/root/file2
ten
+11
"#};
let _buffers = apply_diff(diff, &project, &mut cx.to_async())
.await
.unwrap();
let buffer_1 = project
.update(cx, |project, cx| {
let project_path = project.find_project_path(path!("/root/file1"), cx).unwrap();
project.open_buffer(project_path, cx)
})
.await
.unwrap();
buffer_1.read_with(cx, |buffer, _cx| {
assert_eq!(buffer.text(), buffer_1_text_final);
});
let buffer_2 = project
.update(cx, |project, cx| {
let project_path = project.find_project_path(path!("/root/file2"), cx).unwrap();
project.open_buffer(project_path, cx)
})
.await
.unwrap();
buffer_2.read_with(cx, |buffer, _cx| {
assert_eq!(buffer.text(), buffer_2_text_final);
});
}
#[gpui::test]
async fn test_apply_diff_non_unique(cx: &mut TestAppContext) {
let fs = init_test(cx);
let buffer_1_text = indoc! {r#"
one
two
three
four
five
one
two
three
four
five
"# };
fs.insert_tree(
path!("/root"),
json!({
"file1": buffer_1_text,
}),
)
.await;
let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/root/file1"), cx)
})
.await
.unwrap();
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let diff = indoc! {r#"
--- a/root/file1
+++ b/root/file1
one
two
-three
+3
four
five
"#};
let final_text = indoc! {r#"
one
two
three
four
five
one
two
3
four
five
"#};
apply_diff(diff, &project, &mut cx.to_async())
.await
.expect_err("Non-unique edits should fail");
let ranges = [buffer_snapshot.anchor_before(Point::new(1, 0))
..buffer_snapshot.anchor_after(buffer_snapshot.max_point())];
let (edited_snapshot, edits) = parse_diff(diff, |_path| Some((&buffer_snapshot, &ranges)))
.await
.unwrap();
assert_eq!(edited_snapshot.remote_id(), buffer_snapshot.remote_id());
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
assert_eq!(buffer.text(), final_text);
});
}
#[gpui::test]
async fn test_parse_diff_with_edits_within_line(cx: &mut TestAppContext) {
let fs = init_test(cx);
let buffer_1_text = indoc! {r#"
one two three four
five six seven eight
nine ten eleven twelve
"# };
fs.insert_tree(
path!("/root"),
json!({
"file1": buffer_1_text,
}),
)
.await;
let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/root/file1"), cx)
})
.await
.unwrap();
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let diff = indoc! {r#"
--- a/root/file1
+++ b/root/file1
one two three four
-five six seven eight
+five SIX seven eight!
nine ten eleven twelve
"#};
let (buffer, edits) = parse_diff(diff, |_path| {
Some((&buffer_snapshot, &[(Anchor::MIN..Anchor::MAX)] as &[_]))
})
.await
.unwrap();
let edits = edits
.into_iter()
.map(|(range, text)| (range.to_point(&buffer), text))
.collect::<Vec<_>>();
assert_eq!(
edits,
&[
(Point::new(1, 5)..Point::new(1, 8), "SIX".into()),
(Point::new(1, 20)..Point::new(1, 20), "!".into())
]
);
}
#[gpui::test]
async fn test_apply_diff_unique_via_previous_context(cx: &mut TestAppContext) {
let fs = init_test(cx);
let start = indoc! {r#"
one
two
three
four
five
four
five
"# };
let end = indoc! {r#"
one
two
3
four
5
four
five
"# };
fs.insert_tree(
path!("/root"),
json!({
"file1": start,
}),
)
.await;
let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
let diff = indoc! {r#"
--- a/root/file1
+++ b/root/file1
one
two
-three
+3
four
-five
+5
"#};
let _buffers = apply_diff(diff, &project, &mut cx.to_async())
.await
.unwrap();
let buffer_1 = project
.update(cx, |project, cx| {
let project_path = project.find_project_path(path!("/root/file1"), cx).unwrap();
project.open_buffer(project_path, cx)
})
.await
.unwrap();
buffer_1.read_with(cx, |buffer, _cx| {
assert_eq!(buffer.text(), end);
});
}
fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
});
FakeFs::new(cx.background_executor.clone())
}
}