diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 78c4d9c148eec3..1f88c12a2b7301 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -231,10 +231,14 @@ impl OutlineViewDelegate { cx: &mut Context>, ) { + let Some(selected_match) = self.matches.get(ix) else { + self.selected_match_index = self.matches.len(); + return; + }; + self.selected_match_index = ix; - if navigate && !self.matches.is_empty() { - let selected_match = &self.matches[self.selected_match_index]; + if navigate { let outline_item = &self.outline.items[selected_match.candidate_id]; self.active_editor.update(cx, |active_editor, cx| { @@ -269,6 +273,10 @@ impl PickerDelegate for OutlineViewDelegate { self.selected_match_index } + fn can_select(&self, ix: usize, _window: &mut Window, _cx: &mut Context>) -> bool { + ix < self.matches.len() + } + fn set_selected_index( &mut self, ix: usize, @@ -704,6 +712,70 @@ mod tests { ); } + #[gpui::test] + async fn test_outline_stale_hover_index_after_matches_shrink(cx: &mut TestAppContext) { + init_test(cx); + + let mut source = String::new(); + for index in 0..69 { + source.push_str(&format!("struct Keep{index};\n")); + } + for index in 69..74 { + source.push_str(&format!("struct Drop{index};\n")); + } + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/dir"), json!({ "a.rs": source })) + .await; + + let project = Project::test(fs, [path!("/dir").as_ref()], cx).await; + project.read_with(cx, |project, _| { + project.languages().add(language::rust_lang()) + }); + + let (workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + + let workspace = cx.read(|cx| workspace.read(cx).workspace().clone()); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().update(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/dir/a.rs"), cx) + }) + .await + .unwrap(); + workspace + .update_in(cx, |workspace, window, cx| { + workspace.open_path((worktree_id, rel_path("a.rs")), None, true, window, cx) + }) + .await + .unwrap(); + + let outline_view = open_outline_view(&workspace, cx); + outline_view.read_with(cx, |outline_view, _| { + assert_eq!(outline_view.delegate.matches.len(), 74); + }); + + outline_view + .update_in(cx, |outline_view, window, cx| { + outline_view + .delegate + .update_matches("Keep".to_string(), window, cx) + }) + .await; + outline_view.read_with(cx, |outline_view, _| { + assert_eq!(outline_view.delegate.matches.len(), 69); + }); + + outline_view.update_in(cx, |outline_view, window, cx| { + outline_view.set_selected_index(73, None, false, window, cx); + }); + } + #[gpui::test] async fn test_outline_filtered_selection_prefers_first_match_on_score_ties( cx: &mut TestAppContext,