use std::{ any::{Any, TypeId}, cmp::Ordering, collections::HashSet, ops::Range, time::Duration,}; use anyhow::Context as _;use collections::{BTreeMap, HashMap};use feature_flags::FeatureFlagAppExt;use git::{ diff::{BufferDiff, DiffHunk}, repository::GitFileStatus,};use gpui::{ actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, Model, Render, Subscription, Task, View, WeakView,};use language::{Buffer, BufferRow};use multi_buffer::{ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer};use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};use text::{OffsetRangeExt, ToPoint};use theme::ActiveTheme;use ui::{ div, h_flex, Color, Context, FluentBuilder, Icon, IconName, IntoElement, Label, LabelCommon, ParentElement, SharedString, Styled, ViewContext, VisualContext, WindowContext,};use util::{paths::compare_paths, ResultExt};use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, ItemNavHistory, ToolbarItemLocation, Workspace,}; use crate::{Editor, EditorEvent, DEFAULT_MULTIBUFFER_CONTEXT}; actions!(project_diff, [Deploy]); pub fn init(cx: &mut AppContext) { cx.observe_new_views(ProjectDiffEditor::register).detach();} const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); struct ProjectDiffEditor { buffer_changes: BTreeMap<WorktreeId, HashMap<ProjectEntryId, Changes>>, entry_order: HashMap<WorktreeId, Vec<(ProjectPath, ProjectEntryId)>>, excerpts: Model<MultiBuffer>, editor: View<Editor>, project: Model<Project>, workspace: WeakView<Workspace>, focus_handle: FocusHandle, worktree_rescans: HashMap<WorktreeId, Task<()>>, _subscriptions: Vec<Subscription>,} #[derive(Debug)]struct Changes { _status: GitFileStatus, buffer: Model<Buffer>, hunks: Vec<DiffHunk>,} impl ProjectDiffEditor { fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) { workspace.register_action(Self::deploy); } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) { if !cx.is_staff() { return; } if let Some(existing) = workspace.item_of_type::<Self>(cx) { workspace.activate_item(&existing, true, true, cx); } else { let workspace_handle = cx.view().downgrade(); let project_diff = cx.new_view(|cx| Self::new(workspace.project().clone(), workspace_handle, cx)); workspace.add_item_to_active_pane(Box::new(project_diff), None, true, cx); } } fn new( project: Model<Project>, workspace: WeakView<Workspace>, cx: &mut ViewContext<Self>, ) -> Self { // TODO diff change subscriptions. For that, needed: // * `-20/+50` stats retrieval: some background process that reacts on file changes let focus_handle = cx.focus_handle(); let changed_entries_subscription = cx.subscribe(&project, |project_diff_editor, _, e, cx| { let mut worktree_to_rescan = None; match e { project::Event::WorktreeAdded(id) => { worktree_to_rescan = Some(*id); // project_diff_editor // .buffer_changes // .insert(*id, HashMap::default()); } project::Event::WorktreeRemoved(id) => { project_diff_editor.buffer_changes.remove(id); } project::Event::WorktreeUpdatedEntries(id, _updated_entries) => { // TODO cannot invalidate buffer entries without invalidating the corresponding excerpts and order entries. worktree_to_rescan = Some(*id); // let entry_changes = // project_diff_editor.buffer_changes.entry(*id).or_default(); // for (_, entry_id, change) in updated_entries.iter() { // let changes = entry_changes.entry(*entry_id); // match change { // project::PathChange::Removed => { // if let hash_map::Entry::Occupied(entry) = changes { // entry.remove(); // } // } // // TODO understand the invalidation case better: now, we do that but still rescan the entire worktree // // What if we already have the buffer loaded inside the diff multi buffer and it was edited there? We should not do anything. // _ => match changes { // hash_map::Entry::Occupied(mut o) => o.get_mut().invalidate(), // hash_map::Entry::Vacant(v) => { // v.insert(None); // } // }, // } // } } project::Event::WorktreeUpdatedGitRepositories(id) => { worktree_to_rescan = Some(*id); // project_diff_editor.buffer_changes.clear(); } project::Event::DeletedEntry(id, _entry_id) => { worktree_to_rescan = Some(*id); // if let Some(entries) = project_diff_editor.buffer_changes.get_mut(id) { // entries.remove(entry_id); // } } project::Event::Closed => { project_diff_editor.buffer_changes.clear(); } _ => {} } if let Some(worktree_to_rescan) = worktree_to_rescan { project_diff_editor.schedule_worktree_rescan(worktree_to_rescan, cx); } }); let excerpts = cx.new_model(|cx| MultiBuffer::new(project.read(cx).capability())); let editor = cx.new_view(|cx| { let mut diff_display_editor = Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx); diff_display_editor.set_expand_all_diff_hunks(); diff_display_editor }); let mut new_self = Self { project, workspace, buffer_changes: BTreeMap::default(), entry_order: HashMap::default(), worktree_rescans: HashMap::default(), focus_handle, editor, excerpts, _subscriptions: vec![changed_entries_subscription], }; new_self.schedule_rescan_all(cx); new_self } fn schedule_rescan_all(&mut self, cx: &mut ViewContext<Self>) { let mut current_worktrees = HashSet::<WorktreeId>::default(); for worktree in self.project.read(cx).worktrees(cx).collect::<Vec<_>>() { let worktree_id = worktree.read(cx).id(); current_worktrees.insert(worktree_id); self.schedule_worktree_rescan(worktree_id, cx); } self.worktree_rescans .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); self.buffer_changes .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); self.entry_order .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); } fn schedule_worktree_rescan(&mut self, id: WorktreeId, cx: &mut ViewContext<Self>) { let project = self.project.clone(); self.worktree_rescans.insert( id, cx.spawn(|project_diff_editor, mut cx| async move { cx.background_executor().timer(UPDATE_DEBOUNCE).await; let open_tasks = project .update(&mut cx, |project, cx| { let worktree = project.worktree_for_id(id, cx)?; let applicable_entries = worktree .read(cx) .entries(false, 0) .filter(|entry| !entry.is_external) .filter(|entry| entry.is_file()) .filter_map(|entry| Some((entry.git_status?, entry))) .filter_map(|(git_status, entry)| { Some((git_status, entry.id, project.path_for_entry(entry.id, cx)?)) }) .collect::<Vec<_>>(); Some( applicable_entries .into_iter() .map(|(status, entry_id, entry_path)| { let open_task = project.open_path(entry_path.clone(), cx); (status, entry_id, entry_path, open_task) }) .collect::<Vec<_>>(), ) }) .ok() .flatten() .unwrap_or_default(); let Some((buffers, mut new_entries, change_sets)) = cx .spawn(|mut cx| async move { let mut new_entries = Vec::new(); let mut buffers = HashMap::< ProjectEntryId, ( GitFileStatus, text::BufferSnapshot, Model<Buffer>, BufferDiff, ), >::default(); let mut change_sets = Vec::new(); for (status, entry_id, entry_path, open_task) in open_tasks { let (_, opened_model) = open_task.await.with_context(|| { format!("loading buffer {:?} for git diff", entry_path.path) })?; let buffer = match opened_model.downcast::<Buffer>() { Ok(buffer) => buffer, Err(_model) => anyhow::bail!( "Could not load {:?} as a buffer for git diff", entry_path.path ), }; let change_set = project .update(&mut cx, |project, cx| { project.open_unstaged_changes(buffer.clone(), cx) })? .await?; cx.update(|cx| { buffers.insert( entry_id, ( status, buffer.read(cx).text_snapshot(), buffer, change_set.read(cx).diff_to_buffer.clone(), ), ); })?; change_sets.push(change_set); new_entries.push((entry_path, entry_id)); } Ok((buffers, new_entries, change_sets)) }) .await .log_err() else { return; }; let (new_changes, new_entry_order) = cx .background_executor() .spawn(async move { let mut new_changes = HashMap::<ProjectEntryId, Changes>::default(); for (entry_id, (status, buffer_snapshot, buffer, buffer_diff)) in buffers { new_changes.insert( entry_id, Changes { _status: status, buffer, hunks: buffer_diff .hunks_in_row_range(0..BufferRow::MAX, &buffer_snapshot) .collect::<Vec<_>>(), }, ); } new_entries.sort_by(|(project_path_a, _), (project_path_b, _)| { compare_paths( (project_path_a.path.as_ref(), true), (project_path_b.path.as_ref(), true), ) }); (new_changes, new_entries) }) .await; project_diff_editor .update(&mut cx, |project_diff_editor, cx| { project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx); for change_set in change_sets { project_diff_editor.editor.update(cx, |editor, cx| { editor.diff_map.add_change_set(change_set, cx) }); } }) .ok(); }), ); } fn update_excerpts( &mut self, worktree_id: WorktreeId, new_changes: HashMap<ProjectEntryId, Changes>, new_entry_order: Vec<(ProjectPath, ProjectEntryId)>, cx: &mut ViewContext<ProjectDiffEditor>, ) { if let Some(current_order) = self.entry_order.get(&worktree_id) { let current_entries = self.buffer_changes.entry(worktree_id).or_default(); let mut new_order_entries = new_entry_order.iter().fuse().peekable(); let mut excerpts_to_remove = Vec::new(); let mut new_excerpt_hunks = BTreeMap::< ExcerptId, Vec<(ProjectPath, Model<Buffer>, Vec<Range<text::Anchor>>)>, >::new(); let mut excerpt_to_expand = HashMap::<(u32, ExpandExcerptDirection), Vec<ExcerptId>>::default(); let mut latest_excerpt_id = ExcerptId::min(); for (current_path, current_entry_id) in current_order { let current_changes = match current_entries.get(current_entry_id) { Some(current_changes) => { if current_changes.hunks.is_empty() { continue; } current_changes } None => continue, }; let buffer_excerpts = self .excerpts .read(cx) .excerpts_for_buffer(¤t_changes.buffer, cx); let last_current_excerpt_id = buffer_excerpts.last().map(|(excerpt_id, _)| *excerpt_id); let mut current_excerpts = buffer_excerpts.into_iter().fuse().peekable(); loop { match new_order_entries.peek() { Some((new_path, new_entry)) => { match compare_paths( (current_path.path.as_ref(), true), (new_path.path.as_ref(), true), ) { Ordering::Less => { excerpts_to_remove .extend(current_excerpts.map(|(excerpt_id, _)| excerpt_id)); break; } Ordering::Greater => { if let Some(new_changes) = new_changes.get(new_entry) { if !new_changes.hunks.is_empty() { let hunks = new_excerpt_hunks .entry(latest_excerpt_id) .or_default(); match hunks.binary_search_by(|(probe, ..)| { compare_paths( (new_path.path.as_ref(), true), (probe.path.as_ref(), true), ) }) { Ok(i) => hunks[i].2.extend( new_changes .hunks .iter() .map(|hunk| hunk.buffer_range.clone()), ), Err(i) => hunks.insert( i, ( new_path.clone(), new_changes.buffer.clone(), new_changes .hunks .iter() .map(|hunk| hunk.buffer_range.clone()) .collect(), ), ), } } }; let _ = new_order_entries.next(); } Ordering::Equal => { match new_changes.get(new_entry) { Some(new_changes) => { let buffer_snapshot = new_changes.buffer.read(cx).snapshot(); let mut current_hunks = current_changes.hunks.iter().fuse().peekable(); let mut new_hunks_unchanged = Vec::with_capacity(new_changes.hunks.len()); let mut new_hunks_with_updates = Vec::with_capacity(new_changes.hunks.len()); 'new_changes: for new_hunk in &new_changes.hunks { loop { match current_hunks.peek() { Some(current_hunk) => { match ( current_hunk .buffer_range .start .cmp( &new_hunk .buffer_range .start, &buffer_snapshot, ), current_hunk.buffer_range.end.cmp( &new_hunk.buffer_range.end, &buffer_snapshot, ), ) { ( Ordering::Equal, Ordering::Equal, ) => { new_hunks_unchanged .push(new_hunk); let _ = current_hunks.next(); continue 'new_changes; } (Ordering::Equal, _) | (_, Ordering::Equal) => { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } ( Ordering::Less, Ordering::Greater, ) | ( Ordering::Greater, Ordering::Less, ) => { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } ( Ordering::Less, Ordering::Less, ) => { if current_hunk .buffer_range .start .cmp( &new_hunk .buffer_range .end, &buffer_snapshot, ) .is_le() { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } else { let _ = current_hunks.next(); } } ( Ordering::Greater, Ordering::Greater, ) => { if current_hunk .buffer_range .end .cmp( &new_hunk .buffer_range .start, &buffer_snapshot, ) .is_ge() { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } else {Lines 1–500 of 1246. Load more
use std::{ any::{Any, TypeId}, cmp::Ordering, collections::HashSet, ops::Range, time::Duration, }; use anyhow::Context as _; use collections::{BTreeMap, HashMap}; use feature_flags::FeatureFlagAppExt; use git::{ diff::{BufferDiff, DiffHunk}, repository::GitFileStatus, }; use gpui::{ actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, Model, Render, Subscription, Task, View, WeakView, }; use language::{Buffer, BufferRow}; use multi_buffer::{ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer}; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; use text::{OffsetRangeExt, ToPoint}; use theme::ActiveTheme; use ui::{ div, h_flex, Color, Context, FluentBuilder, Icon, IconName, IntoElement, Label, LabelCommon, ParentElement, SharedString, Styled, ViewContext, VisualContext, WindowContext, }; use util::{paths::compare_paths, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, ItemNavHistory, ToolbarItemLocation, Workspace, }; use crate::{Editor, EditorEvent, DEFAULT_MULTIBUFFER_CONTEXT}; actions!(project_diff, [Deploy]); pub fn init(cx: &mut AppContext) { cx.observe_new_views(ProjectDiffEditor::register).detach(); } const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); struct ProjectDiffEditor { buffer_changes: BTreeMap<WorktreeId, HashMap<ProjectEntryId, Changes>>, entry_order: HashMap<WorktreeId, Vec<(ProjectPath, ProjectEntryId)>>, excerpts: Model<MultiBuffer>, editor: View<Editor>, project: Model<Project>, workspace: WeakView<Workspace>, focus_handle: FocusHandle, worktree_rescans: HashMap<WorktreeId, Task<()>>, _subscriptions: Vec<Subscription>, } #[derive(Debug)] struct Changes { _status: GitFileStatus, buffer: Model<Buffer>, hunks: Vec<DiffHunk>, } impl ProjectDiffEditor { fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) { workspace.register_action(Self::deploy); } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) { if !cx.is_staff() { return; } if let Some(existing) = workspace.item_of_type::<Self>(cx) { workspace.activate_item(&existing, true, true, cx); } else { let workspace_handle = cx.view().downgrade(); let project_diff = cx.new_view(|cx| Self::new(workspace.project().clone(), workspace_handle, cx)); workspace.add_item_to_active_pane(Box::new(project_diff), None, true, cx); } } fn new( project: Model<Project>, workspace: WeakView<Workspace>, cx: &mut ViewContext<Self>, ) -> Self { // TODO diff change subscriptions. For that, needed: // * `-20/+50` stats retrieval: some background process that reacts on file changes let focus_handle = cx.focus_handle(); let changed_entries_subscription = cx.subscribe(&project, |project_diff_editor, _, e, cx| { let mut worktree_to_rescan = None; match e { project::Event::WorktreeAdded(id) => { worktree_to_rescan = Some(*id); // project_diff_editor // .buffer_changes // .insert(*id, HashMap::default()); } project::Event::WorktreeRemoved(id) => { project_diff_editor.buffer_changes.remove(id); } project::Event::WorktreeUpdatedEntries(id, _updated_entries) => { // TODO cannot invalidate buffer entries without invalidating the corresponding excerpts and order entries. worktree_to_rescan = Some(*id); // let entry_changes = // project_diff_editor.buffer_changes.entry(*id).or_default(); // for (_, entry_id, change) in updated_entries.iter() { // let changes = entry_changes.entry(*entry_id); // match change { // project::PathChange::Removed => { // if let hash_map::Entry::Occupied(entry) = changes { // entry.remove(); // } // } // // TODO understand the invalidation case better: now, we do that but still rescan the entire worktree // // What if we already have the buffer loaded inside the diff multi buffer and it was edited there? We should not do anything. // _ => match changes { // hash_map::Entry::Occupied(mut o) => o.get_mut().invalidate(), // hash_map::Entry::Vacant(v) => { // v.insert(None); // } // }, // } // } } project::Event::WorktreeUpdatedGitRepositories(id) => { worktree_to_rescan = Some(*id); // project_diff_editor.buffer_changes.clear(); } project::Event::DeletedEntry(id, _entry_id) => { worktree_to_rescan = Some(*id); // if let Some(entries) = project_diff_editor.buffer_changes.get_mut(id) { // entries.remove(entry_id); // } } project::Event::Closed => { project_diff_editor.buffer_changes.clear(); } _ => {} } if let Some(worktree_to_rescan) = worktree_to_rescan { project_diff_editor.schedule_worktree_rescan(worktree_to_rescan, cx); } }); let excerpts = cx.new_model(|cx| MultiBuffer::new(project.read(cx).capability())); let editor = cx.new_view(|cx| { let mut diff_display_editor = Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx); diff_display_editor.set_expand_all_diff_hunks(); diff_display_editor }); let mut new_self = Self { project, workspace, buffer_changes: BTreeMap::default(), entry_order: HashMap::default(), worktree_rescans: HashMap::default(), focus_handle, editor, excerpts, _subscriptions: vec![changed_entries_subscription], }; new_self.schedule_rescan_all(cx); new_self } fn schedule_rescan_all(&mut self, cx: &mut ViewContext<Self>) { let mut current_worktrees = HashSet::<WorktreeId>::default(); for worktree in self.project.read(cx).worktrees(cx).collect::<Vec<_>>() { let worktree_id = worktree.read(cx).id(); current_worktrees.insert(worktree_id); self.schedule_worktree_rescan(worktree_id, cx); } self.worktree_rescans .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); self.buffer_changes .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); self.entry_order .retain(|worktree_id, _| current_worktrees.contains(worktree_id)); } fn schedule_worktree_rescan(&mut self, id: WorktreeId, cx: &mut ViewContext<Self>) { let project = self.project.clone(); self.worktree_rescans.insert( id, cx.spawn(|project_diff_editor, mut cx| async move { cx.background_executor().timer(UPDATE_DEBOUNCE).await; let open_tasks = project .update(&mut cx, |project, cx| { let worktree = project.worktree_for_id(id, cx)?; let applicable_entries = worktree .read(cx) .entries(false, 0) .filter(|entry| !entry.is_external) .filter(|entry| entry.is_file()) .filter_map(|entry| Some((entry.git_status?, entry))) .filter_map(|(git_status, entry)| { Some((git_status, entry.id, project.path_for_entry(entry.id, cx)?)) }) .collect::<Vec<_>>(); Some( applicable_entries .into_iter() .map(|(status, entry_id, entry_path)| { let open_task = project.open_path(entry_path.clone(), cx); (status, entry_id, entry_path, open_task) }) .collect::<Vec<_>>(), ) }) .ok() .flatten() .unwrap_or_default(); let Some((buffers, mut new_entries, change_sets)) = cx .spawn(|mut cx| async move { let mut new_entries = Vec::new(); let mut buffers = HashMap::< ProjectEntryId, ( GitFileStatus, text::BufferSnapshot, Model<Buffer>, BufferDiff, ), >::default(); let mut change_sets = Vec::new(); for (status, entry_id, entry_path, open_task) in open_tasks { let (_, opened_model) = open_task.await.with_context(|| { format!("loading buffer {:?} for git diff", entry_path.path) })?; let buffer = match opened_model.downcast::<Buffer>() { Ok(buffer) => buffer, Err(_model) => anyhow::bail!( "Could not load {:?} as a buffer for git diff", entry_path.path ), }; let change_set = project .update(&mut cx, |project, cx| { project.open_unstaged_changes(buffer.clone(), cx) })? .await?; cx.update(|cx| { buffers.insert( entry_id, ( status, buffer.read(cx).text_snapshot(), buffer, change_set.read(cx).diff_to_buffer.clone(), ), ); })?; change_sets.push(change_set); new_entries.push((entry_path, entry_id)); } Ok((buffers, new_entries, change_sets)) }) .await .log_err() else { return; }; let (new_changes, new_entry_order) = cx .background_executor() .spawn(async move { let mut new_changes = HashMap::<ProjectEntryId, Changes>::default(); for (entry_id, (status, buffer_snapshot, buffer, buffer_diff)) in buffers { new_changes.insert( entry_id, Changes { _status: status, buffer, hunks: buffer_diff .hunks_in_row_range(0..BufferRow::MAX, &buffer_snapshot) .collect::<Vec<_>>(), }, ); } new_entries.sort_by(|(project_path_a, _), (project_path_b, _)| { compare_paths( (project_path_a.path.as_ref(), true), (project_path_b.path.as_ref(), true), ) }); (new_changes, new_entries) }) .await; project_diff_editor .update(&mut cx, |project_diff_editor, cx| { project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx); for change_set in change_sets { project_diff_editor.editor.update(cx, |editor, cx| { editor.diff_map.add_change_set(change_set, cx) }); } }) .ok(); }), ); } fn update_excerpts( &mut self, worktree_id: WorktreeId, new_changes: HashMap<ProjectEntryId, Changes>, new_entry_order: Vec<(ProjectPath, ProjectEntryId)>, cx: &mut ViewContext<ProjectDiffEditor>, ) { if let Some(current_order) = self.entry_order.get(&worktree_id) { let current_entries = self.buffer_changes.entry(worktree_id).or_default(); let mut new_order_entries = new_entry_order.iter().fuse().peekable(); let mut excerpts_to_remove = Vec::new(); let mut new_excerpt_hunks = BTreeMap::< ExcerptId, Vec<(ProjectPath, Model<Buffer>, Vec<Range<text::Anchor>>)>, >::new(); let mut excerpt_to_expand = HashMap::<(u32, ExpandExcerptDirection), Vec<ExcerptId>>::default(); let mut latest_excerpt_id = ExcerptId::min(); for (current_path, current_entry_id) in current_order { let current_changes = match current_entries.get(current_entry_id) { Some(current_changes) => { if current_changes.hunks.is_empty() { continue; } current_changes } None => continue, }; let buffer_excerpts = self .excerpts .read(cx) .excerpts_for_buffer(¤t_changes.buffer, cx); let last_current_excerpt_id = buffer_excerpts.last().map(|(excerpt_id, _)| *excerpt_id); let mut current_excerpts = buffer_excerpts.into_iter().fuse().peekable(); loop { match new_order_entries.peek() { Some((new_path, new_entry)) => { match compare_paths( (current_path.path.as_ref(), true), (new_path.path.as_ref(), true), ) { Ordering::Less => { excerpts_to_remove .extend(current_excerpts.map(|(excerpt_id, _)| excerpt_id)); break; } Ordering::Greater => { if let Some(new_changes) = new_changes.get(new_entry) { if !new_changes.hunks.is_empty() { let hunks = new_excerpt_hunks .entry(latest_excerpt_id) .or_default(); match hunks.binary_search_by(|(probe, ..)| { compare_paths( (new_path.path.as_ref(), true), (probe.path.as_ref(), true), ) }) { Ok(i) => hunks[i].2.extend( new_changes .hunks .iter() .map(|hunk| hunk.buffer_range.clone()), ), Err(i) => hunks.insert( i, ( new_path.clone(), new_changes.buffer.clone(), new_changes .hunks .iter() .map(|hunk| hunk.buffer_range.clone()) .collect(), ), ), } } }; let _ = new_order_entries.next(); } Ordering::Equal => { match new_changes.get(new_entry) { Some(new_changes) => { let buffer_snapshot = new_changes.buffer.read(cx).snapshot(); let mut current_hunks = current_changes.hunks.iter().fuse().peekable(); let mut new_hunks_unchanged = Vec::with_capacity(new_changes.hunks.len()); let mut new_hunks_with_updates = Vec::with_capacity(new_changes.hunks.len()); 'new_changes: for new_hunk in &new_changes.hunks { loop { match current_hunks.peek() { Some(current_hunk) => { match ( current_hunk .buffer_range .start .cmp( &new_hunk .buffer_range .start, &buffer_snapshot, ), current_hunk.buffer_range.end.cmp( &new_hunk.buffer_range.end, &buffer_snapshot, ), ) { ( Ordering::Equal, Ordering::Equal, ) => { new_hunks_unchanged .push(new_hunk); let _ = current_hunks.next(); continue 'new_changes; } (Ordering::Equal, _) | (_, Ordering::Equal) => { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } ( Ordering::Less, Ordering::Greater, ) | ( Ordering::Greater, Ordering::Less, ) => { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } ( Ordering::Less, Ordering::Less, ) => { if current_hunk .buffer_range .start .cmp( &new_hunk .buffer_range .end, &buffer_snapshot, ) .is_le() { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } else { let _ = current_hunks.next(); } } ( Ordering::Greater, Ordering::Greater, ) => { if current_hunk .buffer_range .end .cmp( &new_hunk .buffer_range .start, &buffer_snapshot, ) .is_ge() { new_hunks_with_updates .push(new_hunk); continue 'new_changes; } else {