1use std::path::PathBuf;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct FileLocation {
6 pub path: PathBuf,
7 pub line: usize,
8 pub column: usize,
9}
10
11impl FileLocation {
12 pub fn new(path: PathBuf, line: usize, column: usize) -> Self {
13 Self { path, line, column }
14 }
15}
16
17pub struct NavigationHistory {
19 history: Vec<FileLocation>,
21 current: usize,
23}
24
25impl NavigationHistory {
26 pub fn new() -> Self {
28 Self {
29 history: Vec::new(),
30 current: 0,
31 }
32 }
33
34 pub fn push(&mut self, location: FileLocation) {
37 if let Some(last) = self.history.last()
39 && last == &location
40 {
41 return;
42 }
43
44 if self.current < self.history.len() {
46 self.history.truncate(self.current);
47 }
48
49 self.history.push(location);
51 self.current = self.history.len();
52 }
53
54 pub fn back(&mut self) -> Option<&FileLocation> {
57 if self.current > 1 {
58 self.current -= 1;
59 self.history.get(self.current - 1)
60 } else {
61 None
62 }
63 }
64
65 pub fn forward(&mut self) -> Option<&FileLocation> {
68 if self.current < self.history.len() {
69 self.current += 1;
70 self.history.get(self.current - 1)
71 } else {
72 None
73 }
74 }
75
76 pub fn current(&self) -> Option<&FileLocation> {
78 if self.current > 0 && self.current <= self.history.len() {
79 self.history.get(self.current - 1)
80 } else {
81 None
82 }
83 }
84
85 pub fn can_go_back(&self) -> bool {
87 self.current > 1
88 }
89
90 pub fn can_go_forward(&self) -> bool {
92 self.current < self.history.len()
93 }
94
95 pub fn len(&self) -> usize {
97 self.history.len()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.history.is_empty()
103 }
104
105 pub fn clear(&mut self) {
107 self.history.clear();
108 self.current = 0;
109 }
110}
111
112impl Default for NavigationHistory {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_navigation_history_creation() {
124 let history = NavigationHistory::new();
125 assert!(history.is_empty());
126 assert_eq!(history.len(), 0);
127 assert!(!history.can_go_back());
128 assert!(!history.can_go_forward());
129 }
130
131 #[test]
132 fn test_push_location() {
133 let mut history = NavigationHistory::new();
134 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
135
136 history.push(loc1.clone());
137 assert_eq!(history.len(), 1);
138 assert_eq!(history.current(), Some(&loc1));
139 assert!(!history.can_go_back());
140 assert!(!history.can_go_forward());
141 }
142
143 #[test]
144 fn test_push_multiple_locations() {
145 let mut history = NavigationHistory::new();
146 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
147 let loc2 = FileLocation::new(PathBuf::from("file2.rs"), 20, 10);
148 let loc3 = FileLocation::new(PathBuf::from("file3.rs"), 30, 15);
149
150 history.push(loc1.clone());
151 history.push(loc2.clone());
152 history.push(loc3.clone());
153
154 assert_eq!(history.len(), 3);
155 assert_eq!(history.current(), Some(&loc3));
156 assert!(history.can_go_back());
157 assert!(!history.can_go_forward());
158 }
159
160 #[test]
161 fn test_duplicate_consecutive_locations() {
162 let mut history = NavigationHistory::new();
163 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
164
165 history.push(loc1.clone());
166 history.push(loc1.clone());
167 history.push(loc1.clone());
168
169 assert_eq!(history.len(), 1);
170 }
171
172 #[test]
173 fn test_back_navigation() {
174 let mut history = NavigationHistory::new();
175 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
176 let loc2 = FileLocation::new(PathBuf::from("file2.rs"), 20, 10);
177 let loc3 = FileLocation::new(PathBuf::from("file3.rs"), 30, 15);
178
179 history.push(loc1.clone());
180 history.push(loc2.clone());
181 history.push(loc3.clone());
182
183 assert_eq!(history.back(), Some(&loc2));
185 assert_eq!(history.current(), Some(&loc2));
186 assert!(history.can_go_back());
187 assert!(history.can_go_forward());
188
189 assert_eq!(history.back(), Some(&loc1));
191 assert_eq!(history.current(), Some(&loc1));
192 assert!(!history.can_go_back());
193 assert!(history.can_go_forward());
194
195 assert_eq!(history.back(), None);
197 }
198
199 #[test]
200 fn test_forward_navigation() {
201 let mut history = NavigationHistory::new();
202 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
203 let loc2 = FileLocation::new(PathBuf::from("file2.rs"), 20, 10);
204 let loc3 = FileLocation::new(PathBuf::from("file3.rs"), 30, 15);
205
206 history.push(loc1.clone());
207 history.push(loc2.clone());
208 history.push(loc3.clone());
209
210 history.back();
212 history.back();
213
214 assert_eq!(history.forward(), Some(&loc2));
216 assert_eq!(history.current(), Some(&loc2));
217
218 assert_eq!(history.forward(), Some(&loc3));
220 assert_eq!(history.current(), Some(&loc3));
221
222 assert_eq!(history.forward(), None);
224 }
225
226 #[test]
227 fn test_truncate_forward_history() {
228 let mut history = NavigationHistory::new();
229 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
230 let loc2 = FileLocation::new(PathBuf::from("file2.rs"), 20, 10);
231 let loc3 = FileLocation::new(PathBuf::from("file3.rs"), 30, 15);
232 let loc4 = FileLocation::new(PathBuf::from("file4.rs"), 40, 20);
233
234 history.push(loc1.clone());
235 history.push(loc2.clone());
236 history.push(loc3.clone());
237
238 history.back();
240 history.back();
241
242 history.push(loc4.clone());
245
246 assert_eq!(history.len(), 2);
248 assert_eq!(history.current(), Some(&loc4));
249 assert!(!history.can_go_forward());
250 }
251
252 #[test]
253 fn test_clear_history() {
254 let mut history = NavigationHistory::new();
255 let loc1 = FileLocation::new(PathBuf::from("file1.rs"), 10, 5);
256 let loc2 = FileLocation::new(PathBuf::from("file2.rs"), 20, 10);
257
258 history.push(loc1);
259 history.push(loc2);
260
261 history.clear();
262
263 assert!(history.is_empty());
264 assert_eq!(history.len(), 0);
265 assert!(!history.can_go_back());
266 assert!(!history.can_go_forward());
267 assert_eq!(history.current(), None);
268 }
269}