add LIFO sorting options for new cards

This commit is contained in:
Damien Elmes 2021-06-08 14:01:46 +10:00
parent afaaa763ec
commit 410660990e
7 changed files with 67 additions and 34 deletions

View file

@ -97,18 +97,21 @@ deck-config-new-gather-priority-tooltip =
to prioritize subdecks that are closer to the top. to prioritize subdecks that are closer to the top.
`Position`: gathers cards from all decks before they are sorted. This `Position`: gathers cards from all decks before they are sorted. This
ensures the oldest cards will be shown first, even if the parent limit is ensures cards appear in strict position order, even if the parent limit is
not high enough to see cards from all decks. not high enough to see cards from all decks.
deck-config-new-gather-priority-deck = Deck deck-config-new-gather-priority-deck = Deck
deck-config-new-gather-priority-position = Position deck-config-new-gather-priority-position-lowest-first = Position (lowest first)
deck-config-new-gather-priority-position-highest-first = Position (highest first)
deck-config-new-card-sort-order = New card sort order deck-config-new-card-sort-order = New card sort order
deck-config-new-card-sort-order-tooltip = deck-config-new-card-sort-order-tooltip =
How cards are sorted after they have been gathered. By default, Anki sorts How cards are sorted after they have been gathered. By default, Anki sorts
by card template first, to avoid multiple cards of the same note from being by card template first, to avoid multiple cards of the same note from being
shown in succession. shown in succession.
deck-config-sort-order-card-template-then-position = Card template, then position deck-config-sort-order-card-template-then-lowest-position = Card template, then lowest position
deck-config-sort-order-card-template-then-highest-position = Card template, then highest position
deck-config-sort-order-card-template-then-random = Card template, then random deck-config-sort-order-card-template-then-random = Card template, then random
deck-config-sort-order-position = Position (siblings together) deck-config-sort-order-lowest-position = Lowest position
deck-config-sort-order-highest-position = Highest position
deck-config-sort-order-random = Random deck-config-sort-order-random = Random
deck-config-new-review-priority = New/review priority deck-config-new-review-priority = New/review priority
deck-config-new-review-priority-tooltip = When to show new cards in relation to review cards. deck-config-new-review-priority-tooltip = When to show new cards in relation to review cards.

View file

