Merge remote-tracking branch 'upstream/main' into cmmr-uses-simulate-config

This commit is contained in:
Luc Mcgrady 2025-04-27 11:06:52 +01:00
commit 283c07d057
16 changed files with 2632 additions and 1246 deletions

View file

@ -223,6 +223,7 @@ derivativeoflog7 <https://github.com/derivativeoflog7>
rreemmii-dev <https://github.com/rreemmii-dev>
babofitos <https://github.com/babofitos>
Jonathan Schoreels <https://github.com/JSchoreels>
JL710
********************

2642
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -35,9 +35,9 @@ git = "https://github.com/ankitects/linkcheck.git"
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
[workspace.dependencies.fsrs]
version = "3.0.0"
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
# rev = "c7717682997a8a6d53d97c7196281e745c5b3c8e"
# version = "3.0.0"
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
rev = "092c20bac7d9239a991ae5b561556ad34c706c16"
# path = "../open-spaced-repetition/fsrs-rs"
[workspace.dependencies]

File diff suppressed because it is too large Load diff

@ -1 +1 @@
Subproject commit 647e3cb0e697c51248201c4fb0df36514e765aa5
Subproject commit 376ae99eb47eae3c5e6298150162715423bc2e4e

@ -1 +1 @@
Subproject commit b3562ed3594d2afa0973edb877cc2f701ff162c3
Subproject commit 393bacec35703f91520e4d2feec37ed6953114f4

View file

@ -40,6 +40,7 @@ qt-accel-layout-horizontal = &Horizontal
qt-accel-zoom-in = Zoom &In
qt-accel-zoom-out = Zoom &Out
qt-accel-reset-zoom = &Reset Zoom
qt-accel-toggle-sidebar = Toggle Sidebar
qt-accel-zoom-editor-in = Zoom Editor &In
qt-accel-zoom-editor-out = Zoom Editor &Out
qt-accel-create-backup = Create &Backup

View file

