1460 lines · 63.2 KB · Rust
RawDownload
use collections::{hash_map, HashMap, HashSet};use git::diff::DiffHunkStatus;use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Task, View};use language::{Buffer, BufferId, Point};use multi_buffer::{    Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,    MultiBufferSnapshot, ToMultiBufferPoint,};use std::{ops::Range, sync::Arc};use text::OffsetRangeExt;use ui::{    prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,    ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,};use util::RangeExt;use workspace::Item; use crate::{    editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,    ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,    DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,    RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,}; #[derive(Debug, Clone)]pub(super) struct HoveredHunk {    pub multi_buffer_range: Range<Anchor>,    pub status: DiffHunkStatus,    pub diff_base_byte_range: Range<usize>,} #[derive(Debug, Default)]pub(super) struct ExpandedHunks {    pub(crate) hunks: Vec<ExpandedHunk>,    diff_base: HashMap<BufferId, DiffBaseBuffer>,    hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,    expand_all: bool,} #[derive(Debug, Clone)]pub(super) struct ExpandedHunk {    pub blocks: Vec<CustomBlockId>,    pub hunk_range: Range<Anchor>,    pub diff_base_byte_range: Range<usize>,    pub status: DiffHunkStatus,    pub folded: bool,} #[derive(Debug)]struct DiffBaseBuffer {    buffer: Model<Buffer>,    diff_base_version: usize,} #[derive(Debug, Clone, PartialEq, Eq)]pub enum DisplayDiffHunk {    Folded {        display_row: DisplayRow,    },     Unfolded {        diff_base_byte_range: Range<usize>,        display_row_range: Range<DisplayRow>,        multi_buffer_range: Range<Anchor>,        status: DiffHunkStatus,    },} impl ExpandedHunks {    pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {        self.hunks            .iter()            .filter(move |hunk| include_folded || !hunk.folded)    }} impl Editor {    pub fn set_expand_all_diff_hunks(&mut self) {        self.expanded_hunks.expand_all = true;    }     pub(super) fn toggle_hovered_hunk(        &mut self,        hovered_hunk: &HoveredHunk,        cx: &mut ViewContext<Editor>,    ) {        let editor_snapshot = self.snapshot(cx);        if let Some(diff_hunk) = to_diff_hunk(hovered_hunk, &editor_snapshot.buffer_snapshot) {            self.toggle_hunks_expanded(vec![diff_hunk], cx);            self.change_selections(None, cx, |selections| selections.refresh());        }    }     pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {        let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);        let selections = self.selections.disjoint_anchors();        self.toggle_hunks_expanded(            hunks_for_selections(&multi_buffer_snapshot, &selections),            cx,        );    }     pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {        let snapshot = self.snapshot(cx);        let display_rows_with_expanded_hunks = self            .expanded_hunks            .hunks(false)            .map(|hunk| &hunk.hunk_range)            .map(|anchor_range| {                (                    anchor_range                        .start                        .to_display_point(&snapshot.display_snapshot)                        .row(),                    anchor_range                        .end                        .to_display_point(&snapshot.display_snapshot)                        .row(),                )            })            .collect::<HashMap<_, _>>();        let hunks = snapshot            .display_snapshot            .buffer_snapshot            .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)            .filter(|hunk| {                let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)                    .to_display_point(&snapshot.display_snapshot)                    ..Point::new(hunk.row_range.end.0, 0)                        .to_display_point(&snapshot.display_snapshot);                let row_range_end =                    display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());                row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())            });        self.toggle_hunks_expanded(hunks.collect(), cx);    }     fn toggle_hunks_expanded(        &mut self,        hunks_to_toggle: Vec<MultiBufferDiffHunk>,        cx: &mut ViewContext<Self>,    ) {        if self.expanded_hunks.expand_all {            return;        }         let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);        let new_toggle_task = cx.spawn(move |editor, mut cx| async move {            if let Some(task) = previous_toggle_task {                task.await;            }             editor                .update(&mut cx, |editor, cx| {                    let snapshot = editor.snapshot(cx);                    let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();                    let mut highlights_to_remove =                        Vec::with_capacity(editor.expanded_hunks.hunks.len());                    let mut blocks_to_remove = HashSet::default();                    let mut hunks_to_expand = Vec::new();                    editor.expanded_hunks.hunks.retain(|expanded_hunk| {                        if expanded_hunk.folded {                            return true;                        }                        let expanded_hunk_row_range = expanded_hunk                            .hunk_range                            .start                            .to_display_point(&snapshot)                            .row()                            ..expanded_hunk                                .hunk_range                                .end                                .to_display_point(&snapshot)                                .row();                        let mut retain = true;                        while let Some(hunk_to_toggle) = hunks_to_toggle.peek() {                            match diff_hunk_to_display(hunk_to_toggle, &snapshot) {                                DisplayDiffHunk::Folded { .. } => {                                    hunks_to_toggle.next();                                    continue;                                }                                DisplayDiffHunk::Unfolded {                                    diff_base_byte_range,                                    display_row_range,                                    multi_buffer_range,                                    status,                                } => {                                    let hunk_to_toggle_row_range = display_row_range;                                    if hunk_to_toggle_row_range.start > expanded_hunk_row_range.end                                    {                                        break;                                    } else if expanded_hunk_row_range == hunk_to_toggle_row_range {                                        highlights_to_remove.push(expanded_hunk.hunk_range.clone());                                        blocks_to_remove                                            .extend(expanded_hunk.blocks.iter().copied());                                        hunks_to_toggle.next();                                        retain = false;                                        break;                                    } else {                                        hunks_to_expand.push(HoveredHunk {                                            status,                                            multi_buffer_range,                                            diff_base_byte_range,                                        });                                        hunks_to_toggle.next();                                        continue;                                    }                                }                            }                        }                         retain                    });                    for hunk in hunks_to_toggle {                        let remaining_hunk_point_range = Point::new(hunk.row_range.start.0, 0)                            ..Point::new(hunk.row_range.end.0, 0);                        let hunk_start = snapshot                            .buffer_snapshot                            .anchor_before(remaining_hunk_point_range.start);                        let hunk_end = snapshot                            .buffer_snapshot                            .anchor_in_excerpt(hunk_start.excerpt_id, hunk.buffer_range.end)                            .unwrap();                        hunks_to_expand.push(HoveredHunk {                            status: hunk_status(&hunk),                            multi_buffer_range: hunk_start..hunk_end,                            diff_base_byte_range: hunk.diff_base_byte_range.clone(),                        });                    }                     editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);                    editor.remove_blocks(blocks_to_remove, None, cx);                    for hunk in hunks_to_expand {                        editor.expand_diff_hunk(None, &hunk, cx);                    }                    cx.notify();                })                .ok();        });         self.expanded_hunks            .hunk_update_tasks            .insert(None, cx.background_executor().spawn(new_toggle_task));    }     pub(super) fn expand_diff_hunk(        &mut self,        diff_base_buffer: Option<Model<Buffer>>,        hunk: &HoveredHunk,        cx: &mut ViewContext<'_, Editor>,    ) -> Option<()> {        let buffer = self.buffer.clone();        let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);        let hunk_range = hunk.multi_buffer_range.clone();        let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {            let buffer = buffer.buffer(hunk_range.start.buffer_id?)?;            let diff_base_buffer = diff_base_buffer                .or_else(|| self.current_diff_base_buffer(&buffer, cx))                .or_else(|| create_diff_base_buffer(&buffer, cx))?;            let deleted_text_lines = buffer.read(cx).diff_base().map(|diff_base| {                let diff_start_row = diff_base                    .offset_to_point(hunk.diff_base_byte_range.start)                    .row;                let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;                diff_end_row - diff_start_row            })?;            Some((diff_base_buffer, deleted_text_lines))        })?;         let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {            probe                .hunk_range                .start                .cmp(&hunk_range.start, &multi_buffer_snapshot)        }) {            Ok(_already_present) => return None,            Err(ix) => ix,        };         let blocks;        match hunk.status {            DiffHunkStatus::Removed => {                blocks = self.insert_blocks(                    [                        self.hunk_header_block(&hunk, cx),                        Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),                    ],                    None,                    cx,                );            }            DiffHunkStatus::Added => {                self.highlight_rows::<DiffRowHighlight>(                    hunk_range.clone(),                    added_hunk_color(cx),                    false,                    cx,                );                blocks = self.insert_blocks([self.hunk_header_block(&hunk, cx)], None, cx);            }            DiffHunkStatus::Modified => {                self.highlight_rows::<DiffRowHighlight>(                    hunk_range.clone(),                    added_hunk_color(cx),                    false,                    cx,                );                blocks = self.insert_blocks(                    [                        self.hunk_header_block(&hunk, cx),                        Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),                    ],                    None,                    cx,                );            }        };        self.expanded_hunks.hunks.insert(            block_insert_index,            ExpandedHunk {                blocks,                hunk_range,                status: hunk.status,                folded: false,                diff_base_byte_range: hunk.diff_base_byte_range.clone(),            },        );         Some(())    }     fn apply_diff_hunks_in_range(        &mut self,        range: Range<Anchor>,        cx: &mut ViewContext<'_, Editor>,    ) -> Option<()> {        let (buffer, range, _) = self            .buffer            .read(cx)            .range_to_buffer_ranges(range, cx)            .into_iter()            .next()?;         buffer.update(cx, |branch_buffer, cx| {            branch_buffer.merge_into_base(vec![range], cx);        });         if let Some(project) = self.project.clone() {            self.save(true, project, cx).detach_and_log_err(cx);        }         None    }     pub(crate) fn apply_all_diff_hunks(        &mut self,        _: &ApplyAllDiffHunks,        cx: &mut ViewContext<Self>,    ) {        let buffers = self.buffer.read(cx).all_buffers();        for branch_buffer in buffers {            branch_buffer.update(cx, |branch_buffer, cx| {                branch_buffer.merge_into_base(Vec::new(), cx);            });        }         if let Some(project) = self.project.clone() {            self.save(true, project, cx).detach_and_log_err(cx);        }    }     pub(crate) fn apply_selected_diff_hunks(        &mut self,        _: &ApplyDiffHunk,        cx: &mut ViewContext<Self>,    ) {        let snapshot = self.buffer.read(cx).snapshot(cx);        let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());        let mut ranges_by_buffer = HashMap::default();        self.transact(cx, |editor, cx| {            for hunk in hunks {                if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {                    ranges_by_buffer                        .entry(buffer.clone())                        .or_insert_with(Vec::new)                        .push(hunk.buffer_range.to_offset(buffer.read(cx)));                }            }             for (buffer, ranges) in ranges_by_buffer {                buffer.update(cx, |buffer, cx| {                    buffer.merge_into_base(ranges, cx);                });            }        });         if let Some(project) = self.project.clone() {            self.save(true, project, cx).detach_and_log_err(cx);        }    }     fn has_multiple_hunks(&self, cx: &AppContext) -> bool {        let snapshot = self.buffer.read(cx).snapshot(cx);        let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);        hunks.nth(1).is_some()    }     fn hunk_header_block(        &self,        hunk: &HoveredHunk,        cx: &mut ViewContext<'_, Editor>,    ) -> BlockProperties<Anchor> {        let is_branch_buffer = self            .buffer            .read(cx)            .point_to_buffer_offset(hunk.multi_buffer_range.start, cx)            .map_or(false, |(buffer, _, _)| {                buffer.read(cx).diff_base_buffer().is_some()            });         let border_color = cx.theme().colors().border_variant;        let bg_color = cx.theme().colors().editor_background;        let gutter_color = match hunk.status {            DiffHunkStatus::Added => cx.theme().status().created,            DiffHunkStatus::Modified => cx.theme().status().modified,            DiffHunkStatus::Removed => cx.theme().status().deleted,        };         BlockProperties {            placement: BlockPlacement::Above(hunk.multi_buffer_range.start),            height: 1,            style: BlockStyle::Sticky,            priority: 0,            render: Arc::new({                let editor = cx.view().clone();                let hunk = hunk.clone();                let has_multiple_hunks = self.has_multiple_hunks(cx);                 move |cx| {                    let hunk_controls_menu_handle =                        editor.read(cx).hunk_controls_menu_handle.clone();                     h_flex()                        .id(cx.block_id)                        .block_mouse_down()                        .h(cx.line_height())                        .w_full()                        .border_t_1()                        .border_color(border_color)                        .bg(bg_color)                        .child(                            div()                                .id("gutter-strip")                                .w(EditorElement::diff_hunk_strip_width(cx.line_height()))                                .h_full()                                .bg(gutter_color)                                .cursor(CursorStyle::PointingHand)                                .on_click({                                    let editor = editor.clone();                                    let hunk = hunk.clone();                                    move |_event, cx| {                                        editor.update(cx, |editor, cx| {                                            editor.toggle_hovered_hunk(&hunk, cx);                                        });                                    }                                }),                        )                        .child(                            h_flex()                                .px_6()                                .size_full()                                .justify_end()                                .child(                                    h_flex()                                        .gap_1()                                        .when(!is_branch_buffer, |row| {                                            row.child(                                                IconButton::new("next-hunk", IconName::ArrowDown)                                                    .shape(IconButtonShape::Square)                                                    .icon_size(IconSize::Small)                                                    .disabled(!has_multiple_hunks)                                                    .tooltip({                                                        let focus_handle = editor.focus_handle(cx);                                                        move |cx| {                                                            Tooltip::for_action_in(                                                                "Next Hunk",                                                                &GoToHunk,                                                                &focus_handle,                                                                cx,                                                            )                                                        }                                                    })                                                    .on_click({                                                        let editor = editor.clone();                                                        let hunk = hunk.clone();                                                        move |_event, cx| {                                                            editor.update(cx, |editor, cx| {                                                                editor.go_to_subsequent_hunk(                                                                    hunk.multi_buffer_range.end,                                                                    cx,

Lines 1–500 of 1460.