6061 lines · 182.2 KB · Rust
RawDownload
use std::{    ops::{Bound, Range, RangeInclusive},    sync::Arc,}; use buffer_diff::{BufferDiff, BufferDiffSnapshot};use collections::HashMap; use gpui::{    Action, AppContext as _, Entity, EventEmitter, Focusable, Font, Subscription, WeakEntity,};use itertools::Itertools;use language::{Buffer, Capability, HighlightedText};use multi_buffer::{    Anchor, BufferOffset, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer,    MultiBufferDiffHunk, MultiBufferPoint, MultiBufferSnapshot, PathKey,};use project::Project;use rope::Point;use settings::DiffViewStyle;use text::{Bias, BufferId, OffsetRangeExt as _, Patch, ToPoint as _};use ui::{    App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,    Styled as _, Window, div,}; 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, Editor, EditorEvent, RenderDiffHunkControlsFn, ToggleSoftWrap,    actions::{DisableBreakpoint, EditLogBreakpoint, EnableBreakpoint, ToggleBreakpoint},    display_map::Companion,};use zed_actions::assistant::InlineAssist; pub(crate) fn convert_lhs_rows_to_rhs(    lhs_excerpt_to_rhs_excerpt: &HashMap<ExcerptId, ExcerptId>,    rhs_snapshot: &MultiBufferSnapshot,    lhs_snapshot: &MultiBufferSnapshot,    lhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),) -> Vec<CompanionExcerptPatch> {    patches_for_range(        lhs_excerpt_to_rhs_excerpt,        lhs_snapshot,        rhs_snapshot,        lhs_bounds,        |diff, range, buffer| diff.patch_for_base_text_range(range, buffer),    )} pub(crate) fn convert_rhs_rows_to_lhs(    rhs_excerpt_to_lhs_excerpt: &HashMap<ExcerptId, ExcerptId>,    lhs_snapshot: &MultiBufferSnapshot,    rhs_snapshot: &MultiBufferSnapshot,    rhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),) -> Vec<CompanionExcerptPatch> {    patches_for_range(        rhs_excerpt_to_lhs_excerpt,        rhs_snapshot,        lhs_snapshot,        rhs_bounds,        |diff, range, buffer| diff.patch_for_buffer_range(range, buffer),    )} 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 rhs_display_map = splittable.rhs_editor.read(cx).display_map.read(cx);    let Some(companion) = rhs_display_map.companion() else {        return HashMap::default();    };    let companion = companion.read(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(rhs_buffer_id) = companion.lhs_to_rhs_buffer(*lhs_buffer_id) else {            continue;        };         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} fn translate_lhs_hunks_to_rhs(    lhs_hunks: &[MultiBufferDiffHunk],    splittable: &SplittableEditor,    cx: &App,) -> Vec<MultiBufferDiffHunk> {    let rhs_display_map = splittable.rhs_editor.read(cx).display_map.read(cx);    let Some(companion) = rhs_display_map.companion() else {        return vec![];    };    let companion = companion.read(cx);    let rhs_snapshot = splittable.rhs_multibuffer.read(cx).snapshot(cx);    let rhs_hunks: Vec<MultiBufferDiffHunk> = rhs_snapshot.diff_hunks().collect();     let mut translated = Vec::new();    for lhs_hunk in lhs_hunks {        let Some(rhs_buffer_id) = companion.lhs_to_rhs_buffer(lhs_hunk.buffer_id) else {            continue;        };        if let Some(rhs_hunk) = rhs_hunks.iter().find(|rhs_hunk| {            rhs_hunk.buffer_id == rhs_buffer_id                && rhs_hunk.diff_base_byte_range == lhs_hunk.diff_base_byte_range        }) {            translated.push(rhs_hunk.clone());        }    }    translated} fn patches_for_range<F>(    excerpt_map: &HashMap<ExcerptId, ExcerptId>,    source_snapshot: &MultiBufferSnapshot,    target_snapshot: &MultiBufferSnapshot,    source_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),    translate_fn: F,) -> Vec<CompanionExcerptPatch>where    F: Fn(&BufferDiffSnapshot, RangeInclusive<Point>, &text::BufferSnapshot) -> Patch<Point>,{    struct PendingExcerpt<'a> {        source_excerpt_id: ExcerptId,        target_excerpt_id: ExcerptId,        source_buffer: &'a text::BufferSnapshot,        target_buffer: &'a text::BufferSnapshot,        buffer_point_range: Range<Point>,        source_context_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 diff = source_snapshot            .diff_for_buffer_id(first.source_buffer.remote_id())            .expect("buffer with no diff when creating patches");        let rhs_buffer = if first.source_buffer.remote_id() == diff.base_text().remote_id() {            first.target_buffer        } else {            first.source_buffer        };         let patch = translate_fn(diff, union_start..=union_end, rhs_buffer);         for excerpt in pending.drain(..) {            result.push(patch_for_excerpt(                source_snapshot,                target_snapshot,                excerpt.source_excerpt_id,                excerpt.target_excerpt_id,                excerpt.target_buffer,                excerpt.source_context_range,                &patch,                excerpt.buffer_point_range,            ));        }    };     for (source_buffer, buffer_offset_range, source_excerpt_id, source_context_range) in        source_snapshot.range_to_buffer_ranges_with_context(source_bounds)    {        let Some(target_excerpt_id) = excerpt_map.get(&source_excerpt_id).copied() else {            continue;        };        let Some(target_buffer) = target_snapshot.buffer_for_excerpt(target_excerpt_id) else {            continue;        };         let buffer_id = source_buffer.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 = buffer_offset_range.to_point(source_buffer);        let source_context_range = source_context_range.to_point(source_buffer);         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_excerpt_id,            target_excerpt_id,            source_buffer,            target_buffer,            buffer_point_range,            source_context_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_excerpt_id: ExcerptId,    target_excerpt_id: ExcerptId,    target_buffer: &text::BufferSnapshot,    source_context_range: Range<Point>,    patch: &Patch<Point>,    source_edited_range: Range<Point>,) -> CompanionExcerptPatch {    let source_multibuffer_range = source_snapshot        .range_for_excerpt(source_excerpt_id)        .expect("no excerpt for source id when creating patch");    let source_excerpt_start_in_multibuffer = source_multibuffer_range.start;    let source_excerpt_start_in_buffer = source_context_range.start;    let source_excerpt_end_in_buffer = source_context_range.end;    let target_multibuffer_range = target_snapshot        .range_for_excerpt(target_excerpt_id)        .expect("no excerpt for target id when creating patch");    let target_excerpt_start_in_multibuffer = target_multibuffer_range.start;    let target_context_range = target_snapshot        .context_range_for_excerpt(target_excerpt_id)        .expect("no range for target id when creating patch");    let target_excerpt_start_in_buffer = target_context_range.start.to_point(&target_buffer);    let target_excerpt_end_in_buffer = target_context_range.end.to_point(&target_buffer);     let edits = patch        .edits()        .iter()        .skip_while(|edit| edit.old.end < source_excerpt_start_in_buffer)        .take_while(|edit| edit.old.start <= source_excerpt_end_in_buffer)        .map(|edit| {            let clamped_source_start = edit.old.start.max(source_excerpt_start_in_buffer);            let clamped_source_end = edit.old.end.min(source_excerpt_end_in_buffer);            let source_multibuffer_start = source_excerpt_start_in_multibuffer                + (clamped_source_start - source_excerpt_start_in_buffer);            let source_multibuffer_end = source_excerpt_start_in_multibuffer                + (clamped_source_end - source_excerpt_start_in_buffer);            let clamped_target_start = edit                .new                .start                .max(target_excerpt_start_in_buffer)                .min(target_excerpt_end_in_buffer);            let clamped_target_end = edit                .new                .end                .max(target_excerpt_start_in_buffer)                .min(target_excerpt_end_in_buffer);            let target_multibuffer_start = target_excerpt_start_in_multibuffer                + (clamped_target_start - target_excerpt_start_in_buffer);            let target_multibuffer_end = target_excerpt_start_in_multibuffer                + (clamped_target_end - target_excerpt_start_in_buffer);            text::Edit {                old: source_multibuffer_start..source_multibuffer_end,                new: target_multibuffer_start..target_multibuffer_end,            }        });     let edits = [text::Edit {        old: source_excerpt_start_in_multibuffer..source_excerpt_start_in_multibuffer,        new: target_excerpt_start_in_multibuffer..target_excerpt_start_in_multibuffer,    }]    .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_excerpt_start_in_multibuffer        + (source_edited_range.start - source_excerpt_start_in_buffer)        ..source_excerpt_start_in_multibuffer            + (source_edited_range.end - source_excerpt_start_in_buffer);     let source_excerpt_end = source_excerpt_start_in_multibuffer        + (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer);    let target_excerpt_end = target_excerpt_start_in_multibuffer        + (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer);     CompanionExcerptPatch {        patch: Patch::new(merged_edits),        edited_range,        source_excerpt_range: source_excerpt_start_in_multibuffer..source_excerpt_end,        target_excerpt_range: target_excerpt_start_in_multibuffer..target_excerpt_end,    }} #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]pub struct ToggleSplitDiff; pub struct SplittableEditor {    rhs_multibuffer: Entity<MultiBuffer>,    rhs_editor: Entity<Editor>,    lhs: Option<LhsEditor>,    workspace: WeakEntity<Workspace>,    split_state: Entity<SplitEditorState>,    searched_side: Option<SplitSide>,    _subscriptions: Vec<Subscription>,} struct LhsEditor {    multibuffer: Entity<MultiBuffer>,    editor: Entity<Editor>,    was_last_focused: bool,    _subscriptions: Vec<Subscription>,} impl SplittableEditor {    pub fn rhs_editor(&self) -> &Entity<Editor> {        &self.rhs_editor    }     pub fn lhs_editor(&self) -> Option<&Entity<Editor>> {        self.lhs.as_ref().map(|s| &s.editor)    }     pub fn is_split(&self) -> bool {        self.lhs.is_some()    }     pub fn set_render_diff_hunk_controls(        &self,        render_diff_hunk_controls: RenderDiffHunkControlsFn,        cx: &mut Context<Self>,    ) {        self.rhs_editor.update(cx, |editor, cx| {            editor.set_render_diff_hunk_controls(render_diff_hunk_controls.clone(), cx);        });         if let Some(lhs) = &self.lhs {            lhs.editor.update(cx, |editor, cx| {                editor.set_render_diff_hunk_controls(render_diff_hunk_controls.clone(), cx);            });        }    }     fn focused_side(&self) -> SplitSide {        if let Some(lhs) = &self.lhs            && lhs.was_last_focused        {            SplitSide::Left        } else {            SplitSide::Right        }    }     pub fn focused_editor(&self) -> &Entity<Editor> {        if let Some(lhs) = &self.lhs            && lhs.was_last_focused        {            &lhs.editor        } else {            &self.rhs_editor        }    }     pub fn new(        style: DiffViewStyle,        rhs_multibuffer: Entity<MultiBuffer>,        project: Entity<Project>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let rhs_editor = cx.new(|cx| {            let mut editor =                Editor::for_multibuffer(rhs_multibuffer.clone(), Some(project.clone()), window, cx);            editor.set_expand_all_diff_hunks(cx);            editor.disable_runnables();            editor.disable_diagnostics(cx);            editor.set_minimap_visibility(crate::MinimapVisibility::Disabled, window, cx);            editor        });        // TODO(split-diff) we might want to tag editor events with whether they came from rhs/lhs        let subscriptions = vec![            cx.subscribe(                &rhs_editor,                |this, _, event: &EditorEvent, cx| match event {                    EditorEvent::ExpandExcerptsRequested {                        excerpt_ids,                        lines,                        direction,                    } => {                        this.expand_excerpts(excerpt_ids.iter().copied(), *lines, *direction, cx);                    }                    _ => cx.emit(event.clone()),                },            ),            cx.subscribe(&rhs_editor, |this, _, event: &SearchEvent, cx| {                if this.searched_side.is_none() || this.searched_side == Some(SplitSide::Right) {                    cx.emit(event.clone());                }            }),        ];         let this = cx.weak_entity();        window.defer(cx, {            let workspace = workspace.downgrade();            let rhs_editor = rhs_editor.downgrade();            move |window, cx| {                workspace                    .update(cx, |workspace, cx| {                        rhs_editor                            .update(cx, |editor, cx| {                                editor.added_to_workspace(workspace, window, cx);                            })                            .ok();                    })                    .ok();                if style == DiffViewStyle::Split {                    this.update(cx, |this, cx| {                        this.split(window, cx);                    })                    .ok();                }            }        });        let split_state = cx.new(|cx| SplitEditorState::new(cx));

Lines 1–500 of 6061.