project_panel: Add file comparison function, supports selecting files for comparison (#35255)
Closes https://github.com/zed-industries/zed/discussions/35010 Closes https://github.com/zed-industries/zed/issues/17100 Closes https://github.com/zed-industries/zed/issues/4523 Release Notes: - Added file comparison function in project panel --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
53b69d29c5
commit
e8db429d24
8 changed files with 295 additions and 45 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -12636,6 +12636,7 @@ dependencies = [
|
|||
"editor",
|
||||
"file_icons",
|
||||
"git",
|
||||
"git_ui",
|
||||
"gpui",
|
||||
"indexmap",
|
||||
"language",
|
||||
|
|
|
@ -848,6 +848,7 @@
|
|||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-d": "project_panel::CompareMarkedFiles",
|
||||
"shift-find": "project_panel::NewSearchInDirectory",
|
||||
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
|
|
|
@ -907,6 +907,7 @@
|
|||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-d": "project_panel::CompareMarkedFiles",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
|
|
|
@ -813,6 +813,7 @@
|
|||
"p": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFileManager",
|
||||
"s": "project_panel::OpenWithSystem",
|
||||
"z d": "project_panel::CompareMarkedFiles",
|
||||
"] c": "project_panel::SelectNextGitEntry",
|
||||
"[ c": "project_panel::SelectPrevGitEntry",
|
||||
"] d": "project_panel::SelectNextDiagnostic",
|
||||
|
|
|
@ -19,6 +19,7 @@ command_palette_hooks.workspace = true
|
|||
db.workspace = true
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
git_ui.workspace = true
|
||||
indexmap.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
|
|
|
@ -16,6 +16,7 @@ use editor::{
|
|||
};
|
||||
use file_icons::FileIcons;
|
||||
use git::status::GitSummary;
|
||||
use git_ui::file_diff_view::FileDiffView;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, ArcCow, AsyncWindowContext, Bounds, ClipboardItem, Context,
|
||||
CursorStyle, DismissEvent, Div, DragMoveEvent, Entity, EventEmitter, ExternalPaths,
|
||||
|
@ -93,7 +94,7 @@ pub struct ProjectPanel {
|
|||
unfolded_dir_ids: HashSet<ProjectEntryId>,
|
||||
// Currently selected leaf entry (see auto-folding for a definition of that) in a file tree
|
||||
selection: Option<SelectedEntry>,
|
||||
marked_entries: BTreeSet<SelectedEntry>,
|
||||
marked_entries: Vec<SelectedEntry>,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
edit_state: Option<EditState>,
|
||||
filename_editor: Entity<Editor>,
|
||||
|
@ -280,6 +281,8 @@ actions!(
|
|||
SelectNextDirectory,
|
||||
/// Selects the previous directory.
|
||||
SelectPrevDirectory,
|
||||
/// Opens a diff view to compare two marked files.
|
||||
CompareMarkedFiles,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -376,7 +379,7 @@ struct DraggedProjectEntryView {
|
|||
selection: SelectedEntry,
|
||||
details: EntryDetails,
|
||||
click_offset: Point<Pixels>,
|
||||
selections: Arc<BTreeSet<SelectedEntry>>,
|
||||
selections: Arc<[SelectedEntry]>,
|
||||
}
|
||||
|
||||
struct ItemColors {
|
||||
|
@ -442,7 +445,15 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
project::Event::ActiveEntryChanged(None) => {
|
||||
this.marked_entries.clear();
|
||||
let is_active_item_file_diff_view = this
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|ws| ws.read(cx).active_item(cx))
|
||||
.map(|item| item.act_as_type(TypeId::of::<FileDiffView>(), cx).is_some())
|
||||
.unwrap_or(false);
|
||||
if !is_active_item_file_diff_view {
|
||||
this.marked_entries.clear();
|
||||
}
|
||||
}
|
||||
project::Event::RevealInProjectPanel(entry_id) => {
|
||||
if let Some(()) = this
|
||||
|
@ -676,7 +687,7 @@ impl ProjectPanel {
|
|||
project_panel.update(cx, |project_panel, _| {
|
||||
let entry = SelectedEntry { worktree_id, entry_id };
|
||||
project_panel.marked_entries.clear();
|
||||
project_panel.marked_entries.insert(entry);
|
||||
project_panel.marked_entries.push(entry);
|
||||
project_panel.selection = Some(entry);
|
||||
});
|
||||
if !focus_opened_item {
|
||||
|
@ -887,6 +898,7 @@ impl ProjectPanel {
|
|||
let should_hide_rename = is_root
|
||||
&& (cfg!(target_os = "windows")
|
||||
|| (settings.hide_root && visible_worktrees_count == 1));
|
||||
let should_show_compare = !is_dir && self.file_abs_paths_to_diff(cx).is_some();
|
||||
|
||||
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
|
||||
menu.context(self.focus_handle.clone()).map(|menu| {
|
||||
|
@ -918,6 +930,10 @@ impl ProjectPanel {
|
|||
.when(is_foldable, |menu| {
|
||||
menu.action("Fold Directory", Box::new(FoldDirectory))
|
||||
})
|
||||
.when(should_show_compare, |menu| {
|
||||
menu.separator()
|
||||
.action("Compare marked files", Box::new(CompareMarkedFiles))
|
||||
})
|
||||
.separator()
|
||||
.action("Cut", Box::new(Cut))
|
||||
.action("Copy", Box::new(Copy))
|
||||
|
@ -1262,7 +1278,7 @@ impl ProjectPanel {
|
|||
};
|
||||
self.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.insert(selection);
|
||||
self.marked_entries.push(selection);
|
||||
}
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
|
@ -2007,7 +2023,7 @@ impl ProjectPanel {
|
|||
};
|
||||
self.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.insert(selection);
|
||||
self.marked_entries.push(selection);
|
||||
}
|
||||
|
||||
self.autoscroll(cx);
|
||||
|
@ -2244,7 +2260,7 @@ impl ProjectPanel {
|
|||
};
|
||||
self.selection = Some(selection);
|
||||
if window.modifiers().shift {
|
||||
self.marked_entries.insert(selection);
|
||||
self.marked_entries.push(selection);
|
||||
}
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
|
@ -2572,6 +2588,43 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn file_abs_paths_to_diff(&self, cx: &Context<Self>) -> Option<(PathBuf, PathBuf)> {
|
||||
let mut selections_abs_path = self
|
||||
.marked_entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let project = self.project.read(cx);
|
||||
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
|
||||
let entry = worktree.read(cx).entry_for_id(entry.entry_id)?;
|
||||
if !entry.is_file() {
|
||||
return None;
|
||||
}
|
||||
worktree.read(cx).absolutize(&entry.path).ok()
|
||||
})
|
||||
.rev();
|
||||
|
||||
let last_path = selections_abs_path.next()?;
|
||||
let previous_to_last = selections_abs_path.next()?;
|
||||
Some((previous_to_last, last_path))
|
||||
}
|
||||
|
||||
fn compare_marked_files(
|
||||
&mut self,
|
||||
_: &CompareMarkedFiles,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let selected_files = self.file_abs_paths_to_diff(cx);
|
||||
if let Some((file_path1, file_path2)) = selected_files {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
FileDiffView::open(file_path1, file_path2, workspace, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn open_system(&mut self, _: &OpenWithSystem, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some((worktree, entry)) = self.selected_entry(cx) {
|
||||
let abs_path = worktree.abs_path().join(&entry.path);
|
||||
|
@ -3914,11 +3967,9 @@ impl ProjectPanel {
|
|||
|
||||
let depth = details.depth;
|
||||
let worktree_id = details.worktree_id;
|
||||
let selections = Arc::new(self.marked_entries.clone());
|
||||
|
||||
let dragged_selection = DraggedSelection {
|
||||
active_selection: selection,
|
||||
marked_selections: selections,
|
||||
marked_selections: Arc::from(self.marked_entries.clone()),
|
||||
};
|
||||
|
||||
let bg_color = if is_marked {
|
||||
|
@ -4089,7 +4140,7 @@ impl ProjectPanel {
|
|||
});
|
||||
if drag_state.items().count() == 1 {
|
||||
this.marked_entries.clear();
|
||||
this.marked_entries.insert(drag_state.active_selection);
|
||||
this.marked_entries.push(drag_state.active_selection);
|
||||
}
|
||||
this.hover_expand_task.take();
|
||||
|
||||
|
@ -4156,65 +4207,69 @@ impl ProjectPanel {
|
|||
}),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(move |this, event: &gpui::ClickEvent, window, cx| {
|
||||
cx.listener(move |project_panel, event: &gpui::ClickEvent, window, cx| {
|
||||
if event.is_right_click() || event.first_focus()
|
||||
|| show_editor
|
||||
{
|
||||
return;
|
||||
}
|
||||
if event.standard_click() {
|
||||
this.mouse_down = false;
|
||||
project_panel.mouse_down = false;
|
||||
}
|
||||
cx.stop_propagation();
|
||||
|
||||
if let Some(selection) = this.selection.filter(|_| event.modifiers().shift) {
|
||||
let current_selection = this.index_for_selection(selection);
|
||||
if let Some(selection) = project_panel.selection.filter(|_| event.modifiers().shift) {
|
||||
let current_selection = project_panel.index_for_selection(selection);
|
||||
let clicked_entry = SelectedEntry {
|
||||
entry_id,
|
||||
worktree_id,
|
||||
};
|
||||
let target_selection = this.index_for_selection(clicked_entry);
|
||||
let target_selection = project_panel.index_for_selection(clicked_entry);
|
||||
if let Some(((_, _, source_index), (_, _, target_index))) =
|
||||
current_selection.zip(target_selection)
|
||||
{
|
||||
let range_start = source_index.min(target_index);
|
||||
let range_end = source_index.max(target_index) + 1;
|
||||
let mut new_selections = BTreeSet::new();
|
||||
this.for_each_visible_entry(
|
||||
let mut new_selections = Vec::new();
|
||||
project_panel.for_each_visible_entry(
|
||||
range_start..range_end,
|
||||
window,
|
||||
cx,
|
||||
|entry_id, details, _, _| {
|
||||
new_selections.insert(SelectedEntry {
|
||||
new_selections.push(SelectedEntry {
|
||||
entry_id,
|
||||
worktree_id: details.worktree_id,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
this.marked_entries = this
|
||||
.marked_entries
|
||||
.union(&new_selections)
|
||||
.cloned()
|
||||
.collect();
|
||||
for selection in &new_selections {
|
||||
if !project_panel.marked_entries.contains(selection) {
|
||||
project_panel.marked_entries.push(*selection);
|
||||
}
|
||||
}
|
||||
|
||||
this.selection = Some(clicked_entry);
|
||||
this.marked_entries.insert(clicked_entry);
|
||||
project_panel.selection = Some(clicked_entry);
|
||||
if !project_panel.marked_entries.contains(&clicked_entry) {
|
||||
project_panel.marked_entries.push(clicked_entry);
|
||||
}
|
||||
}
|
||||
} else if event.modifiers().secondary() {
|
||||
if event.click_count() > 1 {
|
||||
this.split_entry(entry_id, cx);
|
||||
project_panel.split_entry(entry_id, cx);
|
||||
} else {
|
||||
this.selection = Some(selection);
|
||||
if !this.marked_entries.insert(selection) {
|
||||
this.marked_entries.remove(&selection);
|
||||
project_panel.selection = Some(selection);
|
||||
if let Some(position) = project_panel.marked_entries.iter().position(|e| *e == selection) {
|
||||
project_panel.marked_entries.remove(position);
|
||||
} else {
|
||||
project_panel.marked_entries.push(selection);
|
||||
}
|
||||
}
|
||||
} else if kind.is_dir() {
|
||||
this.marked_entries.clear();
|
||||
project_panel.marked_entries.clear();
|
||||
if is_sticky {
|
||||
if let Some((_, _, index)) = this.index_for_entry(entry_id, worktree_id) {
|
||||
this.scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0));
|
||||
if let Some((_, _, index)) = project_panel.index_for_entry(entry_id, worktree_id) {
|
||||
project_panel.scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0));
|
||||
cx.notify();
|
||||
// move down by 1px so that clicked item
|
||||
// don't count as sticky anymore
|
||||
|
@ -4230,16 +4285,16 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
if event.modifiers().alt {
|
||||
this.toggle_expand_all(entry_id, window, cx);
|
||||
project_panel.toggle_expand_all(entry_id, window, cx);
|
||||
} else {
|
||||
this.toggle_expanded(entry_id, window, cx);
|
||||
project_panel.toggle_expanded(entry_id, window, cx);
|
||||
}
|
||||
} else {
|
||||
let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enabled;
|
||||
let click_count = event.click_count();
|
||||
let focus_opened_item = !preview_tabs_enabled || click_count > 1;
|
||||
let allow_preview = preview_tabs_enabled && click_count == 1;
|
||||
this.open_entry(entry_id, focus_opened_item, allow_preview, cx);
|
||||
project_panel.open_entry(entry_id, focus_opened_item, allow_preview, cx);
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
@ -4810,12 +4865,21 @@ impl ProjectPanel {
|
|||
{
|
||||
anyhow::bail!("can't reveal an ignored entry in the project panel");
|
||||
}
|
||||
let is_active_item_file_diff_view = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|ws| ws.read(cx).active_item(cx))
|
||||
.map(|item| item.act_as_type(TypeId::of::<FileDiffView>(), cx).is_some())
|
||||
.unwrap_or(false);
|
||||
if is_active_item_file_diff_view {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let worktree_id = worktree.id();
|
||||
self.expand_entry(worktree_id, entry_id, cx);
|
||||
self.update_visible_entries(Some((worktree_id, entry_id)), cx);
|
||||
self.marked_entries.clear();
|
||||
self.marked_entries.insert(SelectedEntry {
|
||||
self.marked_entries.push(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
});
|
||||
|
@ -5170,6 +5234,7 @@ impl Render for ProjectPanel {
|
|||
.on_action(cx.listener(Self::unfold_directory))
|
||||
.on_action(cx.listener(Self::fold_directory))
|
||||
.on_action(cx.listener(Self::remove_from_project))
|
||||
.on_action(cx.listener(Self::compare_marked_files))
|
||||
.when(!project.is_read_only(cx), |el| {
|
||||
el.on_action(cx.listener(Self::new_file))
|
||||
.on_action(cx.listener(Self::new_directory))
|
||||
|
|
|
@ -8,7 +8,7 @@ use settings::SettingsStore;
|
|||
use std::path::{Path, PathBuf};
|
||||
use util::path;
|
||||
use workspace::{
|
||||
AppState, Pane,
|
||||
AppState, ItemHandle, Pane,
|
||||
item::{Item, ProjectItem},
|
||||
register_project_item,
|
||||
};
|
||||
|
@ -3068,7 +3068,7 @@ async fn test_multiple_marked_entries(cx: &mut gpui::TestAppContext) {
|
|||
panel.update(cx, |this, cx| {
|
||||
let drag = DraggedSelection {
|
||||
active_selection: this.selection.unwrap(),
|
||||
marked_selections: Arc::new(this.marked_entries.clone()),
|
||||
marked_selections: this.marked_entries.clone().into(),
|
||||
};
|
||||
let target_entry = this
|
||||
.project
|
||||
|
@ -5562,10 +5562,10 @@ async fn test_highlight_entry_for_selection_drag(cx: &mut gpui::TestAppContext)
|
|||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
},
|
||||
marked_selections: Arc::new(BTreeSet::from([SelectedEntry {
|
||||
marked_selections: Arc::new([SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
}])),
|
||||
}]),
|
||||
};
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(parent_dir, worktree, &dragged_selection, cx);
|
||||
|
@ -5604,7 +5604,7 @@ async fn test_highlight_entry_for_selection_drag(cx: &mut gpui::TestAppContext)
|
|||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
},
|
||||
marked_selections: Arc::new(BTreeSet::from([
|
||||
marked_selections: Arc::new([
|
||||
SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: child_file.id,
|
||||
|
@ -5613,7 +5613,7 @@ async fn test_highlight_entry_for_selection_drag(cx: &mut gpui::TestAppContext)
|
|||
worktree_id,
|
||||
entry_id: sibling_file.id,
|
||||
},
|
||||
])),
|
||||
]),
|
||||
};
|
||||
let result =
|
||||
panel.highlight_entry_for_selection_drag(parent_dir, worktree, &dragged_selection, cx);
|
||||
|
@ -5821,6 +5821,186 @@ async fn test_hide_root(cx: &mut gpui::TestAppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_compare_selected_files(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"file1.txt": "content of file1",
|
||||
"file2.txt": "content of file2",
|
||||
"dir1": {
|
||||
"file3.txt": "content of file3"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
let file1_path = path!("root/file1.txt");
|
||||
let file2_path = path!("root/file2.txt");
|
||||
select_path_with_mark(&panel, file1_path, cx);
|
||||
select_path_with_mark(&panel, file2_path, cx);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.compare_marked_files(&CompareMarkedFiles, window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
let active_items = workspace
|
||||
.panes()
|
||||
.iter()
|
||||
.filter_map(|pane| pane.read(cx).active_item())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(active_items.len(), 1);
|
||||
let diff_view = active_items
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.downcast::<FileDiffView>()
|
||||
.expect("Open item should be an FileDiffView");
|
||||
assert_eq!(diff_view.tab_content_text(0, cx), "file1.txt ↔ file2.txt");
|
||||
assert_eq!(
|
||||
diff_view.tab_tooltip_text(cx).unwrap(),
|
||||
format!("{} ↔ {}", file1_path, file2_path)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let file1_entry_id = find_project_entry(&panel, file1_path, cx).unwrap();
|
||||
let file2_entry_id = find_project_entry(&panel, file2_path, cx).unwrap();
|
||||
let worktree_id = panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.project
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.next()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.id()
|
||||
});
|
||||
|
||||
let expected_entries = [
|
||||
SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: file1_entry_id,
|
||||
},
|
||||
SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: file2_entry_id,
|
||||
},
|
||||
];
|
||||
panel.update(cx, |panel, _cx| {
|
||||
assert_eq!(
|
||||
&panel.marked_entries, &expected_entries,
|
||||
"Should keep marked entries after comparison"
|
||||
);
|
||||
});
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(file2_entry_id))
|
||||
})
|
||||
});
|
||||
|
||||
panel.update(cx, |panel, _cx| {
|
||||
assert_eq!(
|
||||
&panel.marked_entries, &expected_entries,
|
||||
"Marked entries should persist after focusing back on the project panel"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_compare_files_context_menu(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"file1.txt": "content of file1",
|
||||
"file2.txt": "content of file2",
|
||||
"dir1": {},
|
||||
"dir2": {
|
||||
"file3.txt": "content of file3"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
// Test 1: When only one file is selected, there should be no compare option
|
||||
select_path(&panel, "root/file1.txt", cx);
|
||||
|
||||
let selected_files = panel.update(cx, |panel, cx| panel.file_abs_paths_to_diff(cx));
|
||||
assert_eq!(
|
||||
selected_files, None,
|
||||
"Should not have compare option when only one file is selected"
|
||||
);
|
||||
|
||||
// Test 2: When multiple files are selected, there should be a compare option
|
||||
select_path_with_mark(&panel, "root/file1.txt", cx);
|
||||
select_path_with_mark(&panel, "root/file2.txt", cx);
|
||||
|
||||
let selected_files = panel.update(cx, |panel, cx| panel.file_abs_paths_to_diff(cx));
|
||||
assert!(
|
||||
selected_files.is_some(),
|
||||
"Should have files selected for comparison"
|
||||
);
|
||||
if let Some((file1, file2)) = selected_files {
|
||||
assert!(
|
||||
file1.to_string_lossy().ends_with("file1.txt")
|
||||
&& file2.to_string_lossy().ends_with("file2.txt"),
|
||||
"Should have file1.txt and file2.txt as the selected files when multi-selecting"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 3: Selecting a directory shouldn't count as a comparable file
|
||||
select_path_with_mark(&panel, "root/dir1", cx);
|
||||
|
||||
let selected_files = panel.update(cx, |panel, cx| panel.file_abs_paths_to_diff(cx));
|
||||
assert!(
|
||||
selected_files.is_some(),
|
||||
"Directory selection should not affect comparable files"
|
||||
);
|
||||
if let Some((file1, file2)) = selected_files {
|
||||
assert!(
|
||||
file1.to_string_lossy().ends_with("file1.txt")
|
||||
&& file2.to_string_lossy().ends_with("file2.txt"),
|
||||
"Selecting a directory should not affect the number of comparable files"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4: Selecting one more file
|
||||
select_path_with_mark(&panel, "root/dir2/file3.txt", cx);
|
||||
|
||||
let selected_files = panel.update(cx, |panel, cx| panel.file_abs_paths_to_diff(cx));
|
||||
assert!(
|
||||
selected_files.is_some(),
|
||||
"Directory selection should not affect comparable files"
|
||||
);
|
||||
if let Some((file1, file2)) = selected_files {
|
||||
assert!(
|
||||
file1.to_string_lossy().ends_with("file2.txt")
|
||||
&& file2.to_string_lossy().ends_with("file3.txt"),
|
||||
"Selecting a directory should not affect the number of comparable files"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_path(panel: &Entity<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
|
||||
let path = path.as_ref();
|
||||
panel.update(cx, |panel, cx| {
|
||||
|
@ -5855,7 +6035,7 @@ fn select_path_with_mark(
|
|||
entry_id,
|
||||
};
|
||||
if !panel.marked_entries.contains(&entry) {
|
||||
panel.marked_entries.insert(entry);
|
||||
panel.marked_entries.push(entry);
|
||||
}
|
||||
panel.selection = Some(entry);
|
||||
return;
|
||||
|
|
|
@ -62,7 +62,7 @@ pub struct SelectedEntry {
|
|||
#[derive(Debug)]
|
||||
pub struct DraggedSelection {
|
||||
pub active_selection: SelectedEntry,
|
||||
pub marked_selections: Arc<BTreeSet<SelectedEntry>>,
|
||||
pub marked_selections: Arc<[SelectedEntry]>,
|
||||
}
|
||||
|
||||
impl DraggedSelection {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue