add a separate DeckId search for decks with children

- The "unbury deck" option was broken, as it was ignoring child
decks. It would be nice if we could use active_decks instead, but
plugging that into the old scheduler without breaking undo seems a bit
tricky.
- Remove the implicit From impl for decks, so we need to be forced to
think about whether we want child decks or not.
This commit is contained in:
Damien Elmes 2021-05-20 11:23:10 +10:00
parent b9a4908bfb
commit b412bf97fb
6 changed files with 47 additions and 15 deletions

View file

@ -9,7 +9,7 @@ use crate::{
card::CardQueue, card::CardQueue,
config::SchedulerVersion, config::SchedulerVersion,
prelude::*, prelude::*,
search::{SortMode, StateKind}, search::{SearchNode, SortMode, StateKind},
}; };
impl Card { impl Card {
@ -77,7 +77,10 @@ impl Collection {
UnburyDeckMode::SchedOnly => StateKind::SchedBuried, UnburyDeckMode::SchedOnly => StateKind::SchedBuried,
}; };
self.transact(Op::UnburyUnsuspend, |col| { self.transact(Op::UnburyUnsuspend, |col| {
col.search_cards_into_table(match_all![deck_id, state], SortMode::NoOrder)?; col.search_cards_into_table(
match_all![SearchNode::DeckIdWithChildren(deck_id), state],
SortMode::NoOrder,
)?;
col.unsuspend_or_unbury_searched_cards() col.unsuspend_or_unbury_searched_cards()
}) })
} }

View file

@ -9,7 +9,7 @@ use crate::{
card::{CardQueue, CardType}, card::{CardQueue, CardType},
deckconfig::NewCardInsertOrder, deckconfig::NewCardInsertOrder,
prelude::*, prelude::*,
search::{SortMode, StateKind}, search::{SearchNode, SortMode, StateKind},
}; };
impl Card { impl Card {
@ -186,7 +186,10 @@ impl Collection {
order: NewCardInsertOrder, order: NewCardInsertOrder,
usn: Usn, usn: Usn,
) -> Result<usize> { ) -> Result<usize> {
let cids = self.search_cards(match_all![deck, StateKind::New], SortMode::NoOrder)?; let cids = self.search_cards(
match_all![SearchNode::DeckIdWithoutChildren(deck), StateKind::New],
SortMode::NoOrder,
)?;
self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn) self.sort_cards_inner(&cids, 1, 1, order.into(), false, usn)
} }

View file

