Checkpoint
This commit is contained in:
parent
160e6d5747
commit
168e55db53
1 changed files with 250 additions and 23 deletions
|
@ -1,28 +1,23 @@
|
|||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use buffer_diff::BufferDiff;
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{MultiBuffer, PathKey};
|
||||
use gpui::{App, AppContext, Context, Entity, Task};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _};
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
|
||||
};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
pub enum Diff {
|
||||
Pending {
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
base_text: Arc<String>,
|
||||
buffer: Entity<Buffer>,
|
||||
buffer_diff: Entity<BufferDiff>,
|
||||
},
|
||||
Ready {
|
||||
path: PathBuf,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
_task: Task<Result<()>>,
|
||||
},
|
||||
Pending(PendingDiff),
|
||||
Finalized(FinalizedDiff),
|
||||
}
|
||||
|
||||
impl Diff {
|
||||
|
@ -96,11 +91,11 @@ impl Diff {
|
|||
}
|
||||
});
|
||||
|
||||
Self::Ready {
|
||||
Self::Finalized(FinalizedDiff {
|
||||
multibuffer,
|
||||
path,
|
||||
_task: task,
|
||||
}
|
||||
_update_diff: task,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
|
||||
|
@ -125,18 +120,37 @@ impl Diff {
|
|||
multibuffer
|
||||
});
|
||||
|
||||
Self::Pending {
|
||||
Self::Pending(PendingDiff {
|
||||
multibuffer,
|
||||
base_text: Arc::new(base_text),
|
||||
_subscription: cx.observe(&buffer, |this, _, cx| {
|
||||
if let Diff::Pending(diff) = this {
|
||||
diff.update(cx);
|
||||
}
|
||||
}),
|
||||
buffer,
|
||||
buffer_diff,
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
|
||||
match self {
|
||||
Self::Pending { multibuffer, .. } => multibuffer,
|
||||
Self::Ready { multibuffer, .. } => multibuffer,
|
||||
Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
|
||||
Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,8 +163,10 @@ impl Diff {
|
|||
.map(|buffer| buffer.read(cx).text())
|
||||
.join("\n");
|
||||
let path = match self {
|
||||
Diff::Pending { buffer, .. } => buffer.read(cx).file().map(|file| file.path().as_ref()),
|
||||
Diff::Ready { path, .. } => Some(path.as_path()),
|
||||
Diff::Pending(PendingDiff { buffer, .. }) => {
|
||||
buffer.read(cx).file().map(|file| file.path().as_ref())
|
||||
}
|
||||
Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
|
||||
};
|
||||
format!(
|
||||
"Diff: {}\n```\n{}\n```\n",
|
||||
|
@ -159,3 +175,214 @@ impl Diff {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PendingDiff {
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
base_text: Arc<String>,
|
||||
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.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 diff_snapshot = BufferDiff::update_diff(
|
||||
buffer_diff.clone(),
|
||||
text_snapshot.clone(),
|
||||
Some(base_text),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
buffer_diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot, &text_snapshot, cx)
|
||||
})?;
|
||||
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 language_registry = self.buffer.read(cx).language_registry().clone();
|
||||
|
||||
let path = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| file.path().as_ref())
|
||||
.unwrap_or(Path::new("untitled"))
|
||||
.into();
|
||||
|
||||
// Replace the buffer in the multibuffer with the snapshot
|
||||
let buffer = cx.new(|cx| {
|
||||
let language = self.buffer.read(cx).language().cloned();
|
||||
let buffer = TextBuffer::new_normalized(
|
||||
0,
|
||||
cx.entity_id().as_non_zero_u64().into(),
|
||||
self.buffer.read(cx).line_ending(),
|
||||
self.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();
|
||||
let language_registry = language_registry.clone();
|
||||
async move |_this, cx| {
|
||||
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,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(buffer_diff.clone(), cx);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
|
||||
FinalizedDiff {
|
||||
path,
|
||||
multibuffer: self.multibuffer.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.buffer, cx),
|
||||
self.buffer.clone(),
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
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.buffer.read(cx);
|
||||
let diff = self.diff.read(cx);
|
||||
let mut ranges = diff
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx)
|
||||
.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: PathBuf,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
_update_diff: Task<Result<()>>,
|
||||
}
|
||||
|
||||
async fn build_buffer_diff(
|
||||
old_text: Arc<String>,
|
||||
buffer: &Entity<Buffer>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<BufferDiff>> {
|
||||
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
|
||||
|
||||
let old_text_rope = cx
|
||||
.background_spawn({
|
||||
let old_text = old_text.clone();
|
||||
async move { Rope::from(old_text.as_str()) }
|
||||
})
|
||||
.await;
|
||||
let base_buffer = cx
|
||||
.update(|cx| {
|
||||
Buffer::build_snapshot(
|
||||
old_text_rope,
|
||||
buffer.language().cloned(),
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
buffer.text.clone(),
|
||||
Some(old_text),
|
||||
base_buffer,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
let secondary_diff = cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer, cx);
|
||||
diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
|
||||
diff
|
||||
})?;
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer.text, cx);
|
||||
diff.set_snapshot(diff_snapshot, &buffer, cx);
|
||||
diff.set_secondary_diff(secondary_diff);
|
||||
diff
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue