diff --git a/src/calibre/gui2/tweak_book/completion/basic.py b/src/calibre/gui2/tweak_book/completion/basic.py index ba7317d913..824e88db53 100644 --- a/src/calibre/gui2/tweak_book/completion/basic.py +++ b/src/calibre/gui2/tweak_book/completion/basic.py @@ -12,9 +12,11 @@ from collections import namedtuple, OrderedDict from PyQt5.Qt import QObject, pyqtSignal, Qt from calibre import prepare_string_for_xml +from calibre.ebooks.oeb.base import xml2text from calibre.ebooks.oeb.polish.container import OEB_STYLES, OEB_FONTS, name_to_href +from calibre.ebooks.oeb.polish.parsing import parse from calibre.gui2 import is_gui_thread -from calibre.gui2.tweak_book import current_container +from calibre.gui2.tweak_book import current_container, editors from calibre.gui2.tweak_book.completion.utils import control, data, DataError from calibre.utils.ipc import eintr_retry_call from calibre.utils.matcher import Matcher @@ -44,6 +46,13 @@ def names_data(request_data): c = current_container() return c.mime_map, {n for n, is_linear in c.spine_names} +@data +def file_data(name): + 'Get the data for name. Returns a unicode string if name is a text document/stylesheet' + if name in editors: + return editors[name].get_raw_data() + return current_container().raw_data(name) + def get_data(data_conn, data_type, data=None): eintr_retry_call(data_conn.send, Request(None, data_type, data, None)) result, tb = eintr_retry_call(data_conn.recv) @@ -81,6 +90,57 @@ def complete_names(names_data, data_conn): descriptions = {href:d(name) for name, href in nmap.iteritems()} return items, descriptions, {} + +def description_for_anchor(elem): + def check(x, min_len=4): + if x: + x = x.strip() + if len(x) >= min_len: + return x[:30] + + desc = check(elem.get('title')) + if desc is not None: + return desc + desc = check(elem.text) + if desc is not None: + return desc + if len(elem) > 0: + desc = check(elem[0].text) + if desc is not None: + return desc + # Get full text for tags that have only a few descendants + for i, x in enumerate(elem.iterdescendants('*')): + if i > 5: + break + else: + desc = check(xml2text(elem), min_len=1) + if desc is not None: + return desc + +def create_anchor_map(root): + ans = {} + for elem in root.xpath('//*[@id or @name]'): + anchor = elem.get('id') or elem.get('name') + if anchor and anchor not in ans: + ans[anchor] = description_for_anchor(elem) + return ans + +@control +def complete_anchor(name, data_conn): + if name not in file_cache: + data = raw = get_data(data_conn, 'file_data', name) + if isinstance(raw, type('')): + try: + root = parse(raw, decoder=lambda x:x.decode('utf-8')) + except Exception: + pass + else: + data = (root, create_anchor_map(root)) + file_cache[name] = data + data = file_cache[name] + if isinstance(data, tuple) and len(data) > 1 and isinstance(data[1], dict): + return frozenset(data[1]), data[1], {} + _current_matcher = (None, None, None) def handle_control_request(request, data_conn): diff --git a/src/calibre/gui2/tweak_book/editor/smarts/html.py b/src/calibre/gui2/tweak_book/editor/smarts/html.py index fa0559e903..d790539c4b 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/html.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/html.py @@ -13,14 +13,15 @@ from cssutils import parseStyle from PyQt5.Qt import QTextEdit, Qt from calibre import prepare_string_for_xml, xml_entity_to_unicode +from calibre.ebooks.oeb.polish.container import OEB_DOCS 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, 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, get_text_after_cursor, smart_home, smart_backspace, smart_tab, expand_tabs) +from calibre.utils.icu import utf16_length get_offset = itemgetter(0) PARAGRAPH_SEPARATOR = '\u2029' @@ -646,8 +647,16 @@ class Smarts(NullSmarts): if doc_name and attr in {'href', 'src'}: # A link query = m.group(2) or m.group(3) or '' + c = current_container() names_type = {'a':'text_link', 'img':'image', 'image':'image', 'link':'stylesheet'}.get(tagname) - return 'complete_names', (names_type, doc_name, current_container().root), query + idx = query.find('#') + if idx > -1 and names_type in (None, 'text_link'): + href, query = query[:idx], query[idx+1:] + name = c.href_to_name(href) if href else doc_name + if c.mime_map.get(name) in OEB_DOCS: + return 'complete_anchor', name, query + + return 'complete_names', (names_type, doc_name, c.root), query if __name__ == '__main__': # {{{ from calibre.gui2.tweak_book.editor.widget import launch_editor