Display completion results

This commit is contained in:
Kovid Goyal 2014-12-24 10:32:15 +05:30
parent 8943e2883c
commit 20bc198cb7
5 changed files with 67 additions and 39 deletions

View File

@ -12,7 +12,7 @@ from urlparse import urlparse
from PyQt5.Qt import (
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
QDialogButtonBox, QIcon, QPixmap, QInputDialog, QUrl)
QDialogButtonBox, QIcon, QPixmap, QInputDialog, QUrl, pyqtSignal)
from calibre import prints, isbytestring
from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory
@ -69,6 +69,8 @@ def get_boss():
class Boss(QObject):
handle_completion_result_signal = pyqtSignal(object)
def __init__(self, parent, notify=None):
global _boss
QObject.__init__(self, parent)
@ -83,7 +85,8 @@ class Boss(QObject):
setup_cssutils_serialization()
_boss = self
self.gui = parent
completion_worker().result_callback = self.handle_completion_result
completion_worker().result_callback = self.handle_completion_result_signal.emit
self.handle_completion_result_signal.connect(self.handle_completion_result, Qt.QueuedConnection)
self.completion_request_count = 0
def __call__(self, gui):
@ -676,6 +679,7 @@ class Boss(QObject):
request_id = (self.completion_request_count, name)
self.completion_request_count += 1
completion_worker().queue_completion(request_id, completion_type, completion_data, query)
return request_id[0]
def handle_completion_result(self, result):
name = result.request_id[1]

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from threading import Event
from collections import namedtuple
from collections import namedtuple, OrderedDict
from PyQt5.Qt import QObject, pyqtSignal, Qt
@ -52,15 +52,21 @@ class Name(unicode):
def complete_names(names_data, data_conn):
if not names_cache:
mime_map, spine_names = get_data(data_conn, 'names_data')
names_cache[None] = frozenset(Name(name, mt, spine_names) for name, mt in mime_map.iteritems())
names_cache['text_link'] = frozenset(n for n in names_cache if n.in_spine)
names_cache['stylesheet'] = frozenset(n for n in names_cache if n.mime_type in OEB_STYLES)
names_cache['image'] = frozenset(n for n in names_cache if n.mime_type.startswith('image/'))
names_cache['font'] = frozenset(n for n in names_cache if n.mime_type in OEB_FONTS)
names_cache[None] = all_names = frozenset(Name(name, mt, spine_names) for name, mt in mime_map.iteritems())
names_cache['text_link'] = frozenset(n for n in all_names if n.in_spine)
names_cache['stylesheet'] = frozenset(n for n in all_names if n.mime_type in OEB_STYLES)
names_cache['image'] = frozenset(n for n in all_names if n.mime_type.startswith('image/'))
names_cache['font'] = frozenset(n for n in all_names if n.mime_type in OEB_FONTS)
names_cache['descriptions'] = d = {}
for x, desc in {'text_link':_('Text'), 'stylesheet':_('Stylesheet'), 'image':_('Image'), 'font':_('Font')}.iteritems():
for n in names_cache[x]:
d[n] = desc
names_type, base, root = names_data
names = names_cache.get(names_type, names_cache[None])
ans = frozenset(name_to_href(name, root, base) for name in names)
return ans, {}
nmap = {name:name_to_href(name, root, base) for name in names}
items = frozenset(nmap.itervalues())
descriptions = {href:names_cache.get(name) for name, href in nmap.iteritems()}
return items, descriptions, {}
_current_matcher = (None, None, None)
@ -68,11 +74,15 @@ def handle_control_request(request, data_conn):
global _current_matcher
ans = control_funcs[request.type](request.data, data_conn)
if ans is not None:
items, matcher_kwargs = ans
items, descriptions, matcher_kwargs = ans
fingerprint = hash(items)
if fingerprint != _current_matcher[0] or matcher_kwargs != _current_matcher[1]:
_current_matcher = (fingerprint, matcher_kwargs, Matcher(items, **matcher_kwargs))
ans = _current_matcher[-1](request.query or '', limit=50)
if request.query:
items = _current_matcher[-1](request.query, limit=50)
else:
items = OrderedDict((i, ()) for i in _current_matcher[-1].items)
ans = items, descriptions
return ans
class HandleDataRequest(QObject):

View File

