From b69a2ea200561de9de0edf4d0c4428ee887fb918 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 5 Nov 2025 00:24:12 -0700 Subject: [PATCH] Tag stack 2 --- assets/keymaps/vim.json | 1 + crates/agent_ui/src/agent_diff.rs | 8 +- crates/agent_ui/src/text_thread_editor.rs | 2 +- crates/collab_ui/src/channel_view.rs | 8 +- crates/debugger_ui/src/stack_trace_view.rs | 12 +- crates/diagnostics/src/buffer_diagnostics.rs | 8 +- crates/diagnostics/src/diagnostics.rs | 8 +- crates/editor/src/editor.rs | 64 +++++++++- crates/editor/src/editor_tests.rs | 2 +- crates/editor/src/hover_links.rs | 13 ++ crates/editor/src/items.rs | 3 +- crates/git_ui/src/commit_view.rs | 8 +- crates/git_ui/src/file_diff_view.rs | 8 +- crates/git_ui/src/project_diff.rs | 12 +- crates/git_ui/src/text_diff_view.rs | 8 +- crates/search/src/project_search.rs | 8 +- crates/workspace/src/item.rs | 15 ++- crates/workspace/src/pane.rs | 125 +++++++++++++++++-- crates/workspace/src/workspace.rs | 121 ++++++++++++++++++ 19 files changed, 353 insertions(+), 81 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 9bde6ca7575b958d456d46a002a14e4289fe10fd..b27ae0edc6267795ea3563b6d591486314dd913e 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 63eb2ac49731a5e57b4eae5bf33b821b2e223c25..7b62bc865a82dea4bde8c634a8b794ae477d1a47 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -25,6 +25,7 @@ use std::{ any::{Any, TypeId}, collections::hash_map::Entry, ops::Range, + rc::Rc, sync::Arc, }; use ui::{CommonAnimationExt, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider}; @@ -516,12 +517,7 @@ impl Item for AgentDiffPane { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 991bd52efb1b0d30dc25040587dfde040dd1f58f..b3c2b85382d9cf7de69c7b85efe25cfbff901c7b 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -2530,7 +2530,7 @@ impl Item for TextThreadEditor { fn navigate( &mut self, - data: Box, + data: Rc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 5db588fdb3aad3f523864b5f90600e49eca9d8b6..2675d4b0b4525dc5c1e598dfa17f9351f76bb02d 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -18,6 +18,7 @@ use project::Project; use rpc::proto::ChannelVisibility; use std::{ any::{Any, TypeId}, + rc::Rc, sync::Arc, }; use ui::prelude::*; @@ -515,12 +516,7 @@ impl Item for ChannelView { }))) } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index 07caabaacaf00d2752a04c5ba68be07a5678c40a..612dbf55b3d6c5b45bc775163c40654f87cdf0c9 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}, + rc::Rc, +}; use collections::HashMap; use dap::StackFrameId; @@ -331,12 +334,7 @@ impl Item for StackTraceView { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 8fe503a706027fb6ed2f0b9114450eb79c2aa027..e8b223018e0f0245fbac88d01d675097f544a5c9 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -24,6 +24,7 @@ use settings::Settings; use std::{ any::{Any, TypeId}, cmp::Ordering, + rc::Rc, sync::Arc, }; use text::{Anchor, BufferSnapshot, OffsetRangeExt}; @@ -734,12 +735,7 @@ impl Item for BufferDiagnosticsEditor { self.multibuffer.read(cx).is_dirty(cx) } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5506cdba9ae4aaa7fbf1246aaa7b07e653ad0efc..85e61af4fbf82e8a8d5bbcc975233edf6e5df859 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -35,6 +35,7 @@ use std::{ any::{Any, TypeId}, cmp, ops::{Range, RangeInclusive}, + rc::Rc, sync::Arc, time::Duration, }; @@ -728,12 +729,7 @@ impl Item for ProjectDiagnosticsEditor { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad837e3260998a0f0c5e7c3568436785c0ead031..61b3a951448d48a58696d1ff62f82fc3809ebd4e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -14038,6 +14038,27 @@ impl Editor { ); } + fn finish_tag_jump(&mut self, point: Point, cx: &mut Context) { + let Some(nav_history) = self.nav_history.as_mut() else { + return; + }; + let snapshot = self.buffer.read(cx).snapshot(cx); + let cursor_anchor = snapshot.anchor_after(point); + let cursor_position = cursor_anchor.to_point(&snapshot); + let scroll_state = self.scroll_manager.anchor(); + let scroll_top_row = scroll_state.top_row(&snapshot); + dbg!("finish tag jump", cursor_position); + nav_history.finish_tag_jump( + Some(NavigationData { + cursor_anchor, + cursor_position, + scroll_anchor: scroll_state, + scroll_top_row, + }), + cx, + ); + } + fn push_to_nav_history( &mut self, cursor_anchor: Anchor, @@ -16437,18 +16458,37 @@ impl Editor { let Some(provider) = self.semantics_provider.clone() else { return Task::ready(Ok(Navigated::No)); }; - let head = self + let cursor = self .selections .newest::(&self.display_snapshot(cx)) .head(); - let buffer = self.buffer.read(cx); - let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else { + let multi_buffer = self.buffer.read(cx); + let Some((buffer, head)) = multi_buffer.text_anchor_for_position(cursor, cx) else { return Task::ready(Ok(Navigated::No)); }; let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else { return Task::ready(Ok(Navigated::No)); }; + if let Some(nav_history) = self.nav_history.as_mut() { + let snapshot = self.buffer.read(cx).snapshot(cx); + let cursor_anchor = snapshot.anchor_after(cursor); + let cursor_position = snapshot.offset_to_point(cursor); + let scroll_anchor = self.scroll_manager.anchor(); + let scroll_top_row = scroll_anchor.top_row(&snapshot); + + dbg!("start tag jump", cursor_position); + nav_history.start_tag_jump( + Some(NavigationData { + cursor_anchor, + cursor_position, + scroll_anchor, + scroll_top_row, + }), + cx, + ); + } + cx.spawn_in(window, async move |editor, cx| { let Some(definitions) = definitions.await? else { return Ok(Navigated::No); @@ -16693,6 +16733,7 @@ impl Editor { if !split && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() { + editor.finish_tag_jump(range.start, cx); editor.go_to_singleton_buffer_range(range, window, cx); } else { let pane = workspace.read(cx).active_pane().clone(); @@ -16718,6 +16759,7 @@ impl Editor { // 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.finish_tag_jump(range.start, cx); target_editor.go_to_singleton_buffer_range(range, window, cx); pane.update(cx, |pane, _| pane.enable_history()); }); @@ -17039,6 +17081,7 @@ impl Editor { multibuffer.with_title(title) }); + let first_range = ranges.first().cloned(); let existing = workspace.active_pane().update(cx, |pane, cx| { pane.items() .filter_map(|item| item.downcast::()) @@ -17091,6 +17134,21 @@ impl Editor { }); } }); + cx.defer({ + let editor = editor.clone(); + move |cx| { + let Some(range) = first_range else { return }; + editor.update(cx, |editor, cx| { + let point = editor + .buffer() + .read(cx) + .snapshot(cx) + .summary_for_anchor(&range.start); + + editor.finish_tag_jump(point, cx) + }) + } + }); let item = Box::new(editor); let item_id = item.item_id(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 40a5e487cf34ae35e34bca4ced53681ab684f115..8fa7fa4aa7ba8b8b6438219e2a91bf07282b5524 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -911,7 +911,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 { + Rc::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 f36c82b20277fc748620928e6d7fc49a2b20cd3e..d04b19924aad942442704832108e8ba791aca3a6 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -241,6 +241,19 @@ impl Editor { } }) .collect(); + + // todo!() + // if let Some(nav_history) = self.nav_history.as_mut() { + // nav_history.start_tag_jump( + // Some(NavigationData { + // cursor_anchor, + // cursor_position, + // scroll_anchor: scroll_state, + // scroll_top_row, + // }), + // cx, + // ); + // } let navigate_task = self.navigate_to_hover_links(None, links, modifiers.alt, window, cx); self.select(SelectPhase::End, window, cx); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 726ac800d601f8d98055d4e577b3af4f9ed436e2..63f7715a91bf76cfd4f29a7d2df0af315b189693 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -34,6 +34,7 @@ use std::{ iter, ops::Range, path::{Path, PathBuf}, + rc::Rc, sync::Arc, }; use text::{BufferId, BufferSnapshot, Selection}; @@ -589,7 +590,7 @@ impl Item for Editor { fn navigate( &mut self, - data: Box, + data: Rc, window: &mut Window, cx: &mut Context, ) -> bool { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 0a0c4c18e1f528a9ebaad9a8d9862982632dd04f..933bba0c9a796460b23b58cc4de5c73eb42c5446 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -17,6 +17,7 @@ use std::{ any::{Any, TypeId}, fmt::Write as _, path::PathBuf, + rc::Rc, sync::Arc, }; use ui::{ @@ -527,12 +528,7 @@ impl Item for CommitView { }); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index 387bda808708cf38beded2fe17edd92466885672..3005f33849910074341da3f44fc466428dcca389 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -14,6 +14,7 @@ use std::{ any::{Any, TypeId}, path::PathBuf, pin::pin, + rc::Rc, sync::Arc, time::Duration, }; @@ -301,12 +302,7 @@ impl Item for FileDiffView { }); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index b5906e75853a2ce4f691617013584ce20c26c83c..4a48e35de2e596efb373ef387d1487df23e4695b 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -32,9 +32,12 @@ use project::{ }, }; use settings::{Settings, SettingsStore}; -use std::any::{Any, TypeId}; use std::ops::Range; use std::sync::Arc; +use std::{ + any::{Any, TypeId}, + rc::Rc, +}; use theme::ActiveTheme; use ui::{KeyBinding, Tooltip, prelude::*, vertical_divider}; use util::{ResultExt as _, rel_path::RelPath}; @@ -649,12 +652,7 @@ impl Item for ProjectDiff { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index fd8cd3597377a6de78b3153ccc430afe81b1127e..045b62cd738d69816d51539ed65d1695a9f3069b 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -15,6 +15,7 @@ use std::{ cmp, ops::Range, pin::pin, + rc::Rc, sync::Arc, time::Duration, }; @@ -362,12 +363,7 @@ impl Item for TextDiffView { }); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.diff_editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index f5a9c272d4846a94230286cc3ae2f7903608dd7d..cfd3ae1e51e648d9b4432da7008d1d36bb3ab148 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -36,6 +36,7 @@ use std::{ mem, ops::{Not, Range}, pin::pin, + rc::Rc, sync::Arc, }; use ui::{IconButtonShape, KeyBinding, Toggleable, Tooltip, prelude::*, utils::SearchInputWidth}; @@ -633,12 +634,7 @@ impl Item for ProjectSearchView { }); } - fn navigate( - &mut self, - data: Box, - window: &mut Window, - cx: &mut Context, - ) -> bool { + fn navigate(&mut self, data: Rc, window: &mut Window, cx: &mut Context) -> bool { self.results_editor .update(cx, |editor, cx| editor.navigate(data, window, cx)) } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index ee9a10d9c5344cfa372bf88a95e46de1705ee093..36233e4067f1069c64b2843a660473616f0aed69 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -194,7 +194,7 @@ 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, _: Rc, _window: &mut Window, _: &mut Context) -> bool { false } @@ -449,7 +449,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: Rc, window: &mut Window, cx: &mut App) -> bool; fn item_id(&self) -> EntityId; fn to_any(&self) -> AnyView; fn is_dirty(&self, cx: &App) -> bool; @@ -900,7 +900,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: Rc, window: &mut Window, cx: &mut App) -> bool { self.update(cx, |this, cx| this.navigate(data, window, cx)) } @@ -1277,7 +1277,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, rc::Rc}; use util::rel_path::rel_path; pub struct TestProjectItem { @@ -1510,11 +1510,14 @@ pub mod test { fn navigate( &mut self, - state: Box, + state: Rc, _window: &mut Window, _: &mut Context, ) -> bool { - let state = *state.downcast::().unwrap_or_default(); + let state = state + .downcast_ref::() + .map(|s| s.to_string()) + .unwrap_or_default(); if state != self.state { self.state = state; true diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c2c79b6a5fc3cc337f6dd7273d529fd40f04c8a1..b1be7353eea65f9d6ed283b25c7debfaa41cee0b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -211,6 +211,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. @@ -417,6 +421,9 @@ struct NavHistoryState { backward_stack: VecDeque, forward_stack: VecDeque, closed_stack: VecDeque, + tag_stack: VecDeque<(NavigationEntry, NavigationEntry)>, + tag_stack_pos: usize, + pending_tag_source: Option, paths_by_item: HashMap)>, pane: WeakEntity, next_timestamp: Arc, @@ -438,9 +445,10 @@ impl Default for NavigationMode { } } +#[derive(Clone)] pub struct NavigationEntry { pub item: Arc, - pub data: Option>, + pub data: Option>, pub timestamp: usize, pub is_preview: bool, } @@ -513,6 +521,9 @@ impl Pane { backward_stack: Default::default(), forward_stack: Default::default(), closed_stack: Default::default(), + tag_stack: Default::default(), + tag_stack_pos: 0, + pending_tag_source: None, paths_by_item: Default::default(), pane: handle, next_timestamp, @@ -850,6 +861,24 @@ 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, window, cx) + .detach_and_log_err(cx) + }) + }) + } + } + fn history_updated(&mut self, cx: &mut Context) { self.toolbar.update(cx, |_, cx| cx.notify()); } @@ -3756,6 +3785,7 @@ impl Render for Pane { .on_action(cx.listener(Pane::toggle_zoom)) .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(|pane: &mut Pane, action: &ActivateItem, window, cx| { pane.activate_item( @@ -3996,8 +4026,40 @@ impl ItemNavHistory { self.history.pop(NavigationMode::GoingBack, cx) } - pub fn pop_forward(&mut self, cx: &mut App) -> Option { - self.history.pop(NavigationMode::GoingForward, cx) + pub fn start_tag_jump(&mut self, data: Option, cx: &mut App) + where + D: 'static + Any + Send, + { + if self + .item + .upgrade() + .is_some_and(|item| item.include_in_nav_history()) + { + self.history.start_tag_jump( + data.map(|data| Rc::new(data) as Rc), + self.item.clone(), + self.is_preview, + cx, + ); + } + } + + pub fn finish_tag_jump(&mut self, data: Option, cx: &mut App) + where + D: 'static + Any + Send, + { + if self + .item + .upgrade() + .is_some_and(|item| item.include_in_nav_history()) + { + self.history.finish_tag_jump( + data.map(|data| Rc::new(data) as Rc), + self.item.clone(), + self.is_preview, + cx, + ); + } } } @@ -4075,7 +4137,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Rc::new(data) as Rc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4087,7 +4149,7 @@ impl NavHistory { } state.forward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Rc::new(data) as Rc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4098,7 +4160,7 @@ impl NavHistory { } state.backward_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Rc::new(data) as Rc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4109,7 +4171,7 @@ impl NavHistory { } state.closed_stack.push_back(NavigationEntry { item, - data: data.map(|data| Box::new(data) as Box), + data: data.map(|data| Rc::new(data) as Rc), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), is_preview, }); @@ -4135,6 +4197,55 @@ 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 start_tag_jump( + &mut self, + data: Option>, + item: Arc, + is_preview: bool, + _cx: &mut App, + ) { + self.0.lock().pending_tag_source.replace(NavigationEntry { + item, + data, + timestamp: 0, + is_preview, + }); + } + + pub fn finish_tag_jump( + &mut self, + data: Option>, + item: Arc, + is_preview: bool, + _cx: &mut App, + ) { + let mut state = self.0.lock(); + let Some(source) = state.pending_tag_source.take() else { + debug_panic!("Finished tag jump without starting one?"); + return; + }; + let dest = NavigationEntry { + item, + data, + timestamp: 0, + is_preview, + }; + let truncate_to = state.tag_stack_pos; + state.tag_stack.truncate(truncate_to); + state.tag_stack.push_back((source, dest)); + state.tag_stack_pos += 1; + } + + pub fn tag_stack_back(&mut self) -> Option { + let mut state = self.0.lock(); + if state.tag_stack_pos > 0 { + state.tag_stack_pos -= 1; + Some(state.tag_stack[state.tag_stack_pos].0.clone()) + } else { + None + } + } } impl NavHistoryState { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 84b86a92ec5bdae9e6184037c8bf25488b308ff0..26c90c64edd0cee78d91bc5781877363b22b01b9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1927,6 +1927,127 @@ impl Workspace { .collect() } + fn navigate_tag_history( + &mut self, + pane: WeakEntity, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + let to_load = if let Some(pane) = pane.upgrade() { + pane.update(cx, |pane, cx| { + window.focus(&pane.focus_handle(cx)); + loop { + // Retrieve the weak item handle from the history. + let entry = pane.nav_history_mut().tag_stack_back()?; + + // If the item is still present in this pane, then activate it. + if let Some(index) = entry + .item + .upgrade() + .and_then(|v| pane.index_for_item(v.as_ref())) + { + let prev_active_item_index = pane.active_item_index(); + pane.activate_item(index, true, true, window, cx); + + let mut navigated = prev_active_item_index != pane.active_item_index(); + if let Some(data) = entry.data { + navigated |= pane.active_item()?.navigate(data, window, cx); + } + + if navigated { + break None; + } + } else { + // If the item is no longer present in this pane, then retrieve its + // path info in order to reopen it. + break pane + .nav_history() + .path_for_item(entry.item.id()) + .map(|(project_path, abs_path)| (project_path, abs_path, entry)); + } + } + }) + } else { + None + }; + + if let Some((project_path, abs_path, entry)) = to_load { + // If the item was no longer present, then load it again from its previous path, first try the local path + let open_by_project_path = self.load_path(project_path.clone(), window, cx); + + cx.spawn_in(window, async move |workspace, cx| { + let open_by_project_path = open_by_project_path.await; + let mut navigated = false; + match open_by_project_path + .with_context(|| format!("Navigating to {project_path:?}")) + { + Ok((project_entry_id, build_item)) => { + let prev_active_item_id = pane.update(cx, |pane, _| { + pane.active_item().map(|p| p.item_id()) + })?; + + pane.update_in(cx, |pane, window, cx| { + let item = pane.open_item( + project_entry_id, + project_path, + true, + entry.is_preview, + true, + None, + window, cx, + build_item, + ); + navigated |= Some(item.item_id()) != prev_active_item_id; + if let Some(data) = entry.data { + navigated |= item.navigate(data, window, cx); + } + })?; + } + Err(open_by_project_path_e) => { + // Fall back to opening by abs path, in case an external file was opened and closed, + // and its worktree is now dropped + if let Some(abs_path) = abs_path { + let prev_active_item_id = pane.update(cx, |pane, _| { + pane.active_item().map(|p| p.item_id()) + })?; + let open_by_abs_path = workspace.update_in(cx, |workspace, window, cx| { + workspace.open_abs_path(abs_path.clone(), OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx) + })?; + match open_by_abs_path + .await + .with_context(|| format!("Navigating to {abs_path:?}")) + { + Ok(item) => { + pane.update_in(cx, |pane, window, cx| { + navigated |= Some(item.item_id()) != prev_active_item_id; + if let Some(data) = entry.data { + navigated |= item.navigate(data, window, cx); + } + })?; + } + Err(open_by_abs_path_e) => { + log::error!("Failed to navigate history: {open_by_project_path_e:#} and {open_by_abs_path_e:#}"); + } + } + } + } + } + + if !navigated { + workspace + .update_in(cx, |workspace, window, cx| { + Self::navigate_tag_history(workspace, pane, window, cx) + })? + .await?; + } + + Ok(()) + }) + } else { + Task::ready(Ok(())) + } + } + fn navigate_history( &mut self, pane: WeakEntity,