use anyhow::{Context as _, Result};use buffer_diff::BufferDiff;use collections::BTreeMap;use futures::{StreamExt, channel::mpsc};use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};use language::{Anchor, Buffer, BufferEvent, DiskState, Point};use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};use std::{cmp, ops::Range, sync::Arc};use text::{Edit, Patch, Rope};use util::RangeExt; /// Tracks actions performed by tools in a threadpub struct ActionLog { /// Buffers that we want to notify the model about when they change. tracked_buffers: BTreeMap<Entity<Buffer>, TrackedBuffer>, /// Has the model edited a file since it last checked diagnostics? edited_since_project_diagnostics_check: bool, /// The project this action log is associated with project: Entity<Project>,} impl ActionLog { /// Creates a new, empty action log associated with the given project. pub fn new(project: Entity<Project>) -> Self { Self { tracked_buffers: BTreeMap::default(), edited_since_project_diagnostics_check: false, project, } } /// Notifies a diagnostics check pub fn checked_project_diagnostics(&mut self) { self.edited_since_project_diagnostics_check = false; } /// Returns true if any files have been edited since the last project diagnostics check pub fn has_edited_files_since_project_diagnostics_check(&self) -> bool { self.edited_since_project_diagnostics_check } fn track_buffer( &mut self, buffer: Entity<Buffer>, created: bool, cx: &mut Context<Self>, ) -> &mut TrackedBuffer { let tracked_buffer = self .tracked_buffers .entry(buffer.clone()) .or_insert_with(|| { let open_lsp_handle = self.project.update(cx, |project, cx| { project.register_buffer_with_language_servers(&buffer, cx) }); let text_snapshot = buffer.read(cx).text_snapshot(); let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx)); let (diff_update_tx, diff_update_rx) = mpsc::unbounded(); let base_text; let status; let unreviewed_changes; if created { base_text = Rope::default(); status = TrackedBufferStatus::Created; unreviewed_changes = Patch::new(vec![Edit { old: 0..1, new: 0..text_snapshot.max_point().row + 1, }]) } else { base_text = buffer.read(cx).as_rope().clone(); status = TrackedBufferStatus::Modified; unreviewed_changes = Patch::default(); } TrackedBuffer { buffer: buffer.clone(), base_text, unreviewed_changes, snapshot: text_snapshot.clone(), status, version: buffer.read(cx).version(), diff, diff_update: diff_update_tx, _open_lsp_handle: open_lsp_handle, _maintain_diff: cx.spawn({ let buffer = buffer.clone(); async move |this, cx| { Self::maintain_diff(this, buffer, diff_update_rx, cx) .await .ok(); } }), _subscription: cx.subscribe(&buffer, Self::handle_buffer_event), } }); tracked_buffer.version = buffer.read(cx).version(); tracked_buffer } fn handle_buffer_event( &mut self, buffer: Entity<Buffer>, event: &BufferEvent, cx: &mut Context<Self>, ) { match event { BufferEvent::Edited { .. } => self.handle_buffer_edited(buffer, cx), BufferEvent::FileHandleChanged => { self.handle_buffer_file_changed(buffer, cx); } _ => {} }; } fn handle_buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } fn handle_buffer_file_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; match tracked_buffer.status { TrackedBufferStatus::Created | TrackedBufferStatus::Modified => { if buffer .read(cx) .file() .map_or(false, |file| file.disk_state() == DiskState::Deleted) { // If the buffer had been edited by a tool, but it got // deleted externally, we want to stop tracking it. self.tracked_buffers.remove(&buffer); } cx.notify(); } TrackedBufferStatus::Deleted => { if buffer .read(cx) .file() .map_or(false, |file| file.disk_state() != DiskState::Deleted) { // If the buffer had been deleted by a tool, but it got // resurrected externally, we want to clear the changes we // were tracking and reset the buffer's state. self.tracked_buffers.remove(&buffer); self.track_buffer(buffer, false, cx); } cx.notify(); } } } async fn maintain_diff( this: WeakEntity<Self>, buffer: Entity<Buffer>, mut diff_update: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>, cx: &mut AsyncApp, ) -> Result<()> { while let Some((author, buffer_snapshot)) = diff_update.next().await { let (rebase, diff, language, language_registry) = this.read_with(cx, |this, cx| { let tracked_buffer = this .tracked_buffers .get(&buffer) .context("buffer not tracked")?; let rebase = cx.background_spawn({ let mut base_text = tracked_buffer.base_text.clone(); let old_snapshot = tracked_buffer.snapshot.clone(); let new_snapshot = buffer_snapshot.clone(); let unreviewed_changes = tracked_buffer.unreviewed_changes.clone(); async move { let edits = diff_snapshots(&old_snapshot, &new_snapshot); if let ChangeAuthor::User = author { apply_non_conflicting_edits( &unreviewed_changes, edits, &mut base_text, new_snapshot.as_rope(), ); } (Arc::new(base_text.to_string()), base_text) } }); anyhow::Ok(( rebase, tracked_buffer.diff.clone(), tracked_buffer.buffer.read(cx).language().cloned(), tracked_buffer.buffer.read(cx).language_registry(), )) })??; let (new_base_text, new_base_text_rope) = rebase.await; let diff_snapshot = BufferDiff::update_diff( diff.clone(), buffer_snapshot.clone(), Some(new_base_text), true, false, language, language_registry, cx, ) .await; let mut unreviewed_changes = Patch::default(); if let Ok(diff_snapshot) = diff_snapshot { unreviewed_changes = cx .background_spawn({ let diff_snapshot = diff_snapshot.clone(); let buffer_snapshot = buffer_snapshot.clone(); let new_base_text_rope = new_base_text_rope.clone(); async move { let mut unreviewed_changes = Patch::default(); for hunk in diff_snapshot.hunks_intersecting_range( Anchor::MIN..Anchor::MAX, &buffer_snapshot, ) { let old_range = new_base_text_rope .offset_to_point(hunk.diff_base_byte_range.start) ..new_base_text_rope .offset_to_point(hunk.diff_base_byte_range.end); let new_range = hunk.range.start..hunk.range.end; unreviewed_changes.push(point_to_row_edit( Edit { old: old_range, new: new_range, }, &new_base_text_rope, &buffer_snapshot.as_rope(), )); } unreviewed_changes } }) .await; diff.update(cx, |diff, cx| { diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx) })?; } this.update(cx, |this, cx| { let tracked_buffer = this .tracked_buffers .get_mut(&buffer) .context("buffer not tracked")?; tracked_buffer.base_text = new_base_text_rope; tracked_buffer.snapshot = buffer_snapshot; tracked_buffer.unreviewed_changes = unreviewed_changes; cx.notify(); anyhow::Ok(()) })??; } Ok(()) } /// Track a buffer as read, so we can notify the model about user edits. pub fn buffer_read(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer, false, cx); } /// Track a buffer that was added as context, so we can notify the model about user edits. pub fn buffer_added_as_context(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer, false, cx); } /// Track a buffer as read, so we can notify the model about user edits. pub fn will_create_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer.clone(), true, cx); self.buffer_edited(buffer, cx) } /// Mark a buffer as edited, so we can refresh it in the context pub fn buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.edited_since_project_diagnostics_check = true; let tracked_buffer = self.track_buffer(buffer.clone(), false, cx); if let TrackedBufferStatus::Deleted = tracked_buffer.status { tracked_buffer.status = TrackedBufferStatus::Modified; } tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx); } pub fn will_delete_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let tracked_buffer = self.track_buffer(buffer.clone(), false, cx); match tracked_buffer.status { TrackedBufferStatus::Created => { self.tracked_buffers.remove(&buffer); cx.notify(); } TrackedBufferStatus::Modified => { buffer.update(cx, |buffer, cx| buffer.set_text("", cx)); tracked_buffer.status = TrackedBufferStatus::Deleted; tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx); } TrackedBufferStatus::Deleted => {} } cx.notify(); } pub fn keep_edits_in_range( &mut self, buffer: Entity<Buffer>, buffer_range: Range<impl language::ToPoint>, cx: &mut Context<Self>, ) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; match tracked_buffer.status { TrackedBufferStatus::Deleted => { self.tracked_buffers.remove(&buffer); cx.notify(); } _ => { let buffer = buffer.read(cx); let buffer_range = buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer); let mut delta = 0i32; tracked_buffer.unreviewed_changes.retain_mut(|edit| { edit.old.start = (edit.old.start as i32 + delta) as u32; edit.old.end = (edit.old.end as i32 + delta) as u32; if buffer_range.end.row < edit.new.start || buffer_range.start.row > edit.new.end { true } else { let old_range = tracked_buffer .base_text .point_to_offset(Point::new(edit.old.start, 0)) ..tracked_buffer.base_text.point_to_offset(cmp::min( Point::new(edit.old.end, 0), tracked_buffer.base_text.max_point(), )); let new_range = tracked_buffer .snapshot .point_to_offset(Point::new(edit.new.start, 0)) ..tracked_buffer.snapshot.point_to_offset(cmp::min( Point::new(edit.new.end, 0), tracked_buffer.snapshot.max_point(), )); tracked_buffer.base_text.replace( old_range, &tracked_buffer .snapshot .text_for_range(new_range) .collect::<String>(), ); delta += edit.new_len() as i32 - edit.old_len() as i32; false } }); tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } } } pub fn reject_edits_in_range( &mut self, buffer: Entity<Buffer>, buffer_range: Range<impl language::ToPoint>, cx: &mut Context<Self>, ) -> Task<Result<()>> { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return Task::ready(Ok(())); }; match tracked_buffer.status { TrackedBufferStatus::Created => { let delete = buffer .read(cx) .entry_id(cx) .and_then(|entry_id| { self.project .update(cx, |project, cx| project.delete_entry(entry_id, false, cx)) }) .unwrap_or(Task::ready(Ok(()))); self.tracked_buffers.remove(&buffer); cx.notify(); delete } TrackedBufferStatus::Deleted => { buffer.update(cx, |buffer, cx| { buffer.set_text(tracked_buffer.base_text.to_string(), cx) }); let save = self .project .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)); // Clear all tracked changes for this buffer and start over as if we just read it. self.tracked_buffers.remove(&buffer); self.track_buffer(buffer.clone(), false, cx); cx.notify(); save } TrackedBufferStatus::Modified => { buffer.update(cx, |buffer, cx| { let buffer_range = buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer); let mut edits_to_revert = Vec::new(); for edit in tracked_buffer.unreviewed_changes.edits() { if buffer_range.end.row < edit.new.start { break; } else if buffer_range.start.row > edit.new.end { continue; } let old_range = tracked_buffer .base_text .point_to_offset(Point::new(edit.old.start, 0)) ..tracked_buffer.base_text.point_to_offset(cmp::min( Point::new(edit.old.end, 0), tracked_buffer.base_text.max_point(), )); let old_text = tracked_buffer .base_text .chunks_in_range(old_range) .collect::<String>(); let new_range = tracked_buffer .snapshot .anchor_before(Point::new(edit.new.start, 0)) ..tracked_buffer.snapshot.anchor_after(cmp::min( Point::new(edit.new.end, 0), tracked_buffer.snapshot.max_point(), )); edits_to_revert.push((new_range, old_text)); } buffer.edit(edits_to_revert, None, cx); }); self.project .update(cx, |project, cx| project.save_buffer(buffer, cx)) } } } pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) { self.tracked_buffers .retain(|_buffer, tracked_buffer| match tracked_buffer.status { TrackedBufferStatus::Deleted => false, _ => { tracked_buffer.unreviewed_changes.clear(); tracked_buffer.base_text = tracked_buffer.snapshot.as_rope().clone(); tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); true } }); cx.notify(); } /// Returns the set of buffers that contain changes that haven't been reviewed by the user. pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> { self.tracked_buffers .iter() .filter(|(_, tracked)| tracked.has_changes(cx)) .map(|(buffer, tracked)| (buffer.clone(), tracked.diff.clone())) .collect() } /// Iterate over buffers changed since last read or edited by the model pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> { self.tracked_buffers .iter() .filter(|(buffer, tracked)| { let buffer = buffer.read(cx); tracked.version != buffer.version && buffer .file() .map_or(false, |file| file.disk_state() != DiskState::Deleted) }) .map(|(buffer, _)| buffer) }} fn apply_non_conflicting_edits( patch: &Patch<u32>, edits: Vec<Edit<u32>>, old_text: &mut Rope, new_text: &Rope,) { let mut old_edits = patch.edits().iter().cloned().peekable(); let mut new_edits = edits.into_iter().peekable(); let mut applied_delta = 0i32; let mut rebased_delta = 0i32; while let Some(mut new_edit) = new_edits.next() { let mut conflict = false; // Push all the old edits that are before this new edit or that intersect with it.Lines 1–500 of 1407. Load more
use anyhow::{Context as _, Result}; use buffer_diff::BufferDiff; use collections::BTreeMap; use futures::{StreamExt, channel::mpsc}; use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity}; use language::{Anchor, Buffer, BufferEvent, DiskState, Point}; use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle}; use std::{cmp, ops::Range, sync::Arc}; use text::{Edit, Patch, Rope}; use util::RangeExt; /// Tracks actions performed by tools in a thread pub struct ActionLog { /// Buffers that we want to notify the model about when they change. tracked_buffers: BTreeMap<Entity<Buffer>, TrackedBuffer>, /// Has the model edited a file since it last checked diagnostics? edited_since_project_diagnostics_check: bool, /// The project this action log is associated with project: Entity<Project>, } impl ActionLog { /// Creates a new, empty action log associated with the given project. pub fn new(project: Entity<Project>) -> Self { Self { tracked_buffers: BTreeMap::default(), edited_since_project_diagnostics_check: false, project, } } /// Notifies a diagnostics check pub fn checked_project_diagnostics(&mut self) { self.edited_since_project_diagnostics_check = false; } /// Returns true if any files have been edited since the last project diagnostics check pub fn has_edited_files_since_project_diagnostics_check(&self) -> bool { self.edited_since_project_diagnostics_check } fn track_buffer( &mut self, buffer: Entity<Buffer>, created: bool, cx: &mut Context<Self>, ) -> &mut TrackedBuffer { let tracked_buffer = self .tracked_buffers .entry(buffer.clone()) .or_insert_with(|| { let open_lsp_handle = self.project.update(cx, |project, cx| { project.register_buffer_with_language_servers(&buffer, cx) }); let text_snapshot = buffer.read(cx).text_snapshot(); let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx)); let (diff_update_tx, diff_update_rx) = mpsc::unbounded(); let base_text; let status; let unreviewed_changes; if created { base_text = Rope::default(); status = TrackedBufferStatus::Created; unreviewed_changes = Patch::new(vec![Edit { old: 0..1, new: 0..text_snapshot.max_point().row + 1, }]) } else { base_text = buffer.read(cx).as_rope().clone(); status = TrackedBufferStatus::Modified; unreviewed_changes = Patch::default(); } TrackedBuffer { buffer: buffer.clone(), base_text, unreviewed_changes, snapshot: text_snapshot.clone(), status, version: buffer.read(cx).version(), diff, diff_update: diff_update_tx, _open_lsp_handle: open_lsp_handle, _maintain_diff: cx.spawn({ let buffer = buffer.clone(); async move |this, cx| { Self::maintain_diff(this, buffer, diff_update_rx, cx) .await .ok(); } }), _subscription: cx.subscribe(&buffer, Self::handle_buffer_event), } }); tracked_buffer.version = buffer.read(cx).version(); tracked_buffer } fn handle_buffer_event( &mut self, buffer: Entity<Buffer>, event: &BufferEvent, cx: &mut Context<Self>, ) { match event { BufferEvent::Edited { .. } => self.handle_buffer_edited(buffer, cx), BufferEvent::FileHandleChanged => { self.handle_buffer_file_changed(buffer, cx); } _ => {} }; } fn handle_buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } fn handle_buffer_file_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; match tracked_buffer.status { TrackedBufferStatus::Created | TrackedBufferStatus::Modified => { if buffer .read(cx) .file() .map_or(false, |file| file.disk_state() == DiskState::Deleted) { // If the buffer had been edited by a tool, but it got // deleted externally, we want to stop tracking it. self.tracked_buffers.remove(&buffer); } cx.notify(); } TrackedBufferStatus::Deleted => { if buffer .read(cx) .file() .map_or(false, |file| file.disk_state() != DiskState::Deleted) { // If the buffer had been deleted by a tool, but it got // resurrected externally, we want to clear the changes we // were tracking and reset the buffer's state. self.tracked_buffers.remove(&buffer); self.track_buffer(buffer, false, cx); } cx.notify(); } } } async fn maintain_diff( this: WeakEntity<Self>, buffer: Entity<Buffer>, mut diff_update: mpsc::UnboundedReceiver<(ChangeAuthor, text::BufferSnapshot)>, cx: &mut AsyncApp, ) -> Result<()> { while let Some((author, buffer_snapshot)) = diff_update.next().await { let (rebase, diff, language, language_registry) = this.read_with(cx, |this, cx| { let tracked_buffer = this .tracked_buffers .get(&buffer) .context("buffer not tracked")?; let rebase = cx.background_spawn({ let mut base_text = tracked_buffer.base_text.clone(); let old_snapshot = tracked_buffer.snapshot.clone(); let new_snapshot = buffer_snapshot.clone(); let unreviewed_changes = tracked_buffer.unreviewed_changes.clone(); async move { let edits = diff_snapshots(&old_snapshot, &new_snapshot); if let ChangeAuthor::User = author { apply_non_conflicting_edits( &unreviewed_changes, edits, &mut base_text, new_snapshot.as_rope(), ); } (Arc::new(base_text.to_string()), base_text) } }); anyhow::Ok(( rebase, tracked_buffer.diff.clone(), tracked_buffer.buffer.read(cx).language().cloned(), tracked_buffer.buffer.read(cx).language_registry(), )) })??; let (new_base_text, new_base_text_rope) = rebase.await; let diff_snapshot = BufferDiff::update_diff( diff.clone(), buffer_snapshot.clone(), Some(new_base_text), true, false, language, language_registry, cx, ) .await; let mut unreviewed_changes = Patch::default(); if let Ok(diff_snapshot) = diff_snapshot { unreviewed_changes = cx .background_spawn({ let diff_snapshot = diff_snapshot.clone(); let buffer_snapshot = buffer_snapshot.clone(); let new_base_text_rope = new_base_text_rope.clone(); async move { let mut unreviewed_changes = Patch::default(); for hunk in diff_snapshot.hunks_intersecting_range( Anchor::MIN..Anchor::MAX, &buffer_snapshot, ) { let old_range = new_base_text_rope .offset_to_point(hunk.diff_base_byte_range.start) ..new_base_text_rope .offset_to_point(hunk.diff_base_byte_range.end); let new_range = hunk.range.start..hunk.range.end; unreviewed_changes.push(point_to_row_edit( Edit { old: old_range, new: new_range, }, &new_base_text_rope, &buffer_snapshot.as_rope(), )); } unreviewed_changes } }) .await; diff.update(cx, |diff, cx| { diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx) })?; } this.update(cx, |this, cx| { let tracked_buffer = this .tracked_buffers .get_mut(&buffer) .context("buffer not tracked")?; tracked_buffer.base_text = new_base_text_rope; tracked_buffer.snapshot = buffer_snapshot; tracked_buffer.unreviewed_changes = unreviewed_changes; cx.notify(); anyhow::Ok(()) })??; } Ok(()) } /// Track a buffer as read, so we can notify the model about user edits. pub fn buffer_read(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer, false, cx); } /// Track a buffer that was added as context, so we can notify the model about user edits. pub fn buffer_added_as_context(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer, false, cx); } /// Track a buffer as read, so we can notify the model about user edits. pub fn will_create_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.track_buffer(buffer.clone(), true, cx); self.buffer_edited(buffer, cx) } /// Mark a buffer as edited, so we can refresh it in the context pub fn buffer_edited(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { self.edited_since_project_diagnostics_check = true; let tracked_buffer = self.track_buffer(buffer.clone(), false, cx); if let TrackedBufferStatus::Deleted = tracked_buffer.status { tracked_buffer.status = TrackedBufferStatus::Modified; } tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx); } pub fn will_delete_buffer(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) { let tracked_buffer = self.track_buffer(buffer.clone(), false, cx); match tracked_buffer.status { TrackedBufferStatus::Created => { self.tracked_buffers.remove(&buffer); cx.notify(); } TrackedBufferStatus::Modified => { buffer.update(cx, |buffer, cx| buffer.set_text("", cx)); tracked_buffer.status = TrackedBufferStatus::Deleted; tracked_buffer.schedule_diff_update(ChangeAuthor::Agent, cx); } TrackedBufferStatus::Deleted => {} } cx.notify(); } pub fn keep_edits_in_range( &mut self, buffer: Entity<Buffer>, buffer_range: Range<impl language::ToPoint>, cx: &mut Context<Self>, ) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; match tracked_buffer.status { TrackedBufferStatus::Deleted => { self.tracked_buffers.remove(&buffer); cx.notify(); } _ => { let buffer = buffer.read(cx); let buffer_range = buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer); let mut delta = 0i32; tracked_buffer.unreviewed_changes.retain_mut(|edit| { edit.old.start = (edit.old.start as i32 + delta) as u32; edit.old.end = (edit.old.end as i32 + delta) as u32; if buffer_range.end.row < edit.new.start || buffer_range.start.row > edit.new.end { true } else { let old_range = tracked_buffer .base_text .point_to_offset(Point::new(edit.old.start, 0)) ..tracked_buffer.base_text.point_to_offset(cmp::min( Point::new(edit.old.end, 0), tracked_buffer.base_text.max_point(), )); let new_range = tracked_buffer .snapshot .point_to_offset(Point::new(edit.new.start, 0)) ..tracked_buffer.snapshot.point_to_offset(cmp::min( Point::new(edit.new.end, 0), tracked_buffer.snapshot.max_point(), )); tracked_buffer.base_text.replace( old_range, &tracked_buffer .snapshot .text_for_range(new_range) .collect::<String>(), ); delta += edit.new_len() as i32 - edit.old_len() as i32; false } }); tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } } } pub fn reject_edits_in_range( &mut self, buffer: Entity<Buffer>, buffer_range: Range<impl language::ToPoint>, cx: &mut Context<Self>, ) -> Task<Result<()>> { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return Task::ready(Ok(())); }; match tracked_buffer.status { TrackedBufferStatus::Created => { let delete = buffer .read(cx) .entry_id(cx) .and_then(|entry_id| { self.project .update(cx, |project, cx| project.delete_entry(entry_id, false, cx)) }) .unwrap_or(Task::ready(Ok(()))); self.tracked_buffers.remove(&buffer); cx.notify(); delete } TrackedBufferStatus::Deleted => { buffer.update(cx, |buffer, cx| { buffer.set_text(tracked_buffer.base_text.to_string(), cx) }); let save = self .project .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)); // Clear all tracked changes for this buffer and start over as if we just read it. self.tracked_buffers.remove(&buffer); self.track_buffer(buffer.clone(), false, cx); cx.notify(); save } TrackedBufferStatus::Modified => { buffer.update(cx, |buffer, cx| { let buffer_range = buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer); let mut edits_to_revert = Vec::new(); for edit in tracked_buffer.unreviewed_changes.edits() { if buffer_range.end.row < edit.new.start { break; } else if buffer_range.start.row > edit.new.end { continue; } let old_range = tracked_buffer .base_text .point_to_offset(Point::new(edit.old.start, 0)) ..tracked_buffer.base_text.point_to_offset(cmp::min( Point::new(edit.old.end, 0), tracked_buffer.base_text.max_point(), )); let old_text = tracked_buffer .base_text .chunks_in_range(old_range) .collect::<String>(); let new_range = tracked_buffer .snapshot .anchor_before(Point::new(edit.new.start, 0)) ..tracked_buffer.snapshot.anchor_after(cmp::min( Point::new(edit.new.end, 0), tracked_buffer.snapshot.max_point(), )); edits_to_revert.push((new_range, old_text)); } buffer.edit(edits_to_revert, None, cx); }); self.project .update(cx, |project, cx| project.save_buffer(buffer, cx)) } } } pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) { self.tracked_buffers .retain(|_buffer, tracked_buffer| match tracked_buffer.status { TrackedBufferStatus::Deleted => false, _ => { tracked_buffer.unreviewed_changes.clear(); tracked_buffer.base_text = tracked_buffer.snapshot.as_rope().clone(); tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); true } }); cx.notify(); } /// Returns the set of buffers that contain changes that haven't been reviewed by the user. pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> { self.tracked_buffers .iter() .filter(|(_, tracked)| tracked.has_changes(cx)) .map(|(buffer, tracked)| (buffer.clone(), tracked.diff.clone())) .collect() } /// Iterate over buffers changed since last read or edited by the model pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> { self.tracked_buffers .iter() .filter(|(buffer, tracked)| { let buffer = buffer.read(cx); tracked.version != buffer.version && buffer .file() .map_or(false, |file| file.disk_state() != DiskState::Deleted) }) .map(|(buffer, _)| buffer) } } fn apply_non_conflicting_edits( patch: &Patch<u32>, edits: Vec<Edit<u32>>, old_text: &mut Rope, new_text: &Rope, ) { let mut old_edits = patch.edits().iter().cloned().peekable(); let mut new_edits = edits.into_iter().peekable(); let mut applied_delta = 0i32; let mut rebased_delta = 0i32; while let Some(mut new_edit) = new_edits.next() { let mut conflict = false; // Push all the old edits that are before this new edit or that intersect with it.