3519 lines · 104.0 KB · Rust
RawDownload
use std::ops::{Bound, Range, RangeInclusive}; use buffer_diff::{BufferDiff, BufferDiffSnapshot};use collections::HashMap;use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};use gpui::{Action, AppContext as _, Entity, EventEmitter, Focusable, Subscription, WeakEntity};use language::{Buffer, Capability};use multi_buffer::{    Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, MultiBufferPoint,    MultiBufferSnapshot, PathKey,};use project::Project;use rope::Point;use text::{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,    split_editor_view::{SplitEditorState, SplitEditorView},};use workspace::{ActivatePaneLeft, ActivatePaneRight, Item, Workspace}; use crate::{    Autoscroll, DisplayMap, Editor, EditorEvent, ToggleCodeActions, 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 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>,{    let mut result = Vec::new();    let mut patches = HashMap::default();     for (source_buffer, buffer_offset_range, source_excerpt_id) in        source_snapshot.range_to_buffer_ranges(source_bounds)    {        let target_excerpt_id = excerpt_map.get(&source_excerpt_id).copied().unwrap();        let target_buffer = target_snapshot            .buffer_for_excerpt(target_excerpt_id)            .unwrap();        let patch = patches.entry(source_buffer.remote_id()).or_insert_with(|| {            let diff = source_snapshot                .diff_for_buffer_id(source_buffer.remote_id())                .unwrap();            let rhs_buffer = if source_buffer.remote_id() == diff.base_text().remote_id() {                &target_buffer            } else {                source_buffer            };            // TODO(split-diff) pass only the union of the ranges for the affected excerpts            translate_fn(diff, Point::zero()..=source_buffer.max_point(), rhs_buffer)        });        let buffer_point_range = buffer_offset_range.to_point(source_buffer);         // TODO(split-diff) maybe narrow the patch to only the edited part of the excerpt        // (less useful for project diff, but important if we want to do singleton side-by-side diff)        result.push(patch_for_excerpt(            source_snapshot,            target_snapshot,            source_excerpt_id,            target_excerpt_id,            source_buffer,            target_buffer,            patch,            buffer_point_range,        ));    }     result} fn patch_for_excerpt(    source_snapshot: &MultiBufferSnapshot,    target_snapshot: &MultiBufferSnapshot,    source_excerpt_id: ExcerptId,    target_excerpt_id: ExcerptId,    source_buffer: &text::BufferSnapshot,    target_buffer: &text::BufferSnapshot,    patch: &Patch<Point>,    source_edited_range: Range<Point>,) -> CompanionExcerptPatch {    let source_multibuffer_range = source_snapshot        .range_for_excerpt(source_excerpt_id)        .unwrap();    let source_excerpt_start_in_multibuffer = source_multibuffer_range.start;    let source_context_range = source_snapshot        .context_range_for_excerpt(source_excerpt_id)        .unwrap();    let source_excerpt_start_in_buffer = source_context_range.start.to_point(&source_buffer);    let source_excerpt_end_in_buffer = source_context_range.end.to_point(&source_buffer);    let target_multibuffer_range = target_snapshot        .range_for_excerpt(target_excerpt_id)        .unwrap();    let target_excerpt_start_in_multibuffer = target_multibuffer_range.start;    let target_context_range = target_snapshot        .context_range_for_excerpt(target_excerpt_id)        .unwrap();    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)                .min(source_excerpt_end_in_buffer);            let clamped_source_end = edit                .old                .end                .max(source_excerpt_start_in_buffer)                .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 {                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,    }} pub struct SplitDiffFeatureFlag; impl FeatureFlag for SplitDiffFeatureFlag {    const NAME: &'static str = "split-diff";     fn enabled_for_staff() -> bool {        true    }} #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]struct SplitDiff; #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]struct UnsplitDiff; #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]pub struct ToggleSplitDiff; #[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]struct JumpToCorrespondingRow; /// When locked cursors mode is enabled, cursor movements in one editor will/// update the cursor position in the other editor to the corresponding row.#[derive(Clone, Copy, PartialEq, Eq, Action, Default)]#[action(namespace = editor)]pub struct ToggleLockedCursors; pub struct SplittableEditor {    rhs_multibuffer: Entity<MultiBuffer>,    rhs_editor: Entity<Editor>,    lhs: Option<LhsEditor>,    workspace: WeakEntity<Workspace>,    split_state: Entity<SplitEditorState>,    locked_cursors: bool,    _subscriptions: Vec<Subscription>,} struct LhsEditor {    multibuffer: Entity<MultiBuffer>,    editor: Entity<Editor>,    has_latest_selection: 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 last_selected_editor(&self) -> &Entity<Editor> {        if let Some(lhs) = &self.lhs            && lhs.has_latest_selection        {            &lhs.editor        } else {            &self.rhs_editor        }    }     pub fn new_unsplit(        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        });        // 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);                    }                    EditorEvent::SelectionsChanged { .. } => {                        if let Some(lhs) = &mut this.lhs {                            lhs.has_latest_selection = false;                        }                        cx.emit(event.clone());                    }                    _ => cx.emit(event.clone()),                },            )];         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();            }        });        let split_state = cx.new(|cx| SplitEditorState::new(cx));        Self {            rhs_editor,            rhs_multibuffer,            lhs: None,            workspace: workspace.downgrade(),            split_state,            locked_cursors: false,            _subscriptions: subscriptions,        }    }     fn split(&mut self, _: &SplitDiff, window: &mut Window, cx: &mut Context<Self>) {        if !cx.has_flag::<SplitDiffFeatureFlag>() {            return;        }        if self.lhs.is_some() {            return;        }        let Some(workspace) = self.workspace.upgrade() else {            return;        };        let project = workspace.read(cx).project().clone();         let lhs_multibuffer = cx.new(|cx| {            let mut multibuffer = MultiBuffer::new(Capability::ReadOnly);            multibuffer.set_all_diff_hunks_expanded(cx);            multibuffer        });        let lhs_editor = cx.new(|cx| {            let mut editor =                Editor::for_multibuffer(lhs_multibuffer.clone(), Some(project.clone()), window, cx);            editor.set_number_deleted_lines(true, cx);            editor.set_delegate_expand_excerpts(true);            editor.set_show_vertical_scrollbar(false, cx);            editor.set_minimap_visibility(crate::MinimapVisibility::Disabled, window, cx);            editor        });         let subscriptions =            vec![cx.subscribe(                &lhs_editor,                |this, _, event: &EditorEvent, cx| match event {                    EditorEvent::ExpandExcerptsRequested {                        excerpt_ids,                        lines,                        direction,                    } => {                        if this.lhs.is_some() {                            let rhs_display_map = this.rhs_editor.read(cx).display_map.read(cx);                            let rhs_ids: Vec<_> = excerpt_ids                                .iter()                                .filter_map(|id| {                                    rhs_display_map.companion_excerpt_to_my_excerpt(*id, cx)                                })                                .collect();                            this.expand_excerpts(rhs_ids.into_iter(), *lines, *direction, cx);                        }                    }                    EditorEvent::SelectionsChanged { .. } => {                        if let Some(lhs) = &mut this.lhs {                            lhs.has_latest_selection = true;                        }                        cx.emit(event.clone());                    }                    _ => cx.emit(event.clone()),                },            )];        let mut lhs = LhsEditor {            editor: lhs_editor,            multibuffer: lhs_multibuffer,            has_latest_selection: false,            _subscriptions: subscriptions,        };        let rhs_display_map = self.rhs_editor.read(cx).display_map.clone();        let lhs_display_map = lhs.editor.read(cx).display_map.clone();        let rhs_display_map_id = rhs_display_map.entity_id();         self.rhs_editor.update(cx, |editor, cx| {            editor.set_delegate_expand_excerpts(true);            editor.buffer().update(cx, |rhs_multibuffer, cx| {                rhs_multibuffer.set_show_deleted_hunks(false, cx);                rhs_multibuffer.set_use_extended_diff_range(true, cx);            })        });         let path_diffs: Vec<_> = {            let rhs_multibuffer = self.rhs_multibuffer.read(cx);            rhs_multibuffer                .paths()                .filter_map(|path| {                    let excerpt_id = rhs_multibuffer.excerpts_for_path(path).next()?;                    let snapshot = rhs_multibuffer.snapshot(cx);                    let buffer = snapshot.buffer_for_excerpt(excerpt_id)?;                    let diff = rhs_multibuffer.diff_for(buffer.remote_id())?;                    Some((path.clone(), diff))                })                .collect()        };         let rhs_folded_buffers = rhs_display_map.read(cx).folded_buffers().clone();         let mut companion = Companion::new(            rhs_display_map_id,            rhs_folded_buffers,            convert_rhs_rows_to_lhs,            convert_lhs_rows_to_rhs,        );         for (path, diff) in path_diffs {            for (lhs, rhs) in                lhs.update_path_excerpts_from_rhs(path, &self.rhs_multibuffer, diff.clone(), cx)            {                companion.add_excerpt_mapping(lhs, rhs);            }            companion.add_buffer_mapping(                diff.read(cx).base_text(cx).remote_id(),                diff.read(cx).buffer_id,            );        }         let companion = cx.new(|_| companion);         rhs_display_map.update(cx, |dm, cx| {            dm.set_companion(Some((lhs_display_map.downgrade(), companion.clone())), cx);        });        lhs_display_map.update(cx, |dm, cx| {            dm.set_companion(Some((rhs_display_map.downgrade(), companion)), cx);        });         let shared_scroll_anchor = self            .rhs_editor            .read(cx)            .scroll_manager            .scroll_anchor_entity();        lhs.editor.update(cx, |editor, _cx| {            editor                .scroll_manager                .set_shared_scroll_anchor(shared_scroll_anchor);        });         let this = cx.entity().downgrade();        self.rhs_editor.update(cx, |editor, _cx| {            let this = this.clone();            editor.set_on_local_selections_changed(Some(Box::new(                move |cursor_position, window, cx| {                    let this = this.clone();                    window.defer(cx, move |window, cx| {                        this.update(cx, |this, cx| {                            if this.locked_cursors {                                this.sync_cursor_to_other_side(true, cursor_position, window, cx);                            }                        })                        .ok();                    })                },            )));        });        lhs.editor.update(cx, |editor, _cx| {            let this = this.clone();            editor.set_on_local_selections_changed(Some(Box::new(

Lines 1–500 of 3519.