910 lines · 31.0 KB · Rust
RawDownload
use crate::{git_panel::GitStatusEntry, git_panel_settings::GitPanelSettings, git_status_icon};use anyhow::{Context as _, Result};use buffer_diff::DiffHunkSecondaryStatus;use editor::{    DiffStyleControls, Direction, Editor, EditorEvent, EditorSettings, SplittableEditor,    ToggleSplitDiff,    actions::{GoToHunk, GoToPreviousHunk},    file_status_label_color,};use git::{    Commit, Restore, StageAndNext, StageFile, ToggleStaged, UnstageAndNext, UnstageFile,    repository::RepoPath, status::StageStatus,};use gpui::{    Action, AnyElement, App, AppContext as _, Context, Empty, Entity, EventEmitter, FocusHandle,    Focusable, HighlightStyle, IntoElement, Render, Subscription, Task, WeakEntity, Window,};use language::{Anchor, Buffer, HighlightedText, OffsetRangeExt as _, Point};use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};use project::{    Project,    git_store::{Repository, RepositoryId},};use settings::{Settings, SettingsStore, StatusStyle};use std::{    any::{Any, TypeId},    ops::Range,    sync::Arc,};use ui::{DiffStat, Divider, Tooltip, prelude::*};use util::paths::{PathExt as _, PathStyle};use workspace::{    Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,    Workspace,    item::{ItemEvent, SaveOptions, TabContentParams},    notifications::NotifyTaskExt,    searchable::SearchableItemHandle,}; pub struct SoloDiffView {    repository: Entity<Repository>,    repository_id: RepositoryId,    repo_path: RepoPath,    buffer: Entity<Buffer>,    diff: Entity<buffer_diff::BufferDiff>,    editor: Entity<SplittableEditor>,    workspace: WeakEntity<Workspace>,    showing_full_file: bool,    _settings_subscription: Subscription,} impl SoloDiffView {    pub fn open_or_focus(        entry: GitStatusEntry,        repository: Entity<Repository>,        workspace: WeakEntity<Workspace>,        window: &mut Window,        cx: &mut App,    ) -> Task<Result<Entity<Self>>> {        let Some(workspace_entity) = workspace.upgrade() else {            return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));        };         let existing = workspace_entity            .read(cx)            .items_of_type::<SoloDiffView>(cx)            .find(|item| item.read(cx).matches(&repository, &entry.repo_path, cx));        if let Some(existing) = existing {            workspace_entity.update(cx, |workspace, cx| {                workspace.activate_item(&existing, true, true, window, cx);            });            existing.focus_handle(cx).focus(window, cx);            return Task::ready(Ok(existing));        }         let Some(project_path) = repository            .read(cx)            .repo_path_to_project_path(&entry.repo_path, cx)        else {            return Task::ready(Err(anyhow::anyhow!(                "could not resolve repository path {:?}",                entry.repo_path            )));        };         let project = workspace_entity.read(cx).project().clone();        let repo_path = entry.repo_path;        window.spawn(cx, async move |cx| {            let buffer = project                .update(cx, |project, cx| {                    project.open_buffer(project_path.clone(), cx)                })                .await?;            let diff = project                .update(cx, |project, cx| {                    project.open_uncommitted_diff(buffer.clone(), cx)                })                .await?;             workspace_entity.update_in(cx, |workspace, window, cx| {                let workspace_handle = cx.entity();                let view = cx.new(|cx| {                    Self::new(                        project,                        repository,                        repo_path,                        buffer,                        diff,                        workspace_handle,                        window,                        cx,                    )                });                 workspace.add_item_to_active_pane(Box::new(view.clone()), None, true, window, cx);                view            })        })    }     fn new(        project: Entity<Project>,        repository: Entity<Repository>,        repo_path: RepoPath,        buffer: Entity<Buffer>,        diff: Entity<buffer_diff::BufferDiff>,        workspace: Entity<Workspace>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let repository_id = repository.read(cx).id;        let showing_full_file = EditorSettings::get_global(cx).file_diff.show_full_file;        let multibuffer = cx            .new(|cx| Self::build_multibuffer(buffer.clone(), diff.clone(), showing_full_file, cx));        let editor = cx.new(|cx| {            let editor = SplittableEditor::new(                EditorSettings::get_global(cx).diff_view_style,                multibuffer,                project.clone(),                workspace.clone(),                window,                cx,            );            editor.rhs_editor().update(cx, |editor, cx| {                editor.set_should_serialize(false, cx);                editor.set_allow_git_diff_scrollbar_markers(showing_full_file, cx);                let snapshot = editor.snapshot(window, cx);                editor.go_to_hunk_before_or_after_position(                    &snapshot,                    language::Point::new(0, 0),                    Direction::Next,                    true,                    window,                    cx,                );            });            editor        });         let mut previous_diff_view_style = EditorSettings::get_global(cx).diff_view_style;        let settings_subscription =            cx.observe_global_in::<SettingsStore>(window, move |this, window, cx| {                let diff_view_style = EditorSettings::get_global(cx).diff_view_style;                if diff_view_style != previous_diff_view_style {                    this.editor.update(cx, |editor, cx| {                        if editor.diff_view_style() != diff_view_style {                            editor.toggle_split(&ToggleSplitDiff, window, cx);                        }                    });                    previous_diff_view_style = diff_view_style;                    cx.notify();                }            });         Self {            repository,            repository_id,            repo_path,            buffer,            diff,            editor,            workspace: workspace.downgrade(),            showing_full_file,            _settings_subscription: settings_subscription,        }    }     fn build_multibuffer(        buffer: Entity<Buffer>,        diff: Entity<buffer_diff::BufferDiff>,        showing_full_file: bool,        cx: &mut Context<MultiBuffer>,    ) -> MultiBuffer {        let (ranges, context_line_count) =            Self::excerpt_ranges(&buffer, &diff, showing_full_file, cx);         let mut multibuffer = MultiBuffer::without_headers(buffer.read(cx).capability());        multibuffer.set_excerpts_for_buffer(buffer, ranges, context_line_count, cx);        multibuffer.add_diff(diff, cx);        multibuffer    }     fn excerpt_ranges(        buffer: &Entity<Buffer>,        diff: &Entity<buffer_diff::BufferDiff>,        showing_full_file: bool,        cx: &App,    ) -> (Vec<Range<Point>>, u32) {        if showing_full_file {            (vec![Point::zero()..buffer.read(cx).max_point()], 0)        } else {            (                Self::hunk_ranges(buffer, diff, cx),                excerpt_context_lines(cx),            )        }    }     fn hunk_ranges(        buffer: &Entity<Buffer>,        diff: &Entity<buffer_diff::BufferDiff>,        cx: &App,    ) -> Vec<Range<Point>> {        let buffer = buffer.read(cx);        diff.read(cx)            .snapshot(cx)            .hunks_intersecting_range(                Anchor::min_for_buffer(buffer.remote_id())                    ..Anchor::max_for_buffer(buffer.remote_id()),                buffer,            )            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))            .collect()    }     fn set_showing_full_file(&mut self, showing_full_file: bool, cx: &mut Context<Self>) {        if self.showing_full_file == showing_full_file {            return;        }         let (ranges, context_line_count) =            Self::excerpt_ranges(&self.buffer, &self.diff, showing_full_file, cx);         self.editor.update(cx, |editor, cx| {            let path = PathKey::for_buffer(&self.buffer, cx);            editor.remove_excerpts_for_path(path.clone(), cx);            editor.update_excerpts_for_path(                path,                self.buffer.clone(),                ranges,                context_line_count,                self.diff.clone(),                cx,            );            editor.rhs_editor().update(cx, |editor, cx| {                editor.set_allow_git_diff_scrollbar_markers(showing_full_file, cx);            });        });         self.showing_full_file = showing_full_file;        cx.notify();    }     fn matches(&self, repository: &Entity<Repository>, repo_path: &RepoPath, cx: &App) -> bool {        self.repository_id == repository.read(cx).id && &self.repo_path == repo_path    }     fn button_states(&self, cx: &App) -> SoloDiffButtonStates {        let editor = self.editor.read(cx).rhs_editor().read(cx);        let multibuffer = editor.buffer().read(cx);        let snapshot = multibuffer.snapshot(cx);        let prev_next = snapshot.diff_hunks().nth(1).is_some();        let mut selection = true;         let mut ranges = editor            .selections            .disjoint_anchor_ranges()            .collect::<Vec<_>>();        if !ranges.iter().any(|range| range.start != range.end) {            selection = false;            let anchor = editor.selections.newest_anchor().head();            if let Some((_, excerpt_range)) = snapshot.excerpt_containing(anchor..anchor)                && let Some(range) = snapshot                    .anchor_in_buffer(excerpt_range.context.start)                    .zip(snapshot.anchor_in_buffer(excerpt_range.context.end))                    .map(|(start, end)| start..end)            {                ranges = vec![range];            } else {                ranges = Vec::new();            }        }         let mut stage = false;        let mut unstage = false;        for hunk in editor.diff_hunks_in_ranges(&ranges, &snapshot) {            match hunk.status.secondary {                DiffHunkSecondaryStatus::HasSecondaryHunk                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {                    stage = true;                }                DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => {                    stage = true;                    unstage = true;                }                DiffHunkSecondaryStatus::NoSecondaryHunk                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {                    unstage = true;                }            }        }         let stage_status = self            .repository            .read(cx)            .status_for_path(&self.repo_path)            .map(|entry| entry.status.staging())            .unwrap_or(StageStatus::Unstaged);         SoloDiffButtonStates {            stage,            unstage,            restore: stage || unstage,            prev_next,            selection,            stage_file: stage_status.has_unstaged(),            unstage_file: stage_status.has_staged(),        }    }     fn dispatch_action(&self, action: &dyn Action, window: &mut Window, cx: &mut App) {        self.focus_handle(cx).focus(window, cx);        let action = action.boxed_clone();        cx.defer(move |cx| {            cx.dispatch_action(action.as_ref());        });    }     fn change_file_stage(&self, stage: bool, window: &mut Window, cx: &mut Context<Self>) {        let repository = self.repository.clone();        let repo_path = self.repo_path.clone();        let workspace = self.workspace.clone();        let task = cx.spawn(async move |_, cx| {            repository                .update(cx, |repository, cx| {                    if stage {                        repository.stage_entries(vec![repo_path], cx)                    } else {                        repository.unstage_entries(vec![repo_path], cx)                    }                })                .await                .with_context(|| {                    if stage {                        "failed to stage file"                    } else {                        "failed to unstage file"                    }                })        });        task.detach_and_notify_err(workspace, window, cx);    }} impl EventEmitter<EditorEvent> for SoloDiffView {} impl Focusable for SoloDiffView {    fn focus_handle(&self, cx: &App) -> FocusHandle {        self.editor.focus_handle(cx)    }} impl Item for SoloDiffView {    type Event = EditorEvent;     fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {        Some(Icon::new(IconName::Diff).color(Color::Muted))    }     fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {        Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx))            .color(if params.selected {                Color::Default            } else {                Color::Muted            })            .into_any_element()    }     fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {        self.buffer            .read(cx)            .file()            .and_then(|file| {                Some(                    file.full_path(cx)                        .file_name()?                        .to_string_lossy()                        .to_string(),                )            })            .unwrap_or_else(|| {                self.repo_path                    .as_ref()                    .display(PathStyle::local())                    .into_owned()            })            .into()    }     fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {        Some(            self.buffer                .read(cx)                .file()                .map(|file| file.full_path(cx).compact().to_string_lossy().into_owned())                .unwrap_or_else(|| {                    self.repo_path                        .as_ref()                        .display(PathStyle::local())                        .into_owned()                })                .into(),        )    }     fn to_item_events(event: &EditorEvent, f: &mut dyn FnMut(ItemEvent)) {        Editor::to_item_events(event, f)    }     fn telemetry_event_text(&self) -> Option<&'static str> {        Some("Solo Diff View Opened")    }     fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {        self.editor.deactivated(window, cx);    }     fn act_as_type<'a>(        &'a self,        type_id: TypeId,        self_handle: &'a Entity<Self>,        cx: &'a App,    ) -> Option<gpui::AnyEntity> {        if type_id == TypeId::of::<Self>() {            Some(self_handle.clone().into())        } else if type_id == TypeId::of::<SplittableEditor>() {            None        } else {            self.editor.act_as_type(type_id, cx)        }    }     fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {        Some(Box::new(self.editor.clone()))    }     fn for_each_project_item(        &self,        cx: &App,        f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),    ) {        self.editor.for_each_project_item(cx, f)    }     fn set_nav_history(        &mut self,        nav_history: ItemNavHistory,        _: &mut Window,        cx: &mut Context<Self>,    ) {        self.editor.update(cx, |editor, cx| {            editor.rhs_editor().update(cx, |editor, _| {                editor.set_nav_history(Some(nav_history));            })        });    }     fn navigate(        &mut self,        data: Arc<dyn Any + Send>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> bool {        self.editor.update(cx, |editor, cx| {            editor                .rhs_editor()                .update(cx, |editor, cx| editor.navigate(data, window, cx))        })    }     fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation {        ToolbarItemLocation::PrimaryLeft    }     fn breadcrumbs(&self, cx: &App) -> Option<(Vec<HighlightedText>, Option<gpui::Font>)> {        let text: SharedString = self            .repo_path            .as_ref()            .display(PathStyle::local())

Lines 1–500 of 910.