use std::{ ops::{Range, RangeInclusive}, sync::Arc,}; use buffer_diff::{BufferDiff, BufferDiffSnapshot, DiffHunkStatus};use collections::HashMap; use fs::Fs;use gpui::{ Action, AnyElement, Entity, EventEmitter, Focusable, Font, Pixels, Subscription, WeakEntity, canvas, prelude::*,}; use language::{Buffer, Capability, HighlightedText};use multi_buffer::{ Anchor, AnchorRangeExt as _, BufferOffset, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint, MultiBufferSnapshot, PathKey,};use project::Project;use rope::Point;use settings::{DiffViewStyle, SeedQuerySetting, Settings, SettingsStore, update_settings_file};use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _}; use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers};use util::ResultExt as _; use crate::{ display_map::CompanionExcerptPatch, element::SplitSide, split_editor_view::{SplitEditorState, SplitEditorView},};use workspace::{ ActivatePaneLeft, ActivatePaneRight, Item, ToolbarItemLocation, Workspace, item::{ItemBufferKind, ItemEvent, SaveOptions, TabContentParams}, searchable::{SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},}; use crate::{ Autoscroll, DiffHunkDelegate, Editor, EditorEvent, EditorSettings, ResolvedDiffHunks, ToggleSoftWrap, UncommittedDiffHunkDelegate, actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint}, display_map::Companion,};use zed_actions::{OpenSettingsAt, assistant::InlineAssist}; pub(crate) fn patches_for_lhs_range( rhs_snapshot: &MultiBufferSnapshot, lhs_snapshot: &MultiBufferSnapshot, lhs_bounds: Range<MultiBufferPoint>,) -> Vec<CompanionExcerptPatch> { patches_for_range( lhs_snapshot, rhs_snapshot, lhs_bounds, |diff, range, buffer| diff.patch_for_base_text_range(range, buffer), )} pub(crate) fn patches_for_rhs_range( lhs_snapshot: &MultiBufferSnapshot, rhs_snapshot: &MultiBufferSnapshot, rhs_bounds: Range<MultiBufferPoint>,) -> Vec<CompanionExcerptPatch> { patches_for_range( rhs_snapshot, lhs_snapshot, rhs_bounds, |diff, range, buffer| diff.patch_for_buffer_range(range, buffer), )} fn buffer_range_to_base_text_range( rhs_range: &Range<Point>, diff_snapshot: &BufferDiffSnapshot, rhs_buffer_snapshot: &text::BufferSnapshot,) -> Range<Point> { let start = diff_snapshot .buffer_point_to_base_text_range(Point::new(rhs_range.start.row, 0), rhs_buffer_snapshot) .start; let end = diff_snapshot .buffer_point_to_base_text_range(Point::new(rhs_range.end.row, 0), rhs_buffer_snapshot) .end; let end_column = diff_snapshot.base_text().line_len(end.row); Point::new(start.row, 0)..Point::new(end.row, end_column)} fn translate_lhs_selections_to_rhs( selections_by_buffer: &HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>, splittable: &SplittableEditor, cx: &App,) -> HashMap<Entity<Buffer>, (Vec<Range<BufferOffset>>, Option<u32>)> { let Some(lhs) = &splittable.lhs else { return HashMap::default(); }; let lhs_snapshot = lhs.multibuffer.read(cx).snapshot(cx); let mut translated: HashMap<Entity<Buffer>, (Vec<Range<BufferOffset>>, Option<u32>)> = HashMap::default(); for (lhs_buffer_id, (ranges, scroll_offset)) in selections_by_buffer { let Some(diff) = lhs_snapshot.diff_for_buffer_id(*lhs_buffer_id) else { continue; }; let rhs_buffer_id = diff.buffer_id(); let Some(rhs_buffer) = splittable .rhs_editor .read(cx) .buffer() .read(cx) .buffer(rhs_buffer_id) else { continue; }; let Some(diff) = splittable .rhs_editor .read(cx) .buffer() .read(cx) .diff_for(rhs_buffer_id) else { continue; }; let diff_snapshot = diff.read(cx).snapshot(cx); let rhs_buffer_snapshot = rhs_buffer.read(cx).snapshot(); let base_text_buffer = diff.read(cx).base_text_buffer(); let base_text_snapshot = base_text_buffer.read(cx).snapshot(); let translated_ranges: Vec<Range<BufferOffset>> = ranges .iter() .map(|range| { let start_point = base_text_snapshot.offset_to_point(range.start.0); let end_point = base_text_snapshot.offset_to_point(range.end.0); let rhs_start = diff_snapshot .base_text_point_to_buffer_point(start_point, &rhs_buffer_snapshot); let rhs_end = diff_snapshot.base_text_point_to_buffer_point(end_point, &rhs_buffer_snapshot); BufferOffset(rhs_buffer_snapshot.point_to_offset(rhs_start)) ..BufferOffset(rhs_buffer_snapshot.point_to_offset(rhs_end)) }) .collect(); translated.insert(rhs_buffer, (translated_ranges, *scroll_offset)); } translated} struct SplitLhsDiffHunkDelegate { splittable: WeakEntity<SplittableEditor>,} impl DiffHunkDelegate for SplitLhsDiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.toggle(hunks, editor, window, cx); }); }) .log_err(); } fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.stage_or_unstage(stage, hunks, editor, window, cx); }); }) .log_err(); } fn restore( &self, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.restore(hunks, editor, window, 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 { let Some(splittable) = self.splittable.upgrade() else { return gpui::Empty.into_any_element(); }; let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); delegate.render_hunk_controls( row, status, hunk_range, is_created_file, line_height, editor, window, cx, ) } fn render_hunk_as_staged(&self, status: &DiffHunkStatus, cx: &App) -> bool { let Some(splittable) = self.splittable.upgrade() else { return false; }; let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); delegate.render_hunk_as_staged(status, cx) }} fn patches_for_range<F>( source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_bounds: Range<MultiBufferPoint>, translate_fn: F,) -> Vec<CompanionExcerptPatch>where F: Fn(&BufferDiffSnapshot, RangeInclusive<Point>, &text::BufferSnapshot) -> Patch<Point>,{ struct PendingExcerpt<'a> { source_buffer_snapshot: &'a language::BufferSnapshot, source_excerpt_range: ExcerptRange<text::Anchor>, buffer_point_range: Range<Point>, } let mut result = Vec::new(); let mut current_buffer_id: Option<BufferId> = None; let mut pending_excerpts: Vec<PendingExcerpt<'_>> = Vec::new(); let mut union_context_start: Option<Point> = None; let mut union_context_end: Option<Point> = None; let flush_buffer = |pending: &mut Vec<PendingExcerpt>, union_start: Point, union_end: Point, result: &mut Vec<CompanionExcerptPatch>| { let Some(first) = pending.first() else { return; }; let source_buffer_id = first.source_buffer_snapshot.remote_id(); let Some(diff) = source_snapshot.diff_for_buffer_id(source_buffer_id) else { pending.clear(); return; }; let source_is_lhs = source_buffer_id == diff.base_text().remote_id(); let target_buffer_id = if source_is_lhs { diff.buffer_id() } else { diff.base_text().remote_id() }; let Some(target_buffer) = target_snapshot.buffer_for_id(target_buffer_id) else { pending.clear(); return; }; let rhs_buffer = if source_is_lhs { target_buffer } else { &first.source_buffer_snapshot }; let patch = translate_fn(diff, union_start..=union_end, rhs_buffer); let mut source_excerpts = source_snapshot .excerpts_for_buffer(source_buffer_id) .peekable(); let mut target_excerpts = target_snapshot .excerpts_for_buffer(target_buffer_id) .peekable(); for excerpt in pending.drain(..) { while let Some(source_excerpt_range) = source_excerpts.peek() && source_excerpt_range != &excerpt.source_excerpt_range { source_excerpts.next(); target_excerpts.next(); } if let Some(source_excerpt_range) = source_excerpts.peek() && let Some(target_excerpt_range) = target_excerpts.peek() { result.push(patch_for_excerpt( source_snapshot, target_snapshot, &excerpt.source_buffer_snapshot, target_buffer, source_excerpt_range.clone(), target_excerpt_range.clone(), &patch, excerpt.buffer_point_range, )); } } }; for (buffer_snapshot, source_range, source_excerpt_range) in source_snapshot.range_to_buffer_ranges(source_bounds) { let buffer_id = buffer_snapshot.remote_id(); if current_buffer_id != Some(buffer_id) { if let (Some(start), Some(end)) = (union_context_start.take(), union_context_end.take()) { flush_buffer(&mut pending_excerpts, start, end, &mut result); } current_buffer_id = Some(buffer_id); } let buffer_point_range = source_range.to_point(&buffer_snapshot); let source_context_range = source_excerpt_range.context.to_point(&buffer_snapshot); union_context_start = Some(union_context_start.map_or(source_context_range.start, |s| { s.min(source_context_range.start) })); union_context_end = Some(union_context_end.map_or(source_context_range.end, |e| { e.max(source_context_range.end) })); pending_excerpts.push(PendingExcerpt { source_buffer_snapshot: buffer_snapshot, source_excerpt_range, buffer_point_range, }); } if let (Some(start), Some(end)) = (union_context_start, union_context_end) { flush_buffer(&mut pending_excerpts, start, end, &mut result); } result} fn patch_for_excerpt( source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_buffer_snapshot: &language::BufferSnapshot, target_buffer_snapshot: &language::BufferSnapshot, source_excerpt_range: ExcerptRange<text::Anchor>, target_excerpt_range: ExcerptRange<text::Anchor>, patch: &Patch<Point>, source_edited_range: Range<Point>,) -> CompanionExcerptPatch { let source_buffer_range = source_excerpt_range .context .to_point(source_buffer_snapshot); let source_multibuffer_range = (source_snapshot .anchor_in_buffer(source_excerpt_range.context.start) .expect("buffer should exist in multibuffer") ..source_snapshot .anchor_in_buffer(source_excerpt_range.context.end) .expect("buffer should exist in multibuffer")) .to_point(source_snapshot); let target_buffer_range = target_excerpt_range .context .to_point(target_buffer_snapshot); let target_multibuffer_range = (target_snapshot .anchor_in_buffer(target_excerpt_range.context.start) .expect("buffer should exist in multibuffer") ..target_snapshot .anchor_in_buffer(target_excerpt_range.context.end) .expect("buffer should exist in multibuffer")) .to_point(target_snapshot); let edits = patch .edits() .iter() .skip_while(|edit| edit.old.end < source_buffer_range.start) .take_while(|edit| edit.old.start <= source_buffer_range.end) .map(|edit| { let clamped_source_start = edit.old.start.max(source_buffer_range.start); let clamped_source_end = edit.old.end.min(source_buffer_range.end); let source_multibuffer_start = source_multibuffer_range.start + (clamped_source_start - source_buffer_range.start); let source_multibuffer_end = source_multibuffer_range.start + (clamped_source_end - source_buffer_range.start); let clamped_target_start = edit .new .start .max(target_buffer_range.start) .min(target_buffer_range.end); let clamped_target_end = edit .new .end .max(target_buffer_range.start) .min(target_buffer_range.end); let target_multibuffer_start = target_multibuffer_range.start + (clamped_target_start - target_buffer_range.start); let target_multibuffer_end = target_multibuffer_range.start + (clamped_target_end - target_buffer_range.start); text::Edit { old: source_multibuffer_start..source_multibuffer_end, new: target_multibuffer_start..target_multibuffer_end, } }); let edits = [text::Edit { old: source_multibuffer_range.start..source_multibuffer_range.start, new: target_multibuffer_range.start..target_multibuffer_range.start, }] .into_iter() .chain(edits); let mut merged_edits: Vec<text::Edit<Point>> = Vec::new(); for edit in edits { if let Some(last) = merged_edits.last_mut() { if edit.new.start <= last.new.end || edit.old.start <= last.old.end { last.old.end = last.old.end.max(edit.old.end); last.new.end = last.new.end.max(edit.new.end); continue; } } merged_edits.push(edit); } let edited_range = source_multibuffer_range.start + (source_edited_range.start - source_buffer_range.start) ..source_multibuffer_range.start + (source_edited_range.end - source_buffer_range.start); let source_excerpt_end = source_multibuffer_range.start + (source_buffer_range.end - source_buffer_range.start); let target_excerpt_end = target_multibuffer_range.start + (target_buffer_range.end - target_buffer_range.start); CompanionExcerptPatch { patch: Patch::new(merged_edits), edited_range, source_excerpt_range: source_multibuffer_range.start..source_excerpt_end, target_excerpt_range: target_multibuffer_range.start..target_excerpt_end, }} #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]pub struct ToggleSplitDiff; /// Unified/split diff view toggle buttons, shared by the toolbars of every/// diff view (project diff, branch diff, solo diff) so they can't drift apart.#[derive(gpui::IntoElement)]pub struct DiffStyleControls { splittable_editor: Entity<SplittableEditor>,} impl DiffStyleControls { pub fn new(splittable_editor: Entity<SplittableEditor>) -> Self { Self { splittable_editor } } fn set_diff_view_style( splittable_editor: &Entity<SplittableEditor>, diff_view_style: DiffViewStyle, window: &mut Window, cx: &mut App, ) { update_settings_file(<dyn Fs>::global(cx), cx, move |settings, _| { settings.editor.diff_view_style = Some(diff_view_style); }); splittable_editor.update(cx, |editor, cx| { if editor.diff_view_style() != diff_view_style { editor.toggle_split(&ToggleSplitDiff, window, cx); } }); }} impl RenderOnce for DiffStyleControls {Lines 1–500 of 6495. Load more
use std::{ ops::{Range, RangeInclusive}, sync::Arc, }; use buffer_diff::{BufferDiff, BufferDiffSnapshot, DiffHunkStatus}; use collections::HashMap; use fs::Fs; use gpui::{ Action, AnyElement, Entity, EventEmitter, Focusable, Font, Pixels, Subscription, WeakEntity, canvas, prelude::*, }; use language::{Buffer, Capability, HighlightedText}; use multi_buffer::{ Anchor, AnchorRangeExt as _, BufferOffset, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint, MultiBufferSnapshot, PathKey, }; use project::Project; use rope::Point; use settings::{DiffViewStyle, SeedQuerySetting, Settings, SettingsStore, update_settings_file}; use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _}; use ui::{Toggleable as _, Tooltip, prelude::*, render_modifiers}; use util::ResultExt as _; use crate::{ display_map::CompanionExcerptPatch, element::SplitSide, split_editor_view::{SplitEditorState, SplitEditorView}, }; use workspace::{ ActivatePaneLeft, ActivatePaneRight, Item, ToolbarItemLocation, Workspace, item::{ItemBufferKind, ItemEvent, SaveOptions, TabContentParams}, searchable::{SearchEvent, SearchToken, SearchableItem, SearchableItemHandle}, }; use crate::{ Autoscroll, DiffHunkDelegate, Editor, EditorEvent, EditorSettings, ResolvedDiffHunks, ToggleSoftWrap, UncommittedDiffHunkDelegate, actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint}, display_map::Companion, }; use zed_actions::{OpenSettingsAt, assistant::InlineAssist}; pub(crate) fn patches_for_lhs_range( rhs_snapshot: &MultiBufferSnapshot, lhs_snapshot: &MultiBufferSnapshot, lhs_bounds: Range<MultiBufferPoint>, ) -> Vec<CompanionExcerptPatch> { patches_for_range( lhs_snapshot, rhs_snapshot, lhs_bounds, |diff, range, buffer| diff.patch_for_base_text_range(range, buffer), ) } pub(crate) fn patches_for_rhs_range( lhs_snapshot: &MultiBufferSnapshot, rhs_snapshot: &MultiBufferSnapshot, rhs_bounds: Range<MultiBufferPoint>, ) -> Vec<CompanionExcerptPatch> { patches_for_range( rhs_snapshot, lhs_snapshot, rhs_bounds, |diff, range, buffer| diff.patch_for_buffer_range(range, buffer), ) } fn buffer_range_to_base_text_range( rhs_range: &Range<Point>, diff_snapshot: &BufferDiffSnapshot, rhs_buffer_snapshot: &text::BufferSnapshot, ) -> Range<Point> { let start = diff_snapshot .buffer_point_to_base_text_range(Point::new(rhs_range.start.row, 0), rhs_buffer_snapshot) .start; let end = diff_snapshot .buffer_point_to_base_text_range(Point::new(rhs_range.end.row, 0), rhs_buffer_snapshot) .end; let end_column = diff_snapshot.base_text().line_len(end.row); Point::new(start.row, 0)..Point::new(end.row, end_column) } fn translate_lhs_selections_to_rhs( selections_by_buffer: &HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>, splittable: &SplittableEditor, cx: &App, ) -> HashMap<Entity<Buffer>, (Vec<Range<BufferOffset>>, Option<u32>)> { let Some(lhs) = &splittable.lhs else { return HashMap::default(); }; let lhs_snapshot = lhs.multibuffer.read(cx).snapshot(cx); let mut translated: HashMap<Entity<Buffer>, (Vec<Range<BufferOffset>>, Option<u32>)> = HashMap::default(); for (lhs_buffer_id, (ranges, scroll_offset)) in selections_by_buffer { let Some(diff) = lhs_snapshot.diff_for_buffer_id(*lhs_buffer_id) else { continue; }; let rhs_buffer_id = diff.buffer_id(); let Some(rhs_buffer) = splittable .rhs_editor .read(cx) .buffer() .read(cx) .buffer(rhs_buffer_id) else { continue; }; let Some(diff) = splittable .rhs_editor .read(cx) .buffer() .read(cx) .diff_for(rhs_buffer_id) else { continue; }; let diff_snapshot = diff.read(cx).snapshot(cx); let rhs_buffer_snapshot = rhs_buffer.read(cx).snapshot(); let base_text_buffer = diff.read(cx).base_text_buffer(); let base_text_snapshot = base_text_buffer.read(cx).snapshot(); let translated_ranges: Vec<Range<BufferOffset>> = ranges .iter() .map(|range| { let start_point = base_text_snapshot.offset_to_point(range.start.0); let end_point = base_text_snapshot.offset_to_point(range.end.0); let rhs_start = diff_snapshot .base_text_point_to_buffer_point(start_point, &rhs_buffer_snapshot); let rhs_end = diff_snapshot.base_text_point_to_buffer_point(end_point, &rhs_buffer_snapshot); BufferOffset(rhs_buffer_snapshot.point_to_offset(rhs_start)) ..BufferOffset(rhs_buffer_snapshot.point_to_offset(rhs_end)) }) .collect(); translated.insert(rhs_buffer, (translated_ranges, *scroll_offset)); } translated } struct SplitLhsDiffHunkDelegate { splittable: WeakEntity<SplittableEditor>, } impl DiffHunkDelegate for SplitLhsDiffHunkDelegate { fn toggle( &self, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.toggle(hunks, editor, window, cx); }); }) .log_err(); } fn stage_or_unstage( &self, stage: bool, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.stage_or_unstage(stage, hunks, editor, window, cx); }); }) .log_err(); } fn restore( &self, hunks: Vec<ResolvedDiffHunks>, _editor: &mut Editor, window: &mut Window, cx: &mut Context<Editor>, ) { self.splittable .update(cx, |splittable, cx| { splittable.rhs_editor.update(cx, |editor, cx| { let delegate = editor.diff_hunk_delegate(); delegate.restore(hunks, editor, window, 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 { let Some(splittable) = self.splittable.upgrade() else { return gpui::Empty.into_any_element(); }; let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); delegate.render_hunk_controls( row, status, hunk_range, is_created_file, line_height, editor, window, cx, ) } fn render_hunk_as_staged(&self, status: &DiffHunkStatus, cx: &App) -> bool { let Some(splittable) = self.splittable.upgrade() else { return false; }; let delegate = splittable.read(cx).rhs_editor.read(cx).diff_hunk_delegate(); delegate.render_hunk_as_staged(status, cx) } } fn patches_for_range<F>( source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_bounds: Range<MultiBufferPoint>, translate_fn: F, ) -> Vec<CompanionExcerptPatch> where F: Fn(&BufferDiffSnapshot, RangeInclusive<Point>, &text::BufferSnapshot) -> Patch<Point>, { struct PendingExcerpt<'a> { source_buffer_snapshot: &'a language::BufferSnapshot, source_excerpt_range: ExcerptRange<text::Anchor>, buffer_point_range: Range<Point>, } let mut result = Vec::new(); let mut current_buffer_id: Option<BufferId> = None; let mut pending_excerpts: Vec<PendingExcerpt<'_>> = Vec::new(); let mut union_context_start: Option<Point> = None; let mut union_context_end: Option<Point> = None; let flush_buffer = |pending: &mut Vec<PendingExcerpt>, union_start: Point, union_end: Point, result: &mut Vec<CompanionExcerptPatch>| { let Some(first) = pending.first() else { return; }; let source_buffer_id = first.source_buffer_snapshot.remote_id(); let Some(diff) = source_snapshot.diff_for_buffer_id(source_buffer_id) else { pending.clear(); return; }; let source_is_lhs = source_buffer_id == diff.base_text().remote_id(); let target_buffer_id = if source_is_lhs { diff.buffer_id() } else { diff.base_text().remote_id() }; let Some(target_buffer) = target_snapshot.buffer_for_id(target_buffer_id) else { pending.clear(); return; }; let rhs_buffer = if source_is_lhs { target_buffer } else { &first.source_buffer_snapshot }; let patch = translate_fn(diff, union_start..=union_end, rhs_buffer); let mut source_excerpts = source_snapshot .excerpts_for_buffer(source_buffer_id) .peekable(); let mut target_excerpts = target_snapshot .excerpts_for_buffer(target_buffer_id) .peekable(); for excerpt in pending.drain(..) { while let Some(source_excerpt_range) = source_excerpts.peek() && source_excerpt_range != &excerpt.source_excerpt_range { source_excerpts.next(); target_excerpts.next(); } if let Some(source_excerpt_range) = source_excerpts.peek() && let Some(target_excerpt_range) = target_excerpts.peek() { result.push(patch_for_excerpt( source_snapshot, target_snapshot, &excerpt.source_buffer_snapshot, target_buffer, source_excerpt_range.clone(), target_excerpt_range.clone(), &patch, excerpt.buffer_point_range, )); } } }; for (buffer_snapshot, source_range, source_excerpt_range) in source_snapshot.range_to_buffer_ranges(source_bounds) { let buffer_id = buffer_snapshot.remote_id(); if current_buffer_id != Some(buffer_id) { if let (Some(start), Some(end)) = (union_context_start.take(), union_context_end.take()) { flush_buffer(&mut pending_excerpts, start, end, &mut result); } current_buffer_id = Some(buffer_id); } let buffer_point_range = source_range.to_point(&buffer_snapshot); let source_context_range = source_excerpt_range.context.to_point(&buffer_snapshot); union_context_start = Some(union_context_start.map_or(source_context_range.start, |s| { s.min(source_context_range.start) })); union_context_end = Some(union_context_end.map_or(source_context_range.end, |e| { e.max(source_context_range.end) })); pending_excerpts.push(PendingExcerpt { source_buffer_snapshot: buffer_snapshot, source_excerpt_range, buffer_point_range, }); } if let (Some(start), Some(end)) = (union_context_start, union_context_end) { flush_buffer(&mut pending_excerpts, start, end, &mut result); } result } fn patch_for_excerpt( source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, source_buffer_snapshot: &language::BufferSnapshot, target_buffer_snapshot: &language::BufferSnapshot, source_excerpt_range: ExcerptRange<text::Anchor>, target_excerpt_range: ExcerptRange<text::Anchor>, patch: &Patch<Point>, source_edited_range: Range<Point>, ) -> CompanionExcerptPatch { let source_buffer_range = source_excerpt_range .context .to_point(source_buffer_snapshot); let source_multibuffer_range = (source_snapshot .anchor_in_buffer(source_excerpt_range.context.start) .expect("buffer should exist in multibuffer") ..source_snapshot .anchor_in_buffer(source_excerpt_range.context.end) .expect("buffer should exist in multibuffer")) .to_point(source_snapshot); let target_buffer_range = target_excerpt_range .context .to_point(target_buffer_snapshot); let target_multibuffer_range = (target_snapshot .anchor_in_buffer(target_excerpt_range.context.start) .expect("buffer should exist in multibuffer") ..target_snapshot .anchor_in_buffer(target_excerpt_range.context.end) .expect("buffer should exist in multibuffer")) .to_point(target_snapshot); let edits = patch .edits() .iter() .skip_while(|edit| edit.old.end < source_buffer_range.start) .take_while(|edit| edit.old.start <= source_buffer_range.end) .map(|edit| { let clamped_source_start = edit.old.start.max(source_buffer_range.start); let clamped_source_end = edit.old.end.min(source_buffer_range.end); let source_multibuffer_start = source_multibuffer_range.start + (clamped_source_start - source_buffer_range.start); let source_multibuffer_end = source_multibuffer_range.start + (clamped_source_end - source_buffer_range.start); let clamped_target_start = edit .new .start .max(target_buffer_range.start) .min(target_buffer_range.end); let clamped_target_end = edit .new .end .max(target_buffer_range.start) .min(target_buffer_range.end); let target_multibuffer_start = target_multibuffer_range.start + (clamped_target_start - target_buffer_range.start); let target_multibuffer_end = target_multibuffer_range.start + (clamped_target_end - target_buffer_range.start); text::Edit { old: source_multibuffer_start..source_multibuffer_end, new: target_multibuffer_start..target_multibuffer_end, } }); let edits = [text::Edit { old: source_multibuffer_range.start..source_multibuffer_range.start, new: target_multibuffer_range.start..target_multibuffer_range.start, }] .into_iter() .chain(edits); let mut merged_edits: Vec<text::Edit<Point>> = Vec::new(); for edit in edits { if let Some(last) = merged_edits.last_mut() { if edit.new.start <= last.new.end || edit.old.start <= last.old.end { last.old.end = last.old.end.max(edit.old.end); last.new.end = last.new.end.max(edit.new.end); continue; } } merged_edits.push(edit); } let edited_range = source_multibuffer_range.start + (source_edited_range.start - source_buffer_range.start) ..source_multibuffer_range.start + (source_edited_range.end - source_buffer_range.start); let source_excerpt_end = source_multibuffer_range.start + (source_buffer_range.end - source_buffer_range.start); let target_excerpt_end = target_multibuffer_range.start + (target_buffer_range.end - target_buffer_range.start); CompanionExcerptPatch { patch: Patch::new(merged_edits), edited_range, source_excerpt_range: source_multibuffer_range.start..source_excerpt_end, target_excerpt_range: target_multibuffer_range.start..target_excerpt_end, } } #[derive(Clone, Copy, PartialEq, Eq, Action, Default)] #[action(namespace = editor)] pub struct ToggleSplitDiff; /// Unified/split diff view toggle buttons, shared by the toolbars of every /// diff view (project diff, branch diff, solo diff) so they can't drift apart. #[derive(gpui::IntoElement)] pub struct DiffStyleControls { splittable_editor: Entity<SplittableEditor>, } impl DiffStyleControls { pub fn new(splittable_editor: Entity<SplittableEditor>) -> Self { Self { splittable_editor } } fn set_diff_view_style( splittable_editor: &Entity<SplittableEditor>, diff_view_style: DiffViewStyle, window: &mut Window, cx: &mut App, ) { update_settings_file(<dyn Fs>::global(cx), cx, move |settings, _| { settings.editor.diff_view_style = Some(diff_view_style); }); splittable_editor.update(cx, |editor, cx| { if editor.diff_view_style() != diff_view_style { editor.toggle_split(&ToggleSplitDiff, window, cx); } }); } } impl RenderOnce for DiffStyleControls {