mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
move ops.rs out of undo/
This commit is contained in:
parent
8fc43956c2
commit
90526c61cd
19 changed files with 114 additions and 126 deletions
|
@ -26,7 +26,7 @@ impl CardsService for Backend {
|
||||||
let op = if input.skip_undo_entry {
|
let op = if input.skip_undo_entry {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(UndoableOpKind::UpdateCard)
|
Some(Op::UpdateCard)
|
||||||
};
|
};
|
||||||
let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?;
|
let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?;
|
||||||
col.update_card_with_op(&mut card, op)
|
col.update_card_with_op(&mut card, op)
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl NotesService for Backend {
|
||||||
let op = if input.skip_undo_entry {
|
let op = if input.skip_undo_entry {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(UndoableOpKind::UpdateNote)
|
Some(Op::UpdateNote)
|
||||||
};
|
};
|
||||||
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
|
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
|
||||||
col.update_note_with_op(&mut note, op)
|
col.update_note_with_op(&mut note, op)
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
|
|
||||||
pub(crate) mod undo;
|
pub(crate) mod undo;
|
||||||
|
|
||||||
|
use crate::define_newtype;
|
||||||
use crate::err::{AnkiError, Result};
|
use crate::err::{AnkiError, Result};
|
||||||
use crate::notes::NoteID;
|
use crate::notes::NoteID;
|
||||||
use crate::{
|
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 crate::{deckconf::DeckConf, decks::DeckID};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
@ -139,11 +140,7 @@ impl Card {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub(crate) fn update_card_with_op(
|
pub(crate) fn update_card_with_op(&mut self, card: &mut Card, op: Option<Op>) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
card: &mut Card,
|
|
||||||
op: Option<UndoableOpKind>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let existing = self.storage.get_card(card.id)?.ok_or(AnkiError::NotFound)?;
|
let existing = self.storage.get_card(card.id)?.ok_or(AnkiError::NotFound)?;
|
||||||
self.transact(op, |col| col.update_card_inner(card, existing, col.usn()?))
|
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)?;
|
self.storage.set_search_table_to_card_ids(cards, false)?;
|
||||||
let sched = self.scheduler_version();
|
let sched = self.scheduler_version();
|
||||||
let usn = self.usn()?;
|
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()? {
|
for mut card in col.storage.all_searched_cards()? {
|
||||||
if card.deck_id == deck_id {
|
if card.deck_id == deck_id {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -85,7 +85,7 @@ pub struct Collection {
|
||||||
impl Collection {
|
impl Collection {
|
||||||
/// Execute the provided closure in a transaction, rolling back if
|
/// Execute the provided closure in a transaction, rolling back if
|
||||||
/// an error is returned.
|
/// 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
|
where
|
||||||
F: FnOnce(&mut Collection) -> Result<R>,
|
F: FnOnce(&mut Collection) -> Result<R>,
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,7 +71,7 @@ mod test {
|
||||||
fn undo() -> Result<()> {
|
fn undo() -> Result<()> {
|
||||||
let mut col = open_test_collection();
|
let mut col = open_test_collection();
|
||||||
// the op kind doesn't matter, we just need undo enabled
|
// the op kind doesn't matter, we just need undo enabled
|
||||||
let op = Some(UndoableOpKind::Bury);
|
let op = Some(Op::Bury);
|
||||||
// test key
|
// test key
|
||||||
let key = BoolKey::NormalizeNoteText;
|
let key = BoolKey::NormalizeNoteText;
|
||||||
|
|
||||||
|
|
|
@ -10,16 +10,14 @@ pub use crate::backend_proto::{
|
||||||
deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto,
|
deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto,
|
||||||
DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck,
|
DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images};
|
||||||
backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images,
|
|
||||||
undo::UndoableOpKind,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
deckconf::DeckConfID,
|
deckconf::DeckConfID,
|
||||||
define_newtype,
|
define_newtype,
|
||||||
err::{AnkiError, Result},
|
err::{AnkiError, Result},
|
||||||
i18n::TR,
|
i18n::TR,
|
||||||
|
prelude::*,
|
||||||
text::normalize_to_nfc,
|
text::normalize_to_nfc,
|
||||||
timestamp::TimestampSecs,
|
timestamp::TimestampSecs,
|
||||||
types::Usn,
|
types::Usn,
|
||||||
|
@ -283,7 +281,7 @@ impl Collection {
|
||||||
return Err(AnkiError::invalid_input("deck to add must have id 0"));
|
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()?;
|
let usn = col.usn()?;
|
||||||
col.prepare_deck_for_update(deck, usn)?;
|
col.prepare_deck_for_update(deck, usn)?;
|
||||||
deck.set_modified(usn);
|
deck.set_modified(usn);
|
||||||
|
@ -293,14 +291,14 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_deck(&mut self, deck: &mut Deck) -> Result<()> {
|
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)?;
|
let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?;
|
||||||
col.update_deck_inner(deck, existing_deck, col.usn()?)
|
col.update_deck_inner(deck, existing_deck, col.usn()?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rename_deck(&mut self, did: DeckID, new_human_name: &str) -> Result<()> {
|
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 existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?;
|
||||||
let mut deck = existing_deck.clone();
|
let mut deck = existing_deck.clone();
|
||||||
deck.name = human_deck_name_to_native(new_human_name);
|
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> {
|
pub fn remove_decks_and_child_decks(&mut self, dids: &[DeckID]) -> Result<usize> {
|
||||||
let mut card_count = 0;
|
let mut card_count = 0;
|
||||||
self.transact(Some(UndoableOpKind::RemoveDeck), |col| {
|
self.transact(Some(Op::RemoveDeck), |col| {
|
||||||
let usn = col.usn()?;
|
let usn = col.usn()?;
|
||||||
for did in dids {
|
for did in dids {
|
||||||
if let Some(deck) = col.storage.get_deck(*did)? {
|
if let Some(deck) = col.storage.get_deck(*did)? {
|
||||||
|
@ -627,7 +625,7 @@ impl Collection {
|
||||||
target: Option<DeckID>,
|
target: Option<DeckID>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
self.transact(Some(UndoableOpKind::RenameDeck), |col| {
|
self.transact(Some(Op::RenameDeck), |col| {
|
||||||
let target_deck;
|
let target_deck;
|
||||||
let mut target_name = None;
|
let mut target_name = None;
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
|
|
|
@ -24,6 +24,7 @@ mod markdown;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod notes;
|
pub mod notes;
|
||||||
pub mod notetype;
|
pub mod notetype;
|
||||||
|
pub mod ops;
|
||||||
mod preferences;
|
mod preferences;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod revlog;
|
pub mod revlog;
|
||||||
|
|
|
@ -306,7 +306,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> {
|
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
|
let nt = col
|
||||||
.get_notetype(note.notetype_id)?
|
.get_notetype(note.notetype_id)?
|
||||||
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
|
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
|
||||||
|
@ -335,14 +335,10 @@ impl Collection {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn update_note(&mut self, note: &mut Note) -> Result<()> {
|
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(
|
pub(crate) fn update_note_with_op(&mut self, note: &mut Note, op: Option<Op>) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
note: &mut Note,
|
|
||||||
op: Option<UndoableOpKind>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
||||||
if !note_differs_from_db(&mut existing_note, note) {
|
if !note_differs_from_db(&mut existing_note, note) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
|
@ -398,7 +394,7 @@ impl Collection {
|
||||||
/// Remove provided notes, and any cards that use them.
|
/// Remove provided notes, and any cards that use them.
|
||||||
pub(crate) fn remove_notes(&mut self, nids: &[NoteID]) -> Result<()> {
|
pub(crate) fn remove_notes(&mut self, nids: &[NoteID]) -> Result<()> {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
self.transact(Some(UndoableOpKind::RemoveNote), |col| {
|
self.transact(Some(Op::RemoveNote), |col| {
|
||||||
for nid in nids {
|
for nid in nids {
|
||||||
let nid = *nid;
|
let nid = *nid;
|
||||||
if let Some(_existing_note) = col.storage.get_note(nid)? {
|
if let Some(_existing_note) = col.storage.get_note(nid)? {
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl Collection {
|
||||||
op.changes.last()
|
op.changes.last()
|
||||||
{
|
{
|
||||||
note.id == before_change.id
|
note.id == before_change.id
|
||||||
&& op.kind == UndoableOpKind::UpdateNote
|
&& op.kind == Op::UpdateNote
|
||||||
&& op.timestamp.elapsed_secs() < 60
|
&& op.timestamp.elapsed_secs() < 60
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
57
rslib/src/ops.rs
Normal file
57
rslib/src/ops.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
config::BoolKey,
|
config::BoolKey,
|
||||||
err::Result,
|
err::Result,
|
||||||
|
prelude::*,
|
||||||
scheduler::timing::local_minutes_west_for_stamp,
|
scheduler::timing::local_minutes_west_for_stamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,10 +24,9 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> {
|
pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> {
|
||||||
self.transact(
|
self.transact(Some(Op::UpdatePreferences), |col| {
|
||||||
Some(crate::undo::UndoableOpKind::UpdatePreferences),
|
col.set_preferences_inner(prefs)
|
||||||
|col| col.set_preferences_inner(prefs),
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_preferences_inner(
|
fn set_preferences_inner(
|
||||||
|
|
|
@ -11,9 +11,9 @@ pub use crate::{
|
||||||
i18n::{tr_args, tr_strs, I18n, TR},
|
i18n::{tr_args, tr_strs, I18n, TR},
|
||||||
notes::{Note, NoteID},
|
notes::{Note, NoteID},
|
||||||
notetype::{NoteType, NoteTypeID},
|
notetype::{NoteType, NoteTypeID},
|
||||||
|
ops::Op,
|
||||||
revlog::RevlogID,
|
revlog::RevlogID,
|
||||||
timestamp::{TimestampMillis, TimestampSecs},
|
timestamp::{TimestampMillis, TimestampSecs},
|
||||||
types::Usn,
|
types::Usn,
|
||||||
undo::UndoableOpKind,
|
|
||||||
};
|
};
|
||||||
pub use slog::{debug, Logger};
|
pub use slog::{debug, Logger};
|
||||||
|
|
|
@ -241,9 +241,7 @@ impl Collection {
|
||||||
|
|
||||||
/// Answer card, writing its new state to the database.
|
/// Answer card, writing its new state to the database.
|
||||||
pub fn answer_card(&mut self, answer: &CardAnswer) -> Result<()> {
|
pub fn answer_card(&mut self, answer: &CardAnswer) -> Result<()> {
|
||||||
self.transact(Some(UndoableOpKind::AnswerCard), |col| {
|
self.transact(Some(Op::AnswerCard), |col| col.answer_card_inner(answer))
|
||||||
col.answer_card_inner(answer)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn answer_card_inner(&mut self, answer: &CardAnswer) -> Result<()> {
|
fn answer_card_inner(&mut self, answer: &CardAnswer) -> Result<()> {
|
||||||
|
|
|
@ -69,7 +69,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> {
|
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.storage.set_search_table_to_card_ids(cids, false)?;
|
||||||
col.unsuspend_or_unbury_searched_cards()
|
col.unsuspend_or_unbury_searched_cards()
|
||||||
})
|
})
|
||||||
|
@ -126,8 +126,8 @@ impl Collection {
|
||||||
mode: BuryOrSuspendMode,
|
mode: BuryOrSuspendMode,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let op = match mode {
|
let op = match mode {
|
||||||
BuryOrSuspendMode::Suspend => UndoableOpKind::Suspend,
|
BuryOrSuspendMode::Suspend => Op::Suspend,
|
||||||
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => UndoableOpKind::Bury,
|
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury,
|
||||||
};
|
};
|
||||||
self.transact(Some(op), |col| {
|
self.transact(Some(op), |col| {
|
||||||
col.storage.set_search_table_to_card_ids(cids, false)?;
|
col.storage.set_search_table_to_card_ids(cids, false)?;
|
||||||
|
|
|
@ -7,9 +7,9 @@ use crate::{
|
||||||
decks::DeckID,
|
decks::DeckID,
|
||||||
err::Result,
|
err::Result,
|
||||||
notes::NoteID,
|
notes::NoteID,
|
||||||
|
prelude::*,
|
||||||
search::SortMode,
|
search::SortMode,
|
||||||
types::Usn,
|
types::Usn,
|
||||||
undo::UndoableOpKind,
|
|
||||||
};
|
};
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -106,7 +106,7 @@ impl Collection {
|
||||||
pub fn reschedule_cards_as_new(&mut self, cids: &[CardID], log: bool) -> Result<()> {
|
pub fn reschedule_cards_as_new(&mut self, cids: &[CardID], log: bool) -> Result<()> {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
let mut position = self.get_next_card_position();
|
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)?;
|
col.storage.set_search_table_to_card_ids(cids, true)?;
|
||||||
let cards = col.storage.all_searched_cards_in_search_order()?;
|
let cards = col.storage.all_searched_cards_in_search_order()?;
|
||||||
for mut card in cards {
|
for mut card in cards {
|
||||||
|
|
|
@ -7,8 +7,7 @@ use crate::{
|
||||||
config::StringKey,
|
config::StringKey,
|
||||||
deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
|
deckconf::INITIAL_EASE_FACTOR_THOUSANDS,
|
||||||
err::Result,
|
err::Result,
|
||||||
prelude::AnkiError,
|
prelude::*,
|
||||||
undo::UndoableOpKind,
|
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand::distributions::{Distribution, Uniform};
|
use rand::distributions::{Distribution, Uniform};
|
||||||
|
@ -100,7 +99,7 @@ impl Collection {
|
||||||
let today = self.timing_today()?.days_elapsed;
|
let today = self.timing_today()?.days_elapsed;
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let distribution = Uniform::from(spec.min..=spec.max);
|
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)?;
|
col.storage.set_search_table_to_card_ids(cids, false)?;
|
||||||
for mut card in col.storage.all_searched_cards()? {
|
for mut card in col.storage.all_searched_cards()? {
|
||||||
let original = card.clone();
|
let original = card.clone();
|
||||||
|
|
|
@ -341,7 +341,7 @@ impl Collection {
|
||||||
tags: &[Regex],
|
tags: &[Regex],
|
||||||
mut repl: R,
|
mut repl: R,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
self.transact(Some(UndoableOpKind::UpdateTag), |col| {
|
self.transact(Some(Op::UpdateTag), |col| {
|
||||||
col.transform_notes(nids, |note, _nt| {
|
col.transform_notes(nids, |note, _nt| {
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
for re in tags {
|
for re in tags {
|
||||||
|
@ -392,7 +392,7 @@ impl Collection {
|
||||||
)
|
)
|
||||||
.map_err(|_| AnkiError::invalid_input("invalid regex"))?;
|
.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| {
|
col.transform_notes(nids, |note, _nt| {
|
||||||
let mut need_to_add = true;
|
let mut need_to_add = true;
|
||||||
let mut match_count = 0;
|
let mut match_count = 0;
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
mod changes;
|
mod changes;
|
||||||
mod ops;
|
|
||||||
|
|
||||||
|
pub use crate::ops::Op;
|
||||||
pub(crate) use changes::UndoableChange;
|
pub(crate) use changes::UndoableChange;
|
||||||
pub use ops::UndoableOpKind;
|
|
||||||
|
|
||||||
use crate::backend_proto as pb;
|
use crate::backend_proto as pb;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -15,7 +14,7 @@ const UNDO_LIMIT: usize = 30;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct UndoableOp {
|
pub(crate) struct UndoableOp {
|
||||||
pub kind: UndoableOpKind,
|
pub kind: Op,
|
||||||
pub timestamp: TimestampSecs,
|
pub timestamp: TimestampSecs,
|
||||||
pub changes: Vec<UndoableChange>,
|
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);
|
println!("begin: {:?}", op);
|
||||||
if op.is_none() {
|
if op.is_none() {
|
||||||
self.undo_steps.clear();
|
self.undo_steps.clear();
|
||||||
|
@ -88,11 +87,11 @@ impl UndoManager {
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_undo(&self) -> Option<UndoableOpKind> {
|
fn can_undo(&self) -> Option<Op> {
|
||||||
self.undo_steps.front().map(|s| s.kind)
|
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)
|
self.redo_steps.last().map(|s| s.kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +101,11 @@ impl UndoManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn can_undo(&self) -> Option<UndoableOpKind> {
|
pub fn can_undo(&self) -> Option<Op> {
|
||||||
self.state.undo.can_undo()
|
self.state.undo.can_undo()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_redo(&self) -> Option<UndoableOpKind> {
|
pub fn can_redo(&self) -> Option<Op> {
|
||||||
self.state.undo.can_redo()
|
self.state.undo.can_redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +155,7 @@ impl Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If op is None, clears the undo/redo queues.
|
/// 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);
|
self.state.undo.begin_step(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +218,7 @@ mod test {
|
||||||
|
|
||||||
// record a few undo steps
|
// record a few undo steps
|
||||||
for i in 3..=4 {
|
for i in 3..=4 {
|
||||||
col.transact(Some(UndoableOpKind::UpdateCard), |col| {
|
col.transact(Some(Op::UpdateCard), |col| {
|
||||||
col.get_and_update_card(cid, |card| {
|
col.get_and_update_card(cid, |card| {
|
||||||
card.interval = i;
|
card.interval = i;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -231,41 +230,41 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
|
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);
|
assert_eq!(col.can_redo(), None);
|
||||||
|
|
||||||
// undo a step
|
// undo a step
|
||||||
col.undo().unwrap();
|
col.undo().unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
||||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
|
||||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
|
||||||
|
|
||||||
// and again
|
// and again
|
||||||
col.undo().unwrap();
|
col.undo().unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 2);
|
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 2);
|
||||||
assert_eq!(col.can_undo(), None);
|
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
|
// redo a step
|
||||||
col.redo().unwrap();
|
col.redo().unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
||||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
|
||||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
|
||||||
|
|
||||||
// and another
|
// and another
|
||||||
col.redo().unwrap();
|
col.redo().unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
|
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);
|
assert_eq!(col.can_redo(), None);
|
||||||
|
|
||||||
// and undo the redo
|
// and undo the redo
|
||||||
col.undo().unwrap();
|
col.undo().unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
||||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_undo(), Some(Op::UpdateCard));
|
||||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
assert_eq!(col.can_redo(), Some(Op::UpdateCard));
|
||||||
|
|
||||||
// if any action is performed, it should clear the redo queue
|
// 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| {
|
col.get_and_update_card(cid, |card| {
|
||||||
card.interval = 5;
|
card.interval = 5;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -275,7 +274,7 @@ mod test {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 5);
|
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);
|
assert_eq!(col.can_redo(), None);
|
||||||
|
|
||||||
// and any action that doesn't support undoing will clear both queues
|
// and any action that doesn't support undoing will clear both queues
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue