I use undo_entry = mw.col.undo_status().last_step to get the undo entry of answer card for merge_undo_entries(undo_entry). But it will cause an error when other add-on call add_custom_undo_entry():
Error
An error occurred. Please start Anki while holding down the shift key, which will temporarily disable the add-ons you have installed.
If the issue only occurs when add-ons are enabled, please use the Tools > Add-ons menu item to disable some add-ons and restart Anki, repeating until you discover the add-on that is causing the problem.
When you've discovered the add-on that is causing the problem, please report the issue to the add-on author.
Debug info:
Anki 2.1.65 (aa9a734f) Python 3.9.15 Qt 6.5.0 PyQt 6.5.0
Platform: macOS-13.3.1-arm64-arm-64bit
Flags: frz=True ao=True sv=3
Add-ons, last update check: 2023-08-26 16:56:26
Add-ons possibly involved: <U+2068>FSRS4Anki Helper<U+2069>
Caught exception:
Traceback (most recent call last):
File "aqt.taskman", line 122, in _on_closures_pending
File "aqt.taskman", line 71, in <lambda>
File "aqt.taskman", line 90, in wrapped_done
File "aqt.operations", line 125, in wrapped_done
File "aqt.reviewer", line 460, in after_answer
File "aqt.reviewer", line 473, in _after_answering
File "_aqt.hooks", line 3982, in __call__
File "/Users/jarrettye/Library/Application Support/Anki2/addons21/759844606/schedule/__init__.py", line 8, in reschedule_and_disperse_siblings_when_review
undo_entry = reschedule_when_review(reviewer, card, ease)
File "/Users/jarrettye/Library/Application Support/Anki2/addons21/759844606/schedule/reschedule.py", line 381, in reschedule_when_review
mw.col.merge_undo_entries(undo_entry)
File "anki.collection", line 1054, in merge_undo_entries
File "anki._backend_generated", line 1823, in merge_undo_entries
File "anki._backend", line 156, in _run_command
anki.errors.InvalidInput: target undo op not found
My add-on doesn’t use collection op. I have two feature appended to the hook of reviewer_did_answer_card. And I want to merge them into Answer Card (Anki build-in undo entry).
def on_did_answer_card(reviewer: Reviewer, card: Card, ease: Literal[1, 2, 3, 4]) -> None:
"""Bury card if it was answered 'again' too many times within the specified time."""
# only care about failed cards
if ease != 1:
return
if config['ignore_new_cards'] is True and card.type <= TYPE_LEARNING:
return
agains = agains_in_the_timeframe(card.id)
passed = time_passed().hours()
if agains >= threshold(card):
if config['tag'] or config['flag'] or config.action != Action.No:
CollectionOp(
parent=cast(QWidget, reviewer.web), op=lambda col: act_on_card(col, card)
).success(
lambda out: notify(action_msg(agains, passed)) if out else None
).run_in_background()
elif config['again_notify'] is True:
notify(info_msg(card, agains, passed))
def init():
gui_hooks.reviewer_did_answer_card.append(on_did_answer_card)
def act_on_card(col: Collection, card: Card) -> ResultWithChanges:
pos = col.add_custom_undo_entry("Mortician: modify difficult card")
if config['tag'] and not (note := card.note()).has_tag(config['tag']):
note.add_tag(config['tag'])
col.update_note(note)
if Color.No != config.flag != Color(card.user_flag()):
col.set_user_flag_for_cards(config.flag.value, cids=[card.id, ])
if config.action == Action.Bury:
col.sched.bury_cards(ids=[card.id, ], manual=False)
sched_reset(col)
elif config.action == Action.Suspend:
col.sched.suspend_cards(ids=[card.id, ])
sched_reset(col)
return col.merge_undo_entries(pos)
IIRC CollectionOps are serialized, so if you wrap your code in it, it should prevent other operations from interleaving with yours. That will also avoid hanging the UI if it takes too long.
Please note that changing the cards like this during review (whether in a CollectionOp or not) is going to waste a fair few CPU cycles and lead to slower display of the next card, as it will cause the queues to be rebuilt after every card is answered. A more efficient approach would be to do this processing in bulk, when the user is not in the middle of reviews.
If the other add-on executed first and they merged their changes with the review entry, then your code should merge the changes with the review entry too.
True, but if they’re occurring at the time you run the hook, chances are they’re also using the hook, and if so, they should probably also be merging their changes into it, or it will make it hard for the user to undo reviews.
Sorry, I think we may have crossed-wires here. If you want to continue making changes on every review, I was suggesting you continue using your existing approach (merging into whatever happened before), as chances are that’ll either be the last review, or another add-on that (should have) modified it.