diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8fb9528a5f71cb62bda6811a9ddb862bfd6ccc81..9832ce8fe08fe23d610a1c2ee1a95ad4c2c2574c 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -67,6 +67,7 @@ "ctrl-o": "pane::GoBack", "ctrl-i": "pane::GoForward", "ctrl-]": "editor::GoToDefinition", + "ctrl-t": "pane::GoToOlderTag", "escape": "vim::SwitchToNormalMode", "ctrl-[": "vim::SwitchToNormalMode", "v": "vim::ToggleVisual", diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 282d6257e1fdc1a05d5eda320fc24e4bb1e05750..058933d14ade268c86a6158f851a1aaf3bcb69ef 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -472,7 +472,7 @@ impl Item for AgentDiffPane { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 2fc1b4e35d4535b60d1404e74916ce41dc58b589..85e9f4c444711695f787e36fbcf5341bca2b33c0 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -48,7 +48,7 @@ use settings::{ update_settings_file, }; use std::{ - any::TypeId, + any::{Any, TypeId}, cmp, ops::Range, path::{Path, PathBuf}, @@ -2894,7 +2894,7 @@ impl Item for TextThreadEditor { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index e1708e04ead5a115a329673e173c33b85f98e3e2..f08f207c3eb013c042bc4c3dc8630334c79145b0 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -6759,6 +6759,13 @@ async fn test_preview_tabs(cx: &mut TestAppContext) { cx.run_until_parked(); let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + right_pane.update(cx, |pane, cx| { + // Nav history is now cloned in an pane split, but that's inconvenient + // for this test, which uses the presence of a backwards history item as + // an indication that a preview item was successfully opened + pane.nav_history_mut().clear(cx); + }); + pane.update(cx, |pane, cx| { assert_eq!(pane.items_len(), 1); assert_eq!(get_path(pane, 0, cx), path_1.clone()); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 0e6ecaae6b89cd4ba34abc85a2bc6941b1b085a3..859def4415a401f9f21a1779d5fde6c9101b07b1 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -517,7 +517,7 @@ impl Item for ChannelView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index 1d274ba63da839a4a7e9da9cae4cacdc1d872aa5..9072547c6b01f5c748f34521dba283bb0cf8294d 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -1,4 +1,7 @@ -use std::any::{Any, TypeId}; +use std::{ + any::{Any, TypeId}, + sync::Arc, +}; use collections::HashMap; use dap::StackFrameId; @@ -333,7 +336,7 @@ impl Item for StackTraceView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index ef701241c5c50e2b5b42d9488a7d47985edbe4fd..6360e868d88ddeec677935beeba536d04cbc9131 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -757,7 +757,7 @@ impl Item for BufferDiagnosticsEditor { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 806e164a68aa9d80adc8ad23e6ce9363970c768a..2683582ae92711ef130db44619566cf5328c48bf 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -728,7 +728,7 @@ impl Item for ProjectDiagnosticsEditor { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e9012c17a22cd2082ee49ed1c2b327804275e5b3..554b98285ef8c14e55b88a780de2e25fdb8a6f38 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -203,9 +203,9 @@ use ui::{ }; use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc}; use workspace::{ - CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal, - RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast, - ViewId, Workspace, WorkspaceId, WorkspaceSettings, + CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal, + OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, + TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings, item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions}, notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt}, searchable::{CollapseDirection, SearchEvent}, @@ -1778,7 +1778,7 @@ enum SelectSyntaxNodeScrollBehavior { CursorBottom, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub(crate) struct NavigationData { cursor_anchor: Anchor, cursor_position: Point, @@ -14913,6 +14913,29 @@ impl Editor { ); } + fn navigation_data(&self, cursor_anchor: Anchor, cx: &App) -> NavigationData { + let buffer = self.buffer.read(cx).read(cx); + let cursor_position = cursor_anchor.to_point(&buffer); + let scroll_anchor = self.scroll_manager.anchor(); + let scroll_top_row = scroll_anchor.top_row(&buffer); + drop(buffer); + + NavigationData { + cursor_anchor, + cursor_position, + scroll_anchor, + scroll_top_row, + } + } + + fn navigation_entry(&self, cursor_anchor: Anchor, cx: &App) -> Option { + let Some(history) = self.nav_history.clone() else { + return None; + }; + let data = self.navigation_data(cursor_anchor, cx); + Some(history.navigation_entry(Some(Arc::new(data) as Arc))) + } + fn push_to_nav_history( &mut self, cursor_anchor: Anchor, @@ -14921,29 +14944,16 @@ impl Editor { always: bool, cx: &mut Context, ) { + let data = self.navigation_data(cursor_anchor, cx); if let Some(nav_history) = self.nav_history.as_mut() { - let buffer = self.buffer.read(cx).read(cx); - let cursor_position = cursor_anchor.to_point(&buffer); - let scroll_state = self.scroll_manager.anchor(); - let scroll_top_row = scroll_state.top_row(&buffer); - drop(buffer); - if let Some(new_position) = new_position { - let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs(); + let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs(); if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) { return; } } - nav_history.push( - Some(NavigationData { - cursor_anchor, - cursor_position, - scroll_anchor: scroll_state, - scroll_top_row, - }), - cx, - ); + nav_history.push(Some(data), cx); cx.emit(EditorEvent::PushedToNavHistory { anchor: cursor_anchor, is_deactivate, @@ -17556,6 +17566,8 @@ impl Editor { return Task::ready(Ok(Navigated::No)); }; + let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx); + cx.spawn_in(window, async move |editor, cx| { let Some(definitions) = definitions.await? else { return Ok(Navigated::No); @@ -17571,6 +17583,7 @@ impl Editor { }) .map(HoverLink::Text) .collect::>(), + nav_entry, split, window, cx, @@ -17663,6 +17676,7 @@ impl Editor { &mut self, kind: Option, definitions: Vec, + origin: Option, split: bool, window: &mut Window, cx: &mut Context, @@ -17752,16 +17766,34 @@ impl Editor { .update_in(cx, |workspace, window, cx| { let allow_preview = PreviewTabsSettings::get_global(cx) .enable_preview_multibuffer_from_code_navigation; - Self::open_locations_in_multibuffer( - workspace, - locations, - title, - split, - allow_preview, - MultibufferSelectionMode::First, - window, - cx, - ) + if let Some((target_editor, target_pane)) = + Self::open_locations_in_multibuffer( + workspace, + locations, + title, + split, + allow_preview, + MultibufferSelectionMode::First, + window, + cx, + ) + { + // We create our own nav history instead of using + // `target_editor.nav_history` because `nav_history` + // seems to be populated asynchronously when an item + // is added to a pane + let mut nav_history = target_pane + .update(cx, |pane, _| pane.nav_history_for_item(&target_editor)); + target_editor.update(cx, |editor, cx| { + let nav_data = editor + .navigation_data(editor.selections.newest_anchor().head(), cx); + let target = + Some(nav_history.navigation_entry(Some( + Arc::new(nav_data) as Arc + ))); + nav_history.push_tag(origin, target); + }) + } }) .is_ok(); @@ -17801,21 +17833,26 @@ impl Editor { let target_range = target_ranges.first().unwrap().clone(); editor.update_in(cx, |editor, window, cx| { - let range = target_range.to_point(target_buffer.read(cx)); - let range = editor.range_for_match(&range); + let range = editor.range_for_match(&target_range); let range = collapse_multiline_range(range); if !split && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() { editor.go_to_singleton_buffer_range(range, window, cx); + + let target = + editor.navigation_entry(editor.selections.newest_anchor().head(), cx); + if let Some(mut nav_history) = editor.nav_history.clone() { + nav_history.push_tag(origin, target); + } } else { let Some(workspace) = workspace else { return Navigated::No; }; let pane = workspace.read(cx).active_pane().clone(); window.defer(cx, move |window, cx| { - let target_editor: Entity = + let (target_editor, target_pane): (Entity, Entity) = workspace.update(cx, |workspace, cx| { let pane = if split { workspace.adjacent_pane(window, cx) @@ -17829,8 +17866,8 @@ impl Editor { let allow_new_preview = preview_tabs_settings .enable_preview_file_from_code_navigation; - workspace.open_project_item( - pane, + let editor = workspace.open_project_item( + pane.clone(), target_buffer.clone(), true, true, @@ -17838,13 +17875,30 @@ impl Editor { allow_new_preview, window, cx, - ) + ); + (editor, pane) }); + // We create our own nav history instead of using + // `target_editor.nav_history` because `nav_history` + // seems to be populated asynchronously when an item + // is added to a pane + let mut nav_history = target_pane + .update(cx, |pane, _| pane.nav_history_for_item(&target_editor)); target_editor.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. pane.update(cx, |pane, _| pane.disable_history()); target_editor.go_to_singleton_buffer_range(range, window, cx); + + let nav_data = target_editor.navigation_data( + target_editor.selections.newest_anchor().head(), + cx, + ); + let target = + Some(nav_history.navigation_entry(Some( + Arc::new(nav_data) as Arc + ))); + nav_history.push_tag(origin, target); pane.update(cx, |pane, _| pane.enable_history()); }); }); @@ -18203,10 +18257,10 @@ impl Editor { multibuffer_selection_mode: MultibufferSelectionMode, window: &mut Window, cx: &mut Context, - ) { + ) -> Option<(Entity, Entity)> { if locations.is_empty() { log::error!("bug: open_locations_in_multibuffer called with empty list of locations"); - return; + return None; } let capability = workspace.project().read(cx).capability(); @@ -18287,7 +18341,7 @@ impl Editor { } }); - let item = Box::new(editor); + let item = Box::new(editor.clone()); let pane = if split { workspace.adjacent_pane(window, cx) @@ -18306,6 +18360,8 @@ impl Editor { } pane.add_item(item, activate_pane, true, destination_index, window, cx); }); + + Some((editor, pane)) } pub fn rename( diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ad0c1a2db38bdcb91a24955bce83c5eae1432e49..4a563c32cecff17b77ca26d25d57a06779f1a589 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -954,7 +954,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) { invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok(); let invalid_point = Point::new(9999, 0); editor.navigate( - Box::new(NavigationData { + Arc::new(NavigationData { cursor_anchor: invalid_anchor, cursor_position: invalid_point, scroll_anchor: ScrollAnchor { diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 5ee81909a1c665b847f3aef05cdc495e918f4c6e..e812784e5bb6a8a681daf6ab967db3985383cf10 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -232,6 +232,13 @@ impl Editor { else { return Task::ready(Ok(Navigated::No)); }; + let Some(mb_anchor) = self + .buffer() + .read(cx) + .buffer_anchor_to_anchor(&buffer, anchor, cx) + else { + return Task::ready(Ok(Navigated::No)); + }; let links = hovered_link_state .links .into_iter() @@ -243,8 +250,10 @@ impl Editor { } }) .collect(); + let nav_entry = self.navigation_entry(mb_anchor, cx); let split = Self::is_alt_pressed(&modifiers, cx); - let navigate_task = self.navigate_to_hover_links(None, links, split, window, cx); + let navigate_task = + self.navigate_to_hover_links(None, links, nav_entry, split, window, cx); self.select(SelectPhase::End, window, cx); return navigate_task; } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index fc2d5e648a17568a0c242094219fa5ce71ef1859..dc1cc5f0c7a33eb2913396d44c0b79c5d6442696 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -29,7 +29,7 @@ use project::{ use rpc::proto::{self, update_view}; use settings::Settings; use std::{ - any::TypeId, + any::{Any, TypeId}, borrow::Cow, cmp::{self, Ordering}, iter, @@ -593,11 +593,11 @@ impl Item for Editor { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { - if let Ok(data) = data.downcast::() { + if let Some(data) = data.downcast_ref::() { let newest_selection = self.selections.newest::(&self.display_snapshot(cx)); let buffer = self.buffer.read(cx).read(cx); let offset = if buffer.can_resolve(&data.cursor_anchor) { diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 79bdfd660e0a08fa6bae12920be450d7651f7ee1..41c062b5dfed675fbf1fb2fefc378b00f4ab4bbc 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -76,6 +76,8 @@ pub fn go_to_parent_module( return; }; + let nav_entry = editor.navigation_entry(editor.selections.newest_anchor().head(), cx); + let project = project.clone(); let lsp_store = project.read(cx).lsp_store(); let upstream_client = lsp_store.read(cx).upstream_client(); @@ -123,6 +125,7 @@ pub fn go_to_parent_module( editor.navigate_to_hover_links( Some(GotoDefinitionKind::Declaration), location_links.into_iter().map(HoverLink::Text).collect(), + nav_entry, false, window, cx, diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index f33f877851455c292dd83dcf140ee55407b3b481..46e79f6dd4e0d14b74fa400baa67d10dd5e3d0fa 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1005,7 +1005,7 @@ impl Item for CommitView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index 048aa82cb58b04dee88df81f425583b129d52b75..0f295270d241dc109926a06e2ae6abad62b65a45 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -311,7 +311,7 @@ impl Item for FileDiffView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/git_ui/src/file_history_view.rs b/crates/git_ui/src/file_history_view.rs index f48160719ba5d9b00b8961b75e9ea402c80dd06a..121e44e29eb8ff3bc829522cb0a6b1d00f799c8f 100644 --- a/crates/git_ui/src/file_history_view.rs +++ b/crates/git_ui/src/file_history_view.rs @@ -12,6 +12,7 @@ use project::{ git_store::{GitStore, Repository}, }; use std::any::{Any, TypeId}; +use std::sync::Arc; use time::OffsetDateTime; use ui::{Avatar, Chip, Divider, ListItem, WithScrollbar, prelude::*}; @@ -574,7 +575,12 @@ impl Item for FileHistoryView { Task::ready(None) } - fn navigate(&mut self, _: Box, _window: &mut Window, _: &mut Context) -> bool { + fn navigate( + &mut self, + _: Arc, + _window: &mut Window, + _: &mut Context, + ) -> bool { false } diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index e56ee5a814d04da551c0af5cb387acf291c53ba5..05c8e482107cd30e37c6009f49ea6428fa0804fc 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -840,7 +840,7 @@ impl Item for ProjectDiff { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 59b974248400f0cbe1b8abfecb699b0849d797f9..c05384970a4f234fe011156271fb858c75c149cc 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -369,7 +369,7 @@ impl Item for TextDiffView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 60c5eaccba25fe8be4cfd7186a06f83c5f530661..0bb58e43e5bd21460f0b2338e1408bac5e3447fd 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -642,7 +642,7 @@ impl Item for ProjectSearchView { fn navigate( &mut self, - data: Box, + data: Arc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index eaf5cfd99704c415039bf30f94435bb75c0f79cc..f0ed65c8dac06ad88ad38d190ca22f50b319b2b1 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -219,7 +219,12 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { fn discarded(&self, _project: Entity, _window: &mut Window, _cx: &mut Context) {} fn on_removed(&self, _cx: &App) {} fn workspace_deactivated(&mut self, _window: &mut Window, _: &mut Context) {} - fn navigate(&mut self, _: Box, _window: &mut Window, _: &mut Context) -> bool { + fn navigate( + &mut self, + _: Arc, + _window: &mut Window, + _: &mut Context, + ) -> bool { false } @@ -480,7 +485,7 @@ pub trait ItemHandle: 'static + Send { fn deactivated(&self, window: &mut Window, cx: &mut App); fn on_removed(&self, cx: &App); fn workspace_deactivated(&self, window: &mut Window, cx: &mut App); - fn navigate(&self, data: Box, window: &mut Window, cx: &mut App) -> bool; + fn navigate(&self, data: Arc, window: &mut Window, cx: &mut App) -> bool; fn item_id(&self) -> EntityId; fn to_any_view(&self) -> AnyView; fn is_dirty(&self, cx: &App) -> bool; @@ -944,7 +949,7 @@ impl ItemHandle for Entity { self.update(cx, |this, cx| this.workspace_deactivated(window, cx)); } - fn navigate(&self, data: Box, window: &mut Window, cx: &mut App) -> bool { + fn navigate(&self, data: Arc, window: &mut Window, cx: &mut App) -> bool { self.update(cx, |this, cx| this.navigate(data, window, cx)) } @@ -1331,7 +1336,7 @@ pub mod test { InteractiveElement, IntoElement, Render, SharedString, Task, WeakEntity, Window, }; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; - use std::{any::Any, cell::Cell}; + use std::{any::Any, cell::Cell, sync::Arc}; use util::rel_path::rel_path; pub struct TestProjectItem { @@ -1564,14 +1569,18 @@ pub mod test { fn navigate( &mut self, - state: Box, + state: Arc, _window: &mut Window, _: &mut Context, ) -> bool { - let state = *state.downcast::().unwrap_or_default(); - if state != self.state { - self.state = state; - true + if let Some(state) = state.downcast_ref::>() { + let state = *state.clone(); + if state != self.state { + false + } else { + self.state = state; + true + } } else { false } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 9e24a132b08a666b14f292137b966209af8489af..2096c1fa23bc85690260007f3c89c5fe2b839a00 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -247,6 +247,10 @@ actions!( GoBack, /// Navigates forward in history. GoForward, + /// Navigates back in the tag stack. + GoToOlderTag, + /// Navigates forward in the tag stack. + GoToNewerTag, /// Joins this pane into the next pane. JoinIntoNext, /// Joins all panes into one. @@ -429,6 +433,7 @@ pub struct ActivationHistoryEntry { pub timestamp: usize, } +#[derive(Clone)] pub struct ItemNavHistory { history: NavHistory, item: Arc, @@ -438,11 +443,14 @@ pub struct ItemNavHistory { #[derive(Clone)] pub struct NavHistory(Arc>); +#[derive(Clone)] struct NavHistoryState { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, closed_stack: VecDeque, + tag_stack: VecDeque, + tag_stack_pos: usize, paths_by_item: HashMap)>, pane: WeakEntity, next_timestamp: Arc, @@ -459,13 +467,27 @@ pub enum NavigationMode { Disabled, } +#[derive(Debug, Default, Copy, Clone)] +pub enum TagNavigationMode { + #[default] + Older, + Newer, +} + +#[derive(Clone)] pub struct NavigationEntry { - pub item: Arc, - pub data: Option>, + pub item: Arc, + pub data: Option>, pub timestamp: usize, pub is_preview: bool, } +#[derive(Clone)] +pub struct TagStackEntry { + pub origin: NavigationEntry, + pub target: NavigationEntry, +} + #[derive(Clone)] pub struct DraggedTab { pub pane: Entity, @@ -534,6 +556,8 @@ impl Pane { backward_stack: Default::default(), forward_stack: Default::default(), closed_stack: Default::default(), + tag_stack: Default::default(), + tag_stack_pos: Default::default(), paths_by_item: Default::default(), pane: handle, next_timestamp, @@ -839,6 +863,16 @@ impl Pane { &mut self.nav_history } + pub fn fork_nav_history(&self) -> NavHistory { + let history = self.nav_history.0.lock().clone(); + NavHistory(Arc::new(Mutex::new(history))) + } + + pub fn set_nav_history(&mut self, history: NavHistory, cx: &Context) { + self.nav_history = history; + self.nav_history().0.lock().pane = cx.entity().downgrade(); + } + pub fn disable_history(&mut self) { self.nav_history.disable(); } @@ -879,6 +913,42 @@ impl Pane { } } + pub fn go_to_older_tag( + &mut self, + _: &GoToOlderTag, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(workspace) = self.workspace.upgrade() { + let pane = cx.entity().downgrade(); + window.defer(cx, move |window, cx| { + workspace.update(cx, |workspace, cx| { + workspace + .navigate_tag_history(pane, TagNavigationMode::Older, window, cx) + .detach_and_log_err(cx) + }) + }) + } + } + + pub fn go_to_newer_tag( + &mut self, + _: &GoToNewerTag, + window: &mut Window, + cx: &mut Context, + ) { + if let Some(workspace) = self.workspace.upgrade() { + let pane = cx.entity().downgrade(); + window.defer(cx, move |window, cx| { + workspace.update(cx, |workspace, cx| { + workspace + .navigate_tag_history(pane, TagNavigationMode::Newer, window, cx) + .detach_and_log_err(cx) + }) + }) + } + } + fn history_updated(&mut self, cx: &mut Context) { self.toolbar.update(cx, |_, cx| cx.notify()); } @@ -4159,6 +4229,8 @@ impl Render for Pane { .on_action(cx.listener(Pane::zoom_out)) .on_action(cx.listener(Self::navigate_backward)) .on_action(cx.listener(Self::navigate_forward)) + .on_action(cx.listener(Self::go_to_older_tag)) + .on_action(cx.listener(Self::go_to_newer_tag)) .on_action( cx.listener(|pane: &mut Pane, action: &ActivateItem, window, cx| { pane.activate_item( @@ -4391,7 +4463,7 @@ impl Render for Pane { } impl ItemNavHistory { - pub fn push(&mut self, data: Option, cx: &mut App) { + pub fn push(&mut self, data: Option, cx: &mut App) { if self .item .upgrade() @@ -4402,6 +4474,21 @@ impl ItemNavHistory { } } + pub fn navigation_entry(&self, data: Option>) -> NavigationEntry { + NavigationEntry { + item: self.item.clone(), + data: data, + timestamp: 0, // not used + is_preview: self.is_preview, + } + } + + pub fn push_tag(&mut self, origin: Option, target: Option) { + if let (Some(origin_entry), Some(target_entry)) = (origin, target) { + self.history.push_tag(origin_entry, target_entry); + } + } + pub fn pop_backward(&mut self, cx: &mut App) -> Option { self.history.pop(NavigationMode::GoingBack, cx) } @@ -4459,6 +4546,7 @@ impl NavHistory { && state.forward_stack.is_empty() && state.closed_stack.is_empty() && state.paths_by_item.is_empty() + && state.tag_stack.is_empty() { return; } @@ -4468,6 +4556,8 @@ impl NavHistory { state.forward_stack.clear(); state.closed_stack.clear(); state.paths_by_item.clear(); + state.tag_stack.clear(); + state.tag_stack_pos = 0; state.did_update(cx); } @@ -4488,10 +4578,10 @@ impl NavHistory { entry } - pub fn push( + pub fn push( &mut self, data: Option, - item: Arc, + item: Arc, is_preview: bool, cx: &mut App, ) { @@ -4504,7 +4594,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Arc::new(data) as Arc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4516,7 +4606,7 @@ impl NavHistory { } state.forward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Arc::new(data) as Arc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4527,7 +4617,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Arc::new(data) as Arc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4539,7 +4629,7 @@ impl NavHistory { } state.closed_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Arc::new(data) as Arc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4560,6 +4650,9 @@ impl NavHistory { state .closed_stack .retain(|entry| entry.item.id() != item_id); + state + .tag_stack + .retain(|entry| entry.origin.item.id() != item_id && entry.target.item.id() != item_id); } pub fn rename_item( @@ -4579,6 +4672,41 @@ impl NavHistory { pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option)> { self.0.lock().paths_by_item.get(&item_id).cloned() } + + pub fn push_tag(&mut self, origin: NavigationEntry, target: NavigationEntry) { + let mut state = self.0.lock(); + let truncate_to = state.tag_stack_pos; + state.tag_stack.truncate(truncate_to); + state.tag_stack.push_back(TagStackEntry { origin, target }); + state.tag_stack_pos = state.tag_stack.len(); + } + + pub fn pop_tag(&mut self, mode: TagNavigationMode) -> Option { + let mut state = self.0.lock(); + match mode { + TagNavigationMode::Older => { + if state.tag_stack_pos > 0 { + state.tag_stack_pos -= 1; + state + .tag_stack + .get(state.tag_stack_pos) + .map(|e| e.origin.clone()) + } else { + None + } + } + TagNavigationMode::Newer => { + let entry = state + .tag_stack + .get(state.tag_stack_pos) + .map(|e| e.target.clone()); + if state.tag_stack_pos < state.tag_stack.len() { + state.tag_stack_pos += 1; + } + entry + } + } + } } impl NavHistoryState { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 08e8088abf5003104a5667e74a189ac59694a809..eb8d6414ceea309fa83e96a3996e2aa8c3c71be5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2080,13 +2080,40 @@ impl Workspace { mode: NavigationMode, window: &mut Window, cx: &mut Context, + ) -> Task> { + self.navigate_history_impl(pane, mode, window, |history, cx| history.pop(mode, cx), cx) + } + + fn navigate_tag_history( + &mut self, + pane: WeakEntity, + mode: TagNavigationMode, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + self.navigate_history_impl( + pane, + NavigationMode::Normal, + window, + |history, _cx| history.pop_tag(mode), + cx, + ) + } + + fn navigate_history_impl( + &mut self, + pane: WeakEntity, + mode: NavigationMode, + window: &mut Window, + mut cb: impl FnMut(&mut NavHistory, &mut App) -> Option, + cx: &mut Context, ) -> Task> { let to_load = if let Some(pane) = pane.upgrade() { pane.update(cx, |pane, cx| { window.focus(&pane.focus_handle(cx), cx); loop { // Retrieve the weak item handle from the history. - let entry = pane.nav_history_mut().pop(mode, cx)?; + let entry = cb(pane.nav_history_mut(), cx)?; // If the item is still present in this pane, then activate it. if let Some(index) = entry @@ -4553,7 +4580,9 @@ impl Workspace { if let Some(clone) = task.await { this.update_in(cx, |this, window, cx| { let new_pane = this.add_pane(window, cx); + let nav_history = pane.read(cx).fork_nav_history(); new_pane.update(cx, |pane, cx| { + pane.set_nav_history(nav_history, cx); pane.add_item(clone, true, true, None, window, cx) }); this.center.split(&pane, &new_pane, direction, cx).unwrap();