@ -95,7 +95,11 @@ pub enum SearchNode {
EditedInDays(u32), EditedInDays(u32),
CardTemplate(TemplateKind), CardTemplate(TemplateKind),
Deck(String), Deck(String),
DeckId(DeckId), /// Matches cards in a single deck (original_deck_id is not checked).
DeckIdWithoutChildren(DeckId),
/// Matches cards in a deck or its children (original_deck_id is not
/// checked).
DeckIdWithChildren(DeckId),
IntroducedInDays(u32), IntroducedInDays(u32),
NotetypeId(NotetypeId), NotetypeId(NotetypeId),
Notetype(String), Notetype(String),
@ -197,12 +201,6 @@ impl From<NoteId> for Node {
} }
} }
impl From<DeckId> for Node {
fn from(id: DeckId) -> Self {
Node::Search(SearchNode::DeckId(id))
}
}
impl From<StateKind> for Node { impl From<StateKind> for Node {
fn from(k: StateKind) -> Self { fn from(k: StateKind) -> Self {
Node::Search(SearchNode::State(k)) Node::Search(SearchNode::State(k))
@ -619,7 +617,7 @@ fn parse_state(s: &str) -> ParseResult<SearchNode> {
} }
fn parse_did(s: &str) -> ParseResult<SearchNode> { fn parse_did(s: &str) -> ParseResult<SearchNode> {
parse_i64(s, "did:").map(|n| SearchNode::DeckId(n.into())) parse_i64(s, "did:").map(|n| SearchNode::DeckIdWithoutChildren(n.into()))
} }
fn parse_mid(s: &str) -> ParseResult<SearchNode> { fn parse_mid(s: &str) -> ParseResult<SearchNode> {

View file

@ -140,9 +140,10 @@ impl SqlWriter<'_> {
SearchNode::NotetypeId(ntid) => { SearchNode::NotetypeId(ntid) => {
write!(self.sql, "n.mid = {}", ntid).unwrap(); write!(self.sql, "n.mid = {}", ntid).unwrap();
} }
SearchNode::DeckId(did) => { SearchNode::DeckIdWithoutChildren(did) => {
write!(self.sql, "c.did = {}", did).unwrap(); write!(self.sql, "c.did = {}", did).unwrap();
} }
SearchNode::DeckIdWithChildren(did) => self.write_deck_id_with_children(*did)?,
SearchNode::Notetype(notetype) => self.write_notetype(&norm(notetype)), SearchNode::Notetype(notetype) => self.write_notetype(&norm(notetype)),
SearchNode::Rated { days, ease } => self.write_rated(">", -i64::from(*days), ease)?, SearchNode::Rated { days, ease } => self.write_rated(">", -i64::from(*days), ease)?,
@ -370,6 +371,19 @@ impl SqlWriter<'_> {
Ok(()) Ok(())
} }
fn write_deck_id_with_children(&mut self, deck_id: DeckId) -> Result<()> {
if let Some(parent) = self.col.get_deck(deck_id)? {
let ids = self.col.storage.deck_id_with_children(&parent)?;
let mut buf = String::new();
ids_to_string(&mut buf, &ids);
write!(self.sql, "c.did in {}", buf,).unwrap();
} else {
self.sql.push_str("false")
}
Ok(())
}
fn write_template(&mut self, template: &TemplateKind) { fn write_template(&mut self, template: &TemplateKind) {
match template { match template {
TemplateKind::Ordinal(n) => { TemplateKind::Ordinal(n) => {
@ -567,7 +581,8 @@ impl SearchNode {
SearchNode::AddedInDays(_) => RequiredTable::Cards, SearchNode::AddedInDays(_) => RequiredTable::Cards,
SearchNode::IntroducedInDays(_) => RequiredTable::Cards, SearchNode::IntroducedInDays(_) => RequiredTable::Cards,
SearchNode::Deck(_) => RequiredTable::Cards, SearchNode::Deck(_) => RequiredTable::Cards,
SearchNode::DeckId(_) => RequiredTable::Cards, SearchNode::DeckIdWithoutChildren(_) => RequiredTable::Cards,
SearchNode::DeckIdWithChildren(_) => RequiredTable::Cards,
SearchNode::Rated { .. } => RequiredTable::Cards, SearchNode::Rated { .. } => RequiredTable::Cards,
SearchNode::State(_) => RequiredTable::Cards, SearchNode::State(_) => RequiredTable::Cards,
SearchNode::Flag(_) => RequiredTable::Cards, SearchNode::Flag(_) => RequiredTable::Cards,

View file

@ -87,7 +87,9 @@ fn write_search_node(node: &SearchNode) -> String {
IntroducedInDays(u) => format!("introduced:{}", u), IntroducedInDays(u) => format!("introduced:{}", u),
CardTemplate(t) => write_template(t), CardTemplate(t) => write_template(t),
Deck(s) => maybe_quote(&format!("deck:{}", s)), Deck(s) => maybe_quote(&format!("deck:{}", s)),
DeckId(DeckIdType(i)) => format!("did:{}", i), DeckIdWithoutChildren(DeckIdType(i)) => format!("did:{}", i),
// not exposed on the GUI end
DeckIdWithChildren(_) => "".to_string(),
NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i), NotetypeId(NotetypeIdType(i)) => format!("mid:{}", i),
Notetype(s) => maybe_quote(&format!("note:{}", s)), Notetype(s) => maybe_quote(&format!("note:{}", s)),
Rated { days, ease } => write_rated(days, ease), Rated { days, ease } => write_rated(days, ease),

View file

@ -207,6 +207,17 @@ impl SqliteStorage {
.collect() .collect()
} }
pub(crate) fn deck_id_with_children(&self, parent: &Deck) -> Result<Vec<DeckId>> {
let prefix_start = format!("{}\x1f", parent.name);
let prefix_end = format!("{}\x20", parent.name);
self.db
.prepare_cached("select id from decks where id = ? or (name >= ? and name < ?)")?
.query_and_then(params![parent.id, prefix_start, prefix_end], |row| {
row.get(0).map_err(Into::into)
})?
.collect()
}
pub(crate) fn deck_with_children(&self, deck_id: DeckId) -> Result<Vec<Deck>> { pub(crate) fn deck_with_children(&self, deck_id: DeckId) -> Result<Vec<Deck>> {
let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?; let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?;
let prefix_start = format!("{}\x1f", deck.name); let prefix_start = format!("{}\x1f", deck.name);