dispatch undo operations via enum instead of trait
To coalesce successive note edits into a single undo op we'll need to be able to get the original Undoable type, which is awkward to do with a trait object.
This commit is contained in:
parent
d70e35e0a2
commit
ecea2161e3
@ -50,7 +50,7 @@ use crate::{
|
||||
text::{escape_anki_wildcards, extract_av_tags, sanitize_html_no_images, strip_av_tags, AVTag},
|
||||
timestamp::TimestampSecs,
|
||||
types::Usn,
|
||||
undo::UndoableOp,
|
||||
undo::UndoableOpKind,
|
||||
};
|
||||
use fluent::FluentValue;
|
||||
use futures::future::{AbortHandle, AbortRegistration, Abortable};
|
||||
@ -1049,7 +1049,7 @@ impl BackendService for Backend {
|
||||
let op = if input.skip_undo_entry {
|
||||
None
|
||||
} else {
|
||||
Some(UndoableOp::UpdateNote)
|
||||
Some(UndoableOpKind::UpdateNote)
|
||||
};
|
||||
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
|
||||
col.update_note_with_op(&mut note, op)
|
||||
|
@ -1,14 +1,13 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod undo;
|
||||
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,
|
||||
undo::Undo,
|
||||
};
|
||||
|
||||
use crate::{deckconf::DeckConf, decks::DeckID};
|
||||
|
@ -1,62 +1,37 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::Undo;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardAdded(Card);
|
||||
|
||||
impl Undo for CardAdded {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.remove_card_only(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardRemoved(Card);
|
||||
|
||||
impl Undo for CardRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.readd_deleted_card(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardGraveAdded(CardID, Usn);
|
||||
|
||||
impl Undo for CardGraveAdded {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.remove_card_grave(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardGraveRemoved(CardID, Usn);
|
||||
|
||||
impl Undo for CardGraveRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.add_card_grave_undoable(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardUpdated(Card);
|
||||
|
||||
impl Undo for CardUpdated {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
let current = col
|
||||
.storage
|
||||
.get_card(self.0.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("card disappeared"))?;
|
||||
col.update_card_undoable(&mut self.0.clone(), ¤t)
|
||||
}
|
||||
pub(crate) enum UndoableCardChange {
|
||||
Added(Box<Card>),
|
||||
Updated(Box<Card>),
|
||||
Removed(Box<Card>),
|
||||
GraveAdded(Box<(CardID, Usn)>),
|
||||
GraveRemoved(Box<(CardID, Usn)>),
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn undo_card_change(&mut self, change: UndoableCardChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableCardChange::Added(card) => self.remove_card_only(*card),
|
||||
UndoableCardChange::Updated(mut card) => {
|
||||
let current = self
|
||||
.storage
|
||||
.get_card(card.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("card disappeared"))?;
|
||||
self.update_card_undoable(&mut *card, ¤t)
|
||||
}
|
||||
UndoableCardChange::Removed(card) => self.restore_deleted_card(*card),
|
||||
UndoableCardChange::GraveAdded(e) => self.remove_card_grave(e.0, e.1),
|
||||
UndoableCardChange::GraveRemoved(e) => self.add_card_grave_undoable(e.0, e.1),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_card_undoable(&mut self, card: &mut Card) -> Result<(), AnkiError> {
|
||||
self.storage.add_card(card)?;
|
||||
self.save_undo(Box::new(CardAdded(card.clone())));
|
||||
self.save_undo(UndoableCardChange::Added(Box::new(card.clone())));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -64,7 +39,7 @@ impl Collection {
|
||||
if card.id.0 == 0 {
|
||||
return Err(AnkiError::invalid_input("card id not set"));
|
||||
}
|
||||
self.save_undo(Box::new(CardUpdated(original.clone())));
|
||||
self.save_undo(UndoableCardChange::Updated(Box::new(original.clone())));
|
||||
self.storage.update_card(card)
|
||||
}
|
||||
|
||||
@ -75,30 +50,29 @@ impl Collection {
|
||||
) -> Result<()> {
|
||||
self.add_card_grave_undoable(card.id, usn)?;
|
||||
self.storage.remove_card(card.id)?;
|
||||
self.save_undo(Box::new(CardRemoved(card)));
|
||||
|
||||
self.save_undo(UndoableCardChange::Removed(Box::new(card)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_card_grave_undoable(&mut self, cid: CardID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(Box::new(CardGraveAdded(cid, usn)));
|
||||
self.storage.add_card_grave(cid, usn)
|
||||
}
|
||||
|
||||
fn readd_deleted_card(&mut self, card: Card) -> Result<()> {
|
||||
fn restore_deleted_card(&mut self, card: Card) -> Result<()> {
|
||||
self.storage.add_or_update_card(&card)?;
|
||||
self.save_undo(Box::new(CardAdded(card)));
|
||||
self.save_undo(UndoableCardChange::Added(Box::new(card)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_card_only(&mut self, card: Card) -> Result<()> {
|
||||
self.storage.remove_card(card.id)?;
|
||||
self.save_undo(Box::new(CardRemoved(card)));
|
||||
self.save_undo(UndoableCardChange::Removed(Box::new(card)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_card_grave_undoable(&mut self, cid: CardID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(UndoableCardChange::GraveAdded(Box::new((cid, usn))));
|
||||
self.storage.add_card_grave(cid, usn)
|
||||
}
|
||||
|
||||
fn remove_card_grave(&mut self, cid: CardID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(Box::new(CardGraveRemoved(cid, usn)));
|
||||
self.save_undo(UndoableCardChange::GraveRemoved(Box::new((cid, usn))));
|
||||
self.storage.remove_card_grave(cid)
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,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<UndoableOp>, func: F) -> Result<R>
|
||||
pub(crate) fn transact<F, R>(&mut self, op: Option<UndoableOpKind>, func: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut Collection) -> Result<R>,
|
||||
{
|
||||
|
@ -4,7 +4,7 @@
|
||||
mod counts;
|
||||
mod schema11;
|
||||
mod tree;
|
||||
mod undo;
|
||||
pub(crate) mod undo;
|
||||
|
||||
pub use crate::backend_proto::{
|
||||
deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto,
|
||||
|
@ -2,22 +2,26 @@
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::undo::Undo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DeckUpdated(Deck);
|
||||
|
||||
impl Undo for DeckUpdated {
|
||||
fn undo(mut self: Box<Self>, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
let current = col
|
||||
.storage
|
||||
.get_deck(self.0.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("deck disappeared"))?;
|
||||
col.update_single_deck_undoable(&mut self.0, ¤t)
|
||||
}
|
||||
pub(crate) enum UndoableDeckChange {
|
||||
Updated(Box<Deck>),
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn undo_deck_change(&mut self, change: UndoableDeckChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableDeckChange::Updated(mut deck) => {
|
||||
let current = self
|
||||
.storage
|
||||
.get_deck(deck.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("deck disappeared"))?;
|
||||
self.update_single_deck_undoable(&mut *deck, ¤t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update an individual, existing deck. Caller is responsible for ensuring deck
|
||||
/// is normalized, matches parents, is not a duplicate name, and bumping mtime.
|
||||
/// Clears deck cache.
|
||||
@ -27,7 +31,7 @@ impl Collection {
|
||||
original: &Deck,
|
||||
) -> Result<()> {
|
||||
self.state.deck_cache.clear();
|
||||
self.save_undo(Box::new(DeckUpdated(original.clone())));
|
||||
self.save_undo(UndoableDeckChange::Updated(Box::new(original.clone())));
|
||||
self.storage.update_deck(deck)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod undo;
|
||||
pub(crate) mod undo;
|
||||
|
||||
use crate::backend_proto::note_is_duplicate_or_empty_out::State as DuplicateState;
|
||||
use crate::{
|
||||
@ -300,7 +300,7 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> {
|
||||
self.transact(Some(UndoableOp::AddNote), |col| {
|
||||
self.transact(Some(UndoableOpKind::AddNote), |col| {
|
||||
let nt = col
|
||||
.get_notetype(note.notetype_id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
|
||||
@ -325,13 +325,13 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub fn update_note(&mut self, note: &mut Note) -> Result<()> {
|
||||
self.update_note_with_op(note, Some(UndoableOp::UpdateNote))
|
||||
self.update_note_with_op(note, Some(UndoableOpKind::UpdateNote))
|
||||
}
|
||||
|
||||
pub(crate) fn update_note_with_op(
|
||||
&mut self,
|
||||
note: &mut Note,
|
||||
op: Option<UndoableOp>,
|
||||
op: Option<UndoableOpKind>,
|
||||
) -> Result<()> {
|
||||
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
||||
if !note_modified(&mut existing_note, note) {
|
||||
@ -388,7 +388,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(UndoableOp::RemoveNote), |col| {
|
||||
self.transact(Some(UndoableOpKind::RemoveNote), |col| {
|
||||
for nid in nids {
|
||||
let nid = *nid;
|
||||
if let Some(_existing_note) = col.storage.get_note(nid)? {
|
||||
|
@ -2,62 +2,37 @@
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::undo::Undo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteAdded(Note);
|
||||
|
||||
impl Undo for NoteAdded {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.remove_note_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteRemoved(Note);
|
||||
|
||||
impl Undo for NoteRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.add_note_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteGraveAdded(NoteID, Usn);
|
||||
|
||||
impl Undo for NoteGraveAdded {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.remove_note_grave_for_undo(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteGraveRemoved(NoteID, Usn);
|
||||
|
||||
impl Undo for NoteGraveRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.add_note_grave(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteUpdated(Note);
|
||||
|
||||
impl Undo for NoteUpdated {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
let current = col
|
||||
.storage
|
||||
.get_note(self.0.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("note disappeared"))?;
|
||||
col.update_note_undoable(&mut self.0.clone(), ¤t)
|
||||
}
|
||||
pub(crate) enum UndoableNoteChange {
|
||||
Added(Box<Note>),
|
||||
Updated(Box<Note>),
|
||||
Removed(Box<Note>),
|
||||
GraveAdded(Box<(NoteID, Usn)>),
|
||||
GraveRemoved(Box<(NoteID, Usn)>),
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn undo_note_change(&mut self, change: UndoableNoteChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableNoteChange::Added(note) => self.remove_note_without_grave(*note),
|
||||
UndoableNoteChange::Updated(mut note) => {
|
||||
let current = self
|
||||
.storage
|
||||
.get_note(note.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("note disappeared"))?;
|
||||
self.update_note_undoable(&mut *note, ¤t)
|
||||
}
|
||||
UndoableNoteChange::Removed(note) => self.restore_deleted_note(*note),
|
||||
UndoableNoteChange::GraveAdded(e) => self.remove_note_grave(e.0, e.1),
|
||||
UndoableNoteChange::GraveRemoved(e) => self.add_note_grave(e.0, e.1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves in the undo queue, and commits to DB.
|
||||
/// No validation, card generation or normalization is done.
|
||||
pub(super) fn update_note_undoable(&mut self, note: &mut Note, original: &Note) -> Result<()> {
|
||||
self.save_undo(Box::new(NoteUpdated(original.clone())));
|
||||
self.save_undo(UndoableNoteChange::Updated(Box::new(original.clone())));
|
||||
self.storage.update_note(note)?;
|
||||
|
||||
Ok(())
|
||||
@ -66,7 +41,7 @@ impl Collection {
|
||||
/// Remove a note. Cards must already have been deleted.
|
||||
pub(crate) fn remove_note_only_undoable(&mut self, nid: NoteID, usn: Usn) -> Result<()> {
|
||||
if let Some(note) = self.storage.get_note(nid)? {
|
||||
self.save_undo(Box::new(NoteRemoved(note)));
|
||||
self.save_undo(UndoableNoteChange::Removed(Box::new(note)));
|
||||
self.storage.remove_note(nid)?;
|
||||
self.add_note_grave(nid, usn)?;
|
||||
}
|
||||
@ -76,29 +51,30 @@ impl Collection {
|
||||
/// Add a note, not adding any cards.
|
||||
pub(super) fn add_note_only_undoable(&mut self, note: &mut Note) -> Result<(), AnkiError> {
|
||||
self.storage.add_note(note)?;
|
||||
self.save_undo(Box::new(NoteAdded(note.clone())));
|
||||
self.save_undo(UndoableNoteChange::Added(Box::new(note.clone())));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_note_without_grave(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.remove_note(note.id)?;
|
||||
self.save_undo(UndoableNoteChange::Removed(Box::new(note)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore_deleted_note(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.add_or_update_note(¬e)?;
|
||||
self.save_undo(UndoableNoteChange::Added(Box::new(note)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_note_grave(&mut self, nid: NoteID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(Box::new(NoteGraveAdded(nid, usn)));
|
||||
self.save_undo(UndoableNoteChange::GraveAdded(Box::new((nid, usn))));
|
||||
self.storage.add_note_grave(nid, usn)
|
||||
}
|
||||
|
||||
fn remove_note_grave_for_undo(&mut self, nid: NoteID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(Box::new(NoteGraveRemoved(nid, usn)));
|
||||
fn remove_note_grave(&mut self, nid: NoteID, usn: Usn) -> Result<()> {
|
||||
self.save_undo(UndoableNoteChange::GraveRemoved(Box::new((nid, usn))));
|
||||
self.storage.remove_note_grave(nid)
|
||||
}
|
||||
|
||||
fn remove_note_for_undo(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.remove_note(note.id)?;
|
||||
self.save_undo(Box::new(NoteRemoved(note)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_note_for_undo(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.add_or_update_note(¬e)?;
|
||||
self.save_undo(Box::new(NoteAdded(note)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,6 @@ pub use crate::{
|
||||
revlog::RevlogID,
|
||||
timestamp::{TimestampMillis, TimestampSecs},
|
||||
types::Usn,
|
||||
undo::UndoableOp,
|
||||
undo::UndoableOpKind,
|
||||
};
|
||||
pub use slog::{debug, Logger};
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod undo;
|
||||
pub(crate) mod undo;
|
||||
|
||||
use crate::serde::{default_on_invalid, deserialize_int_from_number};
|
||||
use crate::{define_newtype, prelude::*};
|
||||
|
@ -2,35 +2,35 @@
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::RevlogEntry;
|
||||
use crate::{prelude::*, undo::Undo};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RevlogAdded(RevlogEntry);
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RevlogRemoved(RevlogEntry);
|
||||
|
||||
impl Undo for RevlogAdded {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.storage.remove_revlog_entry(self.0.id)?;
|
||||
col.save_undo(Box::new(RevlogRemoved(self.0)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Undo for RevlogRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.storage.add_revlog_entry(&self.0, false)?;
|
||||
col.save_undo(Box::new(RevlogAdded(self.0)));
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) enum UndoableRevlogChange {
|
||||
Added(Box<RevlogEntry>),
|
||||
Removed(Box<RevlogEntry>),
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn undo_revlog_change(&mut self, change: UndoableRevlogChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableRevlogChange::Added(revlog) => {
|
||||
self.storage.remove_revlog_entry(revlog.id)?;
|
||||
self.save_undo(UndoableRevlogChange::Removed(revlog));
|
||||
Ok(())
|
||||
}
|
||||
UndoableRevlogChange::Removed(revlog) => {
|
||||
self.storage.add_revlog_entry(&revlog, false)?;
|
||||
self.save_undo(UndoableRevlogChange::Added(revlog));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the provided revlog entry, modifying the ID if it is not unique.
|
||||
pub(crate) fn add_revlog_entry_undoable(&mut self, mut entry: RevlogEntry) -> Result<RevlogID> {
|
||||
entry.id = self.storage.add_revlog_entry(&entry, true)?;
|
||||
let id = entry.id;
|
||||
self.save_undo(Box::new(RevlogAdded(entry)));
|
||||
self.save_undo(UndoableRevlogChange::Added(Box::new(entry)));
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +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(UndoableOp::AnswerCard), |col| {
|
||||
self.transact(Some(UndoableOpKind::AnswerCard), |col| {
|
||||
col.answer_card_inner(answer)
|
||||
})
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> {
|
||||
self.transact(Some(UndoableOp::UnburyUnsuspend), |col| {
|
||||
self.transact(Some(UndoableOpKind::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 => UndoableOp::Suspend,
|
||||
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => UndoableOp::Bury,
|
||||
BuryOrSuspendMode::Suspend => UndoableOpKind::Suspend,
|
||||
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => UndoableOpKind::Bury,
|
||||
};
|
||||
self.transact(Some(op), |col| {
|
||||
col.storage.set_search_table_to_card_ids(cids, false)?;
|
||||
|
@ -6,7 +6,7 @@ mod entry;
|
||||
mod learning;
|
||||
mod limits;
|
||||
mod main;
|
||||
mod undo;
|
||||
pub(crate) mod undo;
|
||||
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
@ -21,7 +21,7 @@ pub(crate) use {
|
||||
main::{MainQueueEntry, MainQueueEntryKind},
|
||||
};
|
||||
|
||||
use self::undo::QueueUpdateAfterAnsweringCard;
|
||||
use self::undo::QueueUpdate;
|
||||
|
||||
use super::{states::NextCardStates, timing::SchedTimingToday};
|
||||
|
||||
@ -100,14 +100,14 @@ impl CardQueues {
|
||||
&mut self,
|
||||
card: &Card,
|
||||
timing: SchedTimingToday,
|
||||
) -> Result<QueueUpdateAfterAnsweringCard> {
|
||||
) -> Result<Box<QueueUpdate>> {
|
||||
let entry = self.pop_answered(card.id)?;
|
||||
let requeued_learning = self.maybe_requeue_learning_card(card, timing);
|
||||
|
||||
Ok(QueueUpdateAfterAnsweringCard {
|
||||
Ok(Box::new(QueueUpdate {
|
||||
entry,
|
||||
learning_requeue: requeued_learning,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ impl Collection {
|
||||
) -> Result<()> {
|
||||
if let Some(queues) = &mut self.state.card_queues {
|
||||
let mutation = queues.update_after_answering_card(card, timing)?;
|
||||
self.save_undo(Box::new(mutation));
|
||||
self.save_queue_update_undo(mutation);
|
||||
Ok(())
|
||||
} else {
|
||||
// we currenly allow the queues to be empty for unit tests
|
||||
|
@ -2,48 +2,47 @@
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::{CardQueues, LearningQueueEntry, QueueEntry, QueueEntryKind};
|
||||
use crate::{prelude::*, undo::Undo};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct QueueUpdateAfterAnsweringCard {
|
||||
pub(crate) enum UndoableQueueChange {
|
||||
CardAnswered(Box<QueueUpdate>),
|
||||
CardAnswerUndone(Box<QueueUpdate>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct QueueUpdate {
|
||||
pub entry: QueueEntry,
|
||||
pub learning_requeue: Option<LearningQueueEntry>,
|
||||
}
|
||||
|
||||
impl Undo for QueueUpdateAfterAnsweringCard {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
let queues = col.get_queues()?;
|
||||
if let Some(learning) = self.learning_requeue {
|
||||
queues.remove_requeued_learning_card_after_undo(learning.id);
|
||||
impl Collection {
|
||||
pub(crate) fn undo_queue_change(&mut self, change: UndoableQueueChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableQueueChange::CardAnswered(update) => {
|
||||
let queues = self.get_queues()?;
|
||||
if let Some(learning) = &update.learning_requeue {
|
||||
queues.remove_requeued_learning_card_after_undo(learning.id);
|
||||
}
|
||||
queues.push_undo_entry(update.entry);
|
||||
self.save_undo(UndoableQueueChange::CardAnswerUndone(update));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
UndoableQueueChange::CardAnswerUndone(update) => {
|
||||
// don't try to update existing queue when redoing; just
|
||||
// rebuild it instead
|
||||
self.clear_study_queues();
|
||||
// but preserve undo state for a subsequent undo
|
||||
self.save_undo(UndoableQueueChange::CardAnswered(update));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
queues.push_undo_entry(self.entry);
|
||||
col.save_undo(Box::new(QueueUpdateAfterUndoingAnswer {
|
||||
entry: self.entry,
|
||||
learning_requeue: self.learning_requeue,
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct QueueUpdateAfterUndoingAnswer {
|
||||
pub entry: QueueEntry,
|
||||
pub learning_requeue: Option<LearningQueueEntry>,
|
||||
}
|
||||
|
||||
impl Undo for QueueUpdateAfterUndoingAnswer {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
// don't try to update existing queue when redoing; just
|
||||
// rebuild it instead
|
||||
col.clear_study_queues();
|
||||
// but preserve undo state for a subsequent undo
|
||||
col.save_undo(Box::new(QueueUpdateAfterAnsweringCard {
|
||||
entry: self.entry,
|
||||
learning_requeue: self.learning_requeue,
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
pub(super) fn save_queue_update_undo(&mut self, change: Box<QueueUpdate>) {
|
||||
self.save_undo(UndoableQueueChange::CardAnswered(change))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod undo;
|
||||
pub(crate) mod undo;
|
||||
|
||||
use crate::{
|
||||
backend_proto::TagTreeNode,
|
||||
@ -310,7 +310,7 @@ impl Collection {
|
||||
tags: &[Regex],
|
||||
mut repl: R,
|
||||
) -> Result<usize> {
|
||||
self.transact(Some(UndoableOp::UpdateTag), |col| {
|
||||
self.transact(Some(UndoableOpKind::UpdateTag), |col| {
|
||||
col.transform_notes(nids, |note, _nt| {
|
||||
let mut changed = false;
|
||||
for re in tags {
|
||||
@ -361,7 +361,7 @@ impl Collection {
|
||||
)
|
||||
.map_err(|_| AnkiError::invalid_input("invalid regex"))?;
|
||||
|
||||
self.transact(Some(UndoableOp::UpdateTag), |col| {
|
||||
self.transact(Some(UndoableOpKind::UpdateTag), |col| {
|
||||
col.transform_notes(nids, |note, _nt| {
|
||||
let mut need_to_add = true;
|
||||
let mut match_count = 0;
|
||||
|
@ -2,36 +2,30 @@
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::Tag;
|
||||
use crate::{prelude::*, undo::Undo};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AddedTag(Tag);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RemovedTag(Tag);
|
||||
|
||||
impl Undo for AddedTag {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.remove_single_tag_undoable(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Undo for RemovedTag {
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()> {
|
||||
col.register_tag_undoable(&self.0)
|
||||
}
|
||||
pub(crate) enum UndoableTagChange {
|
||||
Added(Box<Tag>),
|
||||
Removed(Box<Tag>),
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub(crate) fn undo_tag_change(&mut self, change: UndoableTagChange) -> Result<()> {
|
||||
match change {
|
||||
UndoableTagChange::Added(tag) => self.remove_single_tag_undoable(&tag),
|
||||
UndoableTagChange::Removed(tag) => self.register_tag_undoable(&tag),
|
||||
}
|
||||
}
|
||||
/// Adds an already-validated tag to the DB and undo list.
|
||||
/// Caller is responsible for setting usn.
|
||||
pub(super) fn register_tag_undoable(&mut self, tag: &Tag) -> Result<()> {
|
||||
self.save_undo(Box::new(AddedTag(tag.clone())));
|
||||
self.save_undo(UndoableTagChange::Added(Box::new(tag.clone())));
|
||||
self.storage.register_tag(&tag)
|
||||
}
|
||||
|
||||
fn remove_single_tag_undoable(&mut self, tag: &Tag) -> Result<()> {
|
||||
self.save_undo(Box::new(RemovedTag(tag.clone())));
|
||||
self.save_undo(UndoableTagChange::Removed(Box::new(tag.clone())));
|
||||
self.storage.remove_single_tag(&tag.name)
|
||||
}
|
||||
}
|
||||
|
67
rslib/src/undo/changes.rs
Normal file
67
rslib/src/undo/changes.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::{
|
||||
card::undo::UndoableCardChange, decks::undo::UndoableDeckChange,
|
||||
notes::undo::UndoableNoteChange, prelude::*, revlog::undo::UndoableRevlogChange,
|
||||
scheduler::queue::undo::UndoableQueueChange, tags::undo::UndoableTagChange,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum UndoableChange {
|
||||
Card(UndoableCardChange),
|
||||
Note(UndoableNoteChange),
|
||||
Deck(UndoableDeckChange),
|
||||
Tag(UndoableTagChange),
|
||||
Revlog(UndoableRevlogChange),
|
||||
Queue(UndoableQueueChange),
|
||||
}
|
||||
|
||||
impl UndoableChange {
|
||||
pub(super) fn undo(self, col: &mut Collection) -> Result<()> {
|
||||
match self {
|
||||
UndoableChange::Card(c) => col.undo_card_change(c),
|
||||
UndoableChange::Note(c) => col.undo_note_change(c),
|
||||
UndoableChange::Deck(c) => col.undo_deck_change(c),
|
||||
UndoableChange::Tag(c) => col.undo_tag_change(c),
|
||||
UndoableChange::Revlog(c) => col.undo_revlog_change(c),
|
||||
UndoableChange::Queue(c) => col.undo_queue_change(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableCardChange> for UndoableChange {
|
||||
fn from(c: UndoableCardChange) -> Self {
|
||||
UndoableChange::Card(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableNoteChange> for UndoableChange {
|
||||
fn from(c: UndoableNoteChange) -> Self {
|
||||
UndoableChange::Note(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableDeckChange> for UndoableChange {
|
||||
fn from(c: UndoableDeckChange) -> Self {
|
||||
UndoableChange::Deck(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableTagChange> for UndoableChange {
|
||||
fn from(c: UndoableTagChange) -> Self {
|
||||
UndoableChange::Tag(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableRevlogChange> for UndoableChange {
|
||||
fn from(c: UndoableRevlogChange) -> Self {
|
||||
UndoableChange::Revlog(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UndoableQueueChange> for UndoableChange {
|
||||
fn from(c: UndoableQueueChange) -> Self {
|
||||
UndoableChange::Queue(c)
|
||||
}
|
||||
}
|
@ -1,57 +1,22 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
mod changes;
|
||||
mod ops;
|
||||
|
||||
pub(crate) use changes::UndoableChange;
|
||||
pub use ops::UndoableOpKind;
|
||||
|
||||
use crate::backend_proto as pb;
|
||||
use crate::{collection::Collection, err::Result, prelude::*};
|
||||
use std::{collections::VecDeque, fmt};
|
||||
use crate::prelude::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
const UNDO_LIMIT: usize = 30;
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum UndoableOp {
|
||||
UpdateCard,
|
||||
AnswerCard,
|
||||
Bury,
|
||||
Suspend,
|
||||
UnburyUnsuspend,
|
||||
AddNote,
|
||||
RemoveNote,
|
||||
UpdateTag,
|
||||
UpdateNote,
|
||||
}
|
||||
|
||||
impl UndoableOp {
|
||||
pub(crate) fn needs_study_queue_reset(self) -> bool {
|
||||
self != UndoableOp::AnswerCard
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn describe_collection_op(&self, op: UndoableOp) -> String {
|
||||
let key = match op {
|
||||
UndoableOp::UpdateCard => todo!(),
|
||||
UndoableOp::AnswerCard => TR::UndoAnswerCard,
|
||||
UndoableOp::Bury => TR::StudyingBury,
|
||||
UndoableOp::Suspend => TR::StudyingSuspend,
|
||||
UndoableOp::UnburyUnsuspend => TR::UndoUnburyUnsuspend,
|
||||
UndoableOp::AddNote => TR::UndoAddNote,
|
||||
UndoableOp::RemoveNote => TR::StudyingDeleteNote,
|
||||
UndoableOp::UpdateTag => TR::UndoUpdateTag,
|
||||
UndoableOp::UpdateNote => TR::UndoUpdateNote,
|
||||
};
|
||||
|
||||
self.i18n.tr(key).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Undo: fmt::Debug + Send {
|
||||
/// Undo the recorded action.
|
||||
fn undo(self: Box<Self>, col: &mut Collection) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UndoStep {
|
||||
kind: UndoableOp,
|
||||
changes: Vec<Box<dyn Undo>>,
|
||||
struct UndoableOp {
|
||||
kind: UndoableOpKind,
|
||||
changes: Vec<UndoableChange>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -71,21 +36,21 @@ impl Default for UndoMode {
|
||||
pub(crate) struct UndoManager {
|
||||
// undo steps are added to the front of a double-ended queue, so we can
|
||||
// efficiently cap the number of steps we retain in memory
|
||||
undo_steps: VecDeque<UndoStep>,
|
||||
undo_steps: VecDeque<UndoableOp>,
|
||||
// redo steps are added to the end
|
||||
redo_steps: Vec<UndoStep>,
|
||||
redo_steps: Vec<UndoableOp>,
|
||||
mode: UndoMode,
|
||||
current_step: Option<UndoStep>,
|
||||
current_step: Option<UndoableOp>,
|
||||
}
|
||||
|
||||
impl UndoManager {
|
||||
fn save(&mut self, item: Box<dyn Undo>) {
|
||||
fn save(&mut self, item: UndoableChange) {
|
||||
if let Some(step) = self.current_step.as_mut() {
|
||||
step.changes.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_step(&mut self, op: Option<UndoableOp>) {
|
||||
fn begin_step(&mut self, op: Option<UndoableOpKind>) {
|
||||
println!("begin: {:?}", op);
|
||||
if op.is_none() {
|
||||
self.undo_steps.clear();
|
||||
@ -94,7 +59,7 @@ impl UndoManager {
|
||||
// a normal op clears the redo queue
|
||||
self.redo_steps.clear();
|
||||
}
|
||||
self.current_step = op.map(|op| UndoStep {
|
||||
self.current_step = op.map(|op| UndoableOp {
|
||||
kind: op,
|
||||
changes: vec![],
|
||||
});
|
||||
@ -119,21 +84,21 @@ impl UndoManager {
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn can_undo(&self) -> Option<UndoableOp> {
|
||||
fn can_undo(&self) -> Option<UndoableOpKind> {
|
||||
self.undo_steps.front().map(|s| s.kind)
|
||||
}
|
||||
|
||||
fn can_redo(&self) -> Option<UndoableOp> {
|
||||
fn can_redo(&self) -> Option<UndoableOpKind> {
|
||||
self.redo_steps.last().map(|s| s.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn can_undo(&self) -> Option<UndoableOp> {
|
||||
pub fn can_undo(&self) -> Option<UndoableOpKind> {
|
||||
self.state.undo.can_undo()
|
||||
}
|
||||
|
||||
pub fn can_redo(&self) -> Option<UndoableOp> {
|
||||
pub fn can_redo(&self) -> Option<UndoableOpKind> {
|
||||
self.state.undo.can_redo()
|
||||
}
|
||||
|
||||
@ -173,17 +138,17 @@ impl Collection {
|
||||
pb::UndoStatus {
|
||||
undo: self
|
||||
.can_undo()
|
||||
.map(|op| self.describe_collection_op(op))
|
||||
.map(|op| self.describe_op_kind(op))
|
||||
.unwrap_or_default(),
|
||||
redo: self
|
||||
.can_redo()
|
||||
.map(|op| self.describe_collection_op(op))
|
||||
.map(|op| self.describe_op_kind(op))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If op is None, clears the undo/redo queues.
|
||||
pub(crate) fn begin_undoable_operation(&mut self, op: Option<UndoableOp>) {
|
||||
pub(crate) fn begin_undoable_operation(&mut self, op: Option<UndoableOpKind>) {
|
||||
self.state.undo.begin_step(op);
|
||||
}
|
||||
|
||||
@ -202,8 +167,8 @@ impl Collection {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_undo(&mut self, item: Box<dyn Undo>) {
|
||||
self.state.undo.save(item)
|
||||
pub(crate) fn save_undo(&mut self, item: impl Into<UndoableChange>) {
|
||||
self.state.undo.save(item.into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +202,7 @@ mod test {
|
||||
|
||||
// record a few undo steps
|
||||
for i in 3..=4 {
|
||||
col.transact(Some(UndoableOp::UpdateCard), |col| {
|
||||
col.transact(Some(UndoableOpKind::UpdateCard), |col| {
|
||||
col.get_and_update_card(cid, |card| {
|
||||
card.interval = i;
|
||||
Ok(())
|
||||
@ -249,41 +214,41 @@ mod test {
|
||||
}
|
||||
|
||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
|
||||
assert_eq!(col.can_undo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::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(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::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(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
||||
|
||||
// redo a step
|
||||
col.redo().unwrap();
|
||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3);
|
||||
assert_eq!(col.can_undo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
||||
|
||||
// and another
|
||||
col.redo().unwrap();
|
||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4);
|
||||
assert_eq!(col.can_undo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::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(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
||||
assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard));
|
||||
|
||||
// if any action is performed, it should clear the redo queue
|
||||
col.transact(Some(UndoableOp::UpdateCard), |col| {
|
||||
col.transact(Some(UndoableOpKind::UpdateCard), |col| {
|
||||
col.get_and_update_card(cid, |card| {
|
||||
card.interval = 5;
|
||||
Ok(())
|
||||
@ -293,7 +258,7 @@ mod test {
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 5);
|
||||
assert_eq!(col.can_undo(), Some(UndoableOp::UpdateCard));
|
||||
assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard));
|
||||
assert_eq!(col.can_redo(), None);
|
||||
|
||||
// and any action that doesn't support undoing will clear both queues
|
||||
|
41
rslib/src/undo/ops.rs
Normal file
41
rslib/src/undo/ops.rs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 {
|
||||
UpdateCard,
|
||||
AnswerCard,
|
||||
Bury,
|
||||
Suspend,
|
||||
UnburyUnsuspend,
|
||||
AddNote,
|
||||
RemoveNote,
|
||||
UpdateTag,
|
||||
UpdateNote,
|
||||
}
|
||||
|
||||
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::UpdateCard => todo!(),
|
||||
UndoableOpKind::AnswerCard => TR::UndoAnswerCard,
|
||||
UndoableOpKind::Bury => TR::StudyingBury,
|
||||
UndoableOpKind::Suspend => TR::StudyingSuspend,
|
||||
UndoableOpKind::UnburyUnsuspend => TR::UndoUnburyUnsuspend,
|
||||
UndoableOpKind::AddNote => TR::UndoAddNote,
|
||||
UndoableOpKind::RemoveNote => TR::StudyingDeleteNote,
|
||||
UndoableOpKind::UpdateTag => TR::UndoUpdateTag,
|
||||
UndoableOpKind::UpdateNote => TR::UndoUpdateNote,
|
||||
};
|
||||
|
||||
self.i18n.tr(key).to_string()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user