6312 lines · 233.2 KB · Rust
RawDownload
use collections::{BTreeMap, HashMap, IndexSet};use editor::Editor;use git::{    BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote,    commit::ParsedCommitMessage,    parse_git_remote_url,    repository::{        CommitDiff, CommitFile, InitialGraphCommitData, LogOrder, LogSource, RepoPath,        SearchCommitArgs,    },    status::{FileStatus, StatusCode, TrackedStatus},};use git_ui::{    commit_tooltip::{CommitAvatar, CommitDetails, CommitTooltip},    commit_view::CommitView,    git_status_icon,};use gpui::{    Action, Anchor, AnyElement, App, Bounds, ClickEvent, ClipboardItem, DefiniteLength,    DismissEvent, DragMoveEvent, ElementId, Empty, Entity, EventEmitter, FocusHandle, Focusable,    Hsla, MouseButton, MouseDownEvent, PathBuilder, Pixels, Point, ScrollStrategy,    ScrollWheelEvent, SharedString, Subscription, Task, TextStyleRefinement,    UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, point, prelude::*,    px, uniform_list,};use language::line_diff;use menu::{Cancel, SelectFirst, SelectLast, SelectNext, SelectPrevious};use picker::{Picker, PickerDelegate};use project::{    GIT_COMMAND_TASK_TAG, ProjectPath, TaskSourceKind,    git_store::{        CommitDataState, GitGraphEvent, GitStore, GitStoreEvent, GraphDataResponse, Repository,        RepositoryEvent, RepositoryId,    },};use project_panel::ProjectPanel;use search::{    SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch,    ToggleCaseSensitive, buffer_search,};use smallvec::{SmallVec, smallvec};use std::{    cell::Cell,    ops::Range,    rc::Rc,    sync::{Arc, OnceLock},    time::{Duration, Instant},};use task::{ResolvedTask, TaskContext, TaskVariables, VariableName};use theme::AccentColors;use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem};use ui::{    ButtonLike, Chip, ColumnWidthConfig, CommonAnimationExt as _, ContextMenu, ContextMenuEntry,    DiffStat, Divider, HeaderResizeInfo, HighlightedLabel, ListItem, ListItemSpacing,    RedistributableColumnsState, ScrollableHandle, Table, TableInteractionState,    TableRenderContext, TableResizeBehavior, Tooltip, WithScrollbar, bind_redistributable_columns,    prelude::*, render_redistributable_columns_resize_handles, render_table_header,    table_row::TableRow,};use workspace::{    ModalView, Workspace,    item::{Item, ItemEvent, 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);const COMMIT_TAG_LIST_WIDTH_IN_REMS: Rems = rems(10.);const CUSTOM_GIT_COMMANDS_DOCS_SLUG: &str = "tasks#custom-git-commands";// Extra vertical breathing room added to the UI line height when computing// the git graph's row height, so commit dots and lines have space around them.const ROW_VERTICAL_PADDING: Pixels = px(4.0); 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; struct CommitTagPicker {    picker: Entity<Picker<CommitTagPickerDelegate>>,} impl CommitTagPicker {    fn new(tag_names: Vec<SharedString>, window: &mut Window, cx: &mut Context<Self>) -> Self {        let delegate = CommitTagPickerDelegate {            picker: cx.entity().downgrade(),            tag_names,            selected_index: 0,        };        let picker = cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx));        Self { picker }    }} impl EventEmitter<DismissEvent> for CommitTagPicker {}impl ModalView for CommitTagPicker {} impl Focusable for CommitTagPicker {    fn focus_handle(&self, cx: &App) -> FocusHandle {        self.picker.focus_handle(cx)    }} impl Render for CommitTagPicker {    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {        v_flex()            .w(COMMIT_TAG_LIST_WIDTH_IN_REMS)            .child(self.picker.clone())    }} struct CommitTagPickerDelegate {    picker: WeakEntity<CommitTagPicker>,    tag_names: Vec<SharedString>,    selected_index: usize,} impl PickerDelegate for CommitTagPickerDelegate {    type ListItem = ListItem;     fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {        "Copy Tag".into()    }     fn match_count(&self) -> usize {        self.tag_names.len()    }     fn selected_index(&self) -> usize {        self.selected_index    }     fn set_selected_index(        &mut self,        ix: usize,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) {        self.selected_index = ix;    }     fn update_matches(        &mut self,        _query: String,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Task<()> {        Task::ready(())    }     fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {        if let Some(tag_name) = self.tag_names.get(self.selected_index) {            cx.write_to_clipboard(ClipboardItem::new_string(tag_name.to_string()));        }        self.dismissed(window, cx);    }     fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {        self.picker            .update(cx, |_this, cx| cx.emit(DismissEvent))            .ok();    }     fn render_match(        &self,        ix: usize,        selected: bool,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<Self::ListItem> {        Some(            ListItem::new(ix)                .inset(true)                .spacing(ListItemSpacing::Sparse)                .toggle_state(selected)                .child(Label::new(self.tag_names.get(ix)?.clone())),        )    }} #[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()    }} enum QueryState {    Pending(SharedString),    Confirmed((SharedString, Task<()>)),    Empty,} impl QueryState {    fn next_state(&mut self) {        match self {            Self::Confirmed((query, _)) => *self = Self::Pending(std::mem::take(query)),            _ => {}        };    }} struct SearchState {    case_sensitive: bool,    editor: Entity<Editor>,    state: QueryState,    pub matches: IndexSet<Oid>,    pub selected_index: Option<usize>,} 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,    [        /// Copies the SHA of the selected commit to the clipboard.        CopyCommitSha,        /// Copies a tag from the selected commit to the clipboard.        CopyCommitTag,        /// Opens the commit view for the selected commit.        OpenCommitView,        /// Focuses the search field.        FocusSearch,        /// Focuses the next git graph tab stop.        FocusNextTabStop,        /// Focuses the previous git graph tab stop.        FocusPreviousTabStop,        /// Selects a commit half a page above the current selection.        ScrollUp,        /// Selects a commit half a page below the current selection.        ScrollDown,    ]); 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,                                    };

Lines 1–500 of 6312.