Proposed changes editor features (#18373)
This PR adds some more functionality to the Proposed Changes Editor view, which we'll be using in https://github.com/zed-industries/zed/pull/18240 for allowing the assistant to propose changes to a set of buffers. * Add an `Apply All` button, and fully implement applying of changes to the base buffer * Make the proposed changes editor searchable * Fix a bug in branch buffers' diff state management Release Notes: - N/A
This commit is contained in:
parent
3161aedcb0
commit
6167688a63
5 changed files with 240 additions and 75 deletions
|
@ -98,7 +98,9 @@ use language::{
|
||||||
};
|
};
|
||||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||||
use linked_editing_ranges::refresh_linked_ranges;
|
use linked_editing_ranges::refresh_linked_ranges;
|
||||||
use proposed_changes_editor::{ProposedChangesBuffer, ProposedChangesEditor};
|
pub use proposed_changes_editor::{
|
||||||
|
ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||||
|
};
|
||||||
use similar::{ChangeTag, TextDiff};
|
use similar::{ChangeTag, TextDiff};
|
||||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,13 @@ use language::{Buffer, BufferEvent, Capability};
|
||||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::{ops::Range, time::Duration};
|
use std::{any::TypeId, ops::Range, time::Duration};
|
||||||
use text::ToOffset;
|
use text::ToOffset;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Item;
|
use workspace::{
|
||||||
|
searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
|
||||||
|
ToolbarItemView,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ProposedChangesEditor {
|
pub struct ProposedChangesEditor {
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
|
@ -23,6 +26,10 @@ pub struct ProposedChangesBuffer<T> {
|
||||||
pub ranges: Vec<Range<T>>,
|
pub ranges: Vec<Range<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ProposedChangesEditorToolbar {
|
||||||
|
current_editor: Option<View<ProposedChangesEditor>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ProposedChangesEditor {
|
impl ProposedChangesEditor {
|
||||||
pub fn new<T: ToOffset>(
|
pub fn new<T: ToOffset>(
|
||||||
buffers: Vec<ProposedChangesBuffer<T>>,
|
buffers: Vec<ProposedChangesBuffer<T>>,
|
||||||
|
@ -96,6 +103,17 @@ impl ProposedChangesEditor {
|
||||||
self.recalculate_diffs_tx.unbounded_send(buffer).ok();
|
self.recalculate_diffs_tx.unbounded_send(buffer).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
|
||||||
|
let buffers = self.editor.read(cx).buffer.read(cx).all_buffers();
|
||||||
|
for branch_buffer in buffers {
|
||||||
|
if let Some(base_buffer) = branch_buffer.read(cx).diff_base_buffer() {
|
||||||
|
base_buffer.update(cx, |base_buffer, cx| {
|
||||||
|
base_buffer.merge(&branch_buffer, None, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ProposedChangesEditor {
|
impl Render for ProposedChangesEditor {
|
||||||
|
@ -122,4 +140,66 @@ impl Item for ProposedChangesEditor {
|
||||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||||
Some("Proposed changes".into())
|
Some("Proposed changes".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
|
Some(Box::new(self.editor.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn act_as_type<'a>(
|
||||||
|
&'a self,
|
||||||
|
type_id: TypeId,
|
||||||
|
self_handle: &'a View<Self>,
|
||||||
|
_: &'a AppContext,
|
||||||
|
) -> Option<gpui::AnyView> {
|
||||||
|
if type_id == TypeId::of::<Self>() {
|
||||||
|
Some(self_handle.to_any())
|
||||||
|
} else if type_id == TypeId::of::<Editor>() {
|
||||||
|
Some(self.editor.to_any())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProposedChangesEditorToolbar {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_editor: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
|
||||||
|
if self.current_editor.is_some() {
|
||||||
|
ToolbarItemLocation::PrimaryRight
|
||||||
|
} else {
|
||||||
|
ToolbarItemLocation::Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ProposedChangesEditorToolbar {
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let editor = self.current_editor.clone();
|
||||||
|
Button::new("apply-changes", "Apply All").on_click(move |_, cx| {
|
||||||
|
if let Some(editor) = &editor {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.apply_all_changes(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
|
||||||
|
|
||||||
|
impl ToolbarItemView for ProposedChangesEditorToolbar {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn workspace::ItemHandle>,
|
||||||
|
_cx: &mut ViewContext<Self>,
|
||||||
|
) -> workspace::ToolbarItemLocation {
|
||||||
|
self.current_editor =
|
||||||
|
active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
|
||||||
|
self.get_toolbar_item_location()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,11 @@ pub type BufferRow = u32;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum BufferDiffBase {
|
enum BufferDiffBase {
|
||||||
Git(Rope),
|
Git(Rope),
|
||||||
PastBufferVersion(Model<Buffer>, BufferSnapshot),
|
PastBufferVersion {
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
rope: Rope,
|
||||||
|
operations_to_ignore: Vec<clock::Lamport>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An in-memory representation of a source code file, including its text,
|
/// An in-memory representation of a source code file, including its text,
|
||||||
|
@ -795,19 +799,15 @@ impl Buffer {
|
||||||
let this = cx.handle();
|
let this = cx.handle();
|
||||||
cx.new_model(|cx| {
|
cx.new_model(|cx| {
|
||||||
let mut branch = Self {
|
let mut branch = Self {
|
||||||
diff_base: Some(BufferDiffBase::PastBufferVersion(
|
diff_base: Some(BufferDiffBase::PastBufferVersion {
|
||||||
this.clone(),
|
buffer: this.clone(),
|
||||||
self.snapshot(),
|
rope: self.as_rope().clone(),
|
||||||
)),
|
operations_to_ignore: Vec::new(),
|
||||||
|
}),
|
||||||
language: self.language.clone(),
|
language: self.language.clone(),
|
||||||
has_conflict: self.has_conflict,
|
has_conflict: self.has_conflict,
|
||||||
has_unsaved_edits: Cell::new(self.has_unsaved_edits.get_mut().clone()),
|
has_unsaved_edits: Cell::new(self.has_unsaved_edits.get_mut().clone()),
|
||||||
_subscriptions: vec![cx.subscribe(&this, |branch: &mut Self, _, event, cx| {
|
_subscriptions: vec![cx.subscribe(&this, Self::on_base_buffer_event)],
|
||||||
if let BufferEvent::Operation { operation, .. } = event {
|
|
||||||
branch.apply_ops([operation.clone()], cx);
|
|
||||||
branch.diff_base_version += 1;
|
|
||||||
}
|
|
||||||
})],
|
|
||||||
..Self::build(
|
..Self::build(
|
||||||
self.text.branch(),
|
self.text.branch(),
|
||||||
None,
|
None,
|
||||||
|
@ -823,18 +823,74 @@ impl Buffer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge(&mut self, branch: &Model<Self>, cx: &mut ModelContext<Self>) {
|
/// Applies all of the changes in `branch` buffer that intersect the given `range`
|
||||||
let branch = branch.read(cx);
|
/// to this buffer.
|
||||||
let edits = branch
|
pub fn merge(
|
||||||
.edits_since::<usize>(&self.version)
|
&mut self,
|
||||||
|
branch: &Model<Self>,
|
||||||
|
range: Option<Range<Anchor>>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let edits = branch.read_with(cx, |branch, _| {
|
||||||
|
branch
|
||||||
|
.edits_since_in_range::<usize>(
|
||||||
|
&self.version,
|
||||||
|
range.unwrap_or(Anchor::MIN..Anchor::MAX),
|
||||||
|
)
|
||||||
.map(|edit| {
|
.map(|edit| {
|
||||||
(
|
(
|
||||||
edit.old,
|
edit.old,
|
||||||
branch.text_for_range(edit.new).collect::<String>(),
|
branch.text_for_range(edit.new).collect::<String>(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>()
|
||||||
self.edit(edits, None, cx);
|
});
|
||||||
|
let operation = self.edit(edits, None, cx);
|
||||||
|
|
||||||
|
// Prevent this operation from being reapplied to the branch.
|
||||||
|
branch.update(cx, |branch, cx| {
|
||||||
|
if let Some(BufferDiffBase::PastBufferVersion {
|
||||||
|
operations_to_ignore,
|
||||||
|
..
|
||||||
|
}) = &mut branch.diff_base
|
||||||
|
{
|
||||||
|
operations_to_ignore.extend(operation);
|
||||||
|
}
|
||||||
|
cx.emit(BufferEvent::Edited)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_base_buffer_event(
|
||||||
|
&mut self,
|
||||||
|
_: Model<Buffer>,
|
||||||
|
event: &BufferEvent,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let BufferEvent::Operation { operation, .. } = event {
|
||||||
|
if let Some(BufferDiffBase::PastBufferVersion {
|
||||||
|
operations_to_ignore,
|
||||||
|
..
|
||||||
|
}) = &mut self.diff_base
|
||||||
|
{
|
||||||
|
let mut is_ignored = false;
|
||||||
|
if let Operation::Buffer(text::Operation::Edit(buffer_operation)) = &operation {
|
||||||
|
operations_to_ignore.retain(|operation_to_ignore| {
|
||||||
|
match buffer_operation.timestamp.cmp(&operation_to_ignore) {
|
||||||
|
Ordering::Less => true,
|
||||||
|
Ordering::Equal => {
|
||||||
|
is_ignored = true;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ordering::Greater => false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !is_ignored {
|
||||||
|
self.apply_ops([operation.clone()], cx);
|
||||||
|
self.diff_base_version += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1017,9 +1073,8 @@ impl Buffer {
|
||||||
/// Returns the current diff base, see [Buffer::set_diff_base].
|
/// Returns the current diff base, see [Buffer::set_diff_base].
|
||||||
pub fn diff_base(&self) -> Option<&Rope> {
|
pub fn diff_base(&self) -> Option<&Rope> {
|
||||||
match self.diff_base.as_ref()? {
|
match self.diff_base.as_ref()? {
|
||||||
BufferDiffBase::Git(rope) => Some(rope),
|
BufferDiffBase::Git(rope) | BufferDiffBase::PastBufferVersion { rope, .. } => {
|
||||||
BufferDiffBase::PastBufferVersion(_, buffer_snapshot) => {
|
Some(rope)
|
||||||
Some(buffer_snapshot.as_rope())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1050,29 +1105,36 @@ impl Buffer {
|
||||||
self.diff_base_version
|
self.diff_base_version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diff_base_buffer(&self) -> Option<Model<Self>> {
|
||||||
|
match self.diff_base.as_ref()? {
|
||||||
|
BufferDiffBase::Git(_) => None,
|
||||||
|
BufferDiffBase::PastBufferVersion { buffer, .. } => Some(buffer.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Recomputes the diff.
|
/// Recomputes the diff.
|
||||||
pub fn recalculate_diff(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
|
pub fn recalculate_diff(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
|
||||||
let diff_base_rope = match self.diff_base.as_mut()? {
|
let diff_base_rope = match self.diff_base.as_ref()? {
|
||||||
BufferDiffBase::Git(rope) => rope.clone(),
|
BufferDiffBase::Git(rope) => rope.clone(),
|
||||||
BufferDiffBase::PastBufferVersion(base_buffer, base_buffer_snapshot) => {
|
BufferDiffBase::PastBufferVersion { buffer, .. } => buffer.read(cx).as_rope().clone(),
|
||||||
let new_base_snapshot = base_buffer.read(cx).snapshot();
|
|
||||||
*base_buffer_snapshot = new_base_snapshot;
|
|
||||||
base_buffer_snapshot.as_rope().clone()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let snapshot = self.snapshot();
|
|
||||||
|
|
||||||
|
let snapshot = self.snapshot();
|
||||||
let mut diff = self.git_diff.clone();
|
let mut diff = self.git_diff.clone();
|
||||||
let diff = cx.background_executor().spawn(async move {
|
let diff = cx.background_executor().spawn(async move {
|
||||||
diff.update(&diff_base_rope, &snapshot).await;
|
diff.update(&diff_base_rope, &snapshot).await;
|
||||||
diff
|
(diff, diff_base_rope)
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cx.spawn(|this, mut cx| async move {
|
Some(cx.spawn(|this, mut cx| async move {
|
||||||
let buffer_diff = diff.await;
|
let (buffer_diff, diff_base_rope) = diff.await;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.git_diff = buffer_diff;
|
this.git_diff = buffer_diff;
|
||||||
this.non_text_state_update_count += 1;
|
this.non_text_state_update_count += 1;
|
||||||
|
if let Some(BufferDiffBase::PastBufferVersion { rope, .. }) = &mut this.diff_base {
|
||||||
|
*rope = diff_base_rope;
|
||||||
|
cx.emit(BufferEvent::DiffBaseChanged);
|
||||||
|
}
|
||||||
cx.emit(BufferEvent::DiffUpdated);
|
cx.emit(BufferEvent::DiffUpdated);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
|
@ -2413,80 +2413,98 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edits to the branch are not applied to the base.
|
// Edits to the branch are not applied to the base.
|
||||||
branch_buffer.update(cx, |buffer, cx| {
|
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||||
buffer.edit(
|
branch_buffer.edit(
|
||||||
[(Point::new(1, 0)..Point::new(1, 0), "ONE_POINT_FIVE\n")],
|
[
|
||||||
|
(Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
|
||||||
|
(Point::new(2, 0)..Point::new(2, 5), "THREE"),
|
||||||
|
],
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||||
assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
|
assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
|
||||||
assert_eq!(branch_buffer.text(), "one\nONE_POINT_FIVE\ntwo\nthree\n");
|
assert_eq!(branch_buffer.text(), "one\n1.5\ntwo\nTHREE\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The branch buffer maintains a diff with respect to its base buffer.
|
||||||
|
start_recalculating_diff(&branch_buffer, cx);
|
||||||
|
cx.run_until_parked();
|
||||||
|
assert_diff_hunks(
|
||||||
|
&branch_buffer,
|
||||||
|
cx,
|
||||||
|
&[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
|
||||||
|
);
|
||||||
|
|
||||||
// Edits to the base are applied to the branch.
|
// Edits to the base are applied to the branch.
|
||||||
base_buffer.update(cx, |buffer, cx| {
|
base_buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
|
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||||
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
|
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
|
||||||
assert_eq!(
|
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
|
||||||
branch_buffer.text(),
|
|
||||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nthree\n"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_diff_hunks(&branch_buffer, cx, &[(2..3, "", "ONE_POINT_FIVE\n")]);
|
// Until the git diff recalculation is complete, the git diff references
|
||||||
|
// the previous content of the base buffer, so that it stays in sync.
|
||||||
|
start_recalculating_diff(&branch_buffer, cx);
|
||||||
|
assert_diff_hunks(
|
||||||
|
&branch_buffer,
|
||||||
|
cx,
|
||||||
|
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||||
|
);
|
||||||
|
cx.run_until_parked();
|
||||||
|
assert_diff_hunks(
|
||||||
|
&branch_buffer,
|
||||||
|
cx,
|
||||||
|
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||||
|
);
|
||||||
|
|
||||||
// Edits to any replica of the base are applied to the branch.
|
// Edits to any replica of the base are applied to the branch.
|
||||||
base_buffer_replica.update(cx, |buffer, cx| {
|
base_buffer_replica.update(cx, |buffer, cx| {
|
||||||
buffer.edit(
|
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
|
||||||
[(Point::new(2, 0)..Point::new(2, 0), "TWO_POINT_FIVE\n")],
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||||
assert_eq!(
|
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
|
||||||
base_buffer.read(cx).text(),
|
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||||
"ZERO\none\ntwo\nTWO_POINT_FIVE\nthree\n"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
branch_buffer.text(),
|
|
||||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Merging the branch applies all of its changes to the base.
|
// Merging the branch applies all of its changes to the base.
|
||||||
base_buffer.update(cx, |base_buffer, cx| {
|
base_buffer.update(cx, |base_buffer, cx| {
|
||||||
base_buffer.merge(&branch_buffer, cx);
|
base_buffer.merge(&branch_buffer, None, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
base_buffer.text(),
|
base_buffer.read(cx).text(),
|
||||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
|
"ZERO\none\n1.5\ntwo\n2.5\nTHREE\n"
|
||||||
);
|
);
|
||||||
|
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
|
||||||
|
buffer
|
||||||
|
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn assert_diff_hunks(
|
fn assert_diff_hunks(
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
expected_hunks: &[(Range<u32>, &str, &str)],
|
expected_hunks: &[(Range<u32>, &str, &str)],
|
||||||
) {
|
) {
|
||||||
buffer
|
let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
|
||||||
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
|
(buffer.snapshot(), buffer.diff_base().unwrap().to_string())
|
||||||
.detach();
|
});
|
||||||
cx.executor().run_until_parked();
|
|
||||||
|
|
||||||
buffer.read_with(cx, |buffer, _| {
|
|
||||||
let snapshot = buffer.snapshot();
|
|
||||||
assert_hunks(
|
assert_hunks(
|
||||||
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
|
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
|
||||||
&snapshot,
|
&snapshot,
|
||||||
&buffer.diff_base().unwrap().to_string(),
|
&diff_base,
|
||||||
expected_hunks,
|
expected_hunks,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 100)]
|
#[gpui::test(iterations = 100)]
|
||||||
|
|
|
@ -14,6 +14,7 @@ use breadcrumbs::Breadcrumbs;
|
||||||
use client::ZED_URL_SCHEME;
|
use client::ZED_URL_SCHEME;
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
|
use editor::ProposedChangesEditorToolbar;
|
||||||
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
|
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -582,6 +583,8 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewCo
|
||||||
let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
|
let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
|
||||||
toolbar.add_item(buffer_search_bar.clone(), cx);
|
toolbar.add_item(buffer_search_bar.clone(), cx);
|
||||||
|
|
||||||
|
let proposed_change_bar = cx.new_view(|_| ProposedChangesEditorToolbar::new());
|
||||||
|
toolbar.add_item(proposed_change_bar, cx);
|
||||||
let quick_action_bar =
|
let quick_action_bar =
|
||||||
cx.new_view(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx));
|
cx.new_view(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx));
|
||||||
toolbar.add_item(quick_action_bar, cx);
|
toolbar.add_item(quick_action_bar, cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue