pub(super) mod blame; use super::*;use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind}; #[derive(Clone)]pub struct ResolvedDiffHunk { pub buffer_range: Range<text::Anchor>, pub diff_base_byte_range: Range<usize>, pub status: DiffHunkStatus,} #[derive(Clone)]pub struct ResolvedDiffHunks { pub diff: Entity<BufferDiff>, pub buffer_id: BufferId, pub buffer: Option<Entity<Buffer>>, pub hunks: Vec<ResolvedDiffHunk>,} pub trait DiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ); fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ); fn restore( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { if hunks.is_empty() || editor.read_only(cx) { return; } self.stage_or_unstage(false, hunks.clone(), editor, window, cx); editor.transact(window, cx, |editor, window, cx| { editor.restore_diff_hunks(hunks, cx); let selections = editor .selections .all::<MultiBufferOffset>(&editor.display_snapshot(cx)); editor.change_selections( SelectionEffects::no_scroll(), window, cx, |selections_state| { selections_state.select(selections); }, ); }); } fn render_hunk_controls( &self, row: u32, status: &DiffHunkStatus, hunk_range: Range<Anchor>, is_created_file: bool, line_height: Pixels, editor: &Entity<Editor>, window: &mut Window, cx: &mut App, ) -> AnyElement; fn render_hunk_as_staged(&self, status: &DiffHunkStatus, _cx: &App) -> bool { !status.has_secondary_hunk() }} pub struct UncommittedDiffHunkDelegate; impl DiffHunkDelegate for UncommittedDiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { let stage = hunks .iter() .flat_map(|hunks| hunks.hunks.iter()) .any(|hunk| hunk.status.has_secondary_hunk()); self.stage_or_unstage(stage, hunks, editor, window, cx); } fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, _window: &mut Window, cx: &mut Context<Editor>, ) { let Some(project) = editor.project() else { return; }; for hunks in hunks { let Some(buffer) = hunks.buffer else { continue; }; let ranges = hunks .hunks .into_iter() .map(|hunk| hunk.buffer_range) .collect::<Vec<_>>(); if ranges.is_empty() { continue; } let secondary_diff = hunks.diff.read(cx).secondary_diff(); project .update(cx, |project, cx| { if stage { let Some(secondary_diff) = secondary_diff else { return Err(anyhow::anyhow!("diff has no unstaged secondary")); }; project.stage_hunks(buffer, secondary_diff, ranges, cx) } else { project.unstage_uncommitted_hunks(buffer, hunks.diff, ranges, cx) } }) .log_err(); } } fn render_hunk_controls( &self, row: u32, status: &DiffHunkStatus, hunk_range: Range<Anchor>, is_created_file: bool, line_height: Pixels, editor: &Entity<Editor>, window: &mut Window, cx: &mut App, ) -> AnyElement { render_diff_hunk_controls( row, status, hunk_range, is_created_file, line_height, editor, window, cx, ) }} pub struct RestoreOnlyDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate { fn toggle( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn stage_or_unstage( &self, _stage: bool, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn restore( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn render_hunk_controls( &self, _row: u32, _status: &DiffHunkStatus, _hunk_range: Range<Anchor>, _is_created_file: bool, _line_height: Pixels, _editor: &Entity<Editor>, _window: &mut Window, _cx: &mut App, ) -> AnyElement { gpui::Empty.into_any_element() }} pub struct RestoreOnlyUnstagedDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyUnstagedDiffHunkDelegate { fn toggle( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn stage_or_unstage( &self, _stage: bool, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn render_hunk_controls( &self, _row: u32, _status: &DiffHunkStatus, _hunk_range: Range<Anchor>, _is_created_file: bool, _line_height: Pixels, _editor: &Entity<Editor>, _window: &mut Window, _cx: &mut App, ) -> AnyElement { gpui::Empty.into_any_element() } fn render_hunk_as_staged(&self, _status: &DiffHunkStatus, _cx: &App) -> bool { false }} #[derive(Debug, Clone, PartialEq, Eq)]pub(super) enum DisplayDiffHunk { Folded { display_row: DisplayRow, }, Unfolded { is_created_file: bool, diff_base_byte_range: Range<usize>, display_row_range: Range<DisplayRow>, multi_buffer_range: Range<Anchor>, status: DiffHunkStatus, word_diffs: Vec<Range<MultiBufferOffset>>, },} #[derive(Clone)]pub(super) struct InlineBlamePopoverState { pub(super) scroll_handle: ScrollHandle, pub(super) commit_message: Option<ParsedCommitMessage>, pub(super) markdown: Entity<Markdown>,} pub(super) struct InlineBlamePopover { pub(super) position: gpui::Point<Pixels>, pub(super) hide_task: Option<Task<()>>, pub(super) popover_bounds: Option<Bounds<Pixels>>, pub(super) popover_state: InlineBlamePopoverState, pub(super) keyboard_grace: bool,} /// Represents a diff review button indicator that shows up when hovering over lines in the gutter/// in diff view mode.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(super) struct PhantomDiffReviewIndicator { /// The starting anchor of the selection (or the only row if not dragging). pub(super) start: Anchor, /// The ending anchor of the selection. Equal to start_anchor for single-line selection. pub(super) end: Anchor, /// There's a small debounce between hovering over the line and showing the indicator. /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel. pub(super) is_active: bool,} #[derive(Clone, Debug)]pub(super) struct DiffReviewDragState { start_anchor: Anchor, current_anchor: Anchor,} /// Identifies a specific hunk in the diff buffer./// Used as a key to group comments by their location.#[derive(Clone, Debug)]pub(super) struct DiffHunkKey { /// The file path (relative to worktree) this hunk belongs to. pub(super) file_path: Arc<util::rel_path::RelPath>, /// An anchor at the start of the hunk. This tracks position as the buffer changes. pub(super) hunk_start_anchor: Anchor,} /// A review comment stored locally before being sent to the Agent panel.#[derive(Clone)]pub(super) struct StoredReviewComment { /// Unique identifier for this comment (for edit/delete operations). pub(super) id: usize, /// The comment text entered by the user. pub(super) comment: String, /// Anchors for the code range being reviewed. pub(super) range: Range<Anchor>, /// Whether this comment is currently being edited inline. pub(super) is_editing: bool,} /// Represents an active diff review overlay that appears when clicking the "Add Review" button.pub(super) struct DiffReviewOverlay { pub(super) anchor_range: Range<Anchor>, /// The block ID for the overlay. pub(super) block_id: CustomBlockId, /// The editor entity for the review input. pub(super) prompt_editor: Entity<Editor>, /// The hunk key this overlay belongs to. pub(super) hunk_key: DiffHunkKey, /// Whether the comments section is expanded. pub(super) comments_expanded: bool, /// Editors for comments currently being edited inline. /// Key: comment ID, Value: Editor entity for inline editing. pub(super) inline_edit_editors: HashMap<usize, Entity<Editor>>, /// Subscriptions for inline edit editors' action handlers. /// Key: comment ID, Value: Subscription keeping the Newline action handler alive. pub(super) inline_edit_subscriptions: HashMap<usize, Subscription>, /// The current user's avatar URI for display in comment rows. pub(super) user_avatar_uri: Option<SharedUri>, /// Subscription to keep the action handler alive. _subscription: Subscription,} impl DiffReviewDragState { pub(super) fn row_range( &self, snapshot: &DisplaySnapshot, ) -> std::ops::RangeInclusive<DisplayRow> { let start = self.start_anchor.to_display_point(snapshot).row(); let current = self.current_anchor.to_display_point(snapshot).row(); (start..=current).sorted() }} impl StoredReviewComment { fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self { Self { id, comment, range: anchor_range, is_editing: false, } }} impl Editor { pub fn diff_hunks_in_ranges<'a>( &'a self, ranges: &'a [Range<Anchor>], buffer: &'a MultiBufferSnapshot, ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> { ranges.iter().flat_map(move |range| { let end_excerpt = buffer.excerpt_containing(range.end..range.end); let range = range.to_point(buffer); let mut peek_end = range.end; if range.end.row < buffer.max_row().0 { peek_end = Point::new(range.end.row + 1, 0); } buffer .diff_hunks_in_range(range.start..peek_end) .filter(move |hunk| { if let Some((_, excerpt_range)) = &end_excerpt && let Some(end_anchor) = buffer.anchor_in_excerpt(excerpt_range.context.end) && let Some(hunk_end_anchor) = buffer.anchor_in_excerpt(hunk.excerpt_range.context.end) && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt() { false } else { true } }) }) } fn resolve_diff_hunks( &self, hunks: Vec<MultiBufferDiffHunk>, cx: &App, ) -> Vec<ResolvedDiffHunks> { let multibuffer = self.buffer().read(cx); let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id); let mut resolved = Vec::new(); for (source_buffer_id, hunks) in &chunk_by { let Some(diff) = multibuffer.diff_for(source_buffer_id) else { continue; }; let diff_snapshot = diff.read(cx).snapshot(cx); let main_buffer_id = diff_snapshot.buffer_id(); let buffer = multibuffer.buffer(main_buffer_id).or_else(|| { self.project .as_ref() .and_then(|project| project.read(cx).buffer_for_id(main_buffer_id, cx)) }); let mut resolved_hunks = Vec::new(); for hunk in hunks { if hunk.buffer_id == main_buffer_id { resolved_hunks.push(ResolvedDiffHunk { buffer_range: hunk.buffer_range, diff_base_byte_range: hunk.diff_base_byte_range.start.0 ..hunk.diff_base_byte_range.end.0, status: hunk.status, }); } else { let diff_base_byte_range = hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0; let Some(hunk) = diff_snapshot .hunks_intersecting_base_text_range( diff_base_byte_range.clone(), diff_snapshot.buffer_snapshot(), ) .find(|hunk| hunk.diff_base_byte_range == diff_base_byte_range) else { continue; }; let kind = if hunk.buffer_range.start == hunk.buffer_range.end { DiffHunkStatusKind::Deleted } else if hunk.diff_base_byte_range.is_empty() { DiffHunkStatusKind::Added } else { DiffHunkStatusKind::Modified }; resolved_hunks.push(ResolvedDiffHunk { buffer_range: hunk.buffer_range, diff_base_byte_range: hunk.diff_base_byte_range, status: DiffHunkStatus { kind, secondary: hunk.secondary_status, }, }); } } if !resolved_hunks.is_empty() { resolved.push(ResolvedDiffHunks { diff, buffer_id: main_buffer_id, buffer, hunks: resolved_hunks, }); } } resolved } pub fn diff_hunk_delegate(&self) -> Arc<dyn DiffHunkDelegate> { self.diff_hunk_delegate .clone() .unwrap_or_else(|| Arc::new(UncommittedDiffHunkDelegate)) } pub fn set_diff_hunk_delegate( &mut self, delegate: Option<Arc<dyn DiffHunkDelegate>>, cx: &mut Context<Self>, ) { let had_delegate = self.diff_hunk_delegate.is_some(); let has_delegate = delegate.is_some(); self.diff_hunk_delegate = delegate; if !had_delegate && has_delegate { self.load_diff_task.take(); } else if had_delegate && !has_delegate { self.buffer.update(cx, |buffer, cx| { buffer.set_all_diff_hunks_collapsed(cx); }); if let Some(project) = self.project.clone() { self.load_diff_task = Some( self.update_uncommitted_diff_for_buffer( &project, self.buffer.read(cx).all_buffers(),Lines 1–500 of 3178. Load more
pub(super) mod blame; use super::*; use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus}; use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind}; #[derive(Clone)] pub struct ResolvedDiffHunk { pub buffer_range: Range<text::Anchor>, pub diff_base_byte_range: Range<usize>, pub status: DiffHunkStatus, } #[derive(Clone)] pub struct ResolvedDiffHunks { pub diff: Entity<BufferDiff>, pub buffer_id: BufferId, pub buffer: Option<Entity<Buffer>>, pub hunks: Vec<ResolvedDiffHunk>, } pub trait DiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ); fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ); fn restore( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { if hunks.is_empty() || editor.read_only(cx) { return; } self.stage_or_unstage(false, hunks.clone(), editor, window, cx); editor.transact(window, cx, |editor, window, cx| { editor.restore_diff_hunks(hunks, cx); let selections = editor .selections .all::<MultiBufferOffset>(&editor.display_snapshot(cx)); editor.change_selections( SelectionEffects::no_scroll(), window, cx, |selections_state| { selections_state.select(selections); }, ); }); } fn render_hunk_controls( &self, row: u32, status: &DiffHunkStatus, hunk_range: Range<Anchor>, is_created_file: bool, line_height: Pixels, editor: &Entity<Editor>, window: &mut Window, cx: &mut App, ) -> AnyElement; fn render_hunk_as_staged(&self, status: &DiffHunkStatus, _cx: &App) -> bool { !status.has_secondary_hunk() } } pub struct UncommittedDiffHunkDelegate; impl DiffHunkDelegate for UncommittedDiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { let stage = hunks .iter() .flat_map(|hunks| hunks.hunks.iter()) .any(|hunk| hunk.status.has_secondary_hunk()); self.stage_or_unstage(stage, hunks, editor, window, cx); } fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, editor: &mut Editor, _window: &mut Window, cx: &mut Context<Editor>, ) { let Some(project) = editor.project() else { return; }; for hunks in hunks { let Some(buffer) = hunks.buffer else { continue; }; let ranges = hunks .hunks .into_iter() .map(|hunk| hunk.buffer_range) .collect::<Vec<_>>(); if ranges.is_empty() { continue; } let secondary_diff = hunks.diff.read(cx).secondary_diff(); project .update(cx, |project, cx| { if stage { let Some(secondary_diff) = secondary_diff else { return Err(anyhow::anyhow!("diff has no unstaged secondary")); }; project.stage_hunks(buffer, secondary_diff, ranges, cx) } else { project.unstage_uncommitted_hunks(buffer, hunks.diff, ranges, cx) } }) .log_err(); } } fn render_hunk_controls( &self, row: u32, status: &DiffHunkStatus, hunk_range: Range<Anchor>, is_created_file: bool, line_height: Pixels, editor: &Entity<Editor>, window: &mut Window, cx: &mut App, ) -> AnyElement { render_diff_hunk_controls( row, status, hunk_range, is_created_file, line_height, editor, window, cx, ) } } pub struct RestoreOnlyDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyDiffHunkDelegate { fn toggle( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn stage_or_unstage( &self, _stage: bool, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn restore( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn render_hunk_controls( &self, _row: u32, _status: &DiffHunkStatus, _hunk_range: Range<Anchor>, _is_created_file: bool, _line_height: Pixels, _editor: &Entity<Editor>, _window: &mut Window, _cx: &mut App, ) -> AnyElement { gpui::Empty.into_any_element() } } pub struct RestoreOnlyUnstagedDiffHunkDelegate; impl DiffHunkDelegate for RestoreOnlyUnstagedDiffHunkDelegate { fn toggle( &self, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn stage_or_unstage( &self, _stage: bool, _hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, _window: &mut Window, _cx: &mut Context<Editor>, ) { } fn render_hunk_controls( &self, _row: u32, _status: &DiffHunkStatus, _hunk_range: Range<Anchor>, _is_created_file: bool, _line_height: Pixels, _editor: &Entity<Editor>, _window: &mut Window, _cx: &mut App, ) -> AnyElement { gpui::Empty.into_any_element() } fn render_hunk_as_staged(&self, _status: &DiffHunkStatus, _cx: &App) -> bool { false } } #[derive(Debug, Clone, PartialEq, Eq)] pub(super) enum DisplayDiffHunk { Folded { display_row: DisplayRow, }, Unfolded { is_created_file: bool, diff_base_byte_range: Range<usize>, display_row_range: Range<DisplayRow>, multi_buffer_range: Range<Anchor>, status: DiffHunkStatus, word_diffs: Vec<Range<MultiBufferOffset>>, }, } #[derive(Clone)] pub(super) struct InlineBlamePopoverState { pub(super) scroll_handle: ScrollHandle, pub(super) commit_message: Option<ParsedCommitMessage>, pub(super) markdown: Entity<Markdown>, } pub(super) struct InlineBlamePopover { pub(super) position: gpui::Point<Pixels>, pub(super) hide_task: Option<Task<()>>, pub(super) popover_bounds: Option<Bounds<Pixels>>, pub(super) popover_state: InlineBlamePopoverState, pub(super) keyboard_grace: bool, } /// Represents a diff review button indicator that shows up when hovering over lines in the gutter /// in diff view mode. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(super) struct PhantomDiffReviewIndicator { /// The starting anchor of the selection (or the only row if not dragging). pub(super) start: Anchor, /// The ending anchor of the selection. Equal to start_anchor for single-line selection. pub(super) end: Anchor, /// There's a small debounce between hovering over the line and showing the indicator. /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel. pub(super) is_active: bool, } #[derive(Clone, Debug)] pub(super) struct DiffReviewDragState { start_anchor: Anchor, current_anchor: Anchor, } /// Identifies a specific hunk in the diff buffer. /// Used as a key to group comments by their location. #[derive(Clone, Debug)] pub(super) struct DiffHunkKey { /// The file path (relative to worktree) this hunk belongs to. pub(super) file_path: Arc<util::rel_path::RelPath>, /// An anchor at the start of the hunk. This tracks position as the buffer changes. pub(super) hunk_start_anchor: Anchor, } /// A review comment stored locally before being sent to the Agent panel. #[derive(Clone)] pub(super) struct StoredReviewComment { /// Unique identifier for this comment (for edit/delete operations). pub(super) id: usize, /// The comment text entered by the user. pub(super) comment: String, /// Anchors for the code range being reviewed. pub(super) range: Range<Anchor>, /// Whether this comment is currently being edited inline. pub(super) is_editing: bool, } /// Represents an active diff review overlay that appears when clicking the "Add Review" button. pub(super) struct DiffReviewOverlay { pub(super) anchor_range: Range<Anchor>, /// The block ID for the overlay. pub(super) block_id: CustomBlockId, /// The editor entity for the review input. pub(super) prompt_editor: Entity<Editor>, /// The hunk key this overlay belongs to. pub(super) hunk_key: DiffHunkKey, /// Whether the comments section is expanded. pub(super) comments_expanded: bool, /// Editors for comments currently being edited inline. /// Key: comment ID, Value: Editor entity for inline editing. pub(super) inline_edit_editors: HashMap<usize, Entity<Editor>>, /// Subscriptions for inline edit editors' action handlers. /// Key: comment ID, Value: Subscription keeping the Newline action handler alive. pub(super) inline_edit_subscriptions: HashMap<usize, Subscription>, /// The current user's avatar URI for display in comment rows. pub(super) user_avatar_uri: Option<SharedUri>, /// Subscription to keep the action handler alive. _subscription: Subscription, } impl DiffReviewDragState { pub(super) fn row_range( &self, snapshot: &DisplaySnapshot, ) -> std::ops::RangeInclusive<DisplayRow> { let start = self.start_anchor.to_display_point(snapshot).row(); let current = self.current_anchor.to_display_point(snapshot).row(); (start..=current).sorted() } } impl StoredReviewComment { fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self { Self { id, comment, range: anchor_range, is_editing: false, } } } impl Editor { pub fn diff_hunks_in_ranges<'a>( &'a self, ranges: &'a [Range<Anchor>], buffer: &'a MultiBufferSnapshot, ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> { ranges.iter().flat_map(move |range| { let end_excerpt = buffer.excerpt_containing(range.end..range.end); let range = range.to_point(buffer); let mut peek_end = range.end; if range.end.row < buffer.max_row().0 { peek_end = Point::new(range.end.row + 1, 0); } buffer .diff_hunks_in_range(range.start..peek_end) .filter(move |hunk| { if let Some((_, excerpt_range)) = &end_excerpt && let Some(end_anchor) = buffer.anchor_in_excerpt(excerpt_range.context.end) && let Some(hunk_end_anchor) = buffer.anchor_in_excerpt(hunk.excerpt_range.context.end) && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt() { false } else { true } }) }) } fn resolve_diff_hunks( &self, hunks: Vec<MultiBufferDiffHunk>, cx: &App, ) -> Vec<ResolvedDiffHunks> { let multibuffer = self.buffer().read(cx); let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id); let mut resolved = Vec::new(); for (source_buffer_id, hunks) in &chunk_by { let Some(diff) = multibuffer.diff_for(source_buffer_id) else { continue; }; let diff_snapshot = diff.read(cx).snapshot(cx); let main_buffer_id = diff_snapshot.buffer_id(); let buffer = multibuffer.buffer(main_buffer_id).or_else(|| { self.project .as_ref() .and_then(|project| project.read(cx).buffer_for_id(main_buffer_id, cx)) }); let mut resolved_hunks = Vec::new(); for hunk in hunks { if hunk.buffer_id == main_buffer_id { resolved_hunks.push(ResolvedDiffHunk { buffer_range: hunk.buffer_range, diff_base_byte_range: hunk.diff_base_byte_range.start.0 ..hunk.diff_base_byte_range.end.0, status: hunk.status, }); } else { let diff_base_byte_range = hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0; let Some(hunk) = diff_snapshot .hunks_intersecting_base_text_range( diff_base_byte_range.clone(), diff_snapshot.buffer_snapshot(), ) .find(|hunk| hunk.diff_base_byte_range == diff_base_byte_range) else { continue; }; let kind = if hunk.buffer_range.start == hunk.buffer_range.end { DiffHunkStatusKind::Deleted } else if hunk.diff_base_byte_range.is_empty() { DiffHunkStatusKind::Added } else { DiffHunkStatusKind::Modified }; resolved_hunks.push(ResolvedDiffHunk { buffer_range: hunk.buffer_range, diff_base_byte_range: hunk.diff_base_byte_range, status: DiffHunkStatus { kind, secondary: hunk.secondary_status, }, }); } } if !resolved_hunks.is_empty() { resolved.push(ResolvedDiffHunks { diff, buffer_id: main_buffer_id, buffer, hunks: resolved_hunks, }); } } resolved } pub fn diff_hunk_delegate(&self) -> Arc<dyn DiffHunkDelegate> { self.diff_hunk_delegate .clone() .unwrap_or_else(|| Arc::new(UncommittedDiffHunkDelegate)) } pub fn set_diff_hunk_delegate( &mut self, delegate: Option<Arc<dyn DiffHunkDelegate>>, cx: &mut Context<Self>, ) { let had_delegate = self.diff_hunk_delegate.is_some(); let has_delegate = delegate.is_some(); self.diff_hunk_delegate = delegate; if !had_delegate && has_delegate { self.load_diff_task.take(); } else if had_delegate && !has_delegate { self.buffer.update(cx, |buffer, cx| { buffer.set_all_diff_hunks_collapsed(cx); }); if let Some(project) = self.project.clone() { self.load_diff_task = Some( self.update_uncommitted_diff_for_buffer( &project, self.buffer.read(cx).all_buffers(),