@ -320,13 +320,16 @@ message DeckConfig {
} }
enum NewCardGatherPriority { enum NewCardGatherPriority {
NEW_CARD_GATHER_PRIORITY_DECK = 0; NEW_CARD_GATHER_PRIORITY_DECK = 0;
NEW_CARD_GATHER_PRIORITY_POSITION = 1; NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1;
NEW_CARD_GATHER_PRIORITY_HIGHEST_POSITION = 2;
} }
enum NewCardSortOrder { enum NewCardSortOrder {
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_DUE = 0; NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 1; NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1;
NEW_CARD_SORT_ORDER_DUE = 2; NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2;
NEW_CARD_SORT_ORDER_RANDOM = 3; NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3;
NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4;
NEW_CARD_SORT_ORDER_RANDOM = 5;
} }
enum ReviewCardOrder { enum ReviewCardOrder {
REVIEW_CARD_ORDER_DAY = 0; REVIEW_CARD_ORDER_DAY = 0;

View file

@ -63,7 +63,7 @@ impl Default for DeckConfig {
graduating_interval_easy: 4, graduating_interval_easy: 4,
new_card_insert_order: NewCardInsertOrder::Due as i32, new_card_insert_order: NewCardInsertOrder::Due as i32,
new_card_gather_priority: NewCardGatherPriority::Deck as i32, new_card_gather_priority: NewCardGatherPriority::Deck as i32,
new_card_sort_order: NewCardSortOrder::TemplateThenDue as i32, new_card_sort_order: NewCardSortOrder::TemplateThenLowestPosition as i32,
review_order: ReviewCardOrder::Day as i32, review_order: ReviewCardOrder::Day as i32,
new_mix: ReviewMix::MixWithReviews as i32, new_mix: ReviewMix::MixWithReviews as i32,
interday_learning_mix: ReviewMix::MixWithReviews as i32, interday_learning_mix: ReviewMix::MixWithReviews as i32,

View file

@ -209,7 +209,7 @@ impl Collection {
.unwrap_or_else(|| { .unwrap_or_else(|| {
// filtered decks do not space siblings // filtered decks do not space siblings
QueueSortOptions { QueueSortOptions {
new_order: NewCardSortOrder::Due, new_order: NewCardSortOrder::LowestPosition,
..Default::default() ..Default::default()
} }
}); });
@ -272,25 +272,28 @@ impl Collection {
} }
selected_deck_limits.new = selected_deck_limits.new.min(selected_deck_limits.review); selected_deck_limits.new = selected_deck_limits.new.min(selected_deck_limits.review);
let can_exit_early = sort_options.new_gather_priority == NewCardGatherPriority::Deck; let can_exit_early = sort_options.new_gather_priority == NewCardGatherPriority::Deck;
let reverse = sort_options.new_gather_priority == NewCardGatherPriority::HighestPosition;
for deck in &decks { for deck in &decks {
if can_exit_early && selected_deck_limits.new == 0 { if can_exit_early && selected_deck_limits.new == 0 {
break; break;
} }
let limit = remaining.get_mut(&deck.id).unwrap(); let limit = remaining.get_mut(&deck.id).unwrap();
if limit.new > 0 { if limit.new > 0 {
self.storage.for_each_new_card_in_deck(deck.id, |card| { self.storage
let bury = get_bury_mode(card.original_deck_id.or(deck.id)); .for_each_new_card_in_deck(deck.id, reverse, |card| {
if limit.new != 0 { let bury = get_bury_mode(card.original_deck_id.or(deck.id));
if queues.add_new_card(card, bury) { if limit.new != 0 {
limit.new -= 1; if queues.add_new_card(card, bury) {
selected_deck_limits.new = selected_deck_limits.new.saturating_sub(1); limit.new -= 1;
} selected_deck_limits.new =
selected_deck_limits.new.saturating_sub(1);
}
true true
} else { } else {
false false
} }
})?; })?;
} }
} }

View file

@ -10,14 +10,18 @@ use super::{NewCard, NewCardSortOrder, QueueBuilder};
impl QueueBuilder { impl QueueBuilder {
pub(super) fn sort_new(&mut self) { pub(super) fn sort_new(&mut self) {
match self.sort_options.new_order { match self.sort_options.new_order {
NewCardSortOrder::TemplateThenDue => { NewCardSortOrder::TemplateThenLowestPosition => {
self.new.sort_unstable_by(template_then_due); self.new.sort_unstable_by(template_then_lowest_position);
}
NewCardSortOrder::TemplateThenHighestPosition => {
self.new.sort_unstable_by(template_then_highest_position);
} }
NewCardSortOrder::TemplateThenRandom => { NewCardSortOrder::TemplateThenRandom => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime); self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
self.new.sort_unstable_by(template_then_random); self.new.sort_unstable_by(template_then_random);
} }
NewCardSortOrder::Due => self.new.sort_unstable_by(new_position), NewCardSortOrder::LowestPosition => self.new.sort_unstable_by(lowest_position),
NewCardSortOrder::HighestPosition => self.new.sort_unstable_by(highest_position),
NewCardSortOrder::Random => { NewCardSortOrder::Random => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime); self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
self.new.sort_unstable_by(new_hash) self.new.sort_unstable_by(new_hash)
@ -26,18 +30,26 @@ impl QueueBuilder {
} }
} }
fn template_then_due(a: &NewCard, b: &NewCard) -> Ordering { fn template_then_lowest_position(a: &NewCard, b: &NewCard) -> Ordering {
(a.template_index, a.due).cmp(&(b.template_index, b.due)) (a.template_index, a.due).cmp(&(b.template_index, b.due))
} }
fn template_then_highest_position(a: &NewCard, b: &NewCard) -> Ordering {
(a.template_index, b.due).cmp(&(b.template_index, a.due))
}
fn template_then_random(a: &NewCard, b: &NewCard) -> Ordering { fn template_then_random(a: &NewCard, b: &NewCard) -> Ordering {
(a.template_index, a.hash).cmp(&(b.template_index, b.hash)) (a.template_index, a.hash).cmp(&(b.template_index, b.hash))
} }
fn new_position(a: &NewCard, b: &NewCard) -> Ordering { fn lowest_position(a: &NewCard, b: &NewCard) -> Ordering {
a.due.cmp(&b.due) a.due.cmp(&b.due)
} }
fn highest_position(a: &NewCard, b: &NewCard) -> Ordering {
b.due.cmp(&a.due)
}
fn new_hash(a: &NewCard, b: &NewCard) -> Ordering { fn new_hash(a: &NewCard, b: &NewCard) -> Ordering {
a.hash.cmp(&b.hash) a.hash.cmp(&b.hash)
} }

View file

@ -229,12 +229,21 @@ impl super::SqliteStorage {
} }
/// Call func() for each new card, stopping when it returns false /// Call func() for each new card, stopping when it returns false
/// or no more cards found. Cards will arrive in (deck_id, due) order. /// or no more cards found.
pub(crate) fn for_each_new_card_in_deck<F>(&self, deck: DeckId, mut func: F) -> Result<()> pub(crate) fn for_each_new_card_in_deck<F>(
&self,
deck: DeckId,
reverse: bool,
mut func: F,
) -> Result<()>
where where
F: FnMut(NewCard) -> bool, F: FnMut(NewCard) -> bool,
{ {
let mut stmt = self.db.prepare_cached(include_str!("new_cards.sql"))?; let mut stmt = self.db.prepare_cached(&format!(
"{}{}",
include_str!("new_cards.sql"),
if reverse { " order by due desc" } else { "" }
))?;
let mut rows = stmt.query(params![deck])?; let mut rows = stmt.query(params![deck])?;
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
if !func(NewCard { if !func(NewCard {

View file

@ -15,12 +15,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const newGatherPriorityChoices = [ const newGatherPriorityChoices = [
tr.deckConfigNewGatherPriorityDeck(), tr.deckConfigNewGatherPriorityDeck(),
tr.deckConfigNewGatherPriorityPosition(), tr.deckConfigNewGatherPriorityPositionLowestFirst(),
tr.deckConfigNewGatherPriorityPositionHighestFirst(),
]; ];
const newSortOrderChoices = [ const newSortOrderChoices = [
tr.deckConfigSortOrderCardTemplateThenPosition(), tr.deckConfigSortOrderCardTemplateThenLowestPosition(),
tr.deckConfigSortOrderCardTemplateThenHighestPosition(),
tr.deckConfigSortOrderCardTemplateThenRandom(), tr.deckConfigSortOrderCardTemplateThenRandom(),
tr.deckConfigSortOrderPosition(), tr.deckConfigSortOrderLowestPosition(),
tr.deckConfigSortOrderHighestPosition(),
tr.deckConfigSortOrderRandom(), tr.deckConfigSortOrderRandom(),
]; ];
const reviewOrderChoices = [ const reviewOrderChoices = [