undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.
Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-16 05:26:42 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-03-16 07:39:41 +01:00
|
|
|
from typing import Callable, Optional, Sequence
|
undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.
Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-16 05:26:42 +01:00
|
|
|
|
|
|
|
from anki.lang import TR
|
|
|
|
from anki.notes import Note
|
2021-03-17 05:51:59 +01:00
|
|
|
from aqt import AnkiQt, QWidget
|
undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.
Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-16 05:26:42 +01:00
|
|
|
from aqt.main import PerformOpOptionalSuccessCallback
|
|
|
|
from aqt.utils import show_invalid_search_error, showInfo, tr
|
|
|
|
|
|
|
|
|
|
|
|
def add_note(
|
|
|
|
*,
|
|
|
|
mw: AnkiQt,
|
|
|
|
note: Note,
|
|
|
|
target_deck_id: int,
|
|
|
|
success: PerformOpOptionalSuccessCallback = None,
|
|
|
|
) -> None:
|
|
|
|
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
|
|
|
|
|
|
|
|
|
2021-03-16 07:39:41 +01:00
|
|
|
def update_note(*, mw: AnkiQt, note: Note, after_hooks: Callable[[], None]) -> None:
|
|
|
|
mw.perform_op(
|
|
|
|
lambda: mw.col.update_note(note),
|
|
|
|
after_hooks=after_hooks,
|
|
|
|
)
|
undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.
Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-16 05:26:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
def remove_notes(
|
|
|
|
*,
|
|
|
|
mw: AnkiQt,
|
|
|
|
note_ids: Sequence[int],
|
|
|
|
success: PerformOpOptionalSuccessCallback = None,
|
|
|
|
) -> None:
|
|
|
|
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)
|
|
|
|
|
|
|
|
|
|
|
|
def add_tags(*, mw: AnkiQt, note_ids: Sequence[int], space_separated_tags: str) -> None:
|
|
|
|
mw.perform_op(lambda: mw.col.tags.bulk_add(note_ids, space_separated_tags))
|
|
|
|
|
|
|
|
|
|
|
|
def remove_tags(
|
|
|
|
*, mw: AnkiQt, note_ids: Sequence[int], space_separated_tags: str
|
|
|
|
) -> None:
|
|
|
|
mw.perform_op(lambda: mw.col.tags.bulk_remove(note_ids, space_separated_tags))
|
|
|
|
|
|
|
|
|
|
|
|
def find_and_replace(
|
|
|
|
*,
|
|
|
|
mw: AnkiQt,
|
2021-03-17 05:51:59 +01:00
|
|
|
parent: QWidget,
|
undoable ops now return changes directly; add new *_ops.py files
- Introduced a new transact() method that wraps the return value
in a separate struct that describes the changes that were made.
- Changes are now gathered from the undo log, so we don't need to
guess at what was changed - eg if update_note() is called with identical
note contents, no changes are returned. Card changes will only be set
if cards were actually generated by the update_note() call, and tag
will only be set if a new tag was added.
- mw.perform_op() has been updated to expect the op to return the changes,
or a structure with the changes in it, and it will use them to fire the
change hook, instead of fetching the changes from undo_status(), so there
is no risk of race conditions.
- the various calls to mw.perform_op() have been split into separate
files like card_ops.py. Aside from making the code cleaner, this works
around a rather annoying issue with mypy. Because we run it with
no_strict_optional, mypy is happy to accept an operation that returns None,
despite the type signature saying it requires changes to be returned.
Turning no_strict_optional on for the whole codebase is not practical
at the moment, but we can enable it for individual files.
Still todo:
- The cursor keeps moving back to the start of a field when typing -
we need to ignore the refresh hook when we are the initiator.
- The busy cursor icon should probably be delayed a few hundreds ms.
- Still need to think about a nicer way of handling saveNow()
- op_made_changes(), op_affects_study_queue() might be better embedded
as properties in the object instead
2021-03-16 05:26:42 +01:00
|
|
|
note_ids: Sequence[int],
|
|
|
|
search: str,
|
|
|
|
replacement: str,
|
|
|
|
regex: bool,
|
|
|
|
field_name: Optional[str],
|
|
|
|
match_case: bool,
|
|
|
|
) -> None:
|
|
|
|
mw.perform_op(
|
|
|
|
lambda: mw.col.find_and_replace(
|
|
|
|
note_ids=note_ids,
|
|
|
|
search=search,
|
|
|
|
replacement=replacement,
|
|
|
|
regex=regex,
|
|
|
|
field_name=field_name,
|
|
|
|
match_case=match_case,
|
|
|
|
),
|
|
|
|
success=lambda out: showInfo(
|
|
|
|
tr(TR.FINDREPLACE_NOTES_UPDATED, changed=out.count, total=len(note_ids)),
|
|
|
|
parent=parent,
|
|
|
|
),
|
|
|
|
failure=lambda exc: show_invalid_search_error(exc, parent=parent),
|
|
|
|
)
|