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.
`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.
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-tooltip =
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
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-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-new-review-priority = New/review priority
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 {
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 {
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_DUE = 0;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 1;
NEW_CARD_SORT_ORDER_DUE = 2;
NEW_CARD_SORT_ORDER_RANDOM = 3;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1;
NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2;
NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3;
NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4;
NEW_CARD_SORT_ORDER_RANDOM = 5;
}
enum ReviewCardOrder {
REVIEW_CARD_ORDER_DAY = 0;

View File

@ -63,7 +63,7 @@ impl Default for DeckConfig {
graduating_interval_easy: 4,
new_card_insert_order: NewCardInsertOrder::Due 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,
new_mix: ReviewMix::MixWithReviews as i32,
interday_learning_mix: ReviewMix::MixWithReviews as i32,

View File

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

View File

@ -10,14 +10,18 @@ use super::{NewCard, NewCardSortOrder, QueueBuilder};
impl QueueBuilder {
pub(super) fn sort_new(&mut self) {
match self.sort_options.new_order {
NewCardSortOrder::TemplateThenDue => {
self.new.sort_unstable_by(template_then_due);
NewCardSortOrder::TemplateThenLowestPosition => {
self.new.sort_unstable_by(template_then_lowest_position);
}
NewCardSortOrder::TemplateThenHighestPosition => {
self.new.sort_unstable_by(template_then_highest_position);
}
NewCardSortOrder::TemplateThenRandom => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
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 => {
self.new.iter_mut().for_each(NewCard::hash_id_and_mtime);
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))
}
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 {
(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)
}
fn highest_position(a: &NewCard, b: &NewCard) -> Ordering {
b.due.cmp(&a.due)
}
fn new_hash(a: &NewCard, b: &NewCard) -> Ordering {
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
/// or no more cards found. Cards will arrive in (deck_id, due) order.
pub(crate) fn for_each_new_card_in_deck<F>(&self, deck: DeckId, mut func: F) -> Result<()>
/// or no more cards found.
pub(crate) fn for_each_new_card_in_deck<F>(
&self,
deck: DeckId,
reverse: bool,
mut func: F,
) -> Result<()>
where
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])?;
while let Some(row) = rows.next()? {
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 = [
tr.deckConfigNewGatherPriorityDeck(),
tr.deckConfigNewGatherPriorityPosition(),
tr.deckConfigNewGatherPriorityPositionLowestFirst(),
tr.deckConfigNewGatherPriorityPositionHighestFirst(),
];
const newSortOrderChoices = [
tr.deckConfigSortOrderCardTemplateThenPosition(),
tr.deckConfigSortOrderCardTemplateThenLowestPosition(),
tr.deckConfigSortOrderCardTemplateThenHighestPosition(),
tr.deckConfigSortOrderCardTemplateThenRandom(),
tr.deckConfigSortOrderPosition(),
tr.deckConfigSortOrderLowestPosition(),
tr.deckConfigSortOrderHighestPosition(),
tr.deckConfigSortOrderRandom(),
];
const reviewOrderChoices = [