move ops.rs out of undo/

This commit is contained in:
Damien Elmes 2021-03-13 20:10:03 +10:00
parent 8fc43956c2
commit 90526c61cd
19 changed files with 114 additions and 126 deletions

View File

@ -26,7 +26,7 @@ impl CardsService for Backend {
let op = if input.skip_undo_entry {
None
} else {
Some(UndoableOpKind::UpdateCard)
Some(Op::UpdateCard)
};
let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?;
col.update_card_with_op(&mut card, op)

View File

@ -51,7 +51,7 @@ impl NotesService for Backend {
let op = if input.skip_undo_entry {
None
} else {
Some(UndoableOpKind::UpdateNote)
Some(Op::UpdateNote)
};
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
col.update_note_with_op(&mut note, op)

View File

@ -3,12 +3,13 @@
pub(crate) mod undo;
use crate::define_newtype;
use crate::err::{AnkiError, Result};
use crate::notes::NoteID;
use crate::{
collection::Collection, config::SchedulerVersion, timestamp::TimestampSecs, types::Usn,
collection::Collection, config::SchedulerVersion, prelude::*, timestamp::TimestampSecs,
types::Usn,
};
use crate::{define_newtype, undo::UndoableOpKind};
use crate::{deckconf::DeckConf, decks::DeckID};
use num_enum::TryFromPrimitive;
@ -139,11 +140,7 @@ impl Card {
}
impl Collection {
pub(crate) fn update_card_with_op(
&mut self,
card: &mut Card,
op: Option<UndoableOpKind>,
) -> Result<()> {
pub(crate) fn update_card_with_op(&mut self, card: &mut Card, op: Option<Op>) -> Result<()> {
let existing = self.storage.get_card(card.id)?.ok_or(AnkiError::NotFound)?;
self.transact(op, |col| col.update_card_inner(card, existing, col.usn()?))
}
@ -211,7 +208,7 @@ impl Collection {
self.storage.set_search_table_to_card_ids(cards, false)?;
let sched = self.scheduler_version();
let usn = self.usn()?;
self.transact(Some(UndoableOpKind::SetDeck), |col| {
self.transact(Some(Op::SetDeck), |col| {
for mut card in col.storage.all_searched_cards()? {
if card.deck_id == deck_id {
continue;

View File

@ -85,7 +85,7 @@ pub struct Collection {
impl Collection {
/// Execute the provided closure in a transaction, rolling back if
/// an error is returned.
pub(crate) fn transact<F, R>(&mut self, op: Option<UndoableOpKind>, func: F) -> Result<R>
pub(crate) fn transact<F, R>(&mut self, op: Option<Op>, func: F) -> Result<R>
where
F: FnOnce(&mut Collection) -> Result<R>,
{

View File

@ -71,7 +71,7 @@ mod test {
fn undo() -> Result<()> {
let mut col = open_test_collection();
// the op kind doesn't matter, we just need undo enabled
let op = Some(UndoableOpKind::Bury);
let op = Some(Op::Bury);
// test key
let key = BoolKey::NormalizeNoteText;

View File

@ -10,16 +10,14 @@ pub use crate::backend_proto::{
deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto,
DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck,
};
use crate::{
backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images,
undo::UndoableOpKind,
};
use crate::{backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images};
use crate::{
collection::Collection,
deckconf::DeckConfID,
define_newtype,
err::{AnkiError, Result},
i18n::TR,
prelude::*,
text::normalize_to_nfc,
timestamp::TimestampSecs,
types::Usn,
@ -283,7 +281,7 @@ impl Collection {
return Err(AnkiError::invalid_input("deck to add must have id 0"));
}
self.transact(Some(UndoableOpKind::AddDeck), |col| {
self.transact(Some(Op::AddDeck), |col| {
let usn = col.usn()?;
col.prepare_deck_for_update(deck, usn)?;
deck.set_modified(usn);
@ -293,14 +291,14 @@ impl Collection {
}
pub fn update_deck(&mut self, deck: &mut Deck) -> Result<()> {
self.transact(Some(UndoableOpKind::UpdateDeck), |col| {
self.transact(Some(Op::UpdateDeck), |col| {
let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?;
col.update_deck_inner(deck, existing_deck, col.usn()?)
})
}
pub fn rename_deck(&mut self, did: DeckID, new_human_name: &str) -> Result<()> {
self.transact(Some(UndoableOpKind::RenameDeck), |col| {
self.transact(Some(Op::RenameDeck), |col| {
let existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?;
let mut deck = existing_deck.clone();
deck.name = human_deck_name_to_native(new_human_name);
@ -468,7 +466,7 @@ impl Collection {
pub fn remove_decks_and_child_decks(&mut self, dids: &[DeckID]) -> Result<usize> {
let mut card_count = 0;
self.transact(Some(UndoableOpKind::RemoveDeck), |col| {
self.transact(Some(Op::RemoveDeck), |col| {
let usn = col.usn()?;
for did in dids {
if let Some(deck) = col.storage.get_deck(*did)? {
@ -627,7 +625,7 @@ impl Collection {
target: Option<DeckID>,
) -> Result<()> {
let usn = self.usn()?;
self.transact(Some(UndoableOpKind::RenameDeck), |col| {
self.transact(Some(Op::RenameDeck), |col| {
let target_deck;
let mut target_name = None;
if let Some(target) = target {

View File

@ -24,6 +24,7 @@ mod markdown;
pub mod media;
pub mod notes;
pub mod notetype;
pub mod ops;
mod preferences;
pub mod prelude;
pub mod revlog;

View File

@ -306,7 +306,7 @@ impl Collection {
}
pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> {
self.transact(Some(UndoableOpKind::AddNote), |col| {
self.transact(Some(Op::AddNote), |col| {
let nt = col
.get_notetype(note.notetype_id)?
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
@ -335,14 +335,10 @@ impl Collection {
#[cfg(test)]
pub(crate) fn update_note(&mut self, note: &mut Note) -> Result<()> {
self.update_note_with_op(note, Some(UndoableOpKind::UpdateNote))
self.update_note_with_op(note, Some(Op::UpdateNote))
}
pub(crate) fn update_note_with_op(
&mut self,
note: &mut Note,
op: Option<UndoableOpKind>,
) -> Result<()> {
pub(crate) fn update_note_with_op(&mut self, note: &mut Note, op: Option<Op>) -> Result<()> {
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
if !note_differs_from_db(&mut existing_note, note) {
// nothing to do
@ -398,7 +394,7 @@ impl Collection {
/// Remove provided notes, and any cards that use them.
pub(crate) fn remove_notes(&mut self, nids: &[NoteID]) -> Result<()> {
let usn = self.usn()?;
self.transact(Some(UndoableOpKind::RemoveNote), |col| {
self.transact(Some(Op::RemoveNote), |col| {
for nid in nids {
let nid = *nid;
if let Some(_existing_note) = col.storage.get_note(nid)? {

View File

@ -96,7 +96,7 @@ impl Collection {
op.changes.last()
{
note.id == before_change.id
&& op.kind == UndoableOpKind::UpdateNote
&& op.kind == Op::UpdateNote
&& op.timestamp.elapsed_secs() < 60
} else {
false

57
rslib/src/ops.rs Normal file
View File

@ -0,0 +1,57 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Op {
AddDeck,
AddNote,
AnswerCard,
Bury,
RemoveDeck,
RemoveNote,
RenameDeck,
ScheduleAsNew,
SetDueDate,
Suspend,
UnburyUnsuspend,
UpdateCard,
UpdateDeck,
UpdateNote,
UpdatePreferences,
UpdateTag,
SetDeck,
}
impl Op {
pub(crate) fn needs_study_queue_reset(self) -> bool {
self != Op::AnswerCard
}
}
impl Collection {
pub fn describe_op_kind(&self, op: Op) -> String {
let key = match op {
Op::AddDeck => TR::UndoAddDeck,
Op::AddNote => TR::UndoAddNote,
Op::AnswerCard => TR::UndoAnswerCard,
Op::Bury => TR::StudyingBury,
Op::RemoveDeck => TR::DecksDeleteDeck,
Op::RemoveNote => TR::StudyingDeleteNote,
Op::RenameDeck => TR::ActionsRenameDeck,
Op::ScheduleAsNew => TR::UndoForgetCard,
Op::SetDueDate => TR::ActionsSetDueDate,
Op::Suspend => TR::StudyingSuspend,
Op::UnburyUnsuspend => TR::UndoUnburyUnsuspend,
Op::UpdateCard => TR::UndoUpdateCard,
Op::UpdateDeck => TR::UndoUpdateDeck,
Op::UpdateNote => TR::UndoUpdateNote,
Op::UpdatePreferences => TR::PreferencesPreferences,
Op::UpdateTag => TR::UndoUpdateTag,
Op::SetDeck => TR::BrowsingChangeDeck,
};
self.i18n.tr(key).to_string()
}
}

View File

@ -10,6 +10,7 @@ use crate::{
collection::Collection,
config::BoolKey,
err::Result,
prelude::*,
scheduler::timing::local_minutes_west_for_stamp,
};
@ -23,10 +24,9 @@ impl Collection {
}
pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> {
self.transact(
Some(crate::undo::UndoableOpKind::UpdatePreferences),
|col| col.set_preferences_inner(prefs),
)
self.transact(Some(Op::UpdatePreferences), |col| {
col.set_preferences_inner(prefs)
})
}
fn set_preferences_inner(

View File

@ -11,9 +11,9 @@ pub use crate::{
i18n::{tr_args, tr_strs, I18n, TR},
notes::{Note, NoteID},
notetype::{NoteType, NoteTypeID},
ops::Op,
revlog::RevlogID,
timestamp::{TimestampMillis, TimestampSecs},
types::Usn,
undo::UndoableOpKind,
};
pub use slog::{debug, Logger};

View File

@ -241,9 +241,7 @@ impl Collection {
/// Answer card, writing its new state to the database.
pub fn answer_card(&mut self, answer: &CardAnswer) -> Result<()> {
self.transact(Some(UndoableOpKind::AnswerCard), |col| {
col.answer_card_inner(answer)
})
self.transact(Some(Op::AnswerCard), |col| col.answer_card_inner(answer))
}
fn answer_card_inner(&mut self, answer: &CardAnswer) -> Result<()> {

View File

@ -69,7 +69,7 @@ impl Collection {
}
pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> {
self.transact(Some(UndoableOpKind::UnburyUnsuspend), |col| {
self.transact(Some(Op::UnburyUnsuspend), |col| {
col.storage.set_search_table_to_card_ids(cids, false)?;
col.unsuspend_or_unbury_searched_cards()
})
@ -126,8 +126,8 @@ impl Collection {
mode: BuryOrSuspendMode,
) -> Result<()> {
let op = match mode {
BuryOrSuspendMode::Suspend => UndoableOpKind::Suspend,
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => UndoableOpKind::Bury,
BuryOrSuspendMode::Suspend => Op::Suspend,
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury,
};
self.transact(Some(op), |col| {
col.storage.set_search_table_to_card_ids(cids, false)?;

View File

@ -7,9 +7,9 @@ use crate::{
decks::DeckID,
err::Result,
notes::NoteID,
prelude::*,
search::SortMode,
types::Usn,
undo::UndoableOpKind,
};
use rand::seq::SliceRandom;
use std::collections::{HashMap, HashSet};
@ -106,7 +106,7 @@ impl Collection {
pub fn reschedule_cards_as_new(&mut self, cids: &[CardID], log: bool) -> Result<()> {
let usn = self.usn()?;
let mut position = self.get_next_card_position();
self.transact(Some(UndoableOpKind::ScheduleAsNew), |col| {
self.transact(Some(Op::ScheduleAsNew), |col| {
col.storage.set_search_table_to_card_ids(cids, true)?;
let cards = col.storage.all_searched_cards_in_search_order()?;
for mut card in cards {

View File

@ -7,8 +7,7 @@ use crate::{
config::StringKey,
deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
err::Result,
prelude::AnkiError,
undo::UndoableOpKind,
prelude::*,
};
use lazy_static::lazy_static;
use rand::distributions::{Distribution, Uniform};
@ -100,7 +99,7 @@ impl Collection {
let today = self.timing_today()?.days_elapsed;
let mut rng = rand::thread_rng();
let distribution = Uniform::from(spec.min..=spec.max);
self.transact(Some(UndoableOpKind::SetDueDate), |col| {
self.transact(Some(Op::SetDueDate), |col| {
col.storage.set_search_table_to_card_ids(cids, false)?;
for mut card in col.storage.all_searched_cards()? {
let original = card.clone();

View File

@ -341,7 +341,7 @@ impl Collection {
tags: &[Regex],
mut repl: R,
) -> Result<usize> {
self.transact(Some(UndoableOpKind::UpdateTag), |col| {
self.transact(Some(Op::UpdateTag), |col| {
col.transform_notes(nids, |note, _nt| {
let mut changed = false;
for re in tags {
@ -392,7 +392,7 @@ impl Collection {
)
.map_err(|_| AnkiError::invalid_input("invalid regex"))?;
self.transact(Some(UndoableOpKind::UpdateTag), |col| {
self.transact(Some(Op::UpdateTag), |col| {
col.transform_notes(nids, |note, _nt| {
let mut need_to_add = true;
let mut match_count = 0;

View File

@ -2,10 +2,9 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod changes;
mod ops;
pub use crate::ops::Op;
pub(crate) use changes::UndoableChange;
pub use ops::UndoableOpKind;
use crate::backend_proto as pb;
use crate::prelude::*;
@ -15,7 +14,7 @@ const UNDO_LIMIT: usize = 30;
#[derive(Debug)]
pub(crate) struct UndoableOp {
pub kind: UndoableOpKind,
pub kind: Op,
pub timestamp: TimestampSecs,
pub changes: Vec<UndoableChange>,
}
@ -51,7 +50,7 @@ impl UndoManager {
}
}
fn begin_step(&mut self, op: Option<UndoableOpKind>) {
fn begin_step(&mut self, op: Option<Op>) {
println!("begin: {:?}", op);
if op.is_none() {
self.undo_steps.clear();
@ -88,11 +87,11 @@ impl UndoManager {
.unwrap_or(true)
}
fn can_undo(&self) -> Option<UndoableOpKind> {
fn can_undo(&self) -> Option<Op> {
self.undo_steps.front().map(|s| s.kind)
}
fn can_redo(&self) -> Option<UndoableOpKind> {
fn can_redo(&self) -> Option<Op> {
self.redo_steps.last().map(|s| s.kind)
}
@ -102,11 +101,11 @@ impl UndoManager {
}
impl Collection {
pub fn can_undo(&self) -> Option<UndoableOpKind> {
pub fn can_undo(&self) -> Option<Op> {
self.state.undo.can_undo()
}
pub fn can_redo(&self) -> Option<UndoableOpKind> {
pub fn can_redo(&self) -> Option<Op> {
self.state.undo.can_redo()
}
@ -156,7 +155,7 @@ impl Collection {
}
/// If op is None, clears the undo/redo queues.
pub(crate) fn begin_undoable_operation(&mut self, op: Option<UndoableOpKind>) {
pub(crate) fn begin_undoable_operation(&mut self, op: Option<Op>) {
self.state.undo.begin_step(op);
}
@ -219,7 +218,7 @@ mod test {
// record a few undo steps
for i in 3..=4 {
col.transact(Some(UndoableOpKind::UpdateCard), |col| {
col.transact(Some(Op::UpdateCard), |col| {
col.get_and_update_card(cid, |card| {
card.interval = i;
Ok(())
@ -231,41 +230,41 @@ mod test {
}
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), None);
// undo a step
col.undo().unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
// and again
col.undo().unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 2);
assert_eq!(col.can_undo(), None);
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
// redo a step
col.redo().unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
// and another
col.redo().unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), None);
// and undo the redo
col.undo().unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
// if any action is performed, it should clear the redo queue
col.transact(Some(UndoableOpKind::UpdateCard), |col| {
col.transact(Some(Op::UpdateCard), |col| {
col.get_and_update_card(cid, |card| {
card.interval = 5;
Ok(())
@ -275,7 +274,7 @@ mod test {
})
.unwrap();
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 5);
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
assert_eq!(col.can_redo(), None);
// and any action that doesn't support undoing will clear both queues

View File

@ -1,57 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UndoableOpKind {
AddDeck,
AddNote,
AnswerCard,
Bury,
RemoveDeck,
RemoveNote,
RenameDeck,
ScheduleAsNew,
SetDueDate,
Suspend,
UnburyUnsuspend,
UpdateCard,
UpdateDeck,
UpdateNote,
UpdatePreferences,
UpdateTag,
SetDeck,
}
impl UndoableOpKind {
pub(crate) fn needs_study_queue_reset(self) -> bool {
self != UndoableOpKind::AnswerCard
}
}
impl Collection {
pub fn describe_op_kind(&self, op: UndoableOpKind) -> String {
let key = match op {
UndoableOpKind::AddDeck => TR::UndoAddDeck,
UndoableOpKind::AddNote => TR::UndoAddNote,
UndoableOpKind::AnswerCard => TR::UndoAnswerCard,
UndoableOpKind::Bury => TR::StudyingBury,
UndoableOpKind::RemoveDeck => TR::DecksDeleteDeck,
UndoableOpKind::RemoveNote => TR::StudyingDeleteNote,
UndoableOpKind::RenameDeck => TR::ActionsRenameDeck,
UndoableOpKind::ScheduleAsNew => TR::UndoForgetCard,
UndoableOpKind::SetDueDate => TR::ActionsSetDueDate,
UndoableOpKind::Suspend => TR::StudyingSuspend,
UndoableOpKind::UnburyUnsuspend => TR::UndoUnburyUnsuspend,
UndoableOpKind::UpdateCard => TR::UndoUpdateCard,
UndoableOpKind::UpdateDeck => TR::UndoUpdateDeck,
UndoableOpKind::UpdateNote => TR::UndoUpdateNote,
UndoableOpKind::UpdatePreferences => TR::PreferencesPreferences,
UndoableOpKind::UpdateTag => TR::UndoUpdateTag,
UndoableOpKind::SetDeck => TR::BrowsingChangeDeck,
};
self.i18n.tr(key).to_string()
}
}