diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index bb8a21459c..7695bb6bf3 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -83,7 +83,8 @@ class Boss(QObject): setup_cssutils_serialization() _boss = self self.gui = parent - completion_worker() + completion_worker().result_callback = self.handle_completion_result + self.completion_request_count = 0 def __call__(self, gui): self.gui = gui @@ -671,6 +672,17 @@ class Boss(QObject): ' Mark the book as having been modified ' self.gui.action_save.setEnabled(True) + def request_completion(self, name, completion_type, completion_data, query=None): + request_id = (self.completion_request_count, name) + self.completion_request_count += 1 + completion_worker().queue_completion(request_id, completion_type, completion_data, query) + + def handle_completion_result(self, result): + name = result.request_id[1] + editor = editors.get(name) + if editor is not None: + editor.handle_completion_result(result) + def fix_html(self, current): if current: ed = self.gui.central.current_editor @@ -1164,6 +1176,8 @@ class Boss(QObject): editor.link_clicked.connect(self.editor_link_clicked) if getattr(editor, 'syntax', None) == 'html': editor.smart_highlighting_updated.connect(self.gui.live_css.sync_to_editor) + if hasattr(editor, 'set_request_completion'): + editor.set_request_completion(partial(self.request_completion, name), name) if data is not None: if use_template: editor.init_from_template(data) diff --git a/src/calibre/gui2/tweak_book/completion/basic.py b/src/calibre/gui2/tweak_book/completion/basic.py index 5db4146b18..f837e36d99 100644 --- a/src/calibre/gui2/tweak_book/completion/basic.py +++ b/src/calibre/gui2/tweak_book/completion/basic.py @@ -79,7 +79,7 @@ class HandleDataRequest(QObject): # Ensure data is obtained in the GUI thread - call = pyqtSignal(object, object, object) + call = pyqtSignal(object, object) def __init__(self): QObject.__init__(self) diff --git a/src/calibre/gui2/tweak_book/editor/smarts/__init__.py b/src/calibre/gui2/tweak_book/editor/smarts/__init__.py index 828998d119..4815f2a139 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/__init__.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/__init__.py @@ -33,3 +33,6 @@ class NullSmarts(object): def handle_key_press(self, ev, editor): return False + + def get_completion_data(self, editor, ev=None): + return None diff --git a/src/calibre/gui2/tweak_book/editor/smarts/html.py b/src/calibre/gui2/tweak_book/editor/smarts/html.py index 173f53848d..1d976579ec 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/html.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/html.py @@ -16,7 +16,7 @@ from calibre import prepare_string_for_xml, xml_entity_to_unicode from calibre.gui2 import error_dialog from calibre.gui2.tweak_book.editor.syntax.html import ATTR_NAME, ATTR_END, ATTR_START, ATTR_VALUE from calibre.utils.icu import utf16_length -from calibre.gui2.tweak_book import tprefs +from calibre.gui2.tweak_book import tprefs, current_container from calibre.gui2.tweak_book.editor.smarts import NullSmarts from calibre.gui2.tweak_book.editor.smarts.utils import ( no_modifiers, get_leading_whitespace_on_block, get_text_before_cursor, @@ -288,6 +288,7 @@ class Smarts(NullSmarts): Smarts.closing_tag_pat = re.compile(r'<\s*/[^>]+>') Smarts.closing_pat = re.compile(r'<\s*/') Smarts.self_closing_pat = re.compile(r'/\s*>') + Smarts.complete_attr_pat = re.compile(r'''([a-zA-Z0-9_-]+)\s*=\s*(?:'([^']+)|"([^"]+))$''') NullSmarts.__init__(self, *args, **kwargs) self.last_matched_tag = None @@ -625,7 +626,29 @@ class Smarts(NullSmarts): editor.setTextCursor(c) return True -if __name__ == '__main__': + def get_completion_data(self, editor, ev=None): + c = editor.textCursor() + block, offset = c.block(), c.positionInBlock() + oblock, boundary = next_tag_boundary(block, offset, forward=False) + if boundary is None or not boundary.is_start or boundary.closing: + # Not inside a opening tag definition + return + tagname = boundary.name.lower() + startpos = oblock.position() + boundary.offset + c.setPosition(c.position()), c.setPosition(startpos, c.KeepAnchor) + text = c.selectedText() + m = self.complete_attr_pat.search(text) + if m is None: + return + attr = m.group(1).lower().split(':')[-1] + doc_name = editor.highlighter.doc_name + if doc_name and attr in {'href', 'src'}: + # A link + query = m.group(2) or m.group(3) + names_type = {'a':'text_link', 'img':'image', 'image':'image', 'link':'stylesheet'}.get(tagname) + return 'complete_names', (names_type, doc_name, current_container().root), query + +if __name__ == '__main__': # {{{ from calibre.gui2.tweak_book.editor.widget import launch_editor launch_editor('''\ @@ -657,3 +680,4 @@ if __name__ == '__main__': ''', path_is_raw=True, syntax='xml') +# }}} diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index 2fd5087beb..488d66d70d 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -19,6 +19,7 @@ from PyQt5.Qt import ( from calibre import prepare_string_for_xml from calibre.constants import isosx from calibre.gui2.tweak_book import tprefs, TOP +from calibre.gui2.tweak_book.completion.popup import CompletionPopup from calibre.gui2.tweak_book.editor import ( SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY) from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color, theme_format @@ -138,6 +139,8 @@ class TextEdit(PlainTextEdit): def __init__(self, parent=None, expected_geometry=(100, 50)): PlainTextEdit.__init__(self, parent) + self.completion_popup = CompletionPopup(self) + self.request_completion = self.completion_doc_name = None self.gutter_width = 0 self.tw = 2 self.expected_geometry = expected_geometry @@ -769,8 +772,21 @@ class TextEdit(PlainTextEdit): ev.setAccepted(False) return if self.smarts.handle_key_press(ev, self): + self.handle_keypress_completion(ev) return QPlainTextEdit.keyPressEvent(self, ev) + self.handle_keypress_completion(ev) + + def handle_keypress_completion(self, ev): + if self.request_completion is None: + return + result = self.smarts.get_completion_data(self, ev) + if result is None: + return + self.request_completion(*result) + + def handle_completion_result(self, result): + print (result) def replace_possible_unicode_sequence(self): c = self.textCursor() diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index 42f869a7ed..ee1dd71770 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -173,6 +173,7 @@ class Editor(QMainWindow): def change_document_name(self, newname): self.editor.change_document_name(newname) + self.editor.completion_doc_name = newname def get_raw_data(self): # The EPUB spec requires NFC normalization, see section 1.3.6 of @@ -220,6 +221,13 @@ class Editor(QMainWindow): tprefs['insert_tag_mru'] = mru self._build_insert_tag_button_menu() + def set_request_completion(self, callback=None, doc_name=None): + self.editor.request_completion = callback + self.editor.completion_doc_name = doc_name + + def handle_completion_result(self, result): + return self.editor.handle_completion_result(result) + def undo(self): self.editor.undo() @@ -367,6 +375,7 @@ class Editor(QMainWindow): self.editor.smart_highlighting_updated.disconnect() self.editor.setPlainText('') self.editor.smarts = None + self.editor.request_completion = None def _modification_state_changed(self): self.is_synced_to_container = self.is_modified