Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 155 additions & 6 deletions crates/git_ui/src/git_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1112,12 +1112,14 @@ impl GitPanel {
return;
};

if selected_entry == item_count - 1 {
return;
}

let new_index = match &self.view_mode {
GitPanelViewMode::Flat => selected_entry.saturating_add(1),
GitPanelViewMode::Flat => {
if selected_entry >= item_count.saturating_sub(1) {
return;
}

selected_entry.saturating_add(1)
}
GitPanelViewMode::Tree(state) => {
let Some(current_logical_index) = state
.logical_indices
Expand All @@ -1127,7 +1129,15 @@ impl GitPanel {
return;
};

state.logical_indices[current_logical_index.saturating_add(1)]
let Some(new_index) = state
.logical_indices
.get(current_logical_index.saturating_add(1))
.copied()
else {
return;
};

new_index
}
};

Expand Down Expand Up @@ -7211,6 +7221,145 @@ mod tests {
});
}

#[gpui::test]
async fn test_tree_view_select_next_at_last_visible_collapsed_directory(
cx: &mut TestAppContext,
) {
init_test(cx);

let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"bar": {
"bar1.py": "print('bar1')",
"bar2.py": "print('bar2')",
},
"foo": {
"foo1.py": "print('foo1')",
"foo2.py": "print('foo2')",
},
Comment on lines +7234 to +7242

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I would suggest we strive to replicate @mchisolm0's repro with one twist - let's add a GitHeader entry into the mix, so something like this:

Tracked
bar/
  bar1.py
  bar2.py
foo/
  foo1.py
  foo2.py
Untracked
foobar.py

"foobar.py": "print('foobar')",
}),
)
.await;

fs.set_status_for_repo(
path!("/project/.git").as_ref(),
&[
("bar/bar1.py", StatusCode::Modified.worktree()),
("bar/bar2.py", StatusCode::Modified.worktree()),
("foo/foo1.py", StatusCode::Modified.worktree()),
("foo/foo2.py", StatusCode::Modified.worktree()),
("foobar.py", FileStatus::Untracked),
],
);

let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
let window_handle =
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = window_handle
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);

cx.read(|cx| {
project
.read(cx)
.worktrees(cx)
.next()
.unwrap()
.read(cx)
.as_local()
.unwrap()
.scan_complete()
})
.await;

cx.executor().run_until_parked();
cx.update(|_window, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.git_panel.get_or_insert_default().tree_view = Some(true);
})
});
});

let panel = workspace.update_in(cx, GitPanel::new);
let handle = cx.update_window_entity(&panel, |panel, _, _| {
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
});

cx.executor().advance_clock(2 * UPDATE_DEBOUNCE);
handle.await;

let foo_key = panel.read_with(cx, |panel, _| {
panel
.entries
.iter()
.find_map(|entry| match entry {
GitListEntry::Directory(dir) if dir.key.path == repo_path("foo") => {
Some(dir.key.clone())
}
_ => None,
})
.expect("foo directory should exist in tree view")
});

panel.update_in(cx, |panel, window, cx| {
panel.toggle_directory(&foo_key, window, cx);
});

let foo_idx = panel.read_with(cx, |panel, _| {
let state = panel
.view_mode
.tree_state()
.expect("tree view state should exist");
assert_eq!(state.expanded_dirs.get(&foo_key).copied(), Some(false));

let foo_idx = panel
.entries
.iter()
.enumerate()
.find_map(|(index, entry)| match entry {
GitListEntry::Directory(dir) if dir.key.path == repo_path("foo") => Some(index),
_ => None,
})
.expect("foo directory should exist in tree view");

let foo_logical_idx = state
.logical_indices
.iter()
.position(|&index| index == foo_idx)
.expect("foo directory should be visible");
let next_logical_idx = state.logical_indices[foo_logical_idx + 1];
assert!(matches!(
panel.entries.get(next_logical_idx),
Some(GitListEntry::Header(GitHeaderEntry {
header: Section::New
}))
));

foo_idx
});

panel.update_in(cx, |panel, window, cx| {
panel.selected_entry = Some(foo_idx);
panel.select_next(&menu::SelectNext, window, cx);
});

panel.read_with(cx, |panel, _| {
let selected_idx = panel.selected_entry.expect("selection should be set");
let selected_entry = panel
.entries
.get(selected_idx)
.and_then(|entry| entry.status_entry())
.expect("selected entry should be a status entry");
assert_eq!(selected_entry.repo_path, repo_path("foobar.py"));
});
}

fn assert_entry_paths(entries: &[GitListEntry], expected_paths: &[Option<&str>]) {
assert_eq!(entries.len(), expected_paths.len());
for (entry, expected_path) in entries.iter().zip(expected_paths) {
Expand Down