diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index 690265562e1c36e685574ec590819d8f513c128a..d6971da15fde8406ac4d00fb613906c91e25d8d4 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -3452,3 +3452,99 @@ async fn test_paths_with_starting_slash(cx: &mut TestAppContext) { assert_eq!(active_editor.read(cx).title(cx), "file1.txt"); }); } + +#[gpui::test] +async fn test_clear_navigation_history(cx: &mut TestAppContext) { + let app_state = init_test(cx); + app_state + .fs + .as_fake() + .insert_tree( + path!("/src"), + json!({ + "test": { + "first.rs": "// First file", + "second.rs": "// Second file", + "third.rs": "// Third file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await; + let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + + workspace.update_in(cx, |_workspace, window, cx| window.focused(cx)); + + // Open some files to generate navigation history + open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await; + open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await; + let history_before_clear = + open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await; + + assert_eq!( + history_before_clear.len(), + 2, + "Should have history items before clearing" + ); + + // Verify that file finder shows history items + let picker = open_file_picker(&workspace, cx); + cx.simulate_input("fir"); + picker.update(cx, |finder, _| { + let matches = collect_search_matches(finder); + assert!( + !matches.history.is_empty(), + "File finder should show history items before clearing" + ); + }); + workspace.update_in(cx, |_, window, cx| { + window.dispatch_action(menu::Cancel.boxed_clone(), cx); + }); + + // Verify navigation state before clear + workspace.update(cx, |workspace, cx| { + let pane = workspace.active_pane(); + pane.read(cx).can_navigate_backward() + }); + + // Clear navigation history + cx.dispatch_action(workspace::ClearNavigationHistory); + + // Verify that navigation is disabled immediately after clear + workspace.update(cx, |workspace, cx| { + let pane = workspace.active_pane(); + assert!( + !pane.read(cx).can_navigate_backward(), + "Should not be able to navigate backward after clearing history" + ); + assert!( + !pane.read(cx).can_navigate_forward(), + "Should not be able to navigate forward after clearing history" + ); + }); + + // Verify that file finder no longer shows history items + let picker = open_file_picker(&workspace, cx); + cx.simulate_input("fir"); + picker.update(cx, |finder, _| { + let matches = collect_search_matches(finder); + assert!( + matches.history.is_empty(), + "File finder should not show history items after clearing" + ); + }); + workspace.update_in(cx, |_, window, cx| { + window.dispatch_action(menu::Cancel.boxed_clone(), cx); + }); + + // Verify history is empty by opening a new file + // (this should not show any previous history) + let history_after_clear = + open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await; + assert_eq!( + history_after_clear.len(), + 0, + "Should have no history items after clearing" + ); +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 24f4254232b33975d77f227a6fa2af57d49c25fd..d85662733d52390db820957818901fa2e2cfd2a2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -4041,6 +4041,25 @@ impl NavHistory { self.0.lock().mode = NavigationMode::Normal; } + pub fn clear(&mut self, cx: &mut App) { + let mut state = self.0.lock(); + + if state.backward_stack.is_empty() + && state.forward_stack.is_empty() + && state.closed_stack.is_empty() + && state.paths_by_item.is_empty() + { + return; + } + + state.mode = NavigationMode::Normal; + state.backward_stack.clear(); + state.forward_stack.clear(); + state.closed_stack.clear(); + state.paths_by_item.clear(); + state.did_update(cx); + } + pub fn pop(&mut self, mode: NavigationMode, cx: &mut App) -> Option { let mut state = self.0.lock(); let entry = match mode { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7e35510652b1118e9dc8ffa18491d3c2a7904c75..316969812ac34e84f4019a191fda225e255700f0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -199,6 +199,8 @@ actions!( AddFolderToProject, /// Clears all notifications. ClearAllNotifications, + /// Clears all navigation history, including forward/backward navigation, recently opened files, and recently closed tabs. **This action is irreversible**. + ClearNavigationHistory, /// Closes the active dock. CloseActiveDock, /// Closes all docks. @@ -1917,6 +1919,12 @@ impl Workspace { .collect() } + pub fn clear_navigation_history(&mut self, _window: &mut Window, cx: &mut Context) { + for pane in &self.panes { + pane.update(cx, |pane, cx| pane.nav_history_mut().clear(cx)); + } + } + fn navigate_history( &mut self, pane: WeakEntity, @@ -5858,6 +5866,11 @@ impl Workspace { workspace.clear_all_notifications(cx); }, )) + .on_action(cx.listener( + |workspace: &mut Workspace, _: &ClearNavigationHistory, window, cx| { + workspace.clear_navigation_history(window, cx); + }, + )) .on_action(cx.listener( |workspace: &mut Workspace, _: &SuppressNotification, _, cx| { if let Some((notification_id, _)) = workspace.notifications.pop() {