From ce8398f749d16962cd8a522167633d091986f69d Mon Sep 17 00:00:00 2001 From: vcoppe Date: Wed, 14 Oct 2026 08:17:51 +0200 Subject: [PATCH] renaming --- .../engine/src/engine/command/file/delete.rs | 4 +- .../src/engine/command/file/duplicate.rs | 4 +- gpx-rs/engine/src/engine/command/fixture.rs | 4 +- .../engine/command/pattern/update_selected.rs | 4 +- .../src/engine/derived/coordinates_cache.rs | 4 +- .../engine/derived/file_structure_cache.rs | 2 +- .../src/engine/derived/routing_buffer.rs | 8 +- .../src/engine/derived/statistics_cache.rs | 16 +- gpx-rs/engine/src/engine/mod.rs | 49 ++-- gpx-rs/engine/src/engine/state/clipboard.rs | 8 +- .../src/engine/state/{stack.rs => history.rs} | 268 +++++++++--------- gpx-rs/engine/src/engine/state/mod.rs | 6 +- gpx-rs/engine/src/engine/state/order.rs | 2 +- gpx-rs/engine/src/engine/state/selection.rs | 37 +-- gpx-rs/engine/src/engine/tests.rs | 36 +-- gpx-rs/engine/src/storage/persister.rs | 10 +- 16 files changed, 227 insertions(+), 235 deletions(-) rename gpx-rs/engine/src/engine/state/{stack.rs => history.rs} (55%) diff --git a/gpx-rs/engine/src/engine/command/file/delete.rs b/gpx-rs/engine/src/engine/command/file/delete.rs index b60ac4f57..e2b893f46 100644 --- a/gpx-rs/engine/src/engine/command/file/delete.rs +++ b/gpx-rs/engine/src/engine/command/file/delete.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{Apply, CommandError, FileId, Selection, StackEntry, State, Waypoint}; +use crate::{Apply, CommandError, FileId, Files, Selection, State, Waypoint}; /// Deletes the selected elements. With `whole_files`, the files holding the selected elements /// are deleted instead, even if only a track or a waypoint is selected. @@ -75,7 +75,7 @@ impl Apply for Delete { } pub(crate) fn delete_waypoints( - files: &mut StackEntry, + files: &mut Files, file_id: FileId, filter: impl Fn(&Waypoint) -> bool, ) -> Result { diff --git a/gpx-rs/engine/src/engine/command/file/duplicate.rs b/gpx-rs/engine/src/engine/command/file/duplicate.rs index 91d400ab4..545bba912 100644 --- a/gpx-rs/engine/src/engine/command/file/duplicate.rs +++ b/gpx-rs/engine/src/engine/command/file/duplicate.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, rc::Rc}; use crate::{ - Apply, CommandError, FileId, Selection, StackEntry, State, Waypoint, copy_file, copy_segment, + Apply, CommandError, FileId, Files, Selection, State, Waypoint, copy_file, copy_segment, copy_track, copy_waypoint, }; @@ -107,7 +107,7 @@ impl Apply for Duplicate { } fn duplicate_waypoints( - files: &mut StackEntry, + files: &mut Files, file_id: FileId, filter: impl Fn(&Waypoint) -> bool, ) -> Result { diff --git a/gpx-rs/engine/src/engine/command/fixture.rs b/gpx-rs/engine/src/engine/command/fixture.rs index fe21e3c67..3b4ab27af 100644 --- a/gpx-rs/engine/src/engine/command/fixture.rs +++ b/gpx-rs/engine/src/engine/command/fixture.rs @@ -2,11 +2,11 @@ use std::collections::HashSet; -use crate::{Clipboard, FileId, FileOrder, Selection, StackEntry, State, TrackpointCategories}; +use crate::{Clipboard, FileId, FileOrder, Files, Selection, State, TrackpointCategories}; #[derive(Default)] pub struct Fixture { - pub files: StackEntry, + pub files: Files, pub selection: Selection, pub order: FileOrder, pub clipboard: Option, diff --git a/gpx-rs/engine/src/engine/command/pattern/update_selected.rs b/gpx-rs/engine/src/engine/command/pattern/update_selected.rs index b49245083..f81bc83bc 100644 --- a/gpx-rs/engine/src/engine/command/pattern/update_selected.rs +++ b/gpx-rs/engine/src/engine/command/pattern/update_selected.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{File, FileId, Selection, StackEntry, State, Track, TrackSegment, Waypoint}; +use crate::{File, FileId, Files, Selection, State, Track, TrackSegment, Waypoint}; /// What an [`Editor`] hook did to the element it was given. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -70,7 +70,7 @@ fn edit_where( /// Edits a copy of the file (cheap, track points are shared chunks) and stores it back as a /// new snapshot only if something changed. -fn update_file(files: &mut StackEntry, id: FileId, f: impl FnOnce(&mut File) -> Edit) { +fn update_file(files: &mut Files, id: FileId, f: impl FnOnce(&mut File) -> Edit) { let Some(current) = files.get(&id) else { return; }; diff --git a/gpx-rs/engine/src/engine/derived/coordinates_cache.rs b/gpx-rs/engine/src/engine/derived/coordinates_cache.rs index 7ce53c346..49b6b8e09 100644 --- a/gpx-rs/engine/src/engine/derived/coordinates_cache.rs +++ b/gpx-rs/engine/src/engine/derived/coordinates_cache.rs @@ -1,8 +1,6 @@ use std::collections::{HashMap, HashSet}; -use crate::{ - FileId, FileWaypointsRevisionId, StackEntry as Files, TrackSegmentId, TrackSegmentRevisionId, -}; +use crate::{FileId, FileWaypointsRevisionId, Files, TrackSegmentId, TrackSegmentRevisionId}; /// Coordinates buffers, as flat `[lng, lat, lng, lat, ...]` arrays so that they can be handed /// to the UI without per-point calls: one per track segment (its trackpoints) and one per file diff --git a/gpx-rs/engine/src/engine/derived/file_structure_cache.rs b/gpx-rs/engine/src/engine/derived/file_structure_cache.rs index e13668146..1fc477aaa 100644 --- a/gpx-rs/engine/src/engine/derived/file_structure_cache.rs +++ b/gpx-rs/engine/src/engine/derived/file_structure_cache.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{Diff, FileId, FileStructure, StackEntry as Files}; +use crate::{Diff, FileId, FileStructure, Files}; /// The structure of each file, only recomputed for the files that changed according to the /// [`Diff`] of the stack. diff --git a/gpx-rs/engine/src/engine/derived/routing_buffer.rs b/gpx-rs/engine/src/engine/derived/routing_buffer.rs index 8181e4a46..8ae23f682 100644 --- a/gpx-rs/engine/src/engine/derived/routing_buffer.rs +++ b/gpx-rs/engine/src/engine/derived/routing_buffer.rs @@ -1,4 +1,4 @@ -use crate::{FileId, Selection, StackEntry, TrackSegmentId, TrackSegmentRevisionId}; +use crate::{FileId, Files, Selection, TrackSegmentId, TrackSegmentRevisionId}; /// What the routing tool needs about the selected segments (see /// [`Selection::segment_locations`]): where their anchors are among their trackpoints. @@ -27,7 +27,7 @@ pub struct RoutingBuffer { } impl RoutingBuffer { - pub fn update(&mut self, files: Option<&StackEntry>, selection: &Selection, order: &[FileId]) { + pub fn update(&mut self, files: Option<&Files>, selection: &Selection, order: &[FileId]) { self.anchor_indices.clear(); self.anchor_zooms.clear(); self.segment_starts.clear(); @@ -70,11 +70,11 @@ mod tests { use super::*; - fn loaded() -> (StackEntry, FileId) { + fn loaded() -> (Files, FileId) { let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); let file = parse(&data, &mut Default::default()).unwrap(); let id = file.id; - let mut files = StackEntry::default(); + let mut files = Files::default(); files.insert(id, Rc::new(file)); (files, id) } diff --git a/gpx-rs/engine/src/engine/derived/statistics_cache.rs b/gpx-rs/engine/src/engine/derived/statistics_cache.rs index 2f294435f..4073bab36 100644 --- a/gpx-rs/engine/src/engine/derived/statistics_cache.rs +++ b/gpx-rs/engine/src/engine/derived/statistics_cache.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet, hash_map::Entry}; use crate::{ - File, FileId, GlobalStatistics, Selection, StackEntry, Statistics, Track, TrackSegment, + File, FileId, Files, GlobalStatistics, Selection, Statistics, Track, TrackSegment, TrackSegmentRevisionId, }; @@ -14,7 +14,7 @@ pub struct StatisticsCache { } impl StatisticsCache { - pub fn update(&mut self, state: Option<&StackEntry>) { + pub fn update(&mut self, state: Option<&Files>) { let mut stale: HashSet = self.map.keys().copied().collect(); if let Some(state) = state { for file in state.values() { @@ -40,7 +40,7 @@ impl StatisticsCache { /// Statistics of the segments of the selection. pub fn get<'a>( &'a self, - state: Option<&'a StackEntry>, + state: Option<&'a Files>, selection: &Selection, ) -> Vec<&'a Statistics> { self.selected(state, selection, &[]) @@ -53,7 +53,7 @@ impl StatisticsCache { /// files follow `order` (the files missing from it come last). pub fn selected<'a>( &'a self, - state: Option<&'a StackEntry>, + state: Option<&'a Files>, selection: &Selection, order: &[FileId], ) -> Vec> { @@ -159,10 +159,10 @@ mod tests { use super::*; - fn state(path: &str) -> (StackEntry, Rc) { + fn state(path: &str) -> (Files, Rc) { let data = std::fs::read(path).unwrap(); let file = Rc::new(parse(&data, &mut Default::default()).unwrap()); - let mut state = StackEntry::default(); + let mut state = Files::default(); state.insert(file.id, file.clone()); (state, file) } @@ -235,7 +235,7 @@ mod tests { let before: *const Statistics = cache.get(Some(&state), &file_selection(file.id))[0]; // a new snapshot of the file that shares its segments (same revision ids) - let mut next = StackEntry::default(); + let mut next = Files::default(); let renamed = { let mut f = (*file).clone(); f.info.name = "other".to_string(); @@ -258,7 +258,7 @@ mod tests { let mut f = (*file).clone(); f.trk[0].trkseg[0].rev_id = Default::default(); let new_rev = f.trk[0].trkseg[0].rev_id; - let mut next = StackEntry::default(); + let mut next = Files::default(); next.insert(f.id, Rc::new(f)); cache.update(Some(&next)); assert!(cache.map.contains_key(&new_rev)); diff --git a/gpx-rs/engine/src/engine/mod.rs b/gpx-rs/engine/src/engine/mod.rs index bd75da891..1fc6822f9 100644 --- a/gpx-rs/engine/src/engine/mod.rs +++ b/gpx-rs/engine/src/engine/mod.rs @@ -17,7 +17,7 @@ use crate::{ #[derive(Debug, Default)] pub struct Engine { - stack: Stack, + history: History, selection: Selection, order: FileOrder, statistics_cache: StatisticsCache, @@ -56,7 +56,7 @@ impl Engine { /// Global statistics (distance, time, elevation, bounds...) of a file. pub fn file_statistics(&self, id: &FileId) -> Option { - let file = self.stack.current()?.get(id)?; + let file = self.history.current()?.get(id)?; Some(self.statistics_cache.file_global(file)) } @@ -74,7 +74,7 @@ impl Engine { /// A waypoint of a file, with all its data. `None` if it does not exist. pub fn waypoint(&self, file_id: &FileId, id: &WaypointId) -> Option<&Waypoint> { - self.stack + self.history .current()? .get(file_id)? .wpt @@ -90,7 +90,7 @@ impl Engine { index: usize, ) -> Option<&Trackpoint> { let segment = self - .stack + .history .current()? .get(file_id)? .trk @@ -103,7 +103,7 @@ impl Engine { /// For each trackpoint of the selection (numbered as in the statistics), the tolerance up to /// which `Reduce` keeps it, see [`reduction_distances`]. pub fn reduction_distances(&self) -> Vec { - let Some(files) = self.stack.current() else { + let Some(files) = self.history.current() else { return vec![]; }; self.selection @@ -120,7 +120,7 @@ impl Engine { /// What there is to keep of the engine, see [`crate::Persister`]. pub fn snapshot(&self) -> Snapshot { Snapshot { - files: self.stack.current().cloned().unwrap_or_default(), + files: self.history.current().cloned().unwrap_or_default(), order: self.order.0.clone(), categories: self.categories.clone(), } @@ -137,19 +137,19 @@ impl Engine { pub fn restore(&mut self, restored: Restored) -> Outcome { let selection_before = self.selection.clone(); let order_before = self.order.0.clone(); - let before = self.stack.current().cloned(); - let files: StackEntry = restored + let before = self.history.current().cloned(); + let files: Files = restored .files .into_iter() .map(|file| (file.id, file)) .collect(); - let diff = Stack::diff_between(before.as_ref(), Some(&files)); + let diff = History::diff_between(before.as_ref(), Some(&files)); let mut diff = (!(diff.added.is_empty() && diff.removed.is_empty() && diff.modified.is_empty())) .then_some(diff); self.categories = restored.categories; self.order = FileOrder(restored.order); - self.stack = Stack::restored(files); + self.history = History::restored(files); self.refresh(diff.as_ref()); sort_diff(&mut diff, &self.order.0); Outcome { @@ -165,13 +165,13 @@ impl Engine { /// A file as GPX (UTF-8), `None` if it does not exist. pub fn export(&self, id: &FileId, options: ExportOptions) -> Option> { - let file = self.stack.current()?.get(id)?; + let file = self.history.current()?.get(id)?; Some(write(file, &self.categories, options)) } /// The data that the given files have, which the options of `export` can leave out. pub fn exportable_data(&self, ids: &[FileId]) -> ExportOptions { - let Some(files) = self.stack.current() else { + let Some(files) = self.history.current() else { return ExportOptions::NONE; }; ids.iter() @@ -183,12 +183,12 @@ impl Engine { /// Whether there is something to undo. pub fn can_undo(&self) -> bool { - self.stack.can_undo() + self.history.can_undo() } /// Whether there is something to redo. pub fn can_redo(&self) -> bool { - self.stack.can_redo() + self.history.can_redo() } /// Currently selected elements. @@ -216,20 +216,21 @@ impl Engine { // the files change iff there is a diff, but selection and order actions have none let mut diff = match action { Action::Edit(command) => self.edit(command)?, - Action::Undo => self.stack.undo(), - Action::Redo => self.stack.redo(), + Action::Undo => self.history.undo(), + Action::Redo => self.history.redo(), Action::Select { selection, mode } => { - self.selection.select(self.stack.current(), selection, mode); + self.selection + .select(self.history.current(), selection, mode); None } Action::SelectAll => { - if let Some(files) = self.stack.current() { + if let Some(files) = self.history.current() { self.selection.select_all(files, &self.order.0); } None } Action::ArrowSelect { down, add } => { - if let Some(files) = self.stack.current() { + if let Some(files) = self.history.current() { self.selection .select_neighbour(files, &self.order.0, down, add); } @@ -271,7 +272,7 @@ impl Engine { /// Puts the selection in the clipboard, if there is one. fn copy(&mut self, cut: bool) { - if let Some(files) = self.stack.current() + if let Some(files) = self.history.current() && let Some(clipboard) = Clipboard::new(&self.selection, files, &self.order.0, cut) { self.clipboard = Some(clipboard); @@ -279,7 +280,7 @@ impl Engine { } fn edit(&mut self, command: Command) -> Result, CommandError> { - self.stack.create_and_push_next(|files| { + self.history.create_and_push_next(|files| { let mut state = State { categories: &mut self.categories, files, @@ -291,9 +292,9 @@ impl Engine { }) } - /// Brings everything derived from the files back in line with the current stack entry. + /// Brings everything derived from the files back in line with the current history entry. fn refresh(&mut self, diff: Option<&Diff>) { - let current = self.stack.current(); + let current = self.history.current(); match current { Some(files) => { // files coming back after an undo/redo are added at the end of the order @@ -319,7 +320,7 @@ impl Engine { } } -/// Puts the files of a diff in the order of the files (the stack does not know it), so that +/// Puts the files of a diff in the order of the files (the history does not know it), so that /// whatever is done with them one after the other, like giving them colors, follows it. fn sort_diff(diff: &mut Option, order: &[FileId]) { let position = |id: &FileId| order.iter().position(|other| other == id); diff --git a/gpx-rs/engine/src/engine/state/clipboard.rs b/gpx-rs/engine/src/engine/state/clipboard.rs index 6f9684302..eee118835 100644 --- a/gpx-rs/engine/src/engine/state/clipboard.rs +++ b/gpx-rs/engine/src/engine/state/clipboard.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use uuid::Uuid; use crate::{ - File, FileId, Selection, StackEntry, Track, TrackId, TrackSegment, TrackSegmentId, Waypoint, + File, FileId, Files, Selection, Track, TrackId, TrackSegment, TrackSegmentId, Waypoint, WaypointId, }; @@ -71,7 +71,7 @@ impl Clipboard { /// selected does not exist). pub fn new( selection: &Selection, - files: &StackEntry, + files: &Files, order: &[FileId], cut: bool, ) -> Option { @@ -238,14 +238,14 @@ mod tests { file } - fn files_of(files: Vec) -> StackEntry { + fn files_of(files: Vec) -> Files { files .into_iter() .map(|file| (file.id, Rc::new(file))) .collect() } - fn order_of(files: &StackEntry) -> Vec { + fn order_of(files: &Files) -> Vec { files.keys().copied().collect() } diff --git a/gpx-rs/engine/src/engine/state/stack.rs b/gpx-rs/engine/src/engine/state/history.rs similarity index 55% rename from gpx-rs/engine/src/engine/state/stack.rs rename to gpx-rs/engine/src/engine/state/history.rs index 17b9da038..d8d722983 100644 --- a/gpx-rs/engine/src/engine/state/stack.rs +++ b/gpx-rs/engine/src/engine/state/history.rs @@ -6,17 +6,17 @@ use std::{ use crate::{CommandError, File, FileId}; #[derive(Debug, Default)] -pub struct Stack { - entries: Vec, +pub struct History { + entries: Vec, index: Option, - /// Whether the first entry is the state the stack starts from, which is not undone: files + /// Whether the first entry is the state the history starts from, which is not undone: files /// restored from the storage. floor: bool, } -impl Stack { - /// A stack whose first entry is `entry`, from which nothing can be undone. - pub fn restored(entry: StackEntry) -> Self { +impl History { + /// A history whose first entry is `entry`, from which nothing can be undone. + pub fn restored(entry: Files) -> Self { Self { entries: vec![entry], index: Some(0), @@ -24,7 +24,7 @@ impl Stack { } } - pub fn current(&self) -> Option<&StackEntry> { + pub fn current(&self) -> Option<&Files> { self.index.map_or_default(|i| self.get(i)) } @@ -35,18 +35,18 @@ impl Stack { /// history. Files are told apart by their identity, not their content. pub fn create_and_push_next(&mut self, f: F) -> Result, CommandError> where - F: FnOnce(&mut StackEntry) -> Result<(), CommandError>, + F: FnOnce(&mut Files) -> Result<(), CommandError>, { let mut next = self.current().map_or_default(|c| c.clone()); f(&mut next)?; if self.is_current(&next) { return Ok(None); } - Ok(self.record_diff(|stack| stack.push(next))) + Ok(self.record_diff(|history| history.push(next))) } /// Whether `entry` holds exactly the files of the current entry. - fn is_current(&self, entry: &StackEntry) -> bool { + fn is_current(&self, entry: &Files) -> bool { let current = self.current(); let len = current.map_or(0, |current| current.len()); entry.len() == len @@ -58,29 +58,29 @@ impl Stack { } pub fn undo(&mut self) -> Option { - self.record_diff(|stack| { - if let Some(i) = stack.index { + self.record_diff(|history| { + if let Some(i) = history.index { if i == 0 { - if !stack.floor { - stack.index = None; + if !history.floor { + history.index = None; } } else { - stack.index = Some(i - 1); + history.index = Some(i - 1); } } }) } pub fn redo(&mut self) -> Option { - self.record_diff(|stack| match stack.index { + self.record_diff(|history| match history.index { Some(i) => { - if i + 1 < stack.entries.len() { - stack.index = Some(i + 1); + if i + 1 < history.entries.len() { + history.index = Some(i + 1); } } None => { - if !stack.entries.is_empty() { - stack.index = Some(0); + if !history.entries.is_empty() { + history.index = Some(0); } } }) @@ -97,11 +97,11 @@ impl Stack { } } - fn get(&self, index: usize) -> Option<&StackEntry> { + fn get(&self, index: usize) -> Option<&Files> { self.entries.get(index) } - fn push(&mut self, entry: StackEntry) { + fn push(&mut self, entry: Files) { if let Some(i) = self.index { self.entries.truncate(i + 1); } @@ -127,7 +127,7 @@ impl Stack { } /// Which files differ between two states. - pub fn diff_between(prev: Option<&StackEntry>, cur: Option<&StackEntry>) -> Diff { + pub fn diff_between(prev: Option<&Files>, cur: Option<&Files>) -> Diff { let prev_ids: HashSet = prev.map_or_default(|e| e.keys().copied().collect()); let cur_ids: HashSet = cur.map_or_default(|e| e.keys().copied().collect()); let mut modified = vec![]; @@ -146,9 +146,9 @@ impl Stack { } } -pub type StackEntry = HashMap>; +pub type Files = HashMap>; -/// Which files were added, removed or modified by a change of the current stack entry. +/// Which files were added, removed or modified by a change of the current history entry. #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Diff { pub added: Vec, @@ -160,11 +160,11 @@ pub struct Diff { mod tests { use super::*; - impl Stack { + impl History { /// A step that is expected not to fail. fn push_ok( &mut self, - f: impl FnOnce(&mut StackEntry) -> Result<(), CommandError>, + f: impl FnOnce(&mut Files) -> Result<(), CommandError>, ) -> Option { self.create_and_push_next(f).unwrap() } @@ -172,21 +172,21 @@ mod tests { #[test] fn test_new_file() { - let mut stack = Stack::default(); + let mut history = History::default(); - assert!(!stack.can_undo()); - assert!(!stack.can_redo()); - assert!(stack.current().is_none()); + assert!(!history.can_undo()); + assert!(!history.can_redo()); + assert!(history.current().is_none()); - let diff = stack.push_ok(|e| { + let diff = history.push_ok(|e| { let file = Rc::new(File::default()); e.insert(file.id, file); Ok(()) }); - assert!(stack.can_undo()); - assert!(!stack.can_redo()); - assert!(stack.current().is_some()); + assert!(history.can_undo()); + assert!(!history.can_redo()); + assert!(history.current().is_some()); assert!(diff.is_some()); @@ -196,10 +196,10 @@ mod tests { assert!(diff.modified.is_empty()); } - fn add_file(stack: &mut Stack) -> FileId { + fn add_file(history: &mut History) -> FileId { let file = Rc::new(File::default()); let id = file.id; - stack + history .push_ok(|e| { e.insert(id, file); Ok(()) @@ -210,46 +210,46 @@ mod tests { #[test] fn test_failed_command_pushes_nothing() { - let mut stack = Stack::default(); - let diff = stack.create_and_push_next(|e| { + let mut history = History::default(); + let diff = history.create_and_push_next(|e| { e.insert(FileId::default(), Rc::new(File::default())); Err(CommandError::InvalidData("nope".to_string())) }); assert_eq!(diff, Err(CommandError::InvalidData("nope".to_string()))); - assert!(!stack.can_undo()); - assert!(stack.current().is_none()); + assert!(!history.can_undo()); + assert!(history.current().is_none()); } #[test] fn test_undo_redo() { - let mut stack = Stack::default(); - assert!(stack.undo().is_none()); - assert!(stack.redo().is_none()); + let mut history = History::default(); + assert!(history.undo().is_none()); + assert!(history.redo().is_none()); - let id = add_file(&mut stack); + let id = add_file(&mut history); - let diff = stack.undo().unwrap(); + let diff = history.undo().unwrap(); assert_eq!(diff.removed, vec![id]); assert!(diff.added.is_empty()); - assert!(stack.current().is_none()); - assert!(!stack.can_undo()); - assert!(stack.can_redo()); - assert!(stack.undo().is_none()); + assert!(history.current().is_none()); + assert!(!history.can_undo()); + assert!(history.can_redo()); + assert!(history.undo().is_none()); - let diff = stack.redo().unwrap(); + let diff = history.redo().unwrap(); assert_eq!(diff.added, vec![id]); - assert!(stack.current().unwrap().contains_key(&id)); - assert!(stack.can_undo()); - assert!(!stack.can_redo()); - assert!(stack.redo().is_none()); + assert!(history.current().unwrap().contains_key(&id)); + assert!(history.can_undo()); + assert!(!history.can_redo()); + assert!(history.redo().is_none()); } #[test] fn test_modified_and_removed() { - let mut stack = Stack::default(); - let id = add_file(&mut stack); + let mut history = History::default(); + let id = add_file(&mut history); - let diff = stack + let diff = history .push_ok(|e| { let mut file = (**e.get(&id).unwrap()).clone(); file.info.name = "renamed".to_string(); @@ -261,9 +261,9 @@ mod tests { assert!(diff.added.is_empty() && diff.removed.is_empty()); // a step that changes nothing is not a step - assert!(stack.push_ok(|_| Ok(())).is_none()); + assert!(history.push_ok(|_| Ok(())).is_none()); assert!( - stack + history .push_ok(|e| { let file = e[&id].clone(); e.insert(id, file); @@ -272,28 +272,28 @@ mod tests { .is_none() ); - let diff = stack + let diff = history .push_ok(|e| { e.remove(&id); Ok(()) }) .unwrap(); assert_eq!(diff.removed, vec![id]); - assert!(stack.current().unwrap().is_empty()); + assert!(history.current().unwrap().is_empty()); // going back restores the renamed file - stack.undo(); - assert_eq!(stack.current().unwrap()[&id].info.name, "renamed"); + history.undo(); + assert_eq!(history.current().unwrap()[&id].info.name, "renamed"); } #[test] fn test_a_file_is_modified_when_it_is_not_the_same_one_anymore() { - let mut stack = Stack::default(); - let id = add_file(&mut stack); + let mut history = History::default(); + let id = add_file(&mut history); - // a copy, even if it has the same content, is another file for the stack: files are + // a copy, even if it has the same content, is another file for the history: files are // told apart by their identity, comparing them would read all of them - let diff = stack + let diff = history .push_ok(|e| { let copy = (**e.get(&id).unwrap()).clone(); e.insert(id, Rc::new(copy)); @@ -303,8 +303,8 @@ mod tests { assert_eq!(diff.modified, vec![id]); // and the files that were not replaced are not - let other = add_file(&mut stack); - let diff = stack + let other = add_file(&mut history); + let diff = history .push_ok(|e| { let copy = (**e.get(&other).unwrap()).clone(); e.insert(other, Rc::new(copy)); @@ -316,120 +316,120 @@ mod tests { #[test] fn test_push_after_undo_drops_redo_branch() { - let mut stack = Stack::default(); - let first = add_file(&mut stack); - let second = add_file(&mut stack); + let mut history = History::default(); + let first = add_file(&mut history); + let second = add_file(&mut history); - stack.undo(); - assert!(stack.can_redo()); - assert!(!stack.current().unwrap().contains_key(&second)); + history.undo(); + assert!(history.can_redo()); + assert!(!history.current().unwrap().contains_key(&second)); - let third = add_file(&mut stack); - assert!(!stack.can_redo()); - assert!(stack.redo().is_none()); - let current = stack.current().unwrap(); + let third = add_file(&mut history); + assert!(!history.can_redo()); + assert!(history.redo().is_none()); + let current = history.current().unwrap(); assert!(current.contains_key(&first)); assert!(current.contains_key(&third)); assert!(!current.contains_key(&second)); } - fn restored_entry() -> (StackEntry, FileId) { + fn restored_entry() -> (Files, FileId) { let file = Rc::new(File::default()); let id = file.id; - (StackEntry::from([(id, file)]), id) + (Files::from([(id, file)]), id) } #[test] - fn test_restored_stack_starts_from_its_entry() { + fn test_restored_history_starts_from_its_entry() { let (entry, id) = restored_entry(); - let mut stack = Stack::restored(entry); + let mut history = History::restored(entry); - assert!(stack.current().unwrap().contains_key(&id)); + assert!(history.current().unwrap().contains_key(&id)); // there is nothing to go back to, nor forward to - assert!(!stack.can_undo()); - assert!(!stack.can_redo()); - assert!(stack.undo().is_none()); - assert!(stack.redo().is_none()); - assert!(stack.current().unwrap().contains_key(&id)); + assert!(!history.can_undo()); + assert!(!history.can_redo()); + assert!(history.undo().is_none()); + assert!(history.redo().is_none()); + assert!(history.current().unwrap().contains_key(&id)); } #[test] - fn test_restored_stack_cannot_undo_an_empty_entry_either() { - let mut stack = Stack::restored(StackEntry::new()); + fn test_restored_history_cannot_undo_an_empty_entry_either() { + let mut history = History::restored(Files::new()); - assert!(stack.current().is_some_and(|files| files.is_empty())); - assert!(!stack.can_undo()); - assert!(stack.undo().is_none()); - // the stack is not the one of an engine that has done nothing - assert!(stack.current().is_some()); + assert!(history.current().is_some_and(|files| files.is_empty())); + assert!(!history.can_undo()); + assert!(history.undo().is_none()); + // the history is not the one of an engine that has done nothing + assert!(history.current().is_some()); } #[test] - fn test_restored_stack_undoes_what_comes_after_it_and_no_further() { + fn test_restored_history_undoes_what_comes_after_it_and_no_further() { let (entry, restored) = restored_entry(); - let mut stack = Stack::restored(entry); + let mut history = History::restored(entry); - let added = add_file(&mut stack); - assert!(stack.can_undo()); - assert!(!stack.can_redo()); - assert_eq!(stack.current().unwrap().len(), 2); + let added = add_file(&mut history); + assert!(history.can_undo()); + assert!(!history.can_redo()); + assert_eq!(history.current().unwrap().len(), 2); // back to the restored files - let diff = stack.undo().unwrap(); + let diff = history.undo().unwrap(); assert_eq!(diff.removed, vec![added]); assert!(diff.added.is_empty() && diff.modified.is_empty()); - assert_eq!(stack.current().unwrap().len(), 1); - assert!(stack.current().unwrap().contains_key(&restored)); - assert!(!stack.can_undo()); - assert!(stack.can_redo()); - assert!(stack.undo().is_none()); - assert!(stack.current().unwrap().contains_key(&restored)); + assert_eq!(history.current().unwrap().len(), 1); + assert!(history.current().unwrap().contains_key(&restored)); + assert!(!history.can_undo()); + assert!(history.can_redo()); + assert!(history.undo().is_none()); + assert!(history.current().unwrap().contains_key(&restored)); - let diff = stack.redo().unwrap(); + let diff = history.redo().unwrap(); assert_eq!(diff.added, vec![added]); - assert!(stack.can_undo()); - assert!(!stack.can_redo()); + assert!(history.can_undo()); + assert!(!history.can_redo()); // and again - assert!(stack.undo().is_some()); - assert!(stack.undo().is_none()); - assert!(stack.current().unwrap().contains_key(&restored)); + assert!(history.undo().is_some()); + assert!(history.undo().is_none()); + assert!(history.current().unwrap().contains_key(&restored)); } #[test] - fn test_restored_stack_removing_its_files_can_be_undone() { + fn test_restored_history_removing_its_files_can_be_undone() { let (entry, restored) = restored_entry(); - let mut stack = Stack::restored(entry); + let mut history = History::restored(entry); - let diff = stack + let diff = history .push_ok(|e| { e.remove(&restored); Ok(()) }) .unwrap(); assert_eq!(diff.removed, vec![restored]); - assert!(stack.current().unwrap().is_empty()); + assert!(history.current().unwrap().is_empty()); - let diff = stack.undo().unwrap(); + let diff = history.undo().unwrap(); assert_eq!(diff.added, vec![restored]); - assert!(stack.current().unwrap().contains_key(&restored)); - assert!(!stack.can_undo()); + assert!(history.current().unwrap().contains_key(&restored)); + assert!(!history.can_undo()); } #[test] - fn test_restored_stack_push_after_undo_drops_redo_branch() { + fn test_restored_history_push_after_undo_drops_redo_branch() { let (entry, restored) = restored_entry(); - let mut stack = Stack::restored(entry); - add_file(&mut stack); - stack.undo().unwrap(); - assert!(stack.can_redo()); + let mut history = History::restored(entry); + add_file(&mut history); + history.undo().unwrap(); + assert!(history.can_redo()); - let other = add_file(&mut stack); - assert!(!stack.can_redo()); - assert!(stack.can_undo()); - let diff = stack.undo().unwrap(); + let other = add_file(&mut history); + assert!(!history.can_redo()); + assert!(history.can_undo()); + let diff = history.undo().unwrap(); assert_eq!(diff.removed, vec![other]); - assert!(stack.current().unwrap().contains_key(&restored)); - assert!(!stack.can_undo()); + assert!(history.current().unwrap().contains_key(&restored)); + assert!(!history.can_undo()); } } diff --git a/gpx-rs/engine/src/engine/state/mod.rs b/gpx-rs/engine/src/engine/state/mod.rs index 9a2475812..2e5863b42 100644 --- a/gpx-rs/engine/src/engine/state/mod.rs +++ b/gpx-rs/engine/src/engine/state/mod.rs @@ -1,16 +1,16 @@ mod clipboard; +mod history; mod order; mod selection; -mod stack; pub use clipboard::*; +pub use history::*; pub use order::*; pub use selection::*; -pub use stack::*; pub struct State<'a> { pub categories: &'a mut crate::TrackpointCategories, - pub files: &'a mut StackEntry, + pub files: &'a mut Files, pub selection: &'a mut Selection, pub order: &'a mut FileOrder, pub clipboard: &'a mut Option, diff --git a/gpx-rs/engine/src/engine/state/order.rs b/gpx-rs/engine/src/engine/state/order.rs index fce59cf67..d9476c5e7 100644 --- a/gpx-rs/engine/src/engine/state/order.rs +++ b/gpx-rs/engine/src/engine/state/order.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crate::{FileId, StackEntry as Files}; +use crate::{FileId, Files}; #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct FileOrder(pub Vec); diff --git a/gpx-rs/engine/src/engine/state/selection.rs b/gpx-rs/engine/src/engine/state/selection.rs index a9185af1e..15baa6e9c 100644 --- a/gpx-rs/engine/src/engine/state/selection.rs +++ b/gpx-rs/engine/src/engine/state/selection.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crate::{FileId, StackEntry, TrackId, TrackSegmentId, WaypointId}; +use crate::{FileId, Files, TrackId, TrackSegmentId, WaypointId}; /// How a new selection combines with the current one. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -52,12 +52,7 @@ impl Selection { /// Combines `selection` with this one according to `mode`, ignoring the elements of /// `selection` that do not exist in `files`. Nothing to add or toggle leaves the selection /// as it is, while replacing it with nothing deselects everything. - pub fn select( - &mut self, - files: Option<&StackEntry>, - mut selection: Selection, - mode: SelectMode, - ) { + pub fn select(&mut self, files: Option<&Files>, mut selection: Selection, mode: SelectMode) { match files { Some(files) => selection.retain_existing(files), None => selection = Selection::Empty, @@ -72,7 +67,7 @@ impl Selection { /// Selects all the elements of the same kind as the selected ones, see /// [`Selection::all_at_level`]. Does nothing when there is nothing more to select. - pub fn select_all(&mut self, files: &StackEntry, order: &[FileId]) { + pub fn select_all(&mut self, files: &Files, order: &[FileId]) { if let Some(all) = self.all_at_level(files, order) { *self = all; } @@ -81,13 +76,7 @@ impl Selection { /// Moves the selection to the next (`down`) or previous element, see /// [`Selection::neighbour`]. With `add`, the element is added to the selection instead of /// replacing it. - pub fn select_neighbour( - &mut self, - files: &StackEntry, - order: &[FileId], - down: bool, - add: bool, - ) { + pub fn select_neighbour(&mut self, files: &Files, order: &[FileId], down: bool, add: bool) { if let Some(next) = self.neighbour(files, order, down) { if add { self.extend(next); @@ -102,7 +91,7 @@ impl Selection { /// The track segments covered by the selection, in order: the segments of several selected /// files follow `order` (the files missing from it come last), then the tracks and segments /// follow the file. Waypoints are not segments: selecting only waypoints covers none. - pub fn segment_locations(&self, files: &StackEntry, order: &[FileId]) -> Vec { + pub fn segment_locations(&self, files: &Files, order: &[FileId]) -> Vec { let mut locations = vec![]; let mut add = |file_id: FileId, trk_filter: &dyn Fn(&crate::Track) -> bool, @@ -145,7 +134,7 @@ impl Selection { /// Drops what does not exist anymore (e.g. after an undo): files, and the tracks, segments /// and waypoints of the files that remain. A selection left with nothing becomes empty. - pub fn retain_existing(&mut self, files: &StackEntry) { + pub fn retain_existing(&mut self, files: &Files) { let empty = match self { Selection::Empty => false, Selection::File { file_ids } => { @@ -340,7 +329,7 @@ impl Selection { /// the files, all the tracks of the file, all the segments of the track, all the waypoints /// of the file. All the files when nothing is selected. `None` when there is nothing to /// select (the waypoints node, or a file that does not exist). - pub fn all_at_level(&self, files: &StackEntry, order: &[FileId]) -> Option { + pub fn all_at_level(&self, files: &Files, order: &[FileId]) -> Option { match self { Selection::Empty | Selection::File { .. } => Some(Selection::File { file_ids: order.iter().copied().collect(), @@ -375,7 +364,7 @@ impl Selection { /// (`down`) or the previous one among the elements of the same kind, in the same place. /// The tracks, segments and waypoints stop at their ends, the files wrap around and skip /// the ones that are already selected. `None` when there is no such element. - pub fn neighbour(&self, files: &StackEntry, order: &[FileId], down: bool) -> Option { + pub fn neighbour(&self, files: &Files, order: &[FileId], down: bool) -> Option { match self { Selection::Empty | Selection::Waypoints { .. } => None, Selection::File { file_ids } => { @@ -443,7 +432,7 @@ mod tests { fn test_retain_existing() { let file = File::default(); let (kept, gone) = (file.id, FileId::default()); - let mut files = StackEntry::default(); + let mut files = Files::default(); files.insert(kept, Rc::new(file)); let mut selection = Selection::File { @@ -481,7 +470,7 @@ mod tests { }); let (file_id, trk_id) = (file.id, file.trk[0].id); let (seg_id, wpt_id) = (file.trk[0].trkseg[0].id, file.wpt[0].id); - let mut files = StackEntry::default(); + let mut files = Files::default(); files.insert(file_id, Rc::new(file)); let mut selection = Selection::Track { @@ -610,7 +599,7 @@ mod tests { /// A file with 3 tracks of 2 segments each and 3 waypoints, with the ids of everything. struct Tree { - files: StackEntry, + files: Files, order: Vec, file: FileId, tracks: Vec, @@ -644,7 +633,7 @@ mod tests { .collect(), waypoints: file.wpt.iter().map(|w| w.id).collect(), order: vec![file.id], - files: StackEntry::from([(file.id, Rc::new(file))]), + files: Files::from([(file.id, Rc::new(file))]), } } @@ -772,7 +761,7 @@ mod tests { #[test] fn test_neighbour_of_files_wraps_around_and_skips_the_selected() { - let files = StackEntry::default(); + let files = Files::default(); let [a, b, c] = [FileId::default(), FileId::default(), FileId::default()]; let order = [a, b, c]; let select = |ids: &[FileId]| Selection::File { diff --git a/gpx-rs/engine/src/engine/tests.rs b/gpx-rs/engine/src/engine/tests.rs index 3e6cc26dc..5f622cef9 100644 --- a/gpx-rs/engine/src/engine/tests.rs +++ b/gpx-rs/engine/src/engine/tests.rs @@ -120,7 +120,7 @@ fn test_load_file() { fn test_new_file_has_no_statistics() { let mut engine = TestEngine::default(); assert!(new(&mut engine, "empty")); - assert_eq!(engine.stack.current().unwrap().len(), 1); + assert_eq!(engine.history.current().unwrap().len(), 1); assert_eq!(engine.order.0.len(), 1); assert!(engine.statistics().total_distance.is_empty()); } @@ -135,7 +135,7 @@ fn test_statistics_follow_selection() { // a newly created (empty) file becomes the selection assert!(new(&mut engine, "empty")); assert!(engine.statistics().total_distance.is_empty()); - assert_eq!(engine.stack.current().unwrap().len(), 2); + assert_eq!(engine.history.current().unwrap().len(), 2); // loading a file selects it again assert!(load(&mut engine, "data/simple.gpx")); @@ -152,8 +152,8 @@ fn test_invalid_load_is_rejected() { name: "file", }) )); - assert!(engine.stack.current().is_none()); - assert!(!engine.stack.can_undo()); + assert!(engine.history.current().is_none()); + assert!(!engine.history.can_undo()); assert!(engine.order.0.is_empty()); } @@ -162,8 +162,8 @@ fn test_command_with_nothing_to_do_is_rejected() { let mut engine = TestEngine::default(); new(&mut engine, "file"); assert!(!edit(&mut engine, Command::Reverse(crate::Reverse))); - assert!(!engine.stack.can_redo()); - assert_eq!(engine.stack.current().unwrap().len(), 1); + assert!(!engine.history.can_redo()); + assert_eq!(engine.history.current().unwrap().len(), 1); } #[test] @@ -177,7 +177,7 @@ fn test_metadata_renames_selected_file() { desc: "about", }) )); - let file = engine.stack.current().unwrap().values().next().unwrap(); + let file = engine.history.current().unwrap().values().next().unwrap(); assert_eq!(file.info.name, "after"); assert_eq!(file.info.desc.as_deref(), Some("about")); } @@ -194,7 +194,7 @@ fn test_metadata_without_selection_does_not_modify_files() { desc: "", }), ); - let file = engine.stack.current().unwrap().values().next().unwrap(); + let file = engine.history.current().unwrap().values().next().unwrap(); assert_eq!(file.info.name, "file"); } @@ -204,7 +204,7 @@ fn test_edit_changes_statistics_when_segments_change() { load(&mut engine, "data/simple.gpx"); let before = engine .statistics_cache - .get(engine.stack.current(), &engine.selection)[0] as *const _; + .get(engine.history.current(), &engine.selection)[0] as *const _; edit(&mut engine, Command::NewTrack(NewTrack)); edit( &mut engine, @@ -217,7 +217,7 @@ fn test_edit_changes_statistics_when_segments_change() { // styling does not touch the segments: their statistics are reused let after = engine .statistics_cache - .get(engine.stack.current(), &engine.selection)[0] as *const _; + .get(engine.history.current(), &engine.selection)[0] as *const _; assert!(std::ptr::eq(before, after)); } @@ -241,7 +241,7 @@ fn test_undo_redo_keep_selection_and_order_consistent() { assert!(engine.run(Action::Undo)); assert!(engine.order.0.is_empty()); assert!(!engine.run(Action::Undo)); - assert!(engine.stack.current().is_none()); + assert!(engine.history.current().is_none()); } #[test] @@ -381,7 +381,7 @@ fn test_selection_does_not_create_undo_steps() { new(&mut engine, "a"); select_all(&mut engine); engine.run(Action::Undo); - assert!(engine.stack.current().is_none()); + assert!(engine.history.current().is_none()); } fn select_elements(engine: &mut TestEngine, selection: Selection, mode: SelectMode) -> bool { @@ -408,7 +408,7 @@ fn test_select_elements() { let mut engine = TestEngine::default(); load(&mut engine, "data/with_tracks_and_segments.gpx"); let file = engine - .stack + .history .current() .unwrap() .values() @@ -507,7 +507,7 @@ fn test_toggle_elements() { let mut engine = TestEngine::default(); load(&mut engine, "data/with_tracks_and_segments.gpx"); let file = engine - .stack + .history .current() .unwrap() .values() @@ -541,7 +541,7 @@ fn test_selected_elements_are_dropped_when_they_disappear() { let mut engine = TestEngine::default(); load(&mut engine, "data/with_tracks_and_segments.gpx"); let file = engine - .stack + .history .current() .unwrap() .values() @@ -597,7 +597,7 @@ fn test_select_all_follows_the_selected_level() { let load = |engine: &mut TestEngine| load(engine, "data/with_tracks_and_segments.gpx"); load(&mut engine); let file = engine - .stack + .history .current() .unwrap() .values() @@ -648,7 +648,7 @@ fn test_arrow_select() { edit(&mut engine, Command::NewTrack(NewTrack)); } let file = engine - .stack + .history .current() .unwrap() .values() @@ -1153,7 +1153,7 @@ fn test_export_writes_the_file_as_it_is() { let id = engine.order()[0]; let bytes = engine.export(&id, ExportOptions::ALL).unwrap(); let again = crate::parse(&bytes, &mut engine.categories().clone()).unwrap(); - let file = engine.stack.current().unwrap()[&id].clone(); + let file = engine.history.current().unwrap()[&id].clone(); assert_eq!(again.trk.len(), file.trk.len()); assert_eq!(again.trk[0].trkseg[0].len(), file.trk[0].trkseg[0].len()); assert!(again.trk[0].trkseg[0][0].time.is_some()); diff --git a/gpx-rs/engine/src/storage/persister.rs b/gpx-rs/engine/src/storage/persister.rs index 6177b5570..a1df0ca02 100644 --- a/gpx-rs/engine/src/storage/persister.rs +++ b/gpx-rs/engine/src/storage/persister.rs @@ -9,7 +9,7 @@ use super::codec::{ encode_order, encode_trackpoints, encode_waypoints, }; use crate::{ - Batch, ChunkKey, CommitError, File, FileId, Revision, StackEntry, Storage, StorageError, + Batch, ChunkKey, CommitError, File, FileId, Files, Revision, Storage, StorageError, TrackpointCategories, TrackpointChunk, WaypointChunk, }; @@ -17,7 +17,7 @@ use crate::{ /// categories their trackpoints refer to. See [`crate::Engine::snapshot`]. #[derive(Debug, Default, Clone)] pub struct Snapshot { - pub files: StackEntry, + pub files: Files, pub order: Vec, pub categories: TrackpointCategories, } @@ -533,7 +533,11 @@ mod tests { changed.order.clear(); changed.files.clear(); let result = block_on(persister.save(&storage, changed)); - assert_eq!(result, Err(CommitError::Failed(StorageError::Newer)), "{what}"); + assert_eq!( + result, + Err(CommitError::Failed(StorageError::Newer)), + "{what}" + ); assert!(storage.batches.borrow().is_empty(), "{what}"); assert_eq!(storage.inner.counts(), before, "{what}"); }