mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-16 08:19:59 +00:00
renaming
This commit is contained in:
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,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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+134
-134
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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>);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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}");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user