2496 lines · 92.4 KB · Rust
RawDownload
use collections::{BTreeMap, HashMap};use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};use git::{    BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote,    parse_git_remote_url,    repository::{CommitDiff, InitialGraphCommitData, LogOrder, LogSource},};use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView};use gpui::{    AnyElement, App, Bounds, ClipboardItem, Context, Corner, DefiniteLength, ElementId, Entity,    EventEmitter, FocusHandle, Focusable, FontWeight, Hsla, InteractiveElement, ParentElement,    PathBuilder, Pixels, Point, Render, ScrollStrategy, ScrollWheelEvent, SharedString, Styled,    Subscription, Task, WeakEntity, Window, actions, anchored, deferred, point, px,};use menu::{SelectNext, SelectPrevious};use project::{    Project,    git_store::{CommitDataState, GitStoreEvent, Repository, RepositoryEvent},};use settings::Settings;use smallvec::{SmallVec, smallvec};use std::{ops::Range, rc::Rc, sync::Arc, sync::OnceLock};use theme::{AccentColors, ThemeSettings};use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem};use ui::{    CommonAnimationExt as _, ContextMenu, ScrollableHandle, Table, TableColumnWidths,    TableInteractionState, TableResizeBehavior, Tooltip, prelude::*,};use workspace::{    Workspace,    item::{Item, ItemEvent, SerializableItem},}; const COMMIT_CIRCLE_RADIUS: Pixels = px(4.5);const COMMIT_CIRCLE_STROKE_WIDTH: Pixels = px(1.5);const LANE_WIDTH: Pixels = px(16.0);const LEFT_PADDING: Pixels = px(12.0);const LINE_WIDTH: Pixels = px(1.5); actions!(    git_graph,    [        /// Opens the commit view for the selected commit.        OpenCommitView,    ]); pub struct GitGraphFeatureFlag; impl FeatureFlag for GitGraphFeatureFlag {    const NAME: &'static str = "git-graph";} fn timestamp_format() -> &'static [BorrowedFormatItem<'static>] {    static FORMAT: OnceLock<Vec<BorrowedFormatItem<'static>>> = OnceLock::new();    FORMAT.get_or_init(|| {        time::format_description::parse("[day] [month repr:short] [year] [hour]:[minute]")            .unwrap_or_default()    })} fn format_timestamp(timestamp: i64) -> String {    let Ok(datetime) = OffsetDateTime::from_unix_timestamp(timestamp) else {        return "Unknown".to_string();    };     let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);    let local_datetime = datetime.to_offset(local_offset);     local_datetime        .format(timestamp_format())        .unwrap_or_default()} fn accent_colors_count(accents: &AccentColors) -> usize {    accents.0.len()} #[derive(Copy, Clone, Debug)]struct BranchColor(u8); #[derive(Debug)]enum LaneState {    Empty,    Active {        child: Oid,        parent: Oid,        color: Option<BranchColor>,        starting_row: usize,        starting_col: usize,        destination_column: Option<usize>,        segments: SmallVec<[CommitLineSegment; 1]>,    },} impl LaneState {    fn to_commit_lines(        &mut self,        ending_row: usize,        lane_column: usize,        parent_column: usize,        parent_color: BranchColor,    ) -> Option<CommitLine> {        let state = std::mem::replace(self, LaneState::Empty);         match state {            LaneState::Active {                #[cfg_attr(not(test), allow(unused_variables))]                parent,                #[cfg_attr(not(test), allow(unused_variables))]                child,                color,                starting_row,                starting_col,                destination_column,                mut segments,            } => {                let final_destination = destination_column.unwrap_or(parent_column);                let final_color = color.unwrap_or(parent_color);                 Some(CommitLine {                    #[cfg(test)]                    child,                    #[cfg(test)]                    parent,                    child_column: starting_col,                    full_interval: starting_row..ending_row,                    color_idx: final_color.0 as usize,                    segments: {                        match segments.last_mut() {                            Some(CommitLineSegment::Straight { to_row })                                if *to_row == usize::MAX =>                            {                                if final_destination != lane_column {                                    *to_row = ending_row - 1;                                     let curved_line = CommitLineSegment::Curve {                                        to_column: final_destination,                                        on_row: ending_row,                                        curve_kind: CurveKind::Checkout,                                    };                                     if *to_row == starting_row {                                        let last_index = segments.len() - 1;                                        segments[last_index] = curved_line;                                    } else {                                        segments.push(curved_line);                                    }                                } else {                                    *to_row = ending_row;                                }                            }                            Some(CommitLineSegment::Curve {                                on_row,                                to_column,                                curve_kind,                            }) if *on_row == usize::MAX => {                                if *to_column == usize::MAX {                                    *to_column = final_destination;                                }                                if matches!(curve_kind, CurveKind::Merge) {                                    *on_row = starting_row + 1;                                    if *on_row < ending_row {                                        if *to_column != final_destination {                                            segments.push(CommitLineSegment::Straight {                                                to_row: ending_row - 1,                                            });                                            segments.push(CommitLineSegment::Curve {                                                to_column: final_destination,                                                on_row: ending_row,                                                curve_kind: CurveKind::Checkout,                                            });                                        } else {                                            segments.push(CommitLineSegment::Straight {                                                to_row: ending_row,                                            });                                        }                                    } else if *to_column != final_destination {                                        segments.push(CommitLineSegment::Curve {                                            to_column: final_destination,                                            on_row: ending_row,                                            curve_kind: CurveKind::Checkout,                                        });                                    }                                } else {                                    *on_row = ending_row;                                    if *to_column != final_destination {                                        segments.push(CommitLineSegment::Straight {                                            to_row: ending_row,                                        });                                        segments.push(CommitLineSegment::Curve {                                            to_column: final_destination,                                            on_row: ending_row,                                            curve_kind: CurveKind::Checkout,                                        });                                    }                                }                            }                            Some(CommitLineSegment::Curve {                                on_row, to_column, ..                            }) => {                                if *on_row < ending_row {                                    if *to_column != final_destination {                                        segments.push(CommitLineSegment::Straight {                                            to_row: ending_row - 1,                                        });                                        segments.push(CommitLineSegment::Curve {                                            to_column: final_destination,                                            on_row: ending_row,                                            curve_kind: CurveKind::Checkout,                                        });                                    } else {                                        segments.push(CommitLineSegment::Straight {                                            to_row: ending_row,                                        });                                    }                                } else if *to_column != final_destination {                                    segments.push(CommitLineSegment::Curve {                                        to_column: final_destination,                                        on_row: ending_row,                                        curve_kind: CurveKind::Checkout,                                    });                                }                            }                            _ => {}                        }                         segments                    },                })            }            LaneState::Empty => None,        }    }     fn is_empty(&self) -> bool {        match self {            LaneState::Empty => true,            LaneState::Active { .. } => false,        }    }} struct CommitEntry {    data: Arc<InitialGraphCommitData>,    lane: usize,    color_idx: usize,} type ActiveLaneIdx = usize; enum AllCommitCount {    NotLoaded,    Loaded(usize),} #[derive(Debug)]enum CurveKind {    Merge,    Checkout,} #[derive(Debug)]enum CommitLineSegment {    Straight {        to_row: usize,    },    Curve {        to_column: usize,        on_row: usize,        curve_kind: CurveKind,    },} #[derive(Debug)]struct CommitLine {    #[cfg(test)]    child: Oid,    #[cfg(test)]    parent: Oid,    child_column: usize,    full_interval: Range<usize>,    color_idx: usize,    segments: SmallVec<[CommitLineSegment; 1]>,} impl CommitLine {    fn get_first_visible_segment_idx(&self, first_visible_row: usize) -> Option<(usize, usize)> {        if first_visible_row > self.full_interval.end {            return None;        } else if first_visible_row <= self.full_interval.start {            return Some((0, self.child_column));        }         let mut current_column = self.child_column;         for (idx, segment) in self.segments.iter().enumerate() {            match segment {                CommitLineSegment::Straight { to_row } => {                    if *to_row >= first_visible_row {                        return Some((idx, current_column));                    }                }                CommitLineSegment::Curve {                    to_column, on_row, ..                } => {                    if *on_row >= first_visible_row {                        return Some((idx, current_column));                    }                    current_column = *to_column;                }            }        }         None    }} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]struct CommitLineKey {    child: Oid,    parent: Oid,} struct GraphData {    lane_states: SmallVec<[LaneState; 8]>,    lane_colors: HashMap<ActiveLaneIdx, BranchColor>,    parent_to_lanes: HashMap<Oid, SmallVec<[usize; 1]>>,    next_color: BranchColor,    accent_colors_count: usize,    commits: Vec<Rc<CommitEntry>>,    max_commit_count: AllCommitCount,    max_lanes: usize,    lines: Vec<Rc<CommitLine>>,    active_commit_lines: HashMap<CommitLineKey, usize>,    active_commit_lines_by_parent: HashMap<Oid, SmallVec<[usize; 1]>>,} impl GraphData {    fn new(accent_colors_count: usize) -> Self {        GraphData {            lane_states: SmallVec::default(),            lane_colors: HashMap::default(),            parent_to_lanes: HashMap::default(),            next_color: BranchColor(0),            accent_colors_count,            commits: Vec::default(),            max_commit_count: AllCommitCount::NotLoaded,            max_lanes: 0,            lines: Vec::default(),            active_commit_lines: HashMap::default(),            active_commit_lines_by_parent: HashMap::default(),        }    }     fn clear(&mut self) {        self.lane_states.clear();        self.lane_colors.clear();        self.parent_to_lanes.clear();        self.commits.clear();        self.lines.clear();        self.active_commit_lines.clear();        self.active_commit_lines_by_parent.clear();        self.next_color = BranchColor(0);        self.max_commit_count = AllCommitCount::NotLoaded;        self.max_lanes = 0;    }     fn first_empty_lane_idx(&mut self) -> ActiveLaneIdx {        self.lane_states            .iter()            .position(LaneState::is_empty)            .unwrap_or_else(|| {                self.lane_states.push(LaneState::Empty);                self.lane_states.len() - 1            })    }     fn get_lane_color(&mut self, lane_idx: ActiveLaneIdx) -> BranchColor {        let accent_colors_count = self.accent_colors_count;        *self.lane_colors.entry(lane_idx).or_insert_with(|| {            let color_idx = self.next_color;            self.next_color = BranchColor((self.next_color.0 + 1) % accent_colors_count as u8);            color_idx        })    }     fn add_commits(&mut self, commits: &[Arc<InitialGraphCommitData>]) {        self.commits.reserve(commits.len());        self.lines.reserve(commits.len() / 2);         for commit in commits.iter() {            let commit_row = self.commits.len();             let commit_lane = self                .parent_to_lanes                .get(&commit.sha)                .and_then(|lanes| lanes.first().copied());             let commit_lane = commit_lane.unwrap_or_else(|| self.first_empty_lane_idx());             let commit_color = self.get_lane_color(commit_lane);             if let Some(lanes) = self.parent_to_lanes.remove(&commit.sha) {                for lane_column in lanes {                    let state = &mut self.lane_states[lane_column];                     if let LaneState::Active {                        starting_row,                        segments,                        ..                    } = state                    {                        if let Some(CommitLineSegment::Curve {                            to_column,                            curve_kind: CurveKind::Merge,                            ..                        }) = segments.first_mut()                        {                            let curve_row = *starting_row + 1;                            let would_overlap =                                if lane_column != commit_lane && curve_row < commit_row {                                    self.commits[curve_row..commit_row]                                        .iter()                                        .any(|c| c.lane == commit_lane)                                } else {                                    false                                };                             if would_overlap {                                *to_column = lane_column;                            }                        }                    }                     if let Some(commit_line) =                        state.to_commit_lines(commit_row, lane_column, commit_lane, commit_color)                    {                        self.lines.push(Rc::new(commit_line));                    }                }            }             commit                .parents                .iter()                .enumerate()                .for_each(|(parent_idx, parent)| {                    if parent_idx == 0 {                        self.lane_states[commit_lane] = LaneState::Active {                            parent: *parent,                            child: commit.sha,                            color: Some(commit_color),                            starting_col: commit_lane,                            starting_row: commit_row,                            destination_column: None,                            segments: smallvec![CommitLineSegment::Straight { to_row: usize::MAX }],                        };                         self.parent_to_lanes                            .entry(*parent)                            .or_default()                            .push(commit_lane);                    } else {                        let new_lane = self.first_empty_lane_idx();                         self.lane_states[new_lane] = LaneState::Active {                            parent: *parent,                            child: commit.sha,                            color: None,                            starting_col: commit_lane,                            starting_row: commit_row,                            destination_column: None,                            segments: smallvec![CommitLineSegment::Curve {                                to_column: usize::MAX,                                on_row: usize::MAX,                                curve_kind: CurveKind::Merge,                            },],                        };                         self.parent_to_lanes                            .entry(*parent)                            .or_default()                            .push(new_lane);                    }                });             self.max_lanes = self.max_lanes.max(self.lane_states.len());             self.commits.push(Rc::new(CommitEntry {                data: commit.clone(),                lane: commit_lane,                color_idx: commit_color.0 as usize,            }));        }    }} pub fn init(cx: &mut App) {    workspace::register_serializable_item::<GitGraph>(cx);

Lines 1–500 of 2496.