-
Notifications
You must be signed in to change notification settings - Fork 0
/
Tag_Toggler.py
233 lines (202 loc) · 8.58 KB
/
Tag_Toggler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# Tag Toggler 1.1.1 (2016-02-06)
# Copyright: Don March <don@ohspite.net>
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
#
# Tag Toggler is an Anki 2 add-on for quickly adding tags while reviewing.
# Based in part on Quick Tagging by Cayenne Boyer
# (https://github.com/cayennes/Quick_Tagging)
########################################
## CONFIGURATION OPTIONS
## There are two variables to edit--tag_dialog_shortcut and tag_shortcuts.
## Lines with a leading `#` are comments and have no effect. Lines with a
## single `#` are code examples that you can use by removing the leading `#`.
## You can overwrite some previously existing shortcuts, but it's easiest if
## you pick keys that are unused or that are shortcuts when reviewing cards
## only (defined in Reviewer._keyHandler). Some keys (such as 'a' for Add or
## 'b' for Browse are defined elsewhere; the effect of adding Tag Toggle
## functionality to these keys is undefined.
## Change `tag_dialog_shortcut` to the key you want to open a dialog to
## quickly edit tags. Set `tag_dialog_shortcut = None` to disable the
## shortcut.
tag_dialog_shortcut = 't'
# tag_dialog_shortcut = None
## Add items to the `tag_shortcuts` dict to create shortcuts that modify
## tags. The dict keys are the key for the keyboard shortcut, and each should
## refer to a dict to specify the command. Valid keys in that dict are
## 'tags', 'action', and 'after'.
##
## 'tags': Specify tags to modify in a string; separate multiple tags in the
## string with spaces.
##
## 'action': How to modify tags; options are 'add' (the default), 'delete',
## and 'toggle' (delete tag if present, add it if absent).
##
## 'after': What to do to a card after modifying the tags; options are
## 'bury-card', 'bury-note', 'suspend-card' or 'suspend-note'. Also 'suspend'
## and 'bury, which are the same as the '-note' versions.
##
## Example keybinding to add tags:
## 'h': {'tags': 'hard'}
## 'add' is the default action, so this is the same:
## 'h': {'tags': 'hard', 'action': 'add'}
## Modify multiple tags by separating them with spaces:
## 'h': {'tags': 'hard marked'}
## Keybinding to delete tags (if they are present):
## 'h': {'tags': 'hard marked', 'action': 'delete'}
## Keybinding to toggle tag:
## 'H': {'tags': 'hard', 'action': 'toggle'}
## Bury a card after adding a tag:
## 'T': {'tags': 'TODO', 'after': 'bury-card'}
## Suspend a note after adding a tag:
## 'A': {'tags': 'easy', 'after': 'suspend-note'}
tag_shortcuts = {
'5': {'tags': 'Conflict', 'action': 'toggle'},
'.': {'tags': '', 'after': 'bury'},
'm': {'tags': 'marked', 'action': 'toggle'}
# 'H': {'tags': 'hard', 'action': 'toggle'},
# '0': {'tags': 'Hard', 'action': 'add'},
# 'T': {'tags': 'TODO', 'after': 'bury-note'},
# 'A': {'tags': 'easy', 'after': 'suspend-card'},
}
## END CONFIGURATION OPTIONS
########################################
# Testing:
# As far as I know, there is no easy way to automatically test this. Here are
# some keybindings to add to `tag_shortcuts` that cover most cases. (Be sure
# to test `tag_dialog_shortcut` as well).
# The first two should cause an graceful error when Anki is starting up.
# 'z': {'tags': 'test-a', 'action': 'blah'},
# 'Z': {'tags': 'test-a', 'after': 'blah'},
# 'z': {'tags': 'test-a'},
# 'Z': {'tags': 'test-b'},
# 'x': {'tags': 'test-a', 'action': 'delete'},
# 'X': {'tags': 'test-b', 'action': 'delete'},
# 'c': {'tags': 'test-a test-b'},
# 'q': {'tags': 'test-a test-b', 'action': 'delete'},
# 'Q': {'tags': 'test-a test-b', 'action': 'toggle'},
# 'r': {'tags': 'test-a test-b', 'after': 'bury-note'},
# 'R': {'tags': 'test-a test-b', 'after': 'suspend-note'},
# 's': {'tags': 'test-a test-b', 'after': 'bury-card'},
# 'S': {'tags': 'test-a test-b', 'after': 'suspend-card'},
from aqt import mw
from aqt.utils import getTag, tooltip, showInfo
from aqt.reviewer import Reviewer
from anki.hooks import wrap
def tagKeyHandler(self, event, _old):
"""Wrap default _keyHandler with new keybindings."""
key = unicode(event.text())
note = mw.reviewer.card.note()
if tag_dialog_shortcut and key in tag_dialog_shortcut:
mw.checkpoint(_("Edit Tags"))
import win32api
win32api.LoadKeyboardLayout('00000409',1) # to switch to english
edit_tag_dialog(note)
elif key in tag_shortcuts:
binding = tag_shortcuts[key]
if 'action' not in binding:
binding['action'] = 'add'
if ('after' in binding and
binding['after'] in ['suspend', 'suspend-note']):
mw.checkpoint("Edit Tags and Suspend Note")
tooltip_message = 'Suspended note and edited tags: {}'
self.mw.col.sched.suspendCards(
[card.id for card in self.card.note().cards()])
elif 'after' in binding and binding['after'] in ['bury', 'bury-note']:
mw.checkpoint("Edit Tags and Bury Note")
tooltip_message = 'Buried note and edited tags: {}'
mw.col.sched.buryNote(note.id)
elif 'after' in binding and binding['after'] == 'suspend-card':
mw.checkpoint("Edit Tags and Suspend Card")
tooltip_message = 'Suspended card and edited tags: {}'
self.mw.col.sched.suspendCards([self.card.id])
elif 'after' in binding and binding['after'] == 'bury-card':
mw.checkpoint("Edit Tags and Bury Card")
tooltip_message = 'Buried card and edited tags: {}'
mw.col.sched.buryCards([self.card.id])
else:
mw.checkpoint(_("edit Tags"))
tooltip_message = 'Edited tags: {}'
tag_edits = edit_note_tags(note, binding['tags'], binding['action'])
if 'after' in binding:
mw.reset()
else:
redraw_card()
tooltip(tooltip_message.format(tag_edits))
else:
_old(self, event)
def edit_tag_dialog(note):
"""Prompt for tags and add the results to note."""
prompt = _("Edit tag list:")
(tag_string, dialog_status) = getTag(mw, mw.col, prompt, default=note.stringTags())
if dialog_status != 0: # means "Cancel"
note.setTagsFromStr(tag_string)
note.flush()
redraw_card()
tooltip('Tags set to: "{}"'.format(tag_string))
def redraw_card():
"""Refresh the card in case {{Tags}} is in the template."""
# TODO It would be better to do this without resetting the state.
r = mw.reviewer
try:
r.card.load()
except:
# card was removed by clayout
pass
else:
mw.reviewer.cardQueue.append(mw.reviewer.card)
mw.moveToState("review")
def edit_note_tags(note, tags, action='add'):
"""Apply action to each space separated tag in the string `tags`."""
tag_list = mw.col.tags.split(tags)
additions = []
deletions = []
for tag in tag_list:
if action == 'delete':
if note.hasTag(tag):
note.delTag(tag)
deletions.append(tag)
elif action == 'toggle':
if note.hasTag(tag):
note.delTag(tag)
deletions.append(tag)
else:
note.addTag(tag)
additions.append(tag)
else: # action == 'add'
if not note.hasTag(tag):
note.addTag(tag)
additions.append(tag)
note.flush()
messages = []
if additions:
messages.append("added: \"{}\"".format(" ".join(additions)))
if deletions:
messages.append("removed: \"{}\"".format(" ".join(deletions)))
if messages:
return "\n".join(messages)
else:
return "(no changes)"
def shortcuts_are_okay():
error_message = (
"The Tag Toggle add-on will not be started.\n\n"
"Check the configuration for an undefined '{}' "
"value '{}' in tag_shortcuts:\n\n"
"{}")
def check_command(command, command_type, options):
if command_type in command:
value = command[command_type]
if value not in options:
showInfo(error_message.format(command_type, value, command))
return False
return True
for shortcut in tag_shortcuts:
command = tag_shortcuts[shortcut]
if not check_command(command, 'action', ['add', 'delete', 'toggle']):
return False
if not check_command(command, 'after',
['bury', 'bury-card', 'bury-note',
'suspend', 'suspend-card', 'suspend-note']):
return False
return True
if shortcuts_are_okay():
Reviewer._keyHandler = wrap(Reviewer._keyHandler, tagKeyHandler, "around")