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. Load more
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())