Skip to content

Commit 72ff2fe

Browse files
committed
fixed cards containing fresh morphs accidentally being skipped (#359)
1 parent 8ad6ba0 commit 72ff2fe

File tree

5 files changed

+91
-84
lines changed

5 files changed

+91
-84
lines changed

ankimorphs/__init__.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -692,20 +692,6 @@ def test_function() -> None:
692692
assert mw is not None
693693
assert mw.col.db is not None
694694

695-
# am_config = AnkiMorphsConfig()
696-
# title = "AnkiMorphs Error"
697-
# body = (
698-
# 'Clicking "Yes" will remove the following tags from all cards:'
699-
# "<ul>"
700-
# f"<li> {am_config.tag_known_automatically}"
701-
# f"<li> {am_config.tag_ready}"
702-
# f"<li> {am_config.tag_not_ready}"
703-
# f"<li> {am_config.tag_fresh}"
704-
# "</ul>"
705-
# )
706-
707-
# message_box_utils.show_error_box(title=title, body=body, parent=mw)
708-
709695
# with AnkiMorphsDB() as am_db:
710696
# print("Seen_Morphs:")
711697
# am_db.print_table("Seen_Morphs")

ankimorphs/ankimorphs_globals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
# Semantic Versioning https://semver.org/
8-
__version__ = "6.0.0"
8+
__version__ = "6.0.1"
99

1010
DEV_MODE: bool = False
1111

ankimorphs/reviewing_utils.py

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from functools import partial
44
from typing import Callable
55

6+
from anki import errors as anki_errors
67
from anki.cards import Card
78
from anki.collection import UndoStatus
89
from anki.consts import CARD_TYPE_NEW
@@ -19,10 +20,10 @@
1920
from .browser_utils import browse_same_morphs
2021
from .exceptions import CancelledOperationException, CardQueueEmptyException
2122

22-
SET_KNOWN_AND_SKIP_UNDO = "Set known and skip"
23-
ANKIMORPHS_UNDO = "AnkiMorphs custom undo"
23+
SET_KNOWN_AND_SKIP_UNDO_STRING = "Set known and skip"
24+
ANKIMORPHS_CUSTOM_UNDO_STRING = "AnkiMorphs custom undo"
2425
valid_undo_merge_targets: set[str] = {""}
25-
set_known_and_skip_undo: UndoStatus | None = None
26+
set_known_and_skip_undo_status: UndoStatus | None = None
2627

2728

2829
def init_undo_targets() -> None:
@@ -79,8 +80,8 @@ def _get_next_card_background(
7980
am_db = AnkiMorphsDB()
8081

8182
while True:
82-
# If a break occurs in this loop it means 'show the card'
83-
# If a card makes it to the end it is buried/skipped
83+
# breaking out of this loop means the card will be shown.
84+
# reaching the end without breaking means the card is buried (skipped).
8485

8586
if mw.progress.want_cancel(): # user clicked 'x'
8687
raise CancelledOperationException
@@ -108,7 +109,7 @@ def _get_next_card_background(
108109
raise CardQueueEmptyException # handled in _on_failure()
109110

110111
if undo_status.redo != "":
111-
break # The undo stack is dirty, we cannot merge undo entries.
112+
break # the undo stack is dirty, we cannot merge undo entries.
112113

113114
if reviewer.card.type != CARD_TYPE_NEW:
114115
break # we only want to skip new cards
@@ -119,15 +120,21 @@ def _get_next_card_background(
119120
if am_config_filter is None:
120121
break # card did not match any (note type and tags) set in the settings GUI
121122

122-
skipped_cards.process_skip_conditions_of_card(
123-
am_config, am_db, note=note, card_id=reviewer.card.id
124-
)
125-
126-
if not skipped_cards.did_skip_card:
123+
if not skipped_cards.should_skip_card(
124+
am_config=am_config, am_db=am_db, note=note, card_id=reviewer.card.id
125+
):
127126
break
128127

129128
mw.col.sched.buryCards([reviewer.card.id], manual=False)
130-
mw.col.merge_undo_entries(undo_status.last_step)
129+
130+
try:
131+
mw.col.merge_undo_entries(undo_status.last_step)
132+
except anki_errors.InvalidInput:
133+
# if we can't merge into the undo stack due to unusual entries
134+
# (e.g. unbury all cards button pressed), then we create
135+
# a custom entry point instead of crashing anki
136+
mw.col.add_custom_undo_entry(ANKIMORPHS_CUSTOM_UNDO_STRING)
137+
undo_status = mw.col.undo_status()
131138

132139
am_db.con.close()
133140

@@ -144,25 +151,21 @@ def _get_valid_undo_status() -> UndoStatus:
144151
# user undid the previous operation and the undo stack is now
145152
# 'dirty', which means we cannot merge undo entries--you get
146153
# an error if you try.
147-
#
148-
# The new anki undo system only works on the v3 scheduler which
149-
# means we can stop supporting the v2 scheduler and just display
150-
# an error message if it is used.
151154
################################################################
152155
assert mw is not None
153156

154157
undo_status = mw.col.undo_status()
155158

156-
if undo_status.undo == SET_KNOWN_AND_SKIP_UNDO:
159+
if undo_status.undo == SET_KNOWN_AND_SKIP_UNDO_STRING:
157160
# The undo stack has been altered, so we cannot use
158161
# the normal 'last_step' as a merge point, we have
159162
# to use set_known_and_skip_undo last_step instead.
160163
# See comment in set_card_as_known_and_skip for more info
161-
assert set_known_and_skip_undo is not None
162-
undo_status = set_known_and_skip_undo
164+
assert set_known_and_skip_undo_status is not None
165+
undo_status = set_known_and_skip_undo_status
163166
elif undo_status.undo not in valid_undo_merge_targets:
164167
# We have to create a custom undo_targets that can be merged into.
165-
mw.col.add_custom_undo_entry(ANKIMORPHS_UNDO)
168+
mw.col.add_custom_undo_entry(ANKIMORPHS_CUSTOM_UNDO_STRING)
166169
undo_status = mw.col.undo_status()
167170

168171
return undo_status
@@ -240,7 +243,7 @@ def _set_card_as_known_and_skip(am_config: AnkiMorphsConfig) -> None:
240243
# variable--to keep track of where the entries were merged into,
241244
# so we can merge into this point later in am_next_card.
242245
################################################################
243-
global set_known_and_skip_undo
246+
global set_known_and_skip_undo_status
244247

245248
assert mw is not None
246249
assert mw.reviewer is not None
@@ -258,15 +261,15 @@ def _set_card_as_known_and_skip(am_config: AnkiMorphsConfig) -> None:
258261
tooltip("Card is not in the 'new'-queue")
259262
return
260263

261-
mw.col.add_custom_undo_entry(SET_KNOWN_AND_SKIP_UNDO)
262-
set_known_and_skip_undo = mw.col.undo_status()
264+
mw.col.add_custom_undo_entry(SET_KNOWN_AND_SKIP_UNDO_STRING)
265+
set_known_and_skip_undo_status = mw.col.undo_status()
263266

264267
mw.col.sched.buryCards([card.id], manual=False)
265268

266269
note.add_tag(am_config.tag_known_manually)
267270
mw.col.update_note(note)
268271

269-
mw.col.merge_undo_entries(set_known_and_skip_undo.last_step)
272+
mw.col.merge_undo_entries(set_known_and_skip_undo_status.last_step)
270273

271274
with AnkiMorphsDB() as am_db:
272275
am_db.update_seen_morphs_today_single_card(card.id)
@@ -343,50 +346,42 @@ class SkippedCards:
343346
"skipped_cards_with_no_unknowns",
344347
"skipped_cards_with_already_seen_morphs",
345348
"total_cards_skipped",
346-
"did_skip_card",
347349
)
348350

349351
def __init__(self) -> None:
350352
self.skipped_cards_with_no_unknowns = 0
351353
self.skipped_cards_with_already_seen_morphs = 0
352354
self.total_cards_skipped = 0
353-
self.did_skip_card = False
354355

355-
def process_skip_conditions_of_card(
356+
def should_skip_card( # pylint:disable=too-many-return-statements
356357
self,
357358
am_config: AnkiMorphsConfig,
358359
am_db: AnkiMorphsDB,
359360
note: Note,
360361
card_id: int,
361-
) -> None:
362-
self.did_skip_card = False
363-
362+
) -> bool:
364363
learn_now_tag: bool = note.has_tag(am_config.tag_learn_card_now)
365364
known_automatically: bool = note.has_tag(am_config.tag_known_automatically)
366365
known_manually: bool = note.has_tag(am_config.tag_known_manually)
367366
known_tag: bool = known_automatically or known_manually
368367
fresh_tag: bool = note.has_tag(am_config.tag_fresh)
369368

370369
if learn_now_tag:
371-
self.did_skip_card = False
370+
# the user manually chose this card for review, don't skip
371+
return False
372372

373-
elif known_tag:
373+
if known_tag:
374+
# this is the simplest case; the 'known' tag is only applied
375+
# if every single morph is known, so don't need to
376+
# consider fresh morphs here
374377
if am_config.skip_no_unknown_morphs:
375-
self.skipped_cards_with_no_unknowns += 1
376-
self.did_skip_card = True
378+
self._will_skip_no_unknowns()
379+
return True
380+
return False
377381

378-
elif fresh_tag: # the fresh tag is mutually exclusive with the known tag
379-
if am_config.skip_when_contains_fresh_morphs:
380-
card_unknown_morphs_raw = am_db.get_card_morphs(
381-
card_id=card_id,
382-
search_unknowns=True,
383-
only_lemma=am_config.evaluate_morph_lemma,
384-
)
385-
if card_unknown_morphs_raw is None:
386-
self.skipped_cards_with_no_unknowns += 1
387-
self.did_skip_card = True
388-
389-
elif am_config.skip_unknown_morph_seen_today_cards:
382+
if am_config.skip_unknown_morph_seen_today_cards:
383+
# this is the ultimate 'I don't care about fresh morphs'
384+
# setting, so we don't check for those in this case
390385
morphs_already_seen_morphs_today: set[str] = (
391386
am_db.get_all_morphs_seen_today(
392387
only_lemma=am_config.evaluate_morph_lemma
@@ -402,9 +397,36 @@ def process_skip_conditions_of_card(
402397
morph_raw[0] + morph_raw[1] for morph_raw in card_unknown_morphs_raw
403398
}
404399
if card_unknown_morphs.issubset(morphs_already_seen_morphs_today):
405-
self.skipped_cards_with_already_seen_morphs += 1
406-
self.did_skip_card = True
400+
self._will_skip_already_seen()
401+
return True
402+
return False
403+
404+
if fresh_tag:
405+
# note: the fresh tag is mutually exclusive with the known tag
406+
if (
407+
am_config.skip_no_unknown_morphs
408+
and am_config.skip_when_contains_fresh_morphs
409+
):
410+
card_unknown_morphs_raw = am_db.get_card_morphs(
411+
card_id=card_id,
412+
search_unknowns=True,
413+
only_lemma=am_config.evaluate_morph_lemma,
414+
)
415+
if card_unknown_morphs_raw is None:
416+
self._will_skip_no_unknowns()
417+
return True
418+
419+
return False
420+
421+
def _will_skip_no_unknowns(self) -> None:
422+
self.skipped_cards_with_no_unknowns += 1
423+
self._update_total_skipped()
424+
425+
def _will_skip_already_seen(self) -> None:
426+
self.skipped_cards_with_already_seen_morphs += 1
427+
self._update_total_skipped()
407428

429+
def _update_total_skipped(self) -> None:
408430
self.total_cards_skipped = (
409431
self.skipped_cards_with_no_unknowns
410432
+ self.skipped_cards_with_already_seen_morphs

test/fake_configs.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,24 @@
111111

112112

113113
################################################################
114-
# config_dont_skip_contains_fresh_morphs
114+
# config_dont_skip_fresh_morphs
115115
################################################################
116116
# Used with: `card_handling_collection.anki2`.
117117
################################################################
118118
# fmt: off
119-
config_dont_skip_contains_fresh_morphs = copy.deepcopy(config_lemma_evaluation_lemma_extra_fields)
120-
config_dont_skip_contains_fresh_morphs[ConfigKeys.SKIP_DONT_WHEN_CONTAINS_FRESH_MORPHS] = True
121-
config_dont_skip_contains_fresh_morphs[ConfigKeys.SKIP_WHEN_CONTAINS_FRESH_MORPHS] = False
119+
config_dont_skip_fresh_morphs = copy.deepcopy(config_lemma_evaluation_lemma_extra_fields)
120+
config_dont_skip_fresh_morphs[ConfigKeys.SKIP_DONT_WHEN_CONTAINS_FRESH_MORPHS] = True
121+
config_dont_skip_fresh_morphs[ConfigKeys.SKIP_WHEN_CONTAINS_FRESH_MORPHS] = False
122122
# fmt: on
123123

124124
################################################################
125-
# config_skip_known_disabled
125+
# config_disabled_skip_no_unknown_morphs
126126
################################################################
127127
# Used with: `card_handling_collection.anki2`.
128128
################################################################
129129
# fmt: off
130-
config_skip_no_unknown_morphs_disabled = copy.deepcopy(config_dont_skip_contains_fresh_morphs)
131-
config_skip_no_unknown_morphs_disabled[ConfigKeys.SKIP_NO_UNKNOWN_MORPHS] = False
130+
config_disabled_skip_no_unknown_morphs = copy.deepcopy(config_lemma_evaluation_lemma_extra_fields)
131+
config_disabled_skip_no_unknown_morphs[ConfigKeys.SKIP_NO_UNKNOWN_MORPHS] = False
132132
# fmt: on
133133

134134
################################################################

test/tests/review_test.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from functools import partial
22
from test.fake_configs import (
3-
config_dont_skip_contains_fresh_morphs,
3+
config_disabled_skip_no_unknown_morphs,
4+
config_dont_skip_fresh_morphs,
45
config_inflection_evaluation,
56
config_lemma_evaluation_lemma_extra_fields,
6-
config_skip_no_unknown_morphs_disabled,
77
)
88
from test.fake_db import FakeDB
99
from test.fake_environment_module import ( # pylint:disable=unused-import
@@ -122,10 +122,10 @@ def test_custom_review( # pylint:disable=unused-argument
122122

123123
test_cases_morph_status = [
124124
################################################################
125-
# CASE: SKIP ONLY KNOWN AND FRESH
125+
# CASE: SKIP ONLY KNOWN OR FRESH
126126
################################################################
127-
# Test if cards with only known or fresh morphs will
128-
# be skipped, which is the default settings
127+
# Test if cards with only known or fresh morphs are skipped,
128+
# which is the default settings
129129
################################################################
130130
pytest.param(
131131
FakeEnvironmentParams(
@@ -137,31 +137,30 @@ def test_custom_review( # pylint:disable=unused-argument
137137
id="skip_known_and_fresh",
138138
),
139139
################################################################
140-
# CASE: DON'T ONLY KNOWN
140+
# CASE: DISABLE SKIP NO UNKNOWN MORPHS
141141
################################################################
142-
# Test if cards with only known morphs don't get skipped if we
143-
# disable "ConfigKeys.SKIP_ONLY_KNOWN_MORPHS_CARDS"
142+
# Test if cards are skipped when we disable:
143+
# "ConfigKeys.SKIP_ONLY_KNOWN_MORPHS_CARDS"
144144
################################################################
145145
pytest.param(
146146
FakeEnvironmentParams(
147147
actual_col="card_handling_collection",
148-
config=config_skip_no_unknown_morphs_disabled,
148+
config=config_disabled_skip_no_unknown_morphs,
149149
am_db="card_handling_collection.db",
150150
),
151151
[1736763242955, 1736763365474, 1736763249205],
152152
id="dont_skip_known",
153153
),
154154
################################################################
155-
# CASE: DON'T SKIP ONLY KNOWN AND FRESH
155+
# CASE: DON'T SKIP FRESH MORPHS
156156
################################################################
157-
# Test if cards with only known and fresh morphs will
158-
# not be skipped if we disable
159-
# "ConfigKeys.SKIP_ONLY_KNOWN_OR_FRESH_MORPHS_CARDS"
157+
# Test if cards with fresh morphs are skipped if we activate
158+
# "ConfigKeys.SKIP_DONT_WHEN_CONTAINS_FRESH_MORPHS"
160159
################################################################
161160
pytest.param(
162161
FakeEnvironmentParams(
163162
actual_col="card_handling_collection",
164-
config=config_dont_skip_contains_fresh_morphs,
163+
config=config_dont_skip_fresh_morphs,
165164
am_db="card_handling_collection.db",
166165
),
167166
[1736763242955, 1736763249205],

0 commit comments

Comments
 (0)