diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 8557b57f46..f80a6afbbb 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -259,6 +259,13 @@ pub struct SpawnNearestTask { pub reveal: task::RevealStrategy, } +#[derive(Clone, PartialEq, Action)] +#[action(no_json, no_register)] +pub struct DiffClipboardWithSelectionData { + pub clipboard_text: String, + pub editor: Entity, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Default)] pub enum UuidVersion { #[default] @@ -398,6 +405,8 @@ actions!( DeleteToNextSubwordEnd, /// Deletes to the start of the previous subword. DeleteToPreviousSubwordStart, + /// Diffs the text stored in the clipboard against the current selection. + DiffClipboardWithSelection, /// Displays names of all active cursors. DisplayCursorNames, /// Duplicates the current line below. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1f985eeb7c..a31f789fb0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -213,6 +213,7 @@ use workspace::{ notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt}, searchable::SearchEvent, }; +use zed_actions; use crate::{ code_context_menus::CompletionsMenuSource, @@ -12154,6 +12155,41 @@ impl Editor { }); } + pub fn diff_clipboard_with_selection( + &mut self, + _: &DiffClipboardWithSelection, + window: &mut Window, + cx: &mut Context, + ) { + let selections = self.selections.all::(cx); + + if selections.is_empty() { + log::warn!("There should always be at least one selection in Zed. This is a bug."); + return; + }; + + let clipboard_text = match cx.read_from_clipboard() { + Some(item) => match item.entries().first() { + Some(ClipboardEntry::String(text)) => Some(text.text().to_string()), + _ => None, + }, + None => None, + }; + + let Some(clipboard_text) = clipboard_text else { + log::warn!("Clipboard doesn't contain text."); + return; + }; + + window.dispatch_action( + Box::new(DiffClipboardWithSelectionData { + clipboard_text, + editor: cx.entity(), + }), + cx, + ); + } + pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); if let Some(item) = cx.read_from_clipboard() { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index cbff544c7e..1b372a7d53 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -262,6 +262,7 @@ impl EditorElement { register_action(editor, window, Editor::kill_ring_yank); register_action(editor, window, Editor::copy); register_action(editor, window, Editor::copy_and_trim); + register_action(editor, window, Editor::diff_clipboard_with_selection); register_action(editor, window, Editor::paste); register_action(editor, window, Editor::undo); register_action(editor, window, Editor::redo); diff --git a/crates/git_ui/src/diff_view.rs b/crates/git_ui/src/file_diff_view.rs similarity index 89% rename from crates/git_ui/src/diff_view.rs rename to crates/git_ui/src/file_diff_view.rs index 9e03dd5f38..2f8a744ed8 100644 --- a/crates/git_ui/src/diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -1,4 +1,4 @@ -//! DiffView provides a UI for displaying differences between two buffers. +//! FileDiffView provides a UI for displaying differences between two buffers. use anyhow::Result; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; @@ -25,7 +25,7 @@ use workspace::{ searchable::SearchableItemHandle, }; -pub struct DiffView { +pub struct FileDiffView { editor: Entity, old_buffer: Entity, new_buffer: Entity, @@ -35,7 +35,7 @@ pub struct DiffView { const RECALCULATE_DIFF_DEBOUNCE: Duration = Duration::from_millis(250); -impl DiffView { +impl FileDiffView { pub fn open( old_path: PathBuf, new_path: PathBuf, @@ -57,7 +57,7 @@ impl DiffView { workspace.update_in(cx, |workspace, window, cx| { let diff_view = cx.new(|cx| { - DiffView::new( + FileDiffView::new( old_buffer, new_buffer, buffer_diff, @@ -190,15 +190,15 @@ async fn build_buffer_diff( }) } -impl EventEmitter for DiffView {} +impl EventEmitter for FileDiffView {} -impl Focusable for DiffView { +impl Focusable for FileDiffView { fn focus_handle(&self, cx: &App) -> FocusHandle { self.editor.focus_handle(cx) } } -impl Item for DiffView { +impl Item for FileDiffView { type Event = EditorEvent; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { @@ -216,48 +216,37 @@ impl Item for DiffView { } fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { - let old_filename = self - .old_buffer - .read(cx) - .file() - .and_then(|file| { - Some( - file.full_path(cx) - .file_name()? - .to_string_lossy() - .to_string(), - ) - }) - .unwrap_or_else(|| "untitled".into()); - let new_filename = self - .new_buffer - .read(cx) - .file() - .and_then(|file| { - Some( - file.full_path(cx) - .file_name()? - .to_string_lossy() - .to_string(), - ) - }) - .unwrap_or_else(|| "untitled".into()); + let title_text = |buffer: &Entity| { + buffer + .read(cx) + .file() + .and_then(|file| { + Some( + file.full_path(cx) + .file_name()? + .to_string_lossy() + .to_string(), + ) + }) + .unwrap_or_else(|| "untitled".into()) + }; + let old_filename = title_text(&self.old_buffer); + let new_filename = title_text(&self.new_buffer); + format!("{old_filename} ↔ {new_filename}").into() } fn tab_tooltip_text(&self, cx: &App) -> Option { - let old_path = self - .old_buffer - .read(cx) - .file() - .map(|file| file.full_path(cx).compact().to_string_lossy().to_string()) - .unwrap_or_else(|| "untitled".into()); - let new_path = self - .new_buffer - .read(cx) - .file() - .map(|file| file.full_path(cx).compact().to_string_lossy().to_string()) - .unwrap_or_else(|| "untitled".into()); + let path = |buffer: &Entity| { + buffer + .read(cx) + .file() + .map(|file| file.full_path(cx).compact().to_string_lossy().to_string()) + .unwrap_or_else(|| "untitled".into()) + }; + let old_path = path(&self.old_buffer); + let new_path = path(&self.new_buffer); + Some(format!("{old_path} ↔ {new_path}").into()) } @@ -363,7 +352,7 @@ impl Item for DiffView { } } -impl Render for DiffView { +impl Render for FileDiffView { fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { self.editor.clone() } @@ -407,16 +396,16 @@ mod tests { ) .await; - let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; + let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await; let (workspace, mut cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); let diff_view = workspace .update_in(cx, |workspace, window, cx| { - DiffView::open( - PathBuf::from(path!("/test/old_file.txt")), - PathBuf::from(path!("/test/new_file.txt")), + FileDiffView::open( + path!("/test/old_file.txt").into(), + path!("/test/new_file.txt").into(), workspace, window, cx, @@ -510,6 +499,21 @@ mod tests { ", ), ); + + diff_view.read_with(cx, |diff_view, cx| { + assert_eq!( + diff_view.tab_content_text(0, cx), + "old_file.txt ↔ new_file.txt" + ); + assert_eq!( + diff_view.tab_tooltip_text(cx).unwrap(), + format!( + "{} ↔ {}", + path!("test/old_file.txt"), + path!("test/new_file.txt") + ) + ); + }) } #[gpui::test] @@ -533,7 +537,7 @@ mod tests { let diff_view = workspace .update_in(cx, |workspace, window, cx| { - DiffView::open( + FileDiffView::open( PathBuf::from(path!("/test/old_file.txt")), PathBuf::from(path!("/test/new_file.txt")), workspace, diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index 02b9c243fb..2d7fba13c5 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -3,7 +3,7 @@ use std::any::Any; use ::settings::Settings; use command_palette_hooks::CommandPaletteFilter; use commit_modal::CommitModal; -use editor::Editor; +use editor::{Editor, actions::DiffClipboardWithSelectionData}; mod blame_ui; use git::{ repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus}, @@ -15,6 +15,9 @@ use onboarding::GitOnboardingModal; use project_diff::ProjectDiff; use ui::prelude::*; use workspace::Workspace; +use zed_actions; + +use crate::text_diff_view::TextDiffView; mod askpass_modal; pub mod branch_picker; @@ -22,7 +25,7 @@ mod commit_modal; pub mod commit_tooltip; mod commit_view; mod conflict_view; -pub mod diff_view; +pub mod file_diff_view; pub mod git_panel; mod git_panel_settings; pub mod onboarding; @@ -30,6 +33,7 @@ pub mod picker_prompt; pub mod project_diff; pub(crate) mod remote_output; pub mod repository_selector; +pub mod text_diff_view; actions!( git, @@ -152,6 +156,13 @@ pub fn init(cx: &mut App) { workspace.register_action(|workspace, _: &git::OpenModifiedFiles, window, cx| { open_modified_files(workspace, window, cx); }); + workspace.register_action( + |workspace, action: &DiffClipboardWithSelectionData, window, cx| { + if let Some(task) = TextDiffView::open(action, workspace, window, cx) { + task.detach(); + }; + }, + ); }) .detach(); } diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs new file mode 100644 index 0000000000..e7386cf7bd --- /dev/null +++ b/crates/git_ui/src/text_diff_view.rs @@ -0,0 +1,554 @@ +//! TextDiffView currently provides a UI for displaying differences between the clipboard and selected text. + +use anyhow::Result; +use buffer_diff::{BufferDiff, BufferDiffSnapshot}; +use editor::{Editor, EditorEvent, MultiBuffer, ToPoint, actions::DiffClipboardWithSelectionData}; +use futures::{FutureExt, select_biased}; +use gpui::{ + AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, + FocusHandle, Focusable, IntoElement, Render, Task, Window, +}; +use language::{self, Buffer, Point}; +use project::Project; +use std::{ + any::{Any, TypeId}, + ops::Range, + pin::pin, + sync::Arc, + time::Duration, +}; +use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString}; +use util::paths::PathExt; + +use workspace::{ + Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace, + item::{BreadcrumbText, ItemEvent, SaveOptions, TabContentParams}, + searchable::SearchableItemHandle, +}; + +pub struct TextDiffView { + diff_editor: Entity, + title: SharedString, + path: Option, + buffer_changes_tx: watch::Sender<()>, + _recalculate_diff_task: Task>, +} + +const RECALCULATE_DIFF_DEBOUNCE: Duration = Duration::from_millis(250); + +impl TextDiffView { + pub fn open( + diff_data: &DiffClipboardWithSelectionData, + workspace: &Workspace, + window: &mut Window, + cx: &mut App, + ) -> Option>>> { + let source_editor = diff_data.editor.clone(); + + let source_editor_buffer_and_range = source_editor.update(cx, |editor, cx| { + let multibuffer = editor.buffer().read(cx); + let source_buffer = multibuffer.as_singleton()?.clone(); + let selections = editor.selections.all::(cx); + let buffer_snapshot = source_buffer.read(cx); + let first_selection = selections.first()?; + let selection_range = if first_selection.is_empty() { + Point::new(0, 0)..buffer_snapshot.max_point() + } else { + first_selection.start..first_selection.end + }; + + Some((source_buffer, selection_range)) + }); + + let Some((source_buffer, selected_range)) = source_editor_buffer_and_range else { + log::warn!("There should always be at least one selection in Zed. This is a bug."); + return None; + }; + + let clipboard_text = diff_data.clipboard_text.clone(); + + let workspace = workspace.weak_handle(); + + let diff_buffer = cx.new(|cx| { + let source_buffer_snapshot = source_buffer.read(cx).snapshot(); + let diff = BufferDiff::new(&source_buffer_snapshot.text, cx); + diff + }); + + let clipboard_buffer = + build_clipboard_buffer(clipboard_text, &source_buffer, selected_range.clone(), cx); + + let task = window.spawn(cx, async move |cx| { + let project = workspace.update(cx, |workspace, _| workspace.project().clone())?; + + update_diff_buffer(&diff_buffer, &source_buffer, &clipboard_buffer, cx).await?; + + workspace.update_in(cx, |workspace, window, cx| { + let diff_view = cx.new(|cx| { + TextDiffView::new( + clipboard_buffer, + source_editor, + source_buffer, + selected_range, + diff_buffer, + project, + window, + cx, + ) + }); + + let pane = workspace.active_pane(); + pane.update(cx, |pane, cx| { + pane.add_item(Box::new(diff_view.clone()), true, true, None, window, cx); + }); + + diff_view + }) + }); + + Some(task) + } + + pub fn new( + clipboard_buffer: Entity, + source_editor: Entity, + source_buffer: Entity, + source_range: Range, + diff_buffer: Entity, + project: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite); + + multibuffer.push_excerpts( + source_buffer.clone(), + [editor::ExcerptRange::new(source_range)], + cx, + ); + + multibuffer.add_diff(diff_buffer.clone(), cx); + multibuffer + }); + let diff_editor = cx.new(|cx| { + let mut editor = Editor::for_multibuffer(multibuffer, Some(project), window, cx); + editor.start_temporary_diff_override(); + editor.disable_diagnostics(cx); + editor.set_expand_all_diff_hunks(cx); + editor.set_render_diff_hunk_controls( + Arc::new(|_, _, _, _, _, _, _, _| gpui::Empty.into_any_element()), + cx, + ); + editor + }); + + let (buffer_changes_tx, mut buffer_changes_rx) = watch::channel(()); + + cx.subscribe(&source_buffer, move |this, _, event, _| match event { + language::BufferEvent::Edited + | language::BufferEvent::LanguageChanged + | language::BufferEvent::Reparsed => { + this.buffer_changes_tx.send(()).ok(); + } + _ => {} + }) + .detach(); + + let editor = source_editor.read(cx); + let title = editor.buffer().read(cx).title(cx).to_string(); + let selection_location_text = selection_location_text(editor, cx); + let selection_location_title = selection_location_text + .as_ref() + .map(|text| format!("{} @ {}", title, text)) + .unwrap_or(title); + + let path = editor + .buffer() + .read(cx) + .as_singleton() + .and_then(|b| { + b.read(cx) + .file() + .map(|f| f.full_path(cx).compact().to_string_lossy().to_string()) + }) + .unwrap_or("untitled".into()); + + let selection_location_path = selection_location_text + .map(|text| format!("{} @ {}", path, text)) + .unwrap_or(path); + + Self { + diff_editor, + title: format!("Clipboard ↔ {selection_location_title}").into(), + path: Some(format!("Clipboard ↔ {selection_location_path}").into()), + buffer_changes_tx, + _recalculate_diff_task: cx.spawn(async move |_, cx| { + while let Ok(_) = buffer_changes_rx.recv().await { + loop { + let mut timer = cx + .background_executor() + .timer(RECALCULATE_DIFF_DEBOUNCE) + .fuse(); + let mut recv = pin!(buffer_changes_rx.recv().fuse()); + select_biased! { + _ = timer => break, + _ = recv => continue, + } + } + + log::trace!("start recalculating"); + update_diff_buffer(&diff_buffer, &source_buffer, &clipboard_buffer, cx).await?; + log::trace!("finish recalculating"); + } + Ok(()) + }), + } + } +} + +fn build_clipboard_buffer( + clipboard_text: String, + source_buffer: &Entity, + selected_range: Range, + cx: &mut App, +) -> Entity { + let source_buffer_snapshot = source_buffer.read(cx).snapshot(); + cx.new(|cx| { + let mut buffer = language::Buffer::local(source_buffer_snapshot.text(), cx); + let language = source_buffer.read(cx).language().cloned(); + buffer.set_language(language, cx); + + let range_start = source_buffer_snapshot.point_to_offset(selected_range.start); + let range_end = source_buffer_snapshot.point_to_offset(selected_range.end); + buffer.edit([(range_start..range_end, clipboard_text)], None, cx); + + buffer + }) +} + +async fn update_diff_buffer( + diff: &Entity, + source_buffer: &Entity, + clipboard_buffer: &Entity, + cx: &mut AsyncApp, +) -> Result<()> { + let source_buffer_snapshot = source_buffer.read_with(cx, |buffer, _| buffer.snapshot())?; + + let base_buffer_snapshot = clipboard_buffer.read_with(cx, |buffer, _| buffer.snapshot())?; + let base_text = base_buffer_snapshot.text().to_string(); + + let diff_snapshot = cx + .update(|cx| { + BufferDiffSnapshot::new_with_base_buffer( + source_buffer_snapshot.text.clone(), + Some(Arc::new(base_text)), + base_buffer_snapshot, + cx, + ) + })? + .await; + + diff.update(cx, |diff, cx| { + diff.set_snapshot(diff_snapshot, &source_buffer_snapshot.text, cx); + })?; + Ok(()) +} + +impl EventEmitter for TextDiffView {} + +impl Focusable for TextDiffView { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.diff_editor.focus_handle(cx) + } +} + +impl Item for TextDiffView { + type Event = EditorEvent; + + fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { + 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, _: &App) -> SharedString { + self.title.clone() + } + + fn tab_tooltip_text(&self, _: &App) -> Option { + self.path.clone() + } + + fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) { + Editor::to_item_events(event, f) + } + + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("Diff View Opened") + } + + fn deactivated(&mut self, window: &mut Window, cx: &mut Context) { + self.diff_editor + .update(cx, |editor, cx| editor.deactivated(window, cx)); + } + + fn is_singleton(&self, _: &App) -> bool { + false + } + + fn act_as_type<'a>( + &'a self, + type_id: TypeId, + self_handle: &'a Entity, + _: &'a App, + ) -> Option { + if type_id == TypeId::of::() { + Some(self_handle.to_any()) + } else if type_id == TypeId::of::() { + Some(self.diff_editor.to_any()) + } else { + None + } + } + + fn as_searchable(&self, _: &Entity) -> Option> { + Some(Box::new(self.diff_editor.clone())) + } + + fn for_each_project_item( + &self, + cx: &App, + f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem), + ) { + self.diff_editor.for_each_project_item(cx, f) + } + + fn set_nav_history( + &mut self, + nav_history: ItemNavHistory, + _: &mut Window, + cx: &mut Context, + ) { + self.diff_editor.update(cx, |editor, _| { + editor.set_nav_history(Some(nav_history)); + }); + } + + fn navigate( + &mut self, + data: Box, + window: &mut Window, + cx: &mut Context, + ) -> bool { + self.diff_editor + .update(cx, |editor, cx| editor.navigate(data, window, cx)) + } + + fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation { + ToolbarItemLocation::PrimaryLeft + } + + fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option> { + self.diff_editor.breadcrumbs(theme, cx) + } + + fn added_to_workspace( + &mut self, + workspace: &mut Workspace, + window: &mut Window, + cx: &mut Context, + ) { + self.diff_editor.update(cx, |editor, cx| { + editor.added_to_workspace(workspace, window, cx) + }); + } + + fn can_save(&self, cx: &App) -> bool { + // The editor handles the new buffer, so delegate to it + self.diff_editor.read(cx).can_save(cx) + } + + fn save( + &mut self, + options: SaveOptions, + project: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + // Delegate saving to the editor, which manages the new buffer + self.diff_editor + .update(cx, |editor, cx| editor.save(options, project, window, cx)) + } +} + +pub fn selection_location_text(editor: &Editor, cx: &App) -> Option { + let buffer = editor.buffer().read(cx); + let buffer_snapshot = buffer.snapshot(cx); + let first_selection = editor.selections.disjoint.first()?; + + let (start_row, start_column, end_row, end_column) = + if first_selection.start == first_selection.end { + let max_point = buffer_snapshot.max_point(); + (0, 0, max_point.row, max_point.column) + } else { + let selection_start = first_selection.start.to_point(&buffer_snapshot); + let selection_end = first_selection.end.to_point(&buffer_snapshot); + + ( + selection_start.row, + selection_start.column, + selection_end.row, + selection_end.column, + ) + }; + + let range_text = if start_row == end_row { + format!("L{}:{}-{}", start_row + 1, start_column + 1, end_column + 1) + } else { + format!( + "L{}:{}-L{}:{}", + start_row + 1, + start_column + 1, + end_row + 1, + end_column + 1 + ) + }; + + Some(range_text) +} + +impl Render for TextDiffView { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + self.diff_editor.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use editor::{actions, test::editor_test_context::assert_state_with_diff}; + use gpui::{TestAppContext, VisualContext}; + use project::{FakeFs, Project}; + use serde_json::json; + use settings::{Settings, SettingsStore}; + use unindent::unindent; + use util::path; + + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + editor::init_settings(cx); + theme::ThemeSettings::register(cx) + }); + } + + #[gpui::test] + async fn test_diffing_clipboard_against_specific_selection(cx: &mut TestAppContext) { + base_test(true, cx).await; + } + + #[gpui::test] + async fn test_diffing_clipboard_against_empty_selection_uses_full_buffer( + cx: &mut TestAppContext, + ) { + base_test(false, cx).await; + } + + async fn base_test(select_all_text: bool, cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/test"), + json!({ + "a": { + "b": { + "text.txt": "new line 1\nline 2\nnew line 3\nline 4" + } + } + }), + ) + .await; + + let project = Project::test(fs, [path!("/test").as_ref()], cx).await; + + let (workspace, mut cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/test/a/b/text.txt"), cx) + }) + .await + .unwrap(); + + let editor = cx.new_window_entity(|window, cx| { + let mut editor = Editor::for_buffer(buffer, None, window, cx); + editor.set_text("new line 1\nline 2\nnew line 3\nline 4\n", window, cx); + + if select_all_text { + editor.select_all(&actions::SelectAll, window, cx); + } + + editor + }); + + let diff_view = workspace + .update_in(cx, |workspace, window, cx| { + TextDiffView::open( + &DiffClipboardWithSelectionData { + clipboard_text: "old line 1\nline 2\nold line 3\nline 4\n".to_string(), + editor, + }, + workspace, + window, + cx, + ) + }) + .unwrap() + .await + .unwrap(); + + cx.executor().run_until_parked(); + + assert_state_with_diff( + &diff_view.read_with(cx, |diff_view, _| diff_view.diff_editor.clone()), + &mut cx, + &unindent( + " + - old line 1 + + ˇnew line 1 + line 2 + - old line 3 + + new line 3 + line 4 + ", + ), + ); + + diff_view.read_with(cx, |diff_view, cx| { + assert_eq!( + diff_view.tab_content_text(0, cx), + "Clipboard ↔ text.txt @ L1:1-L5:1" + ); + assert_eq!( + diff_view.tab_tooltip_text(cx).unwrap(), + format!("Clipboard ↔ {}", path!("test/a/b/text.txt @ L1:1-L5:1")) + ); + }); + } +} diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index d4e5b2570e..fccb417caa 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -370,7 +370,7 @@ impl std::fmt::Debug for AnyEntity { } } -/// A strong, well typed reference to a struct which is managed +/// A strong, well-typed reference to a struct which is managed /// by GPUI #[derive(Deref, DerefMut)] pub struct Entity { diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index b6feb0073e..2fd9b0a68c 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -12,7 +12,7 @@ use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::channel::{mpsc, oneshot}; use futures::future::join_all; use futures::{FutureExt, SinkExt, StreamExt}; -use git_ui::diff_view::DiffView; +use git_ui::file_diff_view::FileDiffView; use gpui::{App, AsyncApp, Global, WindowHandle}; use language::Point; use recent_projects::{SshSettings, open_ssh_project}; @@ -262,7 +262,7 @@ pub async fn open_paths_with_positions( let old_path = Path::new(&diff_pair[0]).canonicalize()?; let new_path = Path::new(&diff_pair[1]).canonicalize()?; if let Ok(diff_view) = workspace.update(cx, |workspace, window, cx| { - DiffView::open(old_path, new_path, workspace, window, cx) + FileDiffView::open(old_path, new_path, workspace, window, cx) }) { if let Some(diff_view) = diff_view.await.log_err() { items.push(Some(Ok(Box::new(diff_view))))