33from functools import partial
44from typing import Callable
55
6+ from anki import errors as anki_errors
67from anki .cards import Card
78from anki .collection import UndoStatus
89from anki .consts import CARD_TYPE_NEW
1920from .browser_utils import browse_same_morphs
2021from .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"
2425valid_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
2829def 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
0 commit comments