Improve tracking for agent edits (#27857)

Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Antonio Scandurra 2025-04-02 00:13:28 +02:00 committed by GitHub
parent d26c477d86
commit 4a252515b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 757 additions and 795 deletions

8
Cargo.lock generated
View file

@ -693,17 +693,22 @@ name = "assistant_tool"
version = "0.1.0"
dependencies = [
"anyhow",
"async-watch",
"buffer_diff",
"clock",
"collections",
"ctor",
"derive_more",
"env_logger 0.11.7",
"futures 0.3.31",
"gpui",
"icons",
"language",
"language_model",
"log",
"parking_lot",
"pretty_assertions",
"project",
"rand 0.8.5",
"serde",
"serde_json",
"settings",
@ -718,7 +723,6 @@ dependencies = [
"anyhow",
"assistant_tool",
"chrono",
"clock",
"collections",
"feature_flags",
"futures 0.3.31",

View file

@ -149,7 +149,7 @@
{
"context": "AssistantDiff",
"bindings": {
"ctrl-y": "agent::ToggleKeep",
"ctrl-y": "agent::Keep",
"ctrl-k ctrl-r": "agent::Reject"
}
},

View file

@ -241,7 +241,7 @@
"context": "AssistantDiff",
"use_key_equivalents": true,
"bindings": {
"cmd-y": "agent::ToggleKeep",
"cmd-y": "agent::Keep",
"cmd-alt-z": "agent::Reject"
}
},

View file

@ -66,7 +66,7 @@ actions!(
AcceptSuggestedContext,
OpenActiveThreadAsMarkdown,
OpenAssistantDiff,
ToggleKeep,
Keep,
Reject,
RejectAll,
KeepAll

View file

@ -1,4 +1,4 @@
use crate::{Thread, ThreadEvent, ToggleKeep};
use crate::{Thread, ThreadEvent};
use anyhow::Result;
use buffer_diff::DiffHunkStatus;
use collections::HashSet;
@ -78,7 +78,7 @@ impl AssistantDiff {
hunk_range,
is_created_file,
line_height,
_editor: &Entity<Editor>,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
render_diff_hunk_controls(
@ -88,6 +88,7 @@ impl AssistantDiff {
is_created_file,
line_height,
&assistant_diff,
editor,
window,
cx,
)
@ -130,7 +131,7 @@ impl AssistantDiff {
let changed_buffers = thread.action_log().read(cx).changed_buffers(cx);
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
for (buffer, changed) in changed_buffers {
for (buffer, diff_handle) in changed_buffers {
let Some(file) = buffer.read(cx).file().cloned() else {
continue;
};
@ -139,7 +140,7 @@ impl AssistantDiff {
paths_to_delete.remove(&path_key);
let snapshot = buffer.read(cx).snapshot();
let diff = changed.diff.read(cx);
let diff = diff_handle.read(cx);
let diff_hunk_ranges = diff
.hunks_intersecting_range(
language::Anchor::MIN..language::Anchor::MAX,
@ -159,7 +160,7 @@ impl AssistantDiff {
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
);
multibuffer.add_diff(changed.diff.clone(), cx);
multibuffer.add_diff(diff_handle, cx);
(was_empty, is_excerpt_newly_added)
});
@ -221,7 +222,7 @@ impl AssistantDiff {
}
}
fn toggle_keep(&mut self, _: &crate::ToggleKeep, _window: &mut Window, cx: &mut Context<Self>) {
fn keep(&mut self, _: &crate::Keep, _window: &mut Window, cx: &mut Context<Self>) {
let ranges = self
.editor
.read(cx)
@ -240,8 +241,7 @@ impl AssistantDiff {
let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id);
if let Some(buffer) = buffer {
self.thread.update(cx, |thread, cx| {
let accept = hunk.status().has_secondary_hunk();
thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx)
thread.keep_edits_in_range(buffer, hunk.buffer_range, cx)
});
}
}
@ -268,10 +268,9 @@ impl AssistantDiff {
.update(cx, |thread, cx| thread.keep_all_edits(cx));
}
fn review_diff_hunks(
fn keep_edits_in_ranges(
&mut self,
hunk_ranges: Vec<Range<editor::Anchor>>,
accept: bool,
cx: &mut Context<Self>,
) {
let snapshot = self.multibuffer.read(cx).snapshot(cx);
@ -285,7 +284,7 @@ impl AssistantDiff {
let buffer = self.multibuffer.read(cx).buffer(hunk.buffer_id);
if let Some(buffer) = buffer {
self.thread.update(cx, |thread, cx| {
thread.review_edits_in_range(buffer, hunk.buffer_range, accept, cx)
thread.keep_edits_in_range(buffer, hunk.buffer_range, cx)
});
}
}
@ -479,7 +478,7 @@ impl Render for AssistantDiff {
} else {
"AssistantDiff"
})
.on_action(cx.listener(Self::toggle_keep))
.on_action(cx.listener(Self::keep))
.on_action(cx.listener(Self::reject))
.on_action(cx.listener(Self::reject_all))
.on_action(cx.listener(Self::keep_all))
@ -495,16 +494,16 @@ impl Render for AssistantDiff {
fn render_diff_hunk_controls(
row: u32,
status: &DiffHunkStatus,
_status: &DiffHunkStatus,
hunk_range: Range<editor::Anchor>,
is_created_file: bool,
line_height: Pixels,
assistant_diff: &Entity<AssistantDiff>,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = assistant_diff.read(cx).editor.clone();
let editor = editor.clone();
h_flex()
.h(line_height)
.mr_0p5()
@ -519,75 +518,47 @@ fn render_diff_hunk_controls(
.gap_1()
.occlude()
.shadow_md()
.children(if status.has_secondary_hunk() {
vec![
Button::new("reject", "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
&crate::Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
});
}
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(
&crate::ToggleKeep,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let assistant_diff = assistant_diff.clone();
move |_event, _window, cx| {
assistant_diff.update(cx, |diff, cx| {
diff.review_diff_hunks(
vec![hunk_range.start..hunk_range.start],
true,
cx,
);
});
}
}),
]
} else {
vec![
Button::new(("review", row as u64), "Review")
.key_binding(KeyBinding::for_action_in(
&ToggleKeep,
.children(vec![
Button::new("reject", "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
&crate::Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
))
.on_click({
let assistant_diff = assistant_diff.clone();
move |_event, _window, cx| {
assistant_diff.update(cx, |diff, cx| {
diff.review_diff_hunks(
vec![hunk_range.start..hunk_range.start],
false,
cx,
);
});
}
}),
]
})
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.restore_hunks_in_ranges(vec![point..point], window, cx);
});
}
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(
&crate::Keep,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let assistant_diff = assistant_diff.clone();
move |_event, _window, cx| {
assistant_diff.update(cx, |diff, cx| {
diff.keep_edits_in_ranges(vec![hunk_range.start..hunk_range.start], cx);
});
}
}),
])
.when(
!editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
|el| {

View file

@ -245,9 +245,6 @@ impl MessageEditor {
thread
.update(cx, |thread, cx| {
let context = context_store.read(cx).context().clone();
thread.action_log().update(cx, |action_log, cx| {
action_log.clear_reviewed_changes(cx);
});
thread.insert_user_message(user_message, context, checkpoint, cx);
})
.ok();
@ -546,7 +543,7 @@ impl Render for MessageEditor {
parent.child(
v_flex().bg(cx.theme().colors().editor_background).children(
changed_buffers.into_iter().enumerate().flat_map(
|(index, (buffer, changed))| {
|(index, (buffer, _diff))| {
let file = buffer.read(cx).file()?;
let path = file.path();
@ -619,25 +616,13 @@ impl Render for MessageEditor {
.color(Color::Deleted),
),
)
.when(!changed.needs_review, |parent| {
parent.child(
Icon::new(IconName::Check)
.color(Color::Success),
)
})
.child(
div()
.h_full()
.absolute()
.w_8()
.bottom_0()
.map(|this| {
if !changed.needs_review {
this.right_4()
} else {
this.right_0()
}
})
.right_0()
.bg(linear_gradient(
90.,
linear_color_stop(

View file

@ -1679,23 +1679,20 @@ impl Thread {
Ok(String::from_utf8_lossy(&markdown).to_string())
}
pub fn review_edits_in_range(
pub fn keep_edits_in_range(
&mut self,
buffer: Entity<language::Buffer>,
buffer_range: Range<language::Anchor>,
accept: bool,
cx: &mut Context<Self>,
) {
self.action_log.update(cx, |action_log, cx| {
action_log.review_edits_in_range(buffer, buffer_range, accept, cx)
action_log.keep_edits_in_range(buffer, buffer_range, cx)
});
}
/// Keeps all edits across all buffers at once.
/// This provides a more performant alternative to calling review_edits_in_range for each buffer.
pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {
self.action_log
.update(cx, |action_log, _cx| action_log.keep_all_edits());
.update(cx, |action_log, cx| action_log.keep_all_edits(cx));
}
pub fn action_log(&self) -> &Entity<ActionLog> {

View file

@ -13,11 +13,11 @@ path = "src/assistant_tool.rs"
[dependencies]
anyhow.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
clock.workspace = true
collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
icons.workspace = true
language.workspace = true
@ -27,15 +27,21 @@ project.workspace = true
serde.workspace = true
serde_json.workspace = true
text.workspace = true
util.workspace = true
[dev-dependencies]
buffer_diff = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
language_model = { workspace = true, features = ["test-support"] }
log.workspace = true
pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
settings = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,6 @@ path = "src/assistant_tools.rs"
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
clock.workspace = true
chrono.workspace = true
collections.workspace = true
feature_flags.workspace = true
@ -42,7 +41,6 @@ worktree.workspace = true
open = { workspace = true }
[dev-dependencies]
clock = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }

View file

@ -92,10 +92,11 @@ impl Tool for CreateFileTool {
})?
.await
.map_err(|err| anyhow!("Unable to open buffer for {destination_path}: {err}"))?;
let edit_id = buffer.update(cx, |buffer, cx| buffer.set_text(contents, cx))?;
action_log.update(cx, |action_log, cx| {
action_log.will_create_buffer(buffer.clone(), edit_id, cx)
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.set_text(contents, cx));
action_log.update(cx, |action_log, cx| {
action_log.will_create_buffer(buffer.clone(), cx)
});
})?;
project

View file

@ -174,7 +174,6 @@ enum EditorResponse {
struct AppliedAction {
source: String,
buffer: Entity<language::Buffer>,
edit_ids: Vec<clock::Lamport>,
}
#[derive(Debug)]
@ -339,18 +338,17 @@ impl EditToolRequest {
self.push_search_error(error);
}
DiffResult::Diff(diff) => {
let edit_ids = buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.apply_diff(diff, false, cx);
let transaction = buffer.finalize_last_transaction();
transaction.map_or(Vec::new(), |transaction| transaction.edit_ids.clone())
cx.update(|cx| {
buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.apply_diff(diff, cx);
buffer.finalize_last_transaction();
});
self.action_log
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
})?;
self.push_applied_action(AppliedAction {
source,
buffer,
edit_ids,
});
self.push_applied_action(AppliedAction { source, buffer });
}
}
@ -473,9 +471,6 @@ impl EditToolRequest {
for action in applied {
changed_buffers.insert(action.buffer.clone());
self.action_log.update(cx, |log, cx| {
log.buffer_edited(action.buffer, action.edit_ids, cx)
})?;
write!(&mut output, "\n\n{}", action.source)?;
}

View file

@ -1,7 +1,7 @@
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use gpui::{App, AppContext, Entity, Task};
use gpui::{App, AppContext, AsyncApp, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@ -165,7 +165,7 @@ impl Tool for FindReplaceFileTool {
Err(err) => return Task::ready(Err(anyhow!(err))),
};
cx.spawn(async move |cx| {
cx.spawn(async move |cx: &mut AsyncApp| {
let project_path = project.read_with(cx, |project, cx| {
project
.find_project_path(&input.path, cx)
@ -225,20 +225,18 @@ impl Tool for FindReplaceFileTool {
return Err(err)
};
let (edit_ids, snapshot) = buffer.update(cx, |buffer, cx| {
let snapshot = buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.apply_diff(diff, false, cx);
let transaction = buffer.finalize_last_transaction();
let edit_ids = transaction.map_or(Vec::new(), |transaction| transaction.edit_ids.clone());
(edit_ids, buffer.snapshot())
buffer.apply_diff(diff, cx);
buffer.finalize_last_transaction();
buffer.snapshot()
})?;
action_log.update(cx, |log, cx| {
log.buffer_edited(buffer.clone(), edit_ids, cx)
log.buffer_edited(buffer.clone(), cx)
})?;
project.update(cx, |project, cx| {
project.update( cx, |project, cx| {
project.save_buffer(buffer, cx)
})?.await?;
@ -249,6 +247,7 @@ impl Tool for FindReplaceFileTool {
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
})
}
}

View file

@ -518,7 +518,7 @@ mod tests {
// Call replace_flexible and transform the result
replace_with_flexible_indent(old, new, &buffer_snapshot).map(|diff| {
buffer.update(cx, |buffer, cx| {
let _ = buffer.apply_diff(diff, false, cx);
let _ = buffer.apply_diff(diff, cx);
buffer.text()
})
})

View file

@ -377,7 +377,6 @@ impl RealGitRepository {
#[derive(Clone, Debug)]
pub struct GitRepositoryCheckpoint {
ref_name: String,
head_sha: Option<Oid>,
commit_sha: Oid,
}
@ -1213,11 +1212,6 @@ impl GitRepository for RealGitRepository {
Ok(GitRepositoryCheckpoint {
ref_name,
head_sha: if let Some(head_sha) = head_sha {
Some(head_sha.parse()?)
} else {
None
},
commit_sha: checkpoint_sha.parse()?,
})
})
@ -1252,13 +1246,6 @@ impl GitRepository for RealGitRepository {
})
.await?;
if let Some(head_sha) = checkpoint.head_sha {
git.run(&["reset", "--mixed", &head_sha.to_string()])
.await?;
} else {
git.run(&["update-ref", "-d", "HEAD"]).await?;
}
Ok(())
})
.boxed()
@ -1269,10 +1256,6 @@ impl GitRepository for RealGitRepository {
left: GitRepositoryCheckpoint,
right: GitRepositoryCheckpoint,
) -> BoxFuture<Result<bool>> {
if left.head_sha != right.head_sha {
return future::ready(Ok(false)).boxed();
}
let working_directory = self.working_directory();
let git_binary_path = self.git_binary_path.clone();
@ -1768,7 +1751,6 @@ fn checkpoint_author_envs() -> HashMap<String, String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::status::FileStatus;
use gpui::TestAppContext;
#[gpui::test]
@ -1803,7 +1785,6 @@ mod tests {
smol::fs::write(repo_dir.path().join("new_file_before_checkpoint"), "1")
.await
.unwrap();
let sha_before_checkpoint = repo.head_sha().unwrap();
let checkpoint = repo.checkpoint().await.unwrap();
// Ensure the user can't see any branches after creating a checkpoint.
@ -1837,7 +1818,6 @@ mod tests {
repo.gc().await.unwrap();
repo.restore_checkpoint(checkpoint.clone()).await.unwrap();
assert_eq!(repo.head_sha().unwrap(), sha_before_checkpoint);
assert_eq!(
smol::fs::read_to_string(&file_path).await.unwrap(),
"modified before checkpoint"
@ -1901,83 +1881,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_undoing_commit_via_checkpoint(cx: &mut TestAppContext) {
cx.executor().allow_parking();
let repo_dir = tempfile::tempdir().unwrap();
git2::Repository::init(repo_dir.path()).unwrap();
let file_path = repo_dir.path().join("file");
smol::fs::write(&file_path, "initial").await.unwrap();
let repo =
RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap();
repo.stage_paths(
vec![RepoPath::from_str("file")],
Arc::new(HashMap::default()),
)
.await
.unwrap();
repo.commit(
"Initial commit".into(),
None,
Arc::new(checkpoint_author_envs()),
)
.await
.unwrap();
let initial_commit_sha = repo.head_sha().unwrap();
smol::fs::write(repo_dir.path().join("new_file1"), "content1")
.await
.unwrap();
smol::fs::write(repo_dir.path().join("new_file2"), "content2")
.await
.unwrap();
let checkpoint = repo.checkpoint().await.unwrap();
repo.stage_paths(
vec![
RepoPath::from_str("new_file1"),
RepoPath::from_str("new_file2"),
],
Arc::new(HashMap::default()),
)
.await
.unwrap();
repo.commit(
"Commit new files".into(),
None,
Arc::new(checkpoint_author_envs()),
)
.await
.unwrap();
repo.restore_checkpoint(checkpoint).await.unwrap();
assert_eq!(repo.head_sha().unwrap(), initial_commit_sha);
assert_eq!(
smol::fs::read_to_string(repo_dir.path().join("new_file1"))
.await
.unwrap(),
"content1"
);
assert_eq!(
smol::fs::read_to_string(repo_dir.path().join("new_file2"))
.await
.unwrap(),
"content2"
);
assert_eq!(
repo.status(&[]).await.unwrap().entries.as_ref(),
&[
(RepoPath::from_str("new_file1"), FileStatus::Untracked),
(RepoPath::from_str("new_file2"), FileStatus::Untracked)
]
);
}
#[gpui::test]
async fn test_compare_checkpoints(cx: &mut TestAppContext) {
cx.executor().allow_parking();

View file

@ -1321,7 +1321,7 @@ impl Buffer {
this.update(cx, |this, cx| {
if this.version() == diff.base_version {
this.finalize_last_transaction();
this.apply_diff(diff, true, cx);
this.apply_diff(diff, cx);
tx.send(this.finalize_last_transaction().cloned()).ok();
this.has_conflict = false;
this.did_reload(this.version(), this.line_ending(), new_mtime, cx);
@ -1882,14 +1882,7 @@ impl Buffer {
/// Applies a diff to the buffer. If the buffer has changed since the given diff was
/// calculated, then adjust the diff to account for those changes, and discard any
/// parts of the diff that conflict with those changes.
///
/// If `atomic` is true, the diff will be applied as a single edit.
pub fn apply_diff(
&mut self,
diff: Diff,
atomic: bool,
cx: &mut Context<Self>,
) -> Option<TransactionId> {
pub fn apply_diff(&mut self, diff: Diff, cx: &mut Context<Self>) -> Option<TransactionId> {
let snapshot = self.snapshot();
let mut edits_since = snapshot.edits_since::<usize>(&diff.base_version).peekable();
let mut delta = 0;
@ -1919,17 +1912,7 @@ impl Buffer {
self.start_transaction();
self.text.set_line_ending(diff.line_ending);
if atomic {
self.edit(adjusted_edits, None, cx);
} else {
let mut delta = 0isize;
for (range, new_text) in adjusted_edits {
let adjusted_range =
(range.start as isize + delta) as usize..(range.end as isize + delta) as usize;
delta += new_text.len() as isize - range.len() as isize;
self.edit([(adjusted_range, new_text)], None, cx);
}
}
self.edit(adjusted_edits, None, cx);
self.end_transaction(cx)
}

View file

@ -376,7 +376,7 @@ async fn test_apply_diff(cx: &mut TestAppContext) {
let diff = buffer.update(cx, |b, cx| b.diff(text.clone(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, true, cx).unwrap();
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
let actual_offsets = anchors
.iter()
@ -390,7 +390,7 @@ async fn test_apply_diff(cx: &mut TestAppContext) {
let diff = buffer.update(cx, |b, cx| b.diff(text.clone(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, true, cx).unwrap();
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
let actual_offsets = anchors
.iter()
@ -435,7 +435,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
let format_diff = format.await;
buffer.update(cx, |buffer, cx| {
let version_before_format = format_diff.base_version.clone();
buffer.apply_diff(format_diff, true, cx);
buffer.apply_diff(format_diff, cx);
// The outcome depends on the order of concurrent tasks.
//

View file

@ -1230,7 +1230,7 @@ impl LocalLspStore {
.await;
buffer.handle.update(cx, |buffer, cx| {
buffer.start_transaction();
buffer.apply_diff(diff, true, cx);
buffer.apply_diff(diff, cx);
transaction_id_format =
transaction_id_format.or(buffer.end_transaction(cx));
if let Some(transaction_id) = transaction_id_format {
@ -1364,7 +1364,7 @@ impl LocalLspStore {
zlog::trace!(logger => "Applying changes");
buffer.handle.update(cx, |buffer, cx| {
buffer.start_transaction();
buffer.apply_diff(diff, true, cx);
buffer.apply_diff(diff, cx);
transaction_id_format =
transaction_id_format.or(buffer.end_transaction(cx));
if let Some(transaction_id) = transaction_id_format {
@ -1407,7 +1407,7 @@ impl LocalLspStore {
zlog::trace!(logger => "Applying changes");
buffer.handle.update(cx, |buffer, cx| {
buffer.start_transaction();
buffer.apply_diff(diff, true, cx);
buffer.apply_diff(diff, cx);
transaction_id_format =
transaction_id_format.or(buffer.end_transaction(cx));
if let Some(transaction_id) = transaction_id_format {

View file

@ -224,6 +224,15 @@ where
}
}
impl<T> Patch<T> {
pub fn retain_mut<F>(&mut self, f: F)
where
F: FnMut(&mut Edit<T>) -> bool,
{
self.0.retain_mut(f);
}
}
impl<T: Clone> IntoIterator for Patch<T> {
type Item = Edit<T>;
type IntoIter = std::vec::IntoIter<Edit<T>>;