Skip to main content

Mountain/ApplicationState/State/FeatureState/NavigationHistory/
NavigationHistoryState.rs

1use std::sync::{Arc, Mutex as StandardMutex};
2
3use crate::dev_log;
4
5/// Tracks the editor navigation history stack (back/forward).
6///
7/// Implements a cursor-based navigation stack where `Index` points to the
8/// current position. GoBack decrements the index; GoForward increments it.
9/// Pushing a new entry truncates forward history.
10#[derive(Clone)]
11pub struct NavigationHistoryState {
12	/// The ordered list of visited URIs (oldest first).
13	Stack:Arc<StandardMutex<Vec<String>>>,
14
15	/// Current position in the stack (0-based). Points to the active entry.
16	Index:Arc<StandardMutex<usize>>,
17}
18
19impl Default for NavigationHistoryState {
20	fn default() -> Self {
21		dev_log!(
22			"history",
23			"[NavigationHistoryState] Initializing default navigation history state..."
24		);
25
26		Self {
27			Stack:Arc::new(StandardMutex::new(Vec::new())),
28
29			Index:Arc::new(StandardMutex::new(0)),
30		}
31	}
32}
33
34impl NavigationHistoryState {
35	/// Returns `true` if there is a previous location to navigate to.
36	pub fn CanGoBack(&self) -> bool {
37		let Stack = self.Stack.lock().ok().map(|G| G.len()).unwrap_or(0);
38
39		let Index = self.Index.lock().ok().as_deref().copied().unwrap_or(0);
40
41		Stack > 0 && Index > 0
42	}
43
44	/// Returns `true` if there is a next location to navigate to.
45	pub fn CanGoForward(&self) -> bool {
46		let Stack = self.Stack.lock().ok().map(|G| G.len()).unwrap_or(0);
47
48		let Index = self.Index.lock().ok().as_deref().copied().unwrap_or(0);
49
50		Stack > 0 && Index + 1 < Stack
51	}
52
53	/// Decrement the history index (go back). Returns the URI now active, or
54	/// `None` if already at the beginning.
55	pub fn GoBack(&self) -> Option<String> {
56		let Stack = self.Stack.lock().ok()?;
57
58		if Stack.is_empty() {
59			return None;
60		}
61
62		let mut Index = self.Index.lock().ok()?;
63
64		if *Index == 0 {
65			return None;
66		}
67
68		*Index -= 1;
69		let Uri = Stack.get(*Index).cloned();
70
71		dev_log!("history", "[NavigationHistoryState] GoBack → index={} uri={:?}", *Index, Uri);
72
73		Uri
74	}
75
76	/// Increment the history index (go forward). Returns the URI now active,
77	/// or `None` if already at the end.
78	pub fn GoForward(&self) -> Option<String> {
79		let Stack = self.Stack.lock().ok()?;
80
81		if Stack.is_empty() {
82			return None;
83		}
84
85		let mut Index = self.Index.lock().ok()?;
86
87		if *Index + 1 >= Stack.len() {
88			return None;
89		}
90
91		*Index += 1;
92		let Uri = Stack.get(*Index).cloned();
93
94		dev_log!("history", "[NavigationHistoryState] GoForward → index={} uri={:?}", *Index, Uri);
95
96		Uri
97	}
98
99	/// Push a URI onto the navigation stack. Truncates any forward history
100	/// beyond the current index.
101	pub fn Push(&self, Uri:String) {
102		if let (Ok(mut Stack), Ok(mut Index)) = (self.Stack.lock(), self.Index.lock()) {
103			// Truncate forward history
104			let NewIndex = if Stack.is_empty() { 0 } else { *Index + 1 };
105
106			Stack.truncate(NewIndex);
107
108			Stack.push(Uri.clone());
109
110			*Index = Stack.len() - 1;
111			dev_log!("history", "[NavigationHistoryState] Push uri={} index={}", Uri, *Index);
112		}
113	}
114
115	/// Clear the entire navigation stack.
116	pub fn Clear(&self) {
117		if let (Ok(mut Stack), Ok(mut Index)) = (self.Stack.lock(), self.Index.lock()) {
118			Stack.clear();
119
120			*Index = 0;
121			dev_log!("history", "[NavigationHistoryState] Stack cleared");
122		}
123	}
124
125	/// Return all URIs in the stack (oldest first).
126	pub fn GetStack(&self) -> Vec<String> { self.Stack.lock().ok().map(|G| G.clone()).unwrap_or_default() }
127}