@ -12,9 +12,10 @@ from math import ceil
from PyQt5.Qt import (
QWidget, Qt, QStaticText, QTextOption, QSize, QPainter, QTimer, QPen)
from calibre import prints
from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book.widgets import make_highlighted_text
from calibre.utils.icu import string_length
from calibre.utils.matcher import Matcher
class CompletionPopup(QWidget):
@ -23,12 +24,13 @@ class CompletionPopup(QWidget):
def __init__(self, parent, max_height=1000):
QWidget.__init__(self, parent)
self.completion_error_shown = False
self.setFocusPolicy(Qt.NoFocus)
self.setFocusProxy(parent)
self.setVisible(False)
self.matcher = None
self.current_query = self.current_results = self.current_size_hint = None
self.current_results = self.current_size_hint = None
self.max_text_length = 0
self.current_index = -1
self.current_top_index = 0
@ -47,25 +49,11 @@ class CompletionPopup(QWidget):
self.rendered_text_cache.clear()
self.current_size_hint = None
def set_items(self, items, items_are_filenames=False):
kw = {}
if not items_are_filenames:
kw['level1'] = ' '
self.matcher = Matcher(tuple(items), **kw)
self.descriptions = dict(items) if isinstance(items, dict) else {}
self.clear_caches()
self.set_query()
def set_query(self, query='', limit=100):
def set_items(self, items, descriptions=None):
self.current_results = tuple(items.iteritems())
self.current_size_hint = None
self.current_query = query
if self.matcher is None:
self.current_results = ()
else:
if query:
self.current_results = tuple(self.matcher(query, limit=limit).iteritems())
else:
self.current_results = tuple((text, ()) for text in self.matcher.items[:limit])
self.descriptions = descriptions or {}
self.clear_caches()
self.max_text_length = 0
self.current_index = -1
self.current_top_index = 0
@ -94,7 +82,7 @@ class CompletionPopup(QWidget):
sz = self.get_static_text(text, positions).size()
height += int(ceil(sz.height())) + self.TOP_MARGIN + self.BOTTOM_MARGIN
max_width = max(max_width, int(ceil(sz.width())))
self.current_size_hint = QSize(max_width, height)
self.current_size_hint = QSize(max_width, height + 2)
return self.current_size_hint
def iter_visible_items(self):
@ -184,6 +172,26 @@ class CompletionPopup(QWidget):
QWidget.hide(self)
self.relayout_timer.stop()
def handle_result(self, result):
if result.traceback:
prints(result.traceback)
if not self.completion_error_shown:
error_dialog(self, _('Completion failed'), _(
'Failed to get completions, click "Show Details" for more information.'
' Future errors during completion will be suppressed.'), det_msg=result.traceback, show=True)
self.completion_error_shown = True
self.hide()
return
if result.ans is None:
self.hide()
return
items, descriptions = result.ans
if not items:
self.hide()
return
self.set_items(items, descriptions)
self.show()
def handle_keypress(self, ev):
key = ev.key()
if key == Qt.Key_Escape:
@ -214,9 +222,11 @@ class CompletionPopup(QWidget):
return False
if __name__ == '__main__':
from calibre.utils.matcher import Matcher
def test(editor):
c = editor.__c = CompletionPopup(editor.editor, max_height=100)
c.set_items('one two three four five six seven eight nine ten'.split())
m = Matcher('one two three four five six seven eight nine ten'.split())
c.set_items(m('one'))
QTimer.singleShot(10, c.show)
from calibre.gui2.tweak_book.editor.widget import launch_editor
raw = textwrap.dedent('''\

View File

@ -288,7 +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*(?:'([^']+)|"([^"]+))$''')
Smarts.complete_attr_pat = re.compile(r'''([a-zA-Z0-9_-]+)\s*=\s*(?:'([^']*)|"([^"]*))$''')
NullSmarts.__init__(self, *args, **kwargs)
self.last_matched_tag = None
@ -644,7 +644,7 @@ class Smarts(NullSmarts):
doc_name = editor.highlighter.doc_name
if doc_name and attr in {'href', 'src'}:
# A link
query = m.group(2) or m.group(3)
query = m.group(2) or m.group(3) or ''
names_type = {'a':'text_link', 'img':'image', 'image':'image', 'link':'stylesheet'}.get(tagname)
return 'complete_names', (names_type, doc_name, current_container().root), query

View File

@ -141,6 +141,7 @@ class TextEdit(PlainTextEdit):
PlainTextEdit.__init__(self, parent)
self.completion_popup = CompletionPopup(self)
self.request_completion = self.completion_doc_name = None
self.last_completion_request = -1
self.gutter_width = 0
self.tw = 2
self.expected_geometry = expected_geometry
@ -782,11 +783,14 @@ class TextEdit(PlainTextEdit):
return
result = self.smarts.get_completion_data(self, ev)
if result is None:
return
self.request_completion(*result)
self.last_completion_request += 1
self.completion_popup.hide()
else:
self.last_completion_request = self.request_completion(*result)
def handle_completion_result(self, result):
print (result)
if result.request_id[0] >= self.last_completion_request:
self.completion_popup.handle_result(result)
def replace_possible_unicode_sequence(self):
c = self.textCursor()