3302 lines · 124.1 KB · Rust
RawDownload
use collections::{BTreeMap, HashMap};use feature_flags::{FeatureFlagAppExt as _, GitGraphFeatureFlag};use git::{    BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote,    parse_git_remote_url,    repository::{CommitDiff, CommitFile, InitialGraphCommitData, LogOrder, LogSource, RepoPath},    status::{FileStatus, StatusCode, TrackedStatus},};use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView, git_status_icon};use gpui::{    AnyElement, App, Bounds, ClickEvent, ClipboardItem, Corner, DefiniteLength, DragMoveEvent,    ElementId, Empty, Entity, EventEmitter, FocusHandle, Focusable, Hsla, PathBuilder, Pixels,    Point, ScrollStrategy, ScrollWheelEvent, SharedString, Subscription, Task,    UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, point, prelude::*,    px, uniform_list,};use language::line_diff;use menu::{Cancel, SelectFirst, SelectLast, SelectNext, SelectPrevious};use project::{    Project,    git_store::{        CommitDataState, GitGraphEvent, GitStoreEvent, GraphDataResponse, Repository,        RepositoryEvent, RepositoryId,    },};use settings::Settings;use smallvec::{SmallVec, smallvec};use std::{    cell::Cell,    ops::Range,    rc::Rc,    sync::Arc,    sync::OnceLock,    time::{Duration, Instant},};use theme::{AccentColors, ThemeSettings};use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem};use ui::{    ButtonLike, Chip, CommonAnimationExt as _, ContextMenu, DiffStat, Divider, ScrollableHandle,    Table, TableColumnWidths, TableInteractionState, TableResizeBehavior, Tooltip, WithScrollbar,    prelude::*,};use workspace::{    Workspace,    item::{Item, ItemEvent, SerializableItem, TabTooltipContent},}; const COMMIT_CIRCLE_RADIUS: Pixels = px(3.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);const RESIZE_HANDLE_WIDTH: f32 = 8.0;const COPIED_STATE_DURATION: Duration = Duration::from_secs(2); struct CopiedState {    copied_at: Option<Instant>,} impl CopiedState {    fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {        Self { copied_at: None }    }     fn is_copied(&self) -> bool {        self.copied_at            .map(|t| t.elapsed() < COPIED_STATE_DURATION)            .unwrap_or(false)    }     fn mark_copied(&mut self) {        self.copied_at = Some(Instant::now());    }} struct DraggedSplitHandle; #[derive(Clone)]struct ChangedFileEntry {    status: FileStatus,    file_name: SharedString,    dir_path: SharedString,    repo_path: RepoPath,} impl ChangedFileEntry {    fn from_commit_file(file: &CommitFile, _cx: &App) -> Self {        let file_name: SharedString = file            .path            .file_name()            .map(|n| n.to_string())            .unwrap_or_default()            .into();        let dir_path: SharedString = file            .path            .parent()            .map(|p| p.as_unix_str().to_string())            .unwrap_or_default()            .into();         let status_code = match (&file.old_text, &file.new_text) {            (None, Some(_)) => StatusCode::Added,            (Some(_), None) => StatusCode::Deleted,            _ => StatusCode::Modified,        };         let status = FileStatus::Tracked(TrackedStatus {            index_status: status_code,            worktree_status: StatusCode::Unmodified,        });         Self {            status,            file_name,            dir_path,            repo_path: file.path.clone(),        }    }     fn open_in_commit_view(        &self,        commit_sha: &SharedString,        repository: &WeakEntity<Repository>,        workspace: &WeakEntity<Workspace>,        window: &mut Window,        cx: &mut App,    ) {        CommitView::open(            commit_sha.to_string(),            repository.clone(),            workspace.clone(),            None,            Some(self.repo_path.clone()),            window,            cx,        );    }     fn render(        &self,        ix: usize,        commit_sha: SharedString,        repository: WeakEntity<Repository>,        workspace: WeakEntity<Workspace>,        _cx: &App,    ) -> AnyElement {        let file_name = self.file_name.clone();        let dir_path = self.dir_path.clone();         div()            .w_full()            .child(                ButtonLike::new(("changed-file", ix))                    .child(                        h_flex()                            .min_w_0()                            .w_full()                            .gap_1()                            .overflow_hidden()                            .child(git_status_icon(self.status))                            .child(                                Label::new(file_name.clone())                                    .size(LabelSize::Small)                                    .truncate(),                            )                            .when(!dir_path.is_empty(), |this| {                                this.child(                                    Label::new(dir_path.clone())                                        .size(LabelSize::Small)                                        .color(Color::Muted)                                        .truncate_start(),                                )                            }),                    )                    .tooltip({                        let meta = if dir_path.is_empty() {                            file_name                        } else {                            format!("{}/{}", dir_path, file_name).into()                        };                        move |_, cx| Tooltip::with_meta("View Changes", None, meta.clone(), cx)                    })                    .on_click({                        let entry = self.clone();                        move |_, window, cx| {                            entry.open_in_commit_view(                                &commit_sha,                                &repository,                                &workspace,                                window,                                cx,                            );                        }                    }),            )            .into_any_element()    }} pub struct SplitState {    left_ratio: f32,    visible_left_ratio: f32,} impl SplitState {    pub fn new() -> Self {        Self {            left_ratio: 1.0,            visible_left_ratio: 1.0,        }    }     pub fn right_ratio(&self) -> f32 {        1.0 - self.visible_left_ratio    }     fn on_drag_move(        &mut self,        drag_event: &DragMoveEvent<DraggedSplitHandle>,        _window: &mut Window,        _cx: &mut Context<Self>,    ) {        let drag_position = drag_event.event.position;        let bounds = drag_event.bounds;        let bounds_width = bounds.right() - bounds.left();         let min_ratio = 0.1;        let max_ratio = 0.9;         let new_ratio = (drag_position.x - bounds.left()) / bounds_width;        self.visible_left_ratio = new_ratio.clamp(min_ratio, max_ratio);    }     fn commit_ratio(&mut self) {        self.left_ratio = self.visible_left_ratio;    }     fn on_double_click(&mut self) {        self.left_ratio = 1.0;        self.visible_left_ratio = 1.0;    }} actions!(    git_graph,    [        /// Opens the commit view for the selected commit.        OpenCommitView,    ]); 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));                    }

Lines 1–500 of 3302.