@ -280,6 +280,19 @@ class Browser(QMainWindow):
if note_type_id := self.get_active_note_type_id():
add_cards.set_note_type(note_type_id)
# If in the Browser we open Preview and press Ctrl+W there,
# both Preview and Browser windows get closed by Qt out of the box.
# We circumvent that behavior by only closing the currently active window
def _handle_close(self):
active_window = QApplication.activeWindow()
if active_window and active_window != self:
if isinstance(active_window, QDialog):
active_window.reject()
else:
active_window.close()
else:
self.close()
def setupMenus(self) -> None:
# actions
f = self.form
@ -366,6 +379,7 @@ class Browser(QMainWindow):
qconnect(f.actionFind.triggered, self.onFind)
qconnect(f.actionNote.triggered, self.onNote)
qconnect(f.actionSidebar.triggered, self.focusSidebar)
qconnect(f.actionToggleSidebar.triggered, self.toggle_sidebar)
qconnect(f.actionCardList.triggered, self.onCardList)
# help
@ -695,7 +709,7 @@ class Browser(QMainWindow):
def setupSidebar(self) -> None:
dw = self.sidebarDockWidget = QDockWidget(tr.browsing_sidebar(), self)
dw.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
dw.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetClosable)
dw.setObjectName("Sidebar")
dock_area = (
Qt.DockWidgetArea.RightDockWidgetArea
@ -729,8 +743,11 @@ class Browser(QMainWindow):
# UI is more responsive
self.mw.progress.timer(10, self.sidebar.refresh, False, parent=self.sidebar)
def showSidebar(self) -> None:
self.sidebarDockWidget.setVisible(True)
def showSidebar(self, show: bool = True) -> None:
want_visible = not self.sidebarDockWidget.isVisible()
self.sidebarDockWidget.setVisible(show)
if want_visible and show:
self.sidebar.refresh()
def focusSidebar(self) -> None:
self.showSidebar()
@ -741,10 +758,7 @@ class Browser(QMainWindow):
self.sidebar.searchBar.setFocus()
def toggle_sidebar(self) -> None:
want_visible = not self.sidebarDockWidget.isVisible()
self.sidebarDockWidget.setVisible(want_visible)
if want_visible:
self.sidebar.refresh()
self.showSidebar(not self.sidebarDockWidget.isVisible())
# legacy

View file

@ -317,6 +317,8 @@
<addaction name="separator"/>
<addaction name="actionFullScreen"/>
<addaction name="separator"/>
<addaction name="actionToggleSidebar"/>
<addaction name="separator"/>
<addaction name="actionZoomIn"/>
<addaction name="actionZoomOut"/>
<addaction name="actionResetZoom"/>
@ -702,6 +704,11 @@
<string>qt_accel_full_screen</string>
</property>
</action>
<action name="actionToggleSidebar">
<property name="text">
<string>qt_accel_toggle_sidebar</string>
</property>
</action>
<action name="actionZoomIn">
<property name="text">
<string>qt_accel_zoom_editor_in</string>
@ -785,7 +792,7 @@
<sender>actionClose</sender>
<signal>triggered()</signal>
<receiver>Dialog</receiver>
<slot>close()</slot>
<slot>_handle_close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>

View file

@ -590,9 +590,9 @@ impl Collection {
Ok(changed_notes)
}
/// Check if the note's first field is empty or a duplicate. Then for cloze
/// notetypes, check if there is a cloze in a non-cloze field or if there's
/// no cloze at all. For other notetypes, just check if there's a cloze.
/// Check if there is a cloze in a non-cloze field. Then check if the
/// note's first field is empty. For cloze notetypes, check whether there
/// is a cloze at all. Finally, check if the first field is a duplicate.
pub fn note_fields_check(&mut self, note: &Note) -> Result<NoteFieldsState> {
Ok({
let cloze_state = self.field_cloze_check(note)?;

View file

@ -468,7 +468,7 @@ impl Collection {
self.get_config_bool(BoolKey::FsrsShortTermWithStepsEnabled);
let fsrs_allow_short_term = if fsrs_enabled {
let params = config.fsrs_params();
if params.len() == 19 {
if params.len() >= 19 {
params[17] > 0.0 && params[18] > 0.0
} else {
false

View file

@ -116,10 +116,10 @@ impl Collection {
})?;
progress_thread.join().ok();
if let Ok(fsrs) = FSRS::new(Some(current_params)) {
let current_rmse = fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
let current_log_loss = fsrs.evaluate(items.clone(), |_| true)?.log_loss;
let optimized_fsrs = FSRS::new(Some(&params))?;
let optimized_rmse = optimized_fsrs.evaluate(items.clone(), |_| true)?.rmse_bins;
if current_rmse <= optimized_rmse {
let optimized_log_loss = optimized_fsrs.evaluate(items.clone(), |_| true)?.log_loss;
if current_log_loss <= optimized_log_loss {
if num_of_relearning_steps <= 1 {
params = current_params.to_vec();
} else {

View file

@ -23,13 +23,18 @@ impl Collection {
}
let (config, _) = self.simulate_request_to_config(&req)?;
Ok(fsrs
.optimal_retention(&config, &req.params, |ip| {
anki_progress
.update(false, |p| {
p.current = ip.current as u32;
})
.is_ok()
})?
.optimal_retention(
&config,
&req.params,
|ip| {
anki_progress
.update(false, |p| {
p.current = ip.current as u32;
})
.is_ok()
},
None,
)?
.clamp(0.7, 0.95))
}

View file

@ -75,7 +75,6 @@ pub(crate) fn apply_load_balance_and_easy_days(
fn create_review_priority_fn(
review_order: ReviewCardOrder,
deck_size: usize,
params: Vec<f32>,
) -> Option<ReviewPriorityFn> {
// Helper macro to wrap closure in ReviewPriorityFn
macro_rules! wrap {
@ -86,28 +85,28 @@ fn create_review_priority_fn(
match review_order {
// Ease-based ordering
EaseAscending => wrap!(|c| -(c.difficulty * 100.0) as i32),
EaseDescending => wrap!(|c| (c.difficulty * 100.0) as i32),
EaseAscending => wrap!(|c, _w| -(c.difficulty * 100.0) as i32),
EaseDescending => wrap!(|c, _w| (c.difficulty * 100.0) as i32),
// Interval-based ordering
IntervalsAscending => wrap!(|c| c.interval as i32),
IntervalsDescending => wrap!(|c| -(c.interval as i32)),
IntervalsAscending => wrap!(|c, _w| c.interval as i32),
IntervalsDescending => wrap!(|c, _w| -(c.interval as i32)),
// Retrievability-based ordering
RetrievabilityAscending => {
wrap!(move |c| (c.retrievability(&params) * 1000.0) as i32)
wrap!(move |c, w| (c.retrievability(w) * 1000.0) as i32)
}
RetrievabilityDescending => {
wrap!(move |c| -(c.retrievability(&params) * 1000.0) as i32)
wrap!(move |c, w| -(c.retrievability(w) * 1000.0) as i32)
}
// Due date ordering
Day | DayThenDeck | DeckThenDay => {
wrap!(|c| c.scheduled_due() as i32)
wrap!(|c, _w| c.scheduled_due() as i32)
}
// Random ordering
Random => {
wrap!(move |_| rand::thread_rng().gen_range(0..deck_size) as i32)
wrap!(move |_c, _w| rand::thread_rng().gen_range(0..deck_size) as i32)
}
// Not implemented yet
@ -197,7 +196,7 @@ impl Collection {
.review_order
.try_into()
.ok()
.and_then(|order| create_review_priority_fn(order, deck_size, req.params.clone()));
.and_then(|order| create_review_priority_fn(order, deck_size));
let config = SimulatorConfig {
deck_size,

View file

@ -176,7 +176,7 @@ impl Collection {
}
Ok(result.into_iter().rev().collect())
} else {
Ok(revlog.iter().map(stats_revlog_entry).collect())
Ok(revlog.iter().rev().map(stats_revlog_entry).collect())
}
}
}

View file

@ -326,7 +326,7 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
let review_day = due.saturating_sub(ivl);
days_elapsed.saturating_sub(review_day) as u32
};
let decay = card_data.decay.unwrap_or_default();
let decay = card_data.decay.unwrap_or(FSRS5_DEFAULT_DECAY);
Ok(card_data.memory_state().map(|state| {
FSRS::new(None)
.unwrap()
@ -384,10 +384,8 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result
.max(0.0001);
return Ok(Some(
// power should be the reciprocal of the value of DECAY in FSRS-rs,
// which is currently -0.5
-(current_retrievability.powi(-2) - 1.)
/ (desired_retrievability.powi(-2) - 1.),
-(current_retrievability.powf(-1.0 / decay) - 1.)
/ (desired_retrievability.powf(-1.0 / decay) - 1.),
));
}
}