diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 5dc688155..ca754e783 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -234,7 +234,7 @@ class DeckBrowser: if node.collapsed: prefix = "+" else: - prefix = "-" + prefix = "−" def indent() -> str: return " " * 6 * (node.level - 1) diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py index a6d9251e2..89e15246e 100644 --- a/qt/aqt/errors.py +++ b/qt/aqt/errors.py @@ -14,7 +14,7 @@ from markdown import markdown import aqt from anki.collection import HelpPage -from anki.errors import BackendError, Interrupted +from anki.errors import BackendError, CardTypeError, Interrupted from anki.utils import is_win from aqt.addons import AddonManager, AddonMeta from aqt.qt import * @@ -36,6 +36,14 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None: global _mbox error_lines = [] help_page = HelpPage.TROUBLESHOOTING + + # default to PlainText + text_format = Qt.TextFormat.PlainText + + # set CardTypeError messages as rich text to allow HTML formatting + if isinstance(exception, CardTypeError): + text_format = Qt.TextFormat.RichText + if isinstance(exception, BackendError): if exception.context: error_lines.append(exception.context) @@ -51,7 +59,7 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None: ) error_text = "\n".join(error_lines) print(error_lines) - _mbox = _init_message_box(str(exception), error_text, help_page) + _mbox = _init_message_box(str(exception), error_text, help_page, text_format) _mbox.show() @@ -171,7 +179,10 @@ if not os.environ.get("DEBUG"): def _init_message_box( - user_text: str, debug_text: str, help_page=HelpPage.TROUBLESHOOTING + user_text: str, + debug_text: str, + help_page=HelpPage.TROUBLESHOOTING, + text_format=Qt.TextFormat.PlainText, ): global _mbox @@ -179,7 +190,7 @@ def _init_message_box( _mbox.setWindowTitle("Anki") _mbox.setText(user_text) _mbox.setIcon(QMessageBox.Icon.Warning) - _mbox.setTextFormat(Qt.TextFormat.PlainText) + _mbox.setTextFormat(text_format) def show_help(): openHelp(help_page) diff --git a/rslib/i18n/build.rs b/rslib/i18n/build.rs index f604c9167..75bc38787 100644 --- a/rslib/i18n/build.rs +++ b/rslib/i18n/build.rs @@ -8,7 +8,6 @@ mod python; mod typescript; mod write_strings; -use std::env; use std::path::PathBuf; use anki_io::create_dir_all; @@ -32,8 +31,7 @@ fn main() -> Result<()> { python::write_py_interface(&modules)?; // write strings.json file to requested path - println!("cargo:rerun-if-env-changed=STRINGS_JSON"); - if let Ok(path) = env::var("STRINGS_JSON") { + if let Some(path) = option_env!("STRINGS_JSON") { if !path.is_empty() { let path = PathBuf::from(path); let meta_json = serde_json::to_string_pretty(&modules).unwrap(); diff --git a/rslib/i18n/python.rs b/rslib/i18n/python.rs index ca780c041..a564de48d 100644 --- a/rslib/i18n/python.rs +++ b/rslib/i18n/python.rs @@ -1,7 +1,6 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use std::env; use std::fmt::Write; use std::path::PathBuf; @@ -21,7 +20,7 @@ pub fn write_py_interface(modules: &[Module]) -> Result<()> { render_methods(modules, &mut out); render_legacy_enum(modules, &mut out); - if let Ok(path) = env::var("STRINGS_PY") { + if let Some(path) = option_env!("STRINGS_PY") { let path = PathBuf::from(path); create_dir_all(path.parent().unwrap())?; write_file_if_changed(path, out)?; diff --git a/rslib/i18n/typescript.rs b/rslib/i18n/typescript.rs index ce30048e2..0f483cb58 100644 --- a/rslib/i18n/typescript.rs +++ b/rslib/i18n/typescript.rs @@ -1,7 +1,6 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use std::env; use std::fmt::Write; use std::path::PathBuf; @@ -22,7 +21,7 @@ pub fn write_ts_interface(modules: &[Module]) -> Result<()> { render_module_map(modules, &mut ts_out); render_methods(modules, &mut ts_out); - if let Ok(path) = env::var("STRINGS_TS") { + if let Some(path) = option_env!("STRINGS_TS") { let path = PathBuf::from(path); create_dir_all(path.parent().unwrap())?; write_file_if_changed(path, ts_out)?; diff --git a/rslib/io/src/lib.rs b/rslib/io/src/lib.rs index cb44467e6..0fd85e490 100644 --- a/rslib/io/src/lib.rs +++ b/rslib/io/src/lib.rs @@ -335,6 +335,15 @@ pub fn write_file_if_changed(path: impl AsRef, contents: impl AsRef<[u8]>) .map(|existing| existing != contents) .unwrap_or(true) }; + + match std::env::var("CARGO_PKG_NAME") { + Ok(pkg) if pkg == "anki_proto" || pkg == "anki_i18n" => { + // at comptime for the proto/i18n crates, register implicit output as input + println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); + } + _ => {} + } + if changed { write_file(path, contents)?; Ok(true) diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index f6237d6fd..2479b87ae 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -434,9 +434,10 @@ impl SqlWriter<'_> { let timing = self.col.timing_today()?; (timing.days_elapsed, timing.next_day_at, timing.now) }; + const NEW_TYPE: i8 = CardType::New as i8; write!( self.sql, - "extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, c.ivl, {elap}, {next_day_at}, {now}) {op} {r}" + "case when c.type = {NEW_TYPE} then false else (extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, c.ivl, {elap}, {next_day_at}, {now}) {op} {r}) end" ) .unwrap() } diff --git a/rslib/src/storage/card/filtered.rs b/rslib/src/storage/card/filtered.rs index ef436f6e8..03f845f4e 100644 --- a/rslib/src/storage/card/filtered.rs +++ b/rslib/src/storage/card/filtered.rs @@ -54,7 +54,7 @@ fn build_retrievability_query( ) -> String { if fsrs { format!( - "extract_fsrs_relative_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, {today}, ivl, {next_day_at}, {now}) {order}" + "extract_fsrs_relative_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, ivl, {today}, {next_day_at}, {now}) {order}" ) } else { format!( diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 3a5066ff4..9e06edf07 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -837,7 +837,7 @@ impl fmt::Display for ReviewOrderSubclause { let next_day_at = timing.next_day_at.0; let now = timing.now.0; temp_string = - format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, {today}, ivl, {next_day_at}, {now}) {order}"); + format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) {order}"); &temp_string } ReviewOrderSubclause::Added => "nid asc, ord asc", diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index 3ce1baff0..95853afc9 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -332,23 +332,30 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> { return Ok(None); }; let seconds_elapsed = if let Some(last_review_time) = card_data.last_review_time { - now.saturating_sub(last_review_time.0) as u32 + // This and any following + // (x as u32).saturating_sub(y as u32) + // must not be changed to + // x.saturating_sub(y) as u32 + // as x and y are i64's and saturating_sub will therfore allow negative numbers + // before converting to u32 in the latter example. + (now as u32).saturating_sub(last_review_time.0 as u32) } else if due > 365_000 { // (re)learning card in seconds let Ok(ivl) = ctx.get_raw(2).as_i64() else { return Ok(None); }; - let last_review_time = due.saturating_sub(ivl); - now.saturating_sub(last_review_time) as u32 + let last_review_time = (due as u32).saturating_sub(ivl as u32); + (now as u32).saturating_sub(last_review_time) } else { let Ok(ivl) = ctx.get_raw(2).as_i64() else { return Ok(None); }; - let Ok(days_elapsed) = ctx.get_raw(3).as_i64() else { + // timing.days_elapsed + let Ok(today) = ctx.get_raw(3).as_i64() else { return Ok(None); }; - let review_day = due.saturating_sub(ivl); - days_elapsed.saturating_sub(review_day) as u32 * 86_400 + let review_day = (due as u32).saturating_sub(ivl as u32); + (today as u32).saturating_sub(review_day) * 86_400 }; let decay = card_data.decay.unwrap_or(FSRS5_DEFAULT_DECAY); let retrievability = card_data.memory_state().map(|state| { @@ -364,7 +371,7 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> { } /// eg. extract_fsrs_relative_retrievability(card.data, card.due, -/// timing.days_elapsed, card.ivl, timing.next_day_at, timing.now) -> float | +/// card.ivl, timing.days_elapsed, timing.next_day_at, timing.now) -> float | /// null. The higher the number, the higher the card's retrievability relative /// to the configured desired retention. fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result<()> { @@ -378,25 +385,32 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result let Ok(due) = ctx.get_raw(1).as_i64() else { return Ok(None); }; - let Ok(interval) = ctx.get_raw(3).as_i64() else { + let Ok(interval) = ctx.get_raw(2).as_i64() else { return Ok(None); }; + /* + // Unused let Ok(next_day_at) = ctx.get_raw(4).as_i64() else { return Ok(None); }; + */ let Ok(now) = ctx.get_raw(5).as_i64() else { return Ok(None); }; - let days_elapsed = if due > 365_000 { - // (re)learning - (next_day_at as u32).saturating_sub(due as u32) / 86_400 + let secs_elapsed = if due > 365_000 { + // (re)learning card with due in seconds + + // Don't change this to now.subtracting_sub(due) as u32 + // for the same reasons listed in the comment + // in add_extract_fsrs_retrievability + (now as u32).saturating_sub(due as u32) } else { - let Ok(days_elapsed) = ctx.get_raw(2).as_i64() else { + // timing.days_elapsed + let Ok(today) = ctx.get_raw(3).as_i64() else { return Ok(None); }; let review_day = due.saturating_sub(interval); - - (days_elapsed as u32).saturating_sub(review_day as u32) + (today as u32).saturating_sub(review_day as u32) * 86_400 }; if let Ok(card_data) = ctx.get_raw(0).as_str() { if !card_data.is_empty() { @@ -410,23 +424,12 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result let seconds_elapsed = if let Some(last_review_time) = card_data.last_review_time { - now.saturating_sub(last_review_time.0) as u32 - } else if due > 365_000 { - // (re)learning card in seconds - let Ok(ivl) = ctx.get_raw(2).as_i64() else { - return Ok(None); - }; - let last_review_time = due.saturating_sub(ivl); - now.saturating_sub(last_review_time) as u32 + // Don't change this to now.subtracting_sub(due) as u32 + // for the same reasons listed in the comment + // in add_extract_fsrs_retrievability + (now as u32).saturating_sub(last_review_time.0 as u32) } else { - let Ok(ivl) = ctx.get_raw(2).as_i64() else { - return Ok(None); - }; - let Ok(days_elapsed) = ctx.get_raw(3).as_i64() else { - return Ok(None); - }; - let review_day = due.saturating_sub(ivl); - days_elapsed.saturating_sub(review_day) as u32 * 86_400 + secs_elapsed }; let current_retrievability = FSRS::new(None) @@ -441,7 +444,7 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result } } } - + let days_elapsed = secs_elapsed / 86_400; // FSRS data missing; fall back to SM2 ordering Ok(Some( -((days_elapsed as f32) + 0.001) / (interval as f32).max(1.0), diff --git a/tools/minilints/src/main.rs b/tools/minilints/src/main.rs index 6d38278b5..ca645efe6 100644 --- a/tools/minilints/src/main.rs +++ b/tools/minilints/src/main.rs @@ -202,7 +202,7 @@ fn sveltekit_temp_file(path: &str) -> bool { } fn check_cargo_deny() -> Result<()> { - Command::run("cargo install cargo-deny@0.18.3")?; + Command::run("cargo install cargo-deny@0.18.6")?; Command::run("cargo deny check")?; Ok(()) }