This commit is contained in:
vcoppe committed 2026-10-14 08:17:51 +02:00
1 parent 3c2055b50e
commit ce8398f749
16 files changed
+227 -235

No files matched your search

@@ -1,6 +1,6 @@
use std::rc::Rc; 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 /// 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. /// 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( pub(crate) fn delete_waypoints(
files: &mut StackEntry, files: &mut Files,
file_id: FileId, file_id: FileId,
filter: impl Fn(&Waypoint) -> bool, filter: impl Fn(&Waypoint) -> bool,
) -> Result<Selection, CommandError> { ) -> Result<Selection, CommandError> {
@@ -1,7 +1,7 @@
use std::{collections::HashSet, rc::Rc}; use std::{collections::HashSet, rc::Rc};
use crate::{ 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, copy_track, copy_waypoint,
}; };
@@ -107,7 +107,7 @@ impl Apply for Duplicate {
} }
fn duplicate_waypoints( fn duplicate_waypoints(
files: &mut StackEntry, files: &mut Files,
file_id: FileId, file_id: FileId,
filter: impl Fn(&Waypoint) -> bool, filter: impl Fn(&Waypoint) -> bool,
) -> Result<Selection, CommandError> { ) -> Result<Selection, CommandError> {
+2 -2
View File
@@ -2,11 +2,11 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{Clipboard, FileId, FileOrder, Selection, StackEntry, State, TrackpointCategories}; use crate::{Clipboard, FileId, FileOrder, Files, Selection, State, TrackpointCategories};
#[derive(Default)] #[derive(Default)]
pub struct Fixture { pub struct Fixture {
pub files: StackEntry, pub files: Files,
pub selection: Selection, pub selection: Selection,
pub order: FileOrder, pub order: FileOrder,
pub clipboard: Option<Clipboard>, pub clipboard: Option<Clipboard>,
@@ -1,6 +1,6 @@
use std::rc::Rc; 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. /// What an [`Editor`] hook did to the element it was given.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -70,7 +70,7 @@ fn edit_where<T>(
/// Edits a copy of the file (cheap, track points are shared chunks) and stores it back as a /// Edits a copy of the file (cheap, track points are shared chunks) and stores it back as a
/// new snapshot only if something changed. /// 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 { let Some(current) = files.get(&id) else {
return; return;
}; };
@@ -1,8 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::{ use crate::{FileId, FileWaypointsRevisionId, Files, TrackSegmentId, TrackSegmentRevisionId};
FileId, FileWaypointsRevisionId, StackEntry as Files, TrackSegmentId, TrackSegmentRevisionId,
};
/// Coordinates buffers, as flat `[lng, lat, lng, lat, ...]` arrays so that they can be handed /// 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 /// to the UI without per-point calls: one per track segment (its trackpoints) and one per file
@@ -1,6 +1,6 @@
use std::collections::HashMap; 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 /// The structure of each file, only recomputed for the files that changed according to the
/// [`Diff`] of the stack. /// [`Diff`] of the stack.
@@ -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 /// What the routing tool needs about the selected segments (see
/// [`Selection::segment_locations`]): where their anchors are among their trackpoints. /// [`Selection::segment_locations`]): where their anchors are among their trackpoints.
@@ -27,7 +27,7 @@ pub struct RoutingBuffer {
} }
impl 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_indices.clear();
self.anchor_zooms.clear(); self.anchor_zooms.clear();
self.segment_starts.clear(); self.segment_starts.clear();
@@ -70,11 +70,11 @@ mod tests {
use super::*; use super::*;
fn loaded() -> (StackEntry, FileId) { fn loaded() -> (Files, FileId) {
let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap();
let file = parse(&data, &mut Default::default()).unwrap(); let file = parse(&data, &mut Default::default()).unwrap();
let id = file.id; let id = file.id;
let mut files = StackEntry::default(); let mut files = Files::default();
files.insert(id, Rc::new(file)); files.insert(id, Rc::new(file));
(files, id) (files, id)
} }
@@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet, hash_map::Entry}; use std::collections::{HashMap, HashSet, hash_map::Entry};
use crate::{ use crate::{
File, FileId, GlobalStatistics, Selection, StackEntry, Statistics, Track, TrackSegment, File, FileId, Files, GlobalStatistics, Selection, Statistics, Track, TrackSegment,
TrackSegmentRevisionId, TrackSegmentRevisionId,
}; };
@@ -14,7 +14,7 @@ pub struct StatisticsCache {
} }
impl StatisticsCache { impl StatisticsCache {
pub fn update(&mut self, state: Option<&StackEntry>) { pub fn update(&mut self, state: Option<&Files>) {
let mut stale: HashSet<TrackSegmentRevisionId> = self.map.keys().copied().collect(); let mut stale: HashSet<TrackSegmentRevisionId> = self.map.keys().copied().collect();
if let Some(state) = state { if let Some(state) = state {
for file in state.values() { for file in state.values() {
@@ -40,7 +40,7 @@ impl StatisticsCache {
/// Statistics of the segments of the selection. /// Statistics of the segments of the selection.
pub fn get<'a>( pub fn get<'a>(
&'a self, &'a self,
state: Option<&'a StackEntry>, state: Option<&'a Files>,
selection: &Selection, selection: &Selection,
) -> Vec<&'a Statistics> { ) -> Vec<&'a Statistics> {
self.selected(state, selection, &[]) self.selected(state, selection, &[])
@@ -53,7 +53,7 @@ impl StatisticsCache {
/// files follow `order` (the files missing from it come last). /// files follow `order` (the files missing from it come last).
pub fn selected<'a>( pub fn selected<'a>(
&'a self, &'a self,
state: Option<&'a StackEntry>, state: Option<&'a Files>,
selection: &Selection, selection: &Selection,
order: &[FileId], order: &[FileId],
) -> Vec<SelectedSegment<'a>> { ) -> Vec<SelectedSegment<'a>> {
@@ -159,10 +159,10 @@ mod tests {
use super::*; use super::*;
fn state(path: &str) -> (StackEntry, Rc<File>) { fn state(path: &str) -> (Files, Rc<File>) {
let data = std::fs::read(path).unwrap(); let data = std::fs::read(path).unwrap();
let file = Rc::new(parse(&data, &mut Default::default()).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.insert(file.id, file.clone());
(state, file) (state, file)
} }
@@ -235,7 +235,7 @@ mod tests {
let before: *const Statistics = cache.get(Some(&state), &file_selection(file.id))[0]; 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) // 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 renamed = {
let mut f = (*file).clone(); let mut f = (*file).clone();
f.info.name = "other".to_string(); f.info.name = "other".to_string();
@@ -258,7 +258,7 @@ mod tests {
let mut f = (*file).clone(); let mut f = (*file).clone();
f.trk[0].trkseg[0].rev_id = Default::default(); f.trk[0].trkseg[0].rev_id = Default::default();
let new_rev = f.trk[0].trkseg[0].rev_id; 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)); next.insert(f.id, Rc::new(f));
cache.update(Some(&next)); cache.update(Some(&next));
assert!(cache.map.contains_key(&new_rev)); assert!(cache.map.contains_key(&new_rev));
+25 -24
View File
@@ -17,7 +17,7 @@ use crate::{
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Engine { pub struct Engine {
stack: Stack, history: History,
selection: Selection, selection: Selection,
order: FileOrder, order: FileOrder,
statistics_cache: StatisticsCache, statistics_cache: StatisticsCache,
@@ -56,7 +56,7 @@ impl Engine {
/// Global statistics (distance, time, elevation, bounds...) of a file. /// Global statistics (distance, time, elevation, bounds...) of a file.
pub fn file_statistics(&self, id: &FileId) -> Option<GlobalStatistics> { pub fn file_statistics(&self, id: &FileId) -> Option<GlobalStatistics> {
let file = self.stack.current()?.get(id)?; let file = self.history.current()?.get(id)?;
Some(self.statistics_cache.file_global(file)) 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. /// 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> { pub fn waypoint(&self, file_id: &FileId, id: &WaypointId) -> Option<&Waypoint> {
self.stack self.history
.current()? .current()?
.get(file_id)? .get(file_id)?
.wpt .wpt
@@ -90,7 +90,7 @@ impl Engine {
index: usize, index: usize,
) -> Option<&Trackpoint> { ) -> Option<&Trackpoint> {
let segment = self let segment = self
.stack .history
.current()? .current()?
.get(file_id)? .get(file_id)?
.trk .trk
@@ -103,7 +103,7 @@ impl Engine {
/// For each trackpoint of the selection (numbered as in the statistics), the tolerance up to /// For each trackpoint of the selection (numbered as in the statistics), the tolerance up to
/// which `Reduce` keeps it, see [`reduction_distances`]. /// which `Reduce` keeps it, see [`reduction_distances`].
pub fn reduction_distances(&self) -> Vec<f64> { pub fn reduction_distances(&self) -> Vec<f64> {
let Some(files) = self.stack.current() else { let Some(files) = self.history.current() else {
return vec![]; return vec![];
}; };
self.selection self.selection
@@ -120,7 +120,7 @@ impl Engine {
/// What there is to keep of the engine, see [`crate::Persister`]. /// What there is to keep of the engine, see [`crate::Persister`].
pub fn snapshot(&self) -> Snapshot { pub fn snapshot(&self) -> Snapshot {
Snapshot { Snapshot {
files: self.stack.current().cloned().unwrap_or_default(), files: self.history.current().cloned().unwrap_or_default(),
order: self.order.0.clone(), order: self.order.0.clone(),
categories: self.categories.clone(), categories: self.categories.clone(),
} }
@@ -137,19 +137,19 @@ impl Engine {
pub fn restore(&mut self, restored: Restored) -> Outcome { pub fn restore(&mut self, restored: Restored) -> Outcome {
let selection_before = self.selection.clone(); let selection_before = self.selection.clone();
let order_before = self.order.0.clone(); let order_before = self.order.0.clone();
let before = self.stack.current().cloned(); let before = self.history.current().cloned();
let files: StackEntry = restored let files: Files = restored
.files .files
.into_iter() .into_iter()
.map(|file| (file.id, file)) .map(|file| (file.id, file))
.collect(); .collect();
let diff = Stack::diff_between(before.as_ref(), Some(&files)); let diff = History::diff_between(before.as_ref(), Some(&files));
let mut diff = let mut diff =
(!(diff.added.is_empty() && diff.removed.is_empty() && diff.modified.is_empty())) (!(diff.added.is_empty() && diff.removed.is_empty() && diff.modified.is_empty()))
.then_some(diff); .then_some(diff);
self.categories = restored.categories; self.categories = restored.categories;
self.order = FileOrder(restored.order); self.order = FileOrder(restored.order);
self.stack = Stack::restored(files); self.history = History::restored(files);
self.refresh(diff.as_ref()); self.refresh(diff.as_ref());
sort_diff(&mut diff, &self.order.0); sort_diff(&mut diff, &self.order.0);
Outcome { Outcome {
@@ -165,13 +165,13 @@ impl Engine {
/// A file as GPX (UTF-8), `None` if it does not exist. /// A file as GPX (UTF-8), `None` if it does not exist.
pub fn export(&self, id: &FileId, options: ExportOptions) -> Option<Vec<u8>> { pub fn export(&self, id: &FileId, options: ExportOptions) -> Option<Vec<u8>> {
let file = self.stack.current()?.get(id)?; let file = self.history.current()?.get(id)?;
Some(write(file, &self.categories, options)) Some(write(file, &self.categories, options))
} }
/// The data that the given files have, which the options of `export` can leave out. /// The data that the given files have, which the options of `export` can leave out.
pub fn exportable_data(&self, ids: &[FileId]) -> ExportOptions { 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; return ExportOptions::NONE;
}; };
ids.iter() ids.iter()
@@ -183,12 +183,12 @@ impl Engine {
/// Whether there is something to undo. /// Whether there is something to undo.
pub fn can_undo(&self) -> bool { pub fn can_undo(&self) -> bool {
self.stack.can_undo() self.history.can_undo()
} }
/// Whether there is something to redo. /// Whether there is something to redo.
pub fn can_redo(&self) -> bool { pub fn can_redo(&self) -> bool {
self.stack.can_redo() self.history.can_redo()
} }
/// Currently selected elements. /// Currently selected elements.
@@ -216,20 +216,21 @@ impl Engine {
// the files change iff there is a diff, but selection and order actions have none // the files change iff there is a diff, but selection and order actions have none
let mut diff = match action { let mut diff = match action {
Action::Edit(command) => self.edit(command)?, Action::Edit(command) => self.edit(command)?,
Action::Undo => self.stack.undo(), Action::Undo => self.history.undo(),
Action::Redo => self.stack.redo(), Action::Redo => self.history.redo(),
Action::Select { selection, mode } => { Action::Select { selection, mode } => {
self.selection.select(self.stack.current(), selection, mode); self.selection
.select(self.history.current(), selection, mode);
None None
} }
Action::SelectAll => { Action::SelectAll => {
if let Some(files) = self.stack.current() { if let Some(files) = self.history.current() {
self.selection.select_all(files, &self.order.0); self.selection.select_all(files, &self.order.0);
} }
None None
} }
Action::ArrowSelect { down, add } => { Action::ArrowSelect { down, add } => {
if let Some(files) = self.stack.current() { if let Some(files) = self.history.current() {
self.selection self.selection
.select_neighbour(files, &self.order.0, down, add); .select_neighbour(files, &self.order.0, down, add);
} }
@@ -271,7 +272,7 @@ impl Engine {
/// Puts the selection in the clipboard, if there is one. /// Puts the selection in the clipboard, if there is one.
fn copy(&mut self, cut: bool) { 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) && let Some(clipboard) = Clipboard::new(&self.selection, files, &self.order.0, cut)
{ {
self.clipboard = Some(clipboard); self.clipboard = Some(clipboard);
@@ -279,7 +280,7 @@ impl Engine {
} }
fn edit(&mut self, command: Command) -> Result<Option<Diff>, CommandError> { fn edit(&mut self, command: Command) -> Result<Option<Diff>, CommandError> {
self.stack.create_and_push_next(|files| { self.history.create_and_push_next(|files| {
let mut state = State { let mut state = State {
categories: &mut self.categories, categories: &mut self.categories,
files, 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>) { fn refresh(&mut self, diff: Option<&Diff>) {
let current = self.stack.current(); let current = self.history.current();
match current { match current {
Some(files) => { Some(files) => {
// files coming back after an undo/redo are added at the end of the order // 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. /// whatever is done with them one after the other, like giving them colors, follows it.
fn sort_diff(diff: &mut Option<Diff>, order: &[FileId]) { fn sort_diff(diff: &mut Option<Diff>, order: &[FileId]) {
let position = |id: &FileId| order.iter().position(|other| other == id); let position = |id: &FileId| order.iter().position(|other| other == id);
+4 -4
View File
@@ -3,7 +3,7 @@ use std::rc::Rc;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
File, FileId, Selection, StackEntry, Track, TrackId, TrackSegment, TrackSegmentId, Waypoint, File, FileId, Files, Selection, Track, TrackId, TrackSegment, TrackSegmentId, Waypoint,
WaypointId, WaypointId,
}; };
@@ -71,7 +71,7 @@ impl Clipboard {
/// selected does not exist). /// selected does not exist).
pub fn new( pub fn new(
selection: &Selection, selection: &Selection,
files: &StackEntry, files: &Files,
order: &[FileId], order: &[FileId],
cut: bool, cut: bool,
) -> Option<Clipboard> { ) -> Option<Clipboard> {
@@ -238,14 +238,14 @@ mod tests {
file file
} }
fn files_of(files: Vec<File>) -> StackEntry { fn files_of(files: Vec<File>) -> Files {
files files
.into_iter() .into_iter()
.map(|file| (file.id, Rc::new(file))) .map(|file| (file.id, Rc::new(file)))
.collect() .collect()
} }
fn order_of(files: &StackEntry) -> Vec<FileId> { fn order_of(files: &Files) -> Vec<FileId> {
files.keys().copied().collect() files.keys().copied().collect()
} }
@@ -6,17 +6,17 @@ use std::{
use crate::{CommandError, File, FileId}; use crate::{CommandError, File, FileId};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Stack { pub struct History {
entries: Vec<StackEntry>, entries: Vec<Files>,
index: Option<usize>, index: Option<usize>,
/// 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. /// restored from the storage.
floor: bool, floor: bool,
} }
impl Stack { impl History {
/// A stack whose first entry is `entry`, from which nothing can be undone. /// A history whose first entry is `entry`, from which nothing can be undone.
pub fn restored(entry: StackEntry) -> Self { pub fn restored(entry: Files) -> Self {
Self { Self {
entries: vec![entry], entries: vec![entry],
index: Some(0), 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)) 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. /// history. Files are told apart by their identity, not their content.
pub fn create_and_push_next<F>(&mut self, f: F) -> Result<Option<Diff>, CommandError> pub fn create_and_push_next<F>(&mut self, f: F) -> Result<Option<Diff>, CommandError>
where where
F: FnOnce(&mut StackEntry) -> Result<(), CommandError>, F: FnOnce(&mut Files) -> Result<(), CommandError>,
{ {
let mut next = self.current().map_or_default(|c| c.clone()); let mut next = self.current().map_or_default(|c| c.clone());
f(&mut next)?; f(&mut next)?;
if self.is_current(&next) { if self.is_current(&next) {
return Ok(None); 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. /// 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 current = self.current();
let len = current.map_or(0, |current| current.len()); let len = current.map_or(0, |current| current.len());
entry.len() == len entry.len() == len
@@ -58,29 +58,29 @@ impl Stack {
} }
pub fn undo(&mut self) -> Option<Diff> { pub fn undo(&mut self) -> Option<Diff> {
self.record_diff(|stack| { self.record_diff(|history| {
if let Some(i) = stack.index { if let Some(i) = history.index {
if i == 0 { if i == 0 {
if !stack.floor { if !history.floor {
stack.index = None; history.index = None;
} }
} else { } else {
stack.index = Some(i - 1); history.index = Some(i - 1);
} }
} }
}) })
} }
pub fn redo(&mut self) -> Option<Diff> { pub fn redo(&mut self) -> Option<Diff> {
self.record_diff(|stack| match stack.index { self.record_diff(|history| match history.index {
Some(i) => { Some(i) => {
if i + 1 < stack.entries.len() { if i + 1 < history.entries.len() {
stack.index = Some(i + 1); history.index = Some(i + 1);
} }
} }
None => { None => {
if !stack.entries.is_empty() { if !history.entries.is_empty() {
stack.index = Some(0); 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) self.entries.get(index)
} }
fn push(&mut self, entry: StackEntry) { fn push(&mut self, entry: Files) {
if let Some(i) = self.index { if let Some(i) = self.index {
self.entries.truncate(i + 1); self.entries.truncate(i + 1);
} }
@@ -127,7 +127,7 @@ impl Stack {
} }
/// Which files differ between two states. /// 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<FileId> = prev.map_or_default(|e| e.keys().copied().collect()); let prev_ids: HashSet<FileId> = prev.map_or_default(|e| e.keys().copied().collect());
let cur_ids: HashSet<FileId> = cur.map_or_default(|e| e.keys().copied().collect()); let cur_ids: HashSet<FileId> = cur.map_or_default(|e| e.keys().copied().collect());
let mut modified = vec![]; let mut modified = vec![];
@@ -146,9 +146,9 @@ impl Stack {
} }
} }
pub type StackEntry = HashMap<FileId, Rc<File>>; pub type Files = HashMap<FileId, Rc<File>>;
/// 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)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Diff { pub struct Diff {
pub added: Vec<FileId>, pub added: Vec<FileId>,
@@ -160,11 +160,11 @@ pub struct Diff {
mod tests { mod tests {
use super::*; use super::*;
impl Stack { impl History {
/// A step that is expected not to fail. /// A step that is expected not to fail.
fn push_ok( fn push_ok(
&mut self, &mut self,
f: impl FnOnce(&mut StackEntry) -> Result<(), CommandError>, f: impl FnOnce(&mut Files) -> Result<(), CommandError>,
) -> Option<Diff> { ) -> Option<Diff> {
self.create_and_push_next(f).unwrap() self.create_and_push_next(f).unwrap()
} }
@@ -172,21 +172,21 @@ mod tests {
#[test] #[test]
fn test_new_file() { fn test_new_file() {
let mut stack = Stack::default(); let mut history = History::default();
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.current().is_none()); assert!(history.current().is_none());
let diff = stack.push_ok(|e| { let diff = history.push_ok(|e| {
let file = Rc::new(File::default()); let file = Rc::new(File::default());
e.insert(file.id, file); e.insert(file.id, file);
Ok(()) Ok(())
}); });
assert!(stack.can_undo()); assert!(history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.current().is_some()); assert!(history.current().is_some());
assert!(diff.is_some()); assert!(diff.is_some());
@@ -196,10 +196,10 @@ mod tests {
assert!(diff.modified.is_empty()); 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 file = Rc::new(File::default());
let id = file.id; let id = file.id;
stack history
.push_ok(|e| { .push_ok(|e| {
e.insert(id, file); e.insert(id, file);
Ok(()) Ok(())
@@ -210,46 +210,46 @@ mod tests {
#[test] #[test]
fn test_failed_command_pushes_nothing() { fn test_failed_command_pushes_nothing() {
let mut stack = Stack::default(); let mut history = History::default();
let diff = stack.create_and_push_next(|e| { let diff = history.create_and_push_next(|e| {
e.insert(FileId::default(), Rc::new(File::default())); e.insert(FileId::default(), Rc::new(File::default()));
Err(CommandError::InvalidData("nope".to_string())) Err(CommandError::InvalidData("nope".to_string()))
}); });
assert_eq!(diff, Err(CommandError::InvalidData("nope".to_string()))); assert_eq!(diff, Err(CommandError::InvalidData("nope".to_string())));
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(stack.current().is_none()); assert!(history.current().is_none());
} }
#[test] #[test]
fn test_undo_redo() { fn test_undo_redo() {
let mut stack = Stack::default(); let mut history = History::default();
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
assert!(stack.redo().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_eq!(diff.removed, vec![id]);
assert!(diff.added.is_empty()); assert!(diff.added.is_empty());
assert!(stack.current().is_none()); assert!(history.current().is_none());
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(stack.can_redo()); assert!(history.can_redo());
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
let diff = stack.redo().unwrap(); let diff = history.redo().unwrap();
assert_eq!(diff.added, vec![id]); assert_eq!(diff.added, vec![id]);
assert!(stack.current().unwrap().contains_key(&id)); assert!(history.current().unwrap().contains_key(&id));
assert!(stack.can_undo()); assert!(history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.redo().is_none()); assert!(history.redo().is_none());
} }
#[test] #[test]
fn test_modified_and_removed() { fn test_modified_and_removed() {
let mut stack = Stack::default(); let mut history = History::default();
let id = add_file(&mut stack); let id = add_file(&mut history);
let diff = stack let diff = history
.push_ok(|e| { .push_ok(|e| {
let mut file = (**e.get(&id).unwrap()).clone(); let mut file = (**e.get(&id).unwrap()).clone();
file.info.name = "renamed".to_string(); file.info.name = "renamed".to_string();
@@ -261,9 +261,9 @@ mod tests {
assert!(diff.added.is_empty() && diff.removed.is_empty()); assert!(diff.added.is_empty() && diff.removed.is_empty());
// a step that changes nothing is not a step // a step that changes nothing is not a step
assert!(stack.push_ok(|_| Ok(())).is_none()); assert!(history.push_ok(|_| Ok(())).is_none());
assert!( assert!(
stack history
.push_ok(|e| { .push_ok(|e| {
let file = e[&id].clone(); let file = e[&id].clone();
e.insert(id, file); e.insert(id, file);
@@ -272,28 +272,28 @@ mod tests {
.is_none() .is_none()
); );
let diff = stack let diff = history
.push_ok(|e| { .push_ok(|e| {
e.remove(&id); e.remove(&id);
Ok(()) Ok(())
}) })
.unwrap(); .unwrap();
assert_eq!(diff.removed, vec![id]); assert_eq!(diff.removed, vec![id]);
assert!(stack.current().unwrap().is_empty()); assert!(history.current().unwrap().is_empty());
// going back restores the renamed file // going back restores the renamed file
stack.undo(); history.undo();
assert_eq!(stack.current().unwrap()[&id].info.name, "renamed"); assert_eq!(history.current().unwrap()[&id].info.name, "renamed");
} }
#[test] #[test]
fn test_a_file_is_modified_when_it_is_not_the_same_one_anymore() { fn test_a_file_is_modified_when_it_is_not_the_same_one_anymore() {
let mut stack = Stack::default(); let mut history = History::default();
let id = add_file(&mut stack); 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 // told apart by their identity, comparing them would read all of them
let diff = stack let diff = history
.push_ok(|e| { .push_ok(|e| {
let copy = (**e.get(&id).unwrap()).clone(); let copy = (**e.get(&id).unwrap()).clone();
e.insert(id, Rc::new(copy)); e.insert(id, Rc::new(copy));
@@ -303,8 +303,8 @@ mod tests {
assert_eq!(diff.modified, vec![id]); assert_eq!(diff.modified, vec![id]);
// and the files that were not replaced are not // and the files that were not replaced are not
let other = add_file(&mut stack); let other = add_file(&mut history);
let diff = stack let diff = history
.push_ok(|e| { .push_ok(|e| {
let copy = (**e.get(&other).unwrap()).clone(); let copy = (**e.get(&other).unwrap()).clone();
e.insert(other, Rc::new(copy)); e.insert(other, Rc::new(copy));
@@ -316,120 +316,120 @@ mod tests {
#[test] #[test]
fn test_push_after_undo_drops_redo_branch() { fn test_push_after_undo_drops_redo_branch() {
let mut stack = Stack::default(); let mut history = History::default();
let first = add_file(&mut stack); let first = add_file(&mut history);
let second = add_file(&mut stack); let second = add_file(&mut history);
stack.undo(); history.undo();
assert!(stack.can_redo()); assert!(history.can_redo());
assert!(!stack.current().unwrap().contains_key(&second)); assert!(!history.current().unwrap().contains_key(&second));
let third = add_file(&mut stack); let third = add_file(&mut history);
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.redo().is_none()); assert!(history.redo().is_none());
let current = stack.current().unwrap(); let current = history.current().unwrap();
assert!(current.contains_key(&first)); assert!(current.contains_key(&first));
assert!(current.contains_key(&third)); assert!(current.contains_key(&third));
assert!(!current.contains_key(&second)); assert!(!current.contains_key(&second));
} }
fn restored_entry() -> (StackEntry, FileId) { fn restored_entry() -> (Files, FileId) {
let file = Rc::new(File::default()); let file = Rc::new(File::default());
let id = file.id; let id = file.id;
(StackEntry::from([(id, file)]), id) (Files::from([(id, file)]), id)
} }
#[test] #[test]
fn test_restored_stack_starts_from_its_entry() { fn test_restored_history_starts_from_its_entry() {
let (entry, id) = restored_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 // there is nothing to go back to, nor forward to
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
assert!(stack.redo().is_none()); assert!(history.redo().is_none());
assert!(stack.current().unwrap().contains_key(&id)); assert!(history.current().unwrap().contains_key(&id));
} }
#[test] #[test]
fn test_restored_stack_cannot_undo_an_empty_entry_either() { fn test_restored_history_cannot_undo_an_empty_entry_either() {
let mut stack = Stack::restored(StackEntry::new()); let mut history = History::restored(Files::new());
assert!(stack.current().is_some_and(|files| files.is_empty())); assert!(history.current().is_some_and(|files| files.is_empty()));
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
// the stack is not the one of an engine that has done nothing // the history is not the one of an engine that has done nothing
assert!(stack.current().is_some()); assert!(history.current().is_some());
} }
#[test] #[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 (entry, restored) = restored_entry();
let mut stack = Stack::restored(entry); let mut history = History::restored(entry);
let added = add_file(&mut stack); let added = add_file(&mut history);
assert!(stack.can_undo()); assert!(history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert_eq!(stack.current().unwrap().len(), 2); assert_eq!(history.current().unwrap().len(), 2);
// back to the restored files // back to the restored files
let diff = stack.undo().unwrap(); let diff = history.undo().unwrap();
assert_eq!(diff.removed, vec![added]); assert_eq!(diff.removed, vec![added]);
assert!(diff.added.is_empty() && diff.modified.is_empty()); assert!(diff.added.is_empty() && diff.modified.is_empty());
assert_eq!(stack.current().unwrap().len(), 1); assert_eq!(history.current().unwrap().len(), 1);
assert!(stack.current().unwrap().contains_key(&restored)); assert!(history.current().unwrap().contains_key(&restored));
assert!(!stack.can_undo()); assert!(!history.can_undo());
assert!(stack.can_redo()); assert!(history.can_redo());
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
assert!(stack.current().unwrap().contains_key(&restored)); assert!(history.current().unwrap().contains_key(&restored));
let diff = stack.redo().unwrap(); let diff = history.redo().unwrap();
assert_eq!(diff.added, vec![added]); assert_eq!(diff.added, vec![added]);
assert!(stack.can_undo()); assert!(history.can_undo());
assert!(!stack.can_redo()); assert!(!history.can_redo());
// and again // and again
assert!(stack.undo().is_some()); assert!(history.undo().is_some());
assert!(stack.undo().is_none()); assert!(history.undo().is_none());
assert!(stack.current().unwrap().contains_key(&restored)); assert!(history.current().unwrap().contains_key(&restored));
} }
#[test] #[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 (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| { .push_ok(|e| {
e.remove(&restored); e.remove(&restored);
Ok(()) Ok(())
}) })
.unwrap(); .unwrap();
assert_eq!(diff.removed, vec![restored]); 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_eq!(diff.added, vec![restored]);
assert!(stack.current().unwrap().contains_key(&restored)); assert!(history.current().unwrap().contains_key(&restored));
assert!(!stack.can_undo()); assert!(!history.can_undo());
} }
#[test] #[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 (entry, restored) = restored_entry();
let mut stack = Stack::restored(entry); let mut history = History::restored(entry);
add_file(&mut stack); add_file(&mut history);
stack.undo().unwrap(); history.undo().unwrap();
assert!(stack.can_redo()); assert!(history.can_redo());
let other = add_file(&mut stack); let other = add_file(&mut history);
assert!(!stack.can_redo()); assert!(!history.can_redo());
assert!(stack.can_undo()); assert!(history.can_undo());
let diff = stack.undo().unwrap(); let diff = history.undo().unwrap();
assert_eq!(diff.removed, vec![other]); assert_eq!(diff.removed, vec![other]);
assert!(stack.current().unwrap().contains_key(&restored)); assert!(history.current().unwrap().contains_key(&restored));
assert!(!stack.can_undo()); assert!(!history.can_undo());
} }
} }
+3 -3
View File
@@ -1,16 +1,16 @@
mod clipboard; mod clipboard;
mod history;
mod order; mod order;
mod selection; mod selection;
mod stack;
pub use clipboard::*; pub use clipboard::*;
pub use history::*;
pub use order::*; pub use order::*;
pub use selection::*; pub use selection::*;
pub use stack::*;
pub struct State<'a> { pub struct State<'a> {
pub categories: &'a mut crate::TrackpointCategories, pub categories: &'a mut crate::TrackpointCategories,
pub files: &'a mut StackEntry, pub files: &'a mut Files,
pub selection: &'a mut Selection, pub selection: &'a mut Selection,
pub order: &'a mut FileOrder, pub order: &'a mut FileOrder,
pub clipboard: &'a mut Option<Clipboard>, pub clipboard: &'a mut Option<Clipboard>,
+1 -1
View File
@@ -1,6 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{FileId, StackEntry as Files}; use crate::{FileId, Files};
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct FileOrder(pub Vec<FileId>); pub struct FileOrder(pub Vec<FileId>);
+13 -24
View File
@@ -1,6 +1,6 @@
use std::collections::HashSet; 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. /// How a new selection combines with the current one.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -52,12 +52,7 @@ impl Selection {
/// Combines `selection` with this one according to `mode`, ignoring the elements of /// 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 /// `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. /// as it is, while replacing it with nothing deselects everything.
pub fn select( pub fn select(&mut self, files: Option<&Files>, mut selection: Selection, mode: SelectMode) {
&mut self,
files: Option<&StackEntry>,
mut selection: Selection,
mode: SelectMode,
) {
match files { match files {
Some(files) => selection.retain_existing(files), Some(files) => selection.retain_existing(files),
None => selection = Selection::Empty, None => selection = Selection::Empty,
@@ -72,7 +67,7 @@ impl Selection {
/// Selects all the elements of the same kind as the selected ones, see /// 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. /// [`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) { if let Some(all) = self.all_at_level(files, order) {
*self = all; *self = all;
} }
@@ -81,13 +76,7 @@ impl Selection {
/// Moves the selection to the next (`down`) or previous element, see /// Moves the selection to the next (`down`) or previous element, see
/// [`Selection::neighbour`]. With `add`, the element is added to the selection instead of /// [`Selection::neighbour`]. With `add`, the element is added to the selection instead of
/// replacing it. /// replacing it.
pub fn select_neighbour( pub fn select_neighbour(&mut self, files: &Files, order: &[FileId], down: bool, add: bool) {
&mut self,
files: &StackEntry,
order: &[FileId],
down: bool,
add: bool,
) {
if let Some(next) = self.neighbour(files, order, down) { if let Some(next) = self.neighbour(files, order, down) {
if add { if add {
self.extend(next); self.extend(next);
@@ -102,7 +91,7 @@ impl Selection {
/// The track segments covered by the selection, in order: the segments of several selected /// 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 /// 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. /// follow the file. Waypoints are not segments: selecting only waypoints covers none.
pub fn segment_locations(&self, files: &StackEntry, order: &[FileId]) -> Vec<SegmentLocation> { pub fn segment_locations(&self, files: &Files, order: &[FileId]) -> Vec<SegmentLocation> {
let mut locations = vec![]; let mut locations = vec![];
let mut add = |file_id: FileId, let mut add = |file_id: FileId,
trk_filter: &dyn Fn(&crate::Track) -> bool, 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 /// 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. /// 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 { let empty = match self {
Selection::Empty => false, Selection::Empty => false,
Selection::File { file_ids } => { 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 /// 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 /// 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). /// select (the waypoints node, or a file that does not exist).
pub fn all_at_level(&self, files: &StackEntry, order: &[FileId]) -> Option<Selection> { pub fn all_at_level(&self, files: &Files, order: &[FileId]) -> Option<Selection> {
match self { match self {
Selection::Empty | Selection::File { .. } => Some(Selection::File { Selection::Empty | Selection::File { .. } => Some(Selection::File {
file_ids: order.iter().copied().collect(), 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. /// (`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 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. /// the ones that are already selected. `None` when there is no such element.
pub fn neighbour(&self, files: &StackEntry, order: &[FileId], down: bool) -> Option<Selection> { pub fn neighbour(&self, files: &Files, order: &[FileId], down: bool) -> Option<Selection> {
match self { match self {
Selection::Empty | Selection::Waypoints { .. } => None, Selection::Empty | Selection::Waypoints { .. } => None,
Selection::File { file_ids } => { Selection::File { file_ids } => {
@@ -443,7 +432,7 @@ mod tests {
fn test_retain_existing() { fn test_retain_existing() {
let file = File::default(); let file = File::default();
let (kept, gone) = (file.id, FileId::default()); let (kept, gone) = (file.id, FileId::default());
let mut files = StackEntry::default(); let mut files = Files::default();
files.insert(kept, Rc::new(file)); files.insert(kept, Rc::new(file));
let mut selection = Selection::File { let mut selection = Selection::File {
@@ -481,7 +470,7 @@ mod tests {
}); });
let (file_id, trk_id) = (file.id, file.trk[0].id); 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 (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)); files.insert(file_id, Rc::new(file));
let mut selection = Selection::Track { 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. /// A file with 3 tracks of 2 segments each and 3 waypoints, with the ids of everything.
struct Tree { struct Tree {
files: StackEntry, files: Files,
order: Vec<FileId>, order: Vec<FileId>,
file: FileId, file: FileId,
tracks: Vec<TrackId>, tracks: Vec<TrackId>,
@@ -644,7 +633,7 @@ mod tests {
.collect(), .collect(),
waypoints: file.wpt.iter().map(|w| w.id).collect(), waypoints: file.wpt.iter().map(|w| w.id).collect(),
order: vec![file.id], 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] #[test]
fn test_neighbour_of_files_wraps_around_and_skips_the_selected() { 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 [a, b, c] = [FileId::default(), FileId::default(), FileId::default()];
let order = [a, b, c]; let order = [a, b, c];
let select = |ids: &[FileId]| Selection::File { let select = |ids: &[FileId]| Selection::File {
+18 -18
View File
@@ -120,7 +120,7 @@ fn test_load_file() {
fn test_new_file_has_no_statistics() { fn test_new_file_has_no_statistics() {
let mut engine = TestEngine::default(); let mut engine = TestEngine::default();
assert!(new(&mut engine, "empty")); 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_eq!(engine.order.0.len(), 1);
assert!(engine.statistics().total_distance.is_empty()); assert!(engine.statistics().total_distance.is_empty());
} }
@@ -135,7 +135,7 @@ fn test_statistics_follow_selection() {
// a newly created (empty) file becomes the selection // a newly created (empty) file becomes the selection
assert!(new(&mut engine, "empty")); assert!(new(&mut engine, "empty"));
assert!(engine.statistics().total_distance.is_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 // loading a file selects it again
assert!(load(&mut engine, "data/simple.gpx")); assert!(load(&mut engine, "data/simple.gpx"));
@@ -152,8 +152,8 @@ fn test_invalid_load_is_rejected() {
name: "file", name: "file",
}) })
)); ));
assert!(engine.stack.current().is_none()); assert!(engine.history.current().is_none());
assert!(!engine.stack.can_undo()); assert!(!engine.history.can_undo());
assert!(engine.order.0.is_empty()); assert!(engine.order.0.is_empty());
} }
@@ -162,8 +162,8 @@ fn test_command_with_nothing_to_do_is_rejected() {
let mut engine = TestEngine::default(); let mut engine = TestEngine::default();
new(&mut engine, "file"); new(&mut engine, "file");
assert!(!edit(&mut engine, Command::Reverse(crate::Reverse))); assert!(!edit(&mut engine, Command::Reverse(crate::Reverse)));
assert!(!engine.stack.can_redo()); assert!(!engine.history.can_redo());
assert_eq!(engine.stack.current().unwrap().len(), 1); assert_eq!(engine.history.current().unwrap().len(), 1);
} }
#[test] #[test]
@@ -177,7 +177,7 @@ fn test_metadata_renames_selected_file() {
desc: "about", 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.name, "after");
assert_eq!(file.info.desc.as_deref(), Some("about")); assert_eq!(file.info.desc.as_deref(), Some("about"));
} }
@@ -194,7 +194,7 @@ fn test_metadata_without_selection_does_not_modify_files() {
desc: "", 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"); assert_eq!(file.info.name, "file");
} }
@@ -204,7 +204,7 @@ fn test_edit_changes_statistics_when_segments_change() {
load(&mut engine, "data/simple.gpx"); load(&mut engine, "data/simple.gpx");
let before = engine let before = engine
.statistics_cache .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, Command::NewTrack(NewTrack));
edit( edit(
&mut engine, &mut engine,
@@ -217,7 +217,7 @@ fn test_edit_changes_statistics_when_segments_change() {
// styling does not touch the segments: their statistics are reused // styling does not touch the segments: their statistics are reused
let after = engine let after = engine
.statistics_cache .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)); 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.run(Action::Undo));
assert!(engine.order.0.is_empty()); assert!(engine.order.0.is_empty());
assert!(!engine.run(Action::Undo)); assert!(!engine.run(Action::Undo));
assert!(engine.stack.current().is_none()); assert!(engine.history.current().is_none());
} }
#[test] #[test]
@@ -381,7 +381,7 @@ fn test_selection_does_not_create_undo_steps() {
new(&mut engine, "a"); new(&mut engine, "a");
select_all(&mut engine); select_all(&mut engine);
engine.run(Action::Undo); 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 { fn select_elements(engine: &mut TestEngine, selection: Selection, mode: SelectMode) -> bool {
@@ -408,7 +408,7 @@ fn test_select_elements() {
let mut engine = TestEngine::default(); let mut engine = TestEngine::default();
load(&mut engine, "data/with_tracks_and_segments.gpx"); load(&mut engine, "data/with_tracks_and_segments.gpx");
let file = engine let file = engine
.stack .history
.current() .current()
.unwrap() .unwrap()
.values() .values()
@@ -507,7 +507,7 @@ fn test_toggle_elements() {
let mut engine = TestEngine::default(); let mut engine = TestEngine::default();
load(&mut engine, "data/with_tracks_and_segments.gpx"); load(&mut engine, "data/with_tracks_and_segments.gpx");
let file = engine let file = engine
.stack .history
.current() .current()
.unwrap() .unwrap()
.values() .values()
@@ -541,7 +541,7 @@ fn test_selected_elements_are_dropped_when_they_disappear() {
let mut engine = TestEngine::default(); let mut engine = TestEngine::default();
load(&mut engine, "data/with_tracks_and_segments.gpx"); load(&mut engine, "data/with_tracks_and_segments.gpx");
let file = engine let file = engine
.stack .history
.current() .current()
.unwrap() .unwrap()
.values() .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"); let load = |engine: &mut TestEngine| load(engine, "data/with_tracks_and_segments.gpx");
load(&mut engine); load(&mut engine);
let file = engine let file = engine
.stack .history
.current() .current()
.unwrap() .unwrap()
.values() .values()
@@ -648,7 +648,7 @@ fn test_arrow_select() {
edit(&mut engine, Command::NewTrack(NewTrack)); edit(&mut engine, Command::NewTrack(NewTrack));
} }
let file = engine let file = engine
.stack .history
.current() .current()
.unwrap() .unwrap()
.values() .values()
@@ -1153,7 +1153,7 @@ fn test_export_writes_the_file_as_it_is() {
let id = engine.order()[0]; let id = engine.order()[0];
let bytes = engine.export(&id, ExportOptions::ALL).unwrap(); let bytes = engine.export(&id, ExportOptions::ALL).unwrap();
let again = crate::parse(&bytes, &mut engine.categories().clone()).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.len(), file.trk.len());
assert_eq!(again.trk[0].trkseg[0].len(), file.trk[0].trkseg[0].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()); assert!(again.trk[0].trkseg[0][0].time.is_some());
+7 -3
View File
@@ -9,7 +9,7 @@ use super::codec::{
encode_order, encode_trackpoints, encode_waypoints, encode_order, encode_trackpoints, encode_waypoints,
}; };
use crate::{ use crate::{
Batch, ChunkKey, CommitError, File, FileId, Revision, StackEntry, Storage, StorageError, Batch, ChunkKey, CommitError, File, FileId, Files, Revision, Storage, StorageError,
TrackpointCategories, TrackpointChunk, WaypointChunk, TrackpointCategories, TrackpointChunk, WaypointChunk,
}; };
@@ -17,7 +17,7 @@ use crate::{
/// categories their trackpoints refer to. See [`crate::Engine::snapshot`]. /// categories their trackpoints refer to. See [`crate::Engine::snapshot`].
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Snapshot { pub struct Snapshot {
pub files: StackEntry, pub files: Files,
pub order: Vec<FileId>, pub order: Vec<FileId>,
pub categories: TrackpointCategories, pub categories: TrackpointCategories,
} }
@@ -533,7 +533,11 @@ mod tests {
changed.order.clear(); changed.order.clear();
changed.files.clear(); changed.files.clear();
let result = block_on(persister.save(&storage, changed)); 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!(storage.batches.borrow().is_empty(), "{what}");
assert_eq!(storage.inner.counts(), before, "{what}"); assert_eq!(storage.inner.counts(), before, "{what}");
} }