diff --git a/proto/anki/tags.proto b/proto/anki/tags.proto index a1597f7d6..c2ff124cb 100644 --- a/proto/anki/tags.proto +++ b/proto/anki/tags.proto @@ -22,6 +22,7 @@ service TagsService { returns (collection.OpChangesWithCount); rpc FindAndReplaceTag(FindAndReplaceTagRequest) returns (collection.OpChangesWithCount); + rpc CompleteTag(CompleteTagRequest) returns (CompleteTagResponse); } message SetTagCollapsedRequest { @@ -58,3 +59,13 @@ message FindAndReplaceTagRequest { bool regex = 4; bool match_case = 5; } + +message CompleteTagRequest { + // a partial tag, optionally delimited with :: + string input = 1; + uint32 match_limit = 2; +} + +message CompleteTagResponse { + repeated string tags = 1; +} diff --git a/pylib/anki/_backend/genbackend.py b/pylib/anki/_backend/genbackend.py index e8ed939d8..9c4e7795f 100755 --- a/pylib/anki/_backend/genbackend.py +++ b/pylib/anki/_backend/genbackend.py @@ -58,10 +58,11 @@ SKIP_UNROLL_INPUT = { "UpdateDeckConfigs", "AnswerCard", "ChangeNotetype", + "CompleteTag", } SKIP_UNROLL_OUTPUT = {"GetPreferences"} -SKIP_DECODE = {"Graphs", "GetGraphPreferences", "GetChangeNotetypeInfo"} +SKIP_DECODE = {"Graphs", "GetGraphPreferences", "GetChangeNotetypeInfo", "CompleteTag"} def python_type(field): diff --git a/pylib/anki/tags.py b/pylib/anki/tags.py index 757d102b9..c1fd64b2a 100644 --- a/pylib/anki/tags.py +++ b/pylib/anki/tags.py @@ -67,6 +67,11 @@ class TagManager: "Set browser expansion state for tag, registering the tag if missing." return self.col._backend.set_tag_collapsed(name=tag, collapsed=collapsed) + def complete_tag(self, input_bytes: bytes) -> bytes: + input = tags_pb2.CompleteTagRequest() + input.ParseFromString(input_bytes) + return self.col._backend.complete_tag(input) + # Bulk addition/removal from specific notes ############################################################# diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index ac0f65319..ba8f5a82c 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -226,9 +226,6 @@ class AddCards(QDialog): self.addHistory(note) - # workaround for PyQt focus bug - self.editor.hideCompleters() - tooltip(tr.adding_added(), period=500) av_player.stop_and_clear_queue() self._load_new_note(sticky_fields_from=note) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 6c847b300..6a285ebd4 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -84,6 +84,7 @@ _html = """ %s
+ """ @@ -114,7 +115,6 @@ class Editor: self.setupOuter() self.setupWeb() self.setupShortcuts() - self.setupTags() gui_hooks.editor_did_init(self) # Initial setup @@ -302,9 +302,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ def setupShortcuts(self) -> None: # if a third element is provided, enable shortcut even when no field selected - cuts: List[Tuple] = [ - ("Ctrl+Shift+T", self.onFocusTags, True), - ] + cuts: List[Tuple] = [] gui_hooks.editor_did_init_shortcuts(cuts, self) for row in cuts: if len(row) == 2: @@ -430,6 +428,14 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ (_, highlightColor) = cmd.split(":", 1) self.mw.pm.profile["lastHighlightColor"] = highlightColor + elif cmd.startswith("saveTags"): + (type, tagsJson) = cmd.split(":", 1) + self.note.tags = json.loads(tagsJson) + + gui_hooks.editor_did_update_tags(self.note) + if not self.addMode: + self._save_current_note() + elif cmd in self._links: self._links[cmd](self) @@ -450,10 +456,8 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ self.currentField = None if self.note: self.loadNote(focusTo=focusTo) - else: - self.hideCompleters() - if hide: - self.widget.hide() + elif hide: + self.widget.hide() def loadNoteKeepingFocus(self) -> None: self.loadNote(self.currentField) @@ -467,7 +471,6 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ for fld, val in self.note.items() ] self.widget.show() - self.updateTags() note_fields_status = self.note.fields_check() @@ -485,12 +488,13 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ text_color = self.mw.pm.profile.get("lastTextColor", "#00f") highlight_color = self.mw.pm.profile.get("lastHighlightColor", "#00f") - js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s); setColorButtons(%s);" % ( + js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s); setColorButtons(%s); setTags(%s); " % ( json.dumps(data), json.dumps(self.fonts()), json.dumps(focusTo), json.dumps(self.note.id), json.dumps([text_color, highlight_color]), + json.dumps(self.mw.col.tags.canonify(self.note.tags)), ) if self.addMode: @@ -520,7 +524,6 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ # calling code may not expect the callback to fire immediately self.mw.progress.timer(10, callback, False) return - self.blur_tags_if_focused() self.web.evalWithCallback("saveNow(%d)" % keepFocus, lambda res: callback()) saveNow = call_after_note_saved diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index a4448bfc6..09e094fce 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -351,6 +351,10 @@ def change_notetype() -> bytes: return b"" +def complete_tag() -> bytes: + return aqt.mw.col.tags.complete_tag(request.data) + + post_handlers = { "graphData": graph_data, "graphPreferences": graph_preferences, @@ -365,6 +369,7 @@ post_handlers = { # pylint: disable=unnecessary-lambda "i18nResources": i18n_resources, "congratsInfo": congrats_info, + "completeTag": complete_tag, } diff --git a/rslib/src/backend/tags.rs b/rslib/src/backend/tags.rs index 5f91c248d..5601efcc4 100644 --- a/rslib/src/backend/tags.rs +++ b/rslib/src/backend/tags.rs @@ -88,4 +88,11 @@ impl TagsService for Backend { .map(Into::into) }) } + + fn complete_tag(&self, input: pb::CompleteTagRequest) -> Result