use anyhow::Result;use buffer_diff::BufferDiff;use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};use itertools::Itertools;use language::{ Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,};use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};use util::ResultExt; pub enum Diff { Pending(PendingDiff), Finalized(FinalizedDiff),} impl Diff { pub fn finalized( path: String, old_text: Option<String>, new_text: String, language_registry: Arc<LanguageRegistry>, cx: &mut Context<Self>, ) -> Self { let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly)); let new_buffer = cx.new(|cx| Buffer::local(new_text, cx)); let base_text = old_text.clone().unwrap_or(String::new()).into(); let task = cx.spawn({ let multibuffer = multibuffer.clone(); let path = path.clone(); let buffer = new_buffer.clone(); async move |_, cx| { let language = language_registry .load_language_for_file_path(Path::new(&path)) .await .log_err(); buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx)); buffer.update(cx, |buffer, _| buffer.parsing_idle()).await; let diff = build_buffer_diff( old_text.unwrap_or("".into()).into(), &buffer, Some(language_registry.clone()), cx, ) .await?; multibuffer.update(cx, |multibuffer, cx| { let hunk_ranges = { let buffer = buffer.read(cx); diff.read(cx) .snapshot(cx) .hunks_intersecting_range( Anchor::min_for_buffer(buffer.remote_id()) ..Anchor::max_for_buffer(buffer.remote_id()), buffer, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::<Vec<_>>() }; multibuffer.set_excerpts_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), hunk_ranges, excerpt_context_lines(cx), cx, ); multibuffer.add_diff(diff, cx); }); anyhow::Ok(()) } }); Self::Finalized(FinalizedDiff { multibuffer, path, base_text, new_buffer, _update_diff: task, }) } pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self { let buffer_text_snapshot = buffer.read(cx).text_snapshot(); let language = buffer.read(cx).language().cloned(); let language_registry = buffer.read(cx).language_registry(); let buffer_diff = cx.new(|cx| { let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx); diff.language_changed(language.clone(), language_registry.clone(), cx); let secondary_diff = cx.new(|cx| { // For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on // it. As a result, by skipping it we are potentially shaving off a lot of RSS plus we get a snappier feel for large diff // view multibuffers. BufferDiff::new_unchanged(&buffer_text_snapshot, cx) }); diff.set_secondary_diff(secondary_diff); diff }); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly); multibuffer.add_diff(buffer_diff.clone(), cx); multibuffer }); Self::Pending(PendingDiff { multibuffer, base_text: Arc::from(buffer_text_snapshot.text().as_str()), _subscription: cx.observe(&buffer, |this, _, cx| { if let Diff::Pending(diff) = this { diff.update(cx); } }), new_buffer: buffer, diff: buffer_diff, revealed_ranges: Vec::new(), update_diff: Task::ready(Ok(())), }) } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { diff.reveal_range(range, cx); } } pub fn finalize(&mut self, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { *self = Self::Finalized(diff.finalize(cx)); } } /// Returns the original text before any edits were applied. pub fn base_text(&self) -> &Arc<str> { match self { Self::Pending(PendingDiff { base_text, .. }) => base_text, Self::Finalized(FinalizedDiff { base_text, .. }) => base_text, } } /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs). pub fn buffer(&self) -> &Entity<Buffer> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer, Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer, } } pub fn file_path(&self, cx: &App) -> Option<String> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer .read(cx) .file() .map(|file| file.full_path(cx).to_string_lossy().into_owned()), Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()), } } pub fn multibuffer(&self) -> &Entity<MultiBuffer> { match self { Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer, Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer, } } pub fn to_markdown(&self, cx: &App) -> String { let buffer_text = self .multibuffer() .read(cx) .all_buffers() .iter() .map(|buffer| buffer.read(cx).text()) .join("\n"); let path = match self { Diff::Pending(PendingDiff { new_buffer: buffer, .. }) => buffer .read(cx) .file() .map(|file| file.path().display(file.path_style(cx))), Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()), }; format!( "Diff: {}\n```\n{}\n```\n", path.unwrap_or("untitled".into()), buffer_text ) } pub fn has_revealed_range(&self, cx: &App) -> bool { self.multibuffer().read(cx).paths().next().is_some() } pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool { match self { Diff::Pending(PendingDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } Diff::Finalized(FinalizedDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } } }} pub struct PendingDiff { multibuffer: Entity<MultiBuffer>, base_text: Arc<str>, new_buffer: Entity<Buffer>, diff: Entity<BufferDiff>, revealed_ranges: Vec<Range<Anchor>>, _subscription: Subscription, update_diff: Task<Result<()>>,} impl PendingDiff { pub fn update(&mut self, cx: &mut Context<Diff>) { let buffer = self.new_buffer.clone(); let buffer_diff = self.diff.clone(); let base_text = self.base_text.clone(); self.update_diff = cx.spawn(async move |diff, cx| { let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned()); let update = buffer_diff .update(cx, |diff, cx| { diff.update_diff( text_snapshot.clone(), Some(base_text.clone()), None, language, cx, ) }) .await; let (task1, task2) = buffer_diff.update(cx, |diff, cx| { let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx); let task2 = diff .secondary_diff() .unwrap() .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx)); (task1, task2) }); task1.await; task2.await; diff.update(cx, |diff, cx| { if let Diff::Pending(diff) = diff { diff.update_visible_ranges(cx); } }) }); } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) { self.revealed_ranges.push(range); self.update_visible_ranges(cx); } fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff { let ranges = self.excerpt_ranges(cx); let base_text = self.base_text.clone(); let new_buffer = self.new_buffer.read(cx); let language_registry = new_buffer.language_registry(); let path = new_buffer .file() .map(|file| file.path().display(file.path_style(cx))) .unwrap_or("untitled".into()) .into(); let replica_id = new_buffer.replica_id(); // Replace the buffer in the multibuffer with the snapshot let buffer = cx.new(|cx| { let language = self.new_buffer.read(cx).language().cloned(); let buffer = TextBuffer::new_normalized( replica_id, cx.entity_id().as_non_zero_u64().into(), self.new_buffer.read(cx).line_ending(), self.new_buffer.read(cx).as_rope().clone(), ); let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite); buffer.set_language(language, cx); buffer }); let buffer_diff = cx.spawn({ let buffer = buffer.clone(); async move |_this, cx| { buffer.update(cx, |buffer, _| buffer.parsing_idle()).await; build_buffer_diff(base_text, &buffer, language_registry, cx).await } }); let update_diff = cx.spawn(async move |this, cx| { let buffer_diff = buffer_diff.await?; this.update(cx, |this, cx| { this.multibuffer().update(cx, |multibuffer, cx| { let path_key = PathKey::for_buffer(&buffer, cx); multibuffer.clear(cx); multibuffer.set_excerpts_for_path( path_key, buffer, ranges, excerpt_context_lines(cx), cx, ); multibuffer.add_diff(buffer_diff.clone(), cx); }); cx.notify(); }) }); FinalizedDiff { path, base_text: self.base_text.clone(), multibuffer: self.multibuffer.clone(), new_buffer: self.new_buffer.clone(), _update_diff: update_diff, } } fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) { let ranges = self.excerpt_ranges(cx); self.multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( PathKey::for_buffer(&self.new_buffer, cx), self.new_buffer.clone(), ranges, excerpt_context_lines(cx), cx, ); let end = multibuffer.len(cx); Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1) }); cx.notify(); } fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> { let buffer = self.new_buffer.read(cx); let mut ranges = self .diff .read(cx) .snapshot(cx) .hunks_intersecting_range( Anchor::min_for_buffer(buffer.remote_id()) ..Anchor::max_for_buffer(buffer.remote_id()), buffer, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::<Vec<_>>(); ranges.extend( self.revealed_ranges .iter() .map(|range| range.to_point(buffer)), ); ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end))); // Merge adjacent ranges let mut ranges = ranges.into_iter().peekable(); let mut merged_ranges = Vec::new(); while let Some(mut range) = ranges.next() { while let Some(next_range) = ranges.peek() { if range.end >= next_range.start { range.end = range.end.max(next_range.end); ranges.next(); } else { break; } } merged_ranges.push(range); } merged_ranges }} pub struct FinalizedDiff { path: String, base_text: Arc<str>, new_buffer: Entity<Buffer>, multibuffer: Entity<MultiBuffer>, _update_diff: Task<Result<()>>,} async fn build_buffer_diff( old_text: Arc<str>, buffer: &Entity<Buffer>, language_registry: Option<Arc<LanguageRegistry>>, cx: &mut AsyncApp,) -> Result<Entity<BufferDiff>> { let language = cx.update(|cx| buffer.read(cx).language().cloned()); let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot()); let buffer = cx.update(|cx| buffer.read(cx).snapshot()); let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx)); let update = secondary_diff .update(cx, |secondary_diff, cx| { secondary_diff.update_diff( text_snapshot.clone(), Some(old_text), Some(false), language.clone(), cx, ) }) .await; secondary_diff .update(cx, |secondary_diff, cx| { secondary_diff.set_snapshot(update.clone(), &buffer, cx) }) .await; let diff = cx.new(|cx| BufferDiff::new(&buffer, cx)); diff.update(cx, |diff, cx| { diff.language_changed(language, language_registry, cx); diff.set_secondary_diff(secondary_diff); diff.set_snapshot(update.clone(), &buffer, cx) }) .await; Ok(diff)} #[cfg(test)]mod tests { use gpui::{AppContext as _, TestAppContext}; use language::Buffer; use crate::Diff; #[gpui::test] async fn test_pending_diff(cx: &mut TestAppContext) { let buffer = cx.new(|cx| Buffer::local("hello!", cx)); let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx)); buffer.update(cx, |buffer, cx| { buffer.set_text("HELLO!", cx); }); cx.run_until_parked(); }}
use anyhow::Result;
use buffer_diff::BufferDiff;
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
use itertools::Itertools;
use language::{
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
};
use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use util::ResultExt;
pub enum Diff {
Pending(PendingDiff),
Finalized(FinalizedDiff),
}
impl Diff {
pub fn finalized(
path: String,
old_text: Option<String>,
new_text: String,
language_registry: Arc<LanguageRegistry>,
cx: &mut Context<Self>,
) -> Self {
let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
let base_text = old_text.clone().unwrap_or(String::new()).into();
let task = cx.spawn({
let multibuffer = multibuffer.clone();
let path = path.clone();
let buffer = new_buffer.clone();
async move |_, cx| {
let language = language_registry
.load_language_for_file_path(Path::new(&path))
.await
.log_err();
buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));
buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
let diff = build_buffer_diff(
old_text.unwrap_or("".into()).into(),
&buffer,
Some(language_registry.clone()),
cx,
)
.await?;
multibuffer.update(cx, |multibuffer, cx| {
let hunk_ranges = {
let buffer = buffer.read(cx);
diff.read(cx)
.snapshot(cx)
.hunks_intersecting_range(
Anchor::min_for_buffer(buffer.remote_id())
..Anchor::max_for_buffer(buffer.remote_id()),
buffer,
)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
.collect::<Vec<_>>()
};
multibuffer.set_excerpts_for_path(
PathKey::for_buffer(&buffer, cx),
buffer.clone(),
hunk_ranges,
excerpt_context_lines(cx),
cx,
);
multibuffer.add_diff(diff, cx);
});
anyhow::Ok(())
}
});
Self::Finalized(FinalizedDiff {
multibuffer,
path,
base_text,
new_buffer,
_update_diff: task,
})
}
pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
let buffer_text_snapshot = buffer.read(cx).text_snapshot();
let language = buffer.read(cx).language().cloned();
let language_registry = buffer.read(cx).language_registry();
let buffer_diff = cx.new(|cx| {
let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
diff.language_changed(language.clone(), language_registry.clone(), cx);
let secondary_diff = cx.new(|cx| {
// For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
// it. As a result, by skipping it we are potentially shaving off a lot of RSS plus we get a snappier feel for large diff
// view multibuffers.
BufferDiff::new_unchanged(&buffer_text_snapshot, cx)
});
diff.set_secondary_diff(secondary_diff);
diff
});
let multibuffer = cx.new(|cx| {
let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
multibuffer.add_diff(buffer_diff.clone(), cx);
multibuffer
});
Self::Pending(PendingDiff {
multibuffer,
base_text: Arc::from(buffer_text_snapshot.text().as_str()),
_subscription: cx.observe(&buffer, |this, _, cx| {
if let Diff::Pending(diff) = this {
diff.update(cx);
}
}),
new_buffer: buffer,
diff: buffer_diff,
revealed_ranges: Vec::new(),
update_diff: Task::ready(Ok(())),
})
}
pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
if let Self::Pending(diff) = self {
diff.reveal_range(range, cx);
}
}
pub fn finalize(&mut self, cx: &mut Context<Self>) {
if let Self::Pending(diff) = self {
*self = Self::Finalized(diff.finalize(cx));
}
}
/// Returns the original text before any edits were applied.
pub fn base_text(&self) -> &Arc<str> {
match self {
Self::Pending(PendingDiff { base_text, .. }) => base_text,
Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
}
}
/// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
pub fn buffer(&self) -> &Entity<Buffer> {
match self {
Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
}
}
pub fn file_path(&self, cx: &App) -> Option<String> {
match self {
Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer
.read(cx)
.file()
.map(|file| file.full_path(cx).to_string_lossy().into_owned()),
Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),
}
}
pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
match self {
Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
}
}
pub fn to_markdown(&self, cx: &App) -> String {
let buffer_text = self
.multibuffer()
.read(cx)
.all_buffers()
.iter()
.map(|buffer| buffer.read(cx).text())
.join("\n");
let path = match self {
Diff::Pending(PendingDiff {
new_buffer: buffer, ..
}) => buffer
.read(cx)
.file()
.map(|file| file.path().display(file.path_style(cx))),
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
};
format!(
"Diff: {}\n```\n{}\n```\n",
path.unwrap_or("untitled".into()),
buffer_text
)
}
pub fn has_revealed_range(&self, cx: &App) -> bool {
self.multibuffer().read(cx).paths().next().is_some()
}
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
match self {
Diff::Pending(PendingDiff {
base_text,
new_buffer,
..
}) => {
base_text.as_ref() != old_text
|| !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
}
Diff::Finalized(FinalizedDiff {
base_text,
new_buffer,
..
}) => {
base_text.as_ref() != old_text
|| !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
}
}
}
}
pub struct PendingDiff {
multibuffer: Entity<MultiBuffer>,
base_text: Arc<str>,
new_buffer: Entity<Buffer>,
diff: Entity<BufferDiff>,
revealed_ranges: Vec<Range<Anchor>>,
_subscription: Subscription,
update_diff: Task<Result<()>>,
}
impl PendingDiff {
pub fn update(&mut self, cx: &mut Context<Diff>) {
let buffer = self.new_buffer.clone();
let buffer_diff = self.diff.clone();
let base_text = self.base_text.clone();
self.update_diff = cx.spawn(async move |diff, cx| {
let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
let update = buffer_diff
.update(cx, |diff, cx| {
diff.update_diff(
text_snapshot.clone(),
Some(base_text.clone()),
None,
language,
cx,
)
})
.await;
let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
let task2 = diff
.secondary_diff()
.unwrap()
.update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
(task1, task2)
});
task1.await;
task2.await;
diff.update(cx, |diff, cx| {
if let Diff::Pending(diff) = diff {
diff.update_visible_ranges(cx);
}
})
});
}
pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
self.revealed_ranges.push(range);
self.update_visible_ranges(cx);
}
fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
let ranges = self.excerpt_ranges(cx);
let base_text = self.base_text.clone();
let new_buffer = self.new_buffer.read(cx);
let language_registry = new_buffer.language_registry();
let path = new_buffer
.file()
.map(|file| file.path().display(file.path_style(cx)))
.unwrap_or("untitled".into())
.into();
let replica_id = new_buffer.replica_id();
// Replace the buffer in the multibuffer with the snapshot
let buffer = cx.new(|cx| {
let language = self.new_buffer.read(cx).language().cloned();
let buffer = TextBuffer::new_normalized(
replica_id,
cx.entity_id().as_non_zero_u64().into(),
self.new_buffer.read(cx).line_ending(),
self.new_buffer.read(cx).as_rope().clone(),
);
let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
buffer.set_language(language, cx);
buffer
});
let buffer_diff = cx.spawn({
let buffer = buffer.clone();
async move |_this, cx| {
buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
build_buffer_diff(base_text, &buffer, language_registry, cx).await
}
});
let update_diff = cx.spawn(async move |this, cx| {
let buffer_diff = buffer_diff.await?;
this.update(cx, |this, cx| {
this.multibuffer().update(cx, |multibuffer, cx| {
let path_key = PathKey::for_buffer(&buffer, cx);
multibuffer.clear(cx);
multibuffer.set_excerpts_for_path(
path_key,
buffer,
ranges,
excerpt_context_lines(cx),
cx,
);
multibuffer.add_diff(buffer_diff.clone(), cx);
});
cx.notify();
})
});
FinalizedDiff {
path,
base_text: self.base_text.clone(),
multibuffer: self.multibuffer.clone(),
new_buffer: self.new_buffer.clone(),
_update_diff: update_diff,
}
}
fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
let ranges = self.excerpt_ranges(cx);
self.multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path(
PathKey::for_buffer(&self.new_buffer, cx),
self.new_buffer.clone(),
ranges,
excerpt_context_lines(cx),
cx,
);
let end = multibuffer.len(cx);
Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
});
cx.notify();
}
fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
let buffer = self.new_buffer.read(cx);
let mut ranges = self
.diff
.read(cx)
.snapshot(cx)
.hunks_intersecting_range(
Anchor::min_for_buffer(buffer.remote_id())
..Anchor::max_for_buffer(buffer.remote_id()),
buffer,
)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
.collect::<Vec<_>>();
ranges.extend(
self.revealed_ranges
.iter()
.map(|range| range.to_point(buffer)),
);
ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
// Merge adjacent ranges
let mut ranges = ranges.into_iter().peekable();
let mut merged_ranges = Vec::new();
while let Some(mut range) = ranges.next() {
while let Some(next_range) = ranges.peek() {
if range.end >= next_range.start {
range.end = range.end.max(next_range.end);
ranges.next();
} else {
break;
}
}
merged_ranges.push(range);
}
merged_ranges
}
}
pub struct FinalizedDiff {
path: String,
base_text: Arc<str>,
new_buffer: Entity<Buffer>,
multibuffer: Entity<MultiBuffer>,
_update_diff: Task<Result<()>>,
}
async fn build_buffer_diff(
old_text: Arc<str>,
buffer: &Entity<Buffer>,
language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut AsyncApp,
) -> Result<Entity<BufferDiff>> {
let language = cx.update(|cx| buffer.read(cx).language().cloned());
let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
let buffer = cx.update(|cx| buffer.read(cx).snapshot());
let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
let update = secondary_diff
.update(cx, |secondary_diff, cx| {
secondary_diff.update_diff(
text_snapshot.clone(),
Some(old_text),
Some(false),
language.clone(),
cx,
)
})
.await;
secondary_diff
.update(cx, |secondary_diff, cx| {
secondary_diff.set_snapshot(update.clone(), &buffer, cx)
})
.await;
let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
diff.update(cx, |diff, cx| {
diff.language_changed(language, language_registry, cx);
diff.set_secondary_diff(secondary_diff);
diff.set_snapshot(update.clone(), &buffer, cx)
})
.await;
Ok(diff)
}
#[cfg(test)]
mod tests {
use gpui::{AppContext as _, TestAppContext};
use language::Buffer;
use crate::Diff;
#[gpui::test]
async fn test_pending_diff(cx: &mut TestAppContext) {
let buffer = cx.new(|cx| Buffer::local("hello!", cx));
let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| {
buffer.set_text("HELLO!", cx);
});
cx.run_until_parked();
}
}