1323 lines · 59.1 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, ToPoint,};use std::{ops::Range, sync::Arc};use ui::{    prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,    ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,};use util::RangeExt; use crate::{    editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, BlockDisposition,    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<()>>,} #[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(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>,    ) {        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 remaining_hunk in hunks_to_toggle {                        let remaining_hunk_point_range =                            Point::new(remaining_hunk.row_range.start.0, 0)                                ..Point::new(remaining_hunk.row_range.end.0, 0);                        hunks_to_expand.push(HoveredHunk {                            status: hunk_status(&remaining_hunk),                            multi_buffer_range: snapshot                                .buffer_snapshot                                .anchor_before(remaining_hunk_point_range.start)                                ..snapshot                                    .buffer_snapshot                                    .anchor_after(remaining_hunk_point_range.end),                            diff_base_byte_range: remaining_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 multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);        let multi_buffer_row_range = hunk            .multi_buffer_range            .start            .to_point(&multi_buffer_snapshot)            ..hunk.multi_buffer_range.end.to_point(&multi_buffer_snapshot);        let hunk_start = hunk.multi_buffer_range.start;        let hunk_end = hunk.multi_buffer_range.end;         let buffer = self.buffer().clone();        let snapshot = self.snapshot(cx);        let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {            let hunk = buffer_diff_hunk(&snapshot.buffer_snapshot, multi_buffer_row_range.clone())?;            let mut buffer_ranges = buffer.range_to_buffer_ranges(multi_buffer_row_range, cx);            if buffer_ranges.len() == 1 {                let (buffer, _, _) = buffer_ranges.pop()?;                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 buffer = buffer.read(cx);                let deleted_text_lines = buffer.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))            } else {                None            }        })?;         let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {            probe                .hunk_range                .start                .cmp(&hunk_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_start..hunk_end,                    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_start..hunk_end,                    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: hunk_start..hunk_end,                status: hunk.status,                folded: false,                diff_base_byte_range: hunk.diff_base_byte_range.clone(),            },        );         Some(())    }     fn hunk_header_block(        &self,        hunk: &HoveredHunk,        cx: &mut ViewContext<'_, Editor>,    ) -> BlockProperties<Anchor> {        let border_color = cx.theme().colors().border_variant;        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 {            position: hunk.multi_buffer_range.start,            height: 1,            style: BlockStyle::Sticky,            disposition: BlockDisposition::Above,            priority: 0,            render: Box::new({                let editor = cx.view().clone();                let hunk = hunk.clone();                move |cx| {                    let hunk_controls_menu_handle =                        editor.read(cx).hunk_controls_menu_handle.clone();                     h_flex()                        .id(cx.block_id)                        .h(cx.line_height())                        .w_full()                        .border_t_1()                        .border_color(border_color)                        .bg(cx.theme().colors().editor_background)                        .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()                                .pl_2()                                .pr_6()                                .size_full()                                .justify_between()                                .child(                                    h_flex()                                        .gap_1()                                        .child(                                            IconButton::new("next-hunk", IconName::ArrowDown)                                                .shape(IconButtonShape::Square)                                                .icon_size(IconSize::Small)                                                .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| {                                                            let snapshot = editor.snapshot(cx);                                                            let position = hunk                                                                .multi_buffer_range                                                                .end                                                                .to_point(                                                                    &snapshot.buffer_snapshot,                                                                );                                                            if let Some(hunk) = editor                                                                .go_to_hunk_after_position(                                                                    &snapshot, position, cx,                                                                )                                                            {                                                                let multi_buffer_start = snapshot                                                                    .buffer_snapshot                                                                    .anchor_before(Point::new(                                                                        hunk.row_range.start.0,                                                                        0,                                                                    ));                                                                let multi_buffer_end = snapshot                                                                    .buffer_snapshot                                                                    .anchor_after(Point::new(                                                                        hunk.row_range.end.0,                                                                        0,                                                                    ));                                                                editor.expand_diff_hunk(                                                                    None,                                                                    &HoveredHunk {                                                                        multi_buffer_range:                                                                            multi_buffer_start                                                                                ..multi_buffer_end,                                                                        status: hunk_status(&hunk),                                                                        diff_base_byte_range: hunk                                                                            .diff_base_byte_range,                                                                    },                                                                    cx,                                                                );                                                            }                                                        });                                                    }                                                }),                                        )                                        .child(                                            IconButton::new("prev-hunk", IconName::ArrowUp)                                                .shape(IconButtonShape::Square)                                                .icon_size(IconSize::Small)                                                .tooltip({                                                    let focus_handle = editor.focus_handle(cx);                                                    move |cx| {                                                        Tooltip::for_action_in(                                                            "Previous Hunk",                                                            &GoToPrevHunk,                                                            &focus_handle,                                                            cx,                                                        )                                                    }                                                })                                                .on_click({                                                    let editor = editor.clone();                                                    let hunk = hunk.clone();                                                    move |_event, cx| {                                                        editor.update(cx, |editor, cx| {                                                            let snapshot = editor.snapshot(cx);                                                            let position = hunk                                                                .multi_buffer_range                                                                .start                                                                .to_point(                                                                    &snapshot.buffer_snapshot,                                                                );                                                            let hunk = editor                                                                .go_to_hunk_before_position(                                                                    &snapshot, position, cx,                                                                );                                                            if let Some(hunk) = hunk {                                                                let multi_buffer_start = snapshot                                                                    .buffer_snapshot                                                                    .anchor_before(Point::new(                                                                        hunk.row_range.start.0,                                                                        0,                                                                    ));                                                                let multi_buffer_end = snapshot                                                                    .buffer_snapshot                                                                    .anchor_after(Point::new(                                                                        hunk.row_range.end.0,                                                                        0,                                                                    ));                                                                editor.expand_diff_hunk(                                                                    None,

Lines 1–500 of 1323.