mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit Book: 2x speedup for inline spell checking. Useful if you are editing large HTML files (over 100KB in size).
Qt's text layout engine becomes very slow if we store arbitrary python objects in QTextCharFormat classes. So refactor to not do that.
This commit is contained in:
parent
81e6ed1551
commit
58203bbaa5
@ -35,6 +35,7 @@ def editor_from_syntax(syntax, parent=None):
|
||||
|
||||
SYNTAX_PROPERTY = QTextCharFormat.UserProperty
|
||||
SPELL_PROPERTY = SYNTAX_PROPERTY + 1
|
||||
SPELL_LOCALE_PROPERTY = SPELL_PROPERTY + 1
|
||||
|
||||
class SyntaxTextCharFormat(QTextCharFormat):
|
||||
|
||||
@ -47,4 +48,16 @@ class SyntaxTextCharFormat(QTextCharFormat):
|
||||
id(self), self.foreground().color().name(), self.fontItalic(), self.fontWeight() >= QFont.DemiBold)
|
||||
__str__ = __repr__
|
||||
|
||||
class StoreLocale(object):
|
||||
|
||||
__slots__ = ('enabled',)
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = False
|
||||
|
||||
def __enter__(self):
|
||||
self.enabled = True
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.enabled = False
|
||||
store_locale = StoreLocale()
|
||||
|
@ -129,28 +129,32 @@ class SyntaxHighlighter(object):
|
||||
try:
|
||||
block = doc.findBlock(position)
|
||||
while block.isValid() and (block.position() < end_pos or force_next_highlight):
|
||||
ud, new_ud = self.get_user_data(block)
|
||||
orig_state = ud.state
|
||||
pblock = block.previous()
|
||||
if pblock.isValid():
|
||||
start_state = pblock.userData()
|
||||
if start_state is None:
|
||||
start_state = self.user_data_factory().state
|
||||
else:
|
||||
start_state = start_state.state.copy()
|
||||
else:
|
||||
start_state = self.user_data_factory().state
|
||||
ud.clear(state=start_state) # Ensure no stale user data lingers
|
||||
formats = []
|
||||
for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode(block.text())):
|
||||
if fmt is not None:
|
||||
formats.append((i, num, fmt))
|
||||
formats, force_next_highlight = self.parse_single_block(block)
|
||||
self.apply_format_changes(doc, block, formats)
|
||||
force_next_highlight = new_ud or ud.state != orig_state
|
||||
block = block.next()
|
||||
finally:
|
||||
doc.contentsChange.connect(self.reformat_blocks)
|
||||
|
||||
def parse_single_block(self, block):
|
||||
ud, new_ud = self.get_user_data(block)
|
||||
orig_state = ud.state
|
||||
pblock = block.previous()
|
||||
if pblock.isValid():
|
||||
start_state = pblock.userData()
|
||||
if start_state is None:
|
||||
start_state = self.user_data_factory().state
|
||||
else:
|
||||
start_state = start_state.state.copy()
|
||||
else:
|
||||
start_state = self.user_data_factory().state
|
||||
ud.clear(state=start_state) # Ensure no stale user data lingers
|
||||
formats = []
|
||||
for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode(block.text())):
|
||||
if fmt is not None:
|
||||
formats.append((i, num, fmt))
|
||||
force_next_highlight = new_ud or ud.state != orig_state
|
||||
return formats, force_next_highlight
|
||||
|
||||
def reformat_block(self, block):
|
||||
if block.isValid():
|
||||
self.reformat_blocks(block.position(), 0, 1)
|
||||
|
@ -16,7 +16,7 @@ from calibre.ebooks.oeb.polish.spell import html_spell_tags, xml_spell_tags
|
||||
from calibre.spell.dictionary import parse_lang_code
|
||||
from calibre.spell.break_iterator import split_into_words_and_positions
|
||||
from calibre.gui2.tweak_book import dictionaries, tprefs
|
||||
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat, SPELL_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor import SyntaxTextCharFormat, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter, run_loop
|
||||
from calibre.gui2.tweak_book.editor.syntax.css import (
|
||||
create_formats as create_css_formats, state_map as css_state_map, CSSState, CSSUserData)
|
||||
@ -235,9 +235,12 @@ def check_spelling(text, tpos, tlen, fmt, locale, sfmt):
|
||||
if recognized:
|
||||
split_ans.append((length, fmt))
|
||||
else:
|
||||
wsfmt = SyntaxTextCharFormat(sfmt)
|
||||
wsfmt.setProperty(SPELL_PROPERTY, (ctext[start:ppos], locale))
|
||||
split_ans.append((length, wsfmt))
|
||||
if store_locale.enabled:
|
||||
s = SyntaxTextCharFormat(sfmt)
|
||||
s.setProperty(SPELL_LOCALE_PROPERTY, locale)
|
||||
split_ans.append((length, s))
|
||||
else:
|
||||
split_ans.append((length, sfmt))
|
||||
if ppos < tlen:
|
||||
split_ans.append((tlen - ppos, fmt))
|
||||
return split_ans
|
||||
@ -486,6 +489,7 @@ def create_formats(highlighter, add_css=True):
|
||||
f.setFontWeight(QFont.Bold)
|
||||
if add_css:
|
||||
formats['css_sub_formats'] = create_css_formats(highlighter)
|
||||
formats['spell'].setProperty(SPELL_PROPERTY, True)
|
||||
return formats
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ from PyQt4.Qt import (
|
||||
|
||||
from calibre import prepare_string_for_xml, xml_entity_to_unicode
|
||||
from calibre.gui2.tweak_book import tprefs, TOP
|
||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY, SPELL_PROPERTY
|
||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale
|
||||
from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color, theme_format
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighlighter
|
||||
@ -427,7 +427,7 @@ class TextEdit(PlainTextEdit):
|
||||
block = c.block()
|
||||
while block.isValid():
|
||||
for r in block.layout().additionalFormats():
|
||||
if r.format.property(SPELL_PROPERTY).toPyObject() is not None:
|
||||
if r.format.property(SPELL_PROPERTY).toBool():
|
||||
if not from_cursor or block.position() + r.start + r.length > c.position():
|
||||
c.setPosition(block.position() + r.start)
|
||||
c.setPosition(c.position() + r.length, c.KeepAnchor)
|
||||
@ -568,26 +568,42 @@ class TextEdit(PlainTextEdit):
|
||||
return False
|
||||
return QPlainTextEdit.event(self, ev)
|
||||
|
||||
def text_for_range(self, block, r):
|
||||
c = self.textCursor()
|
||||
c.setPosition(block.position() + r.start)
|
||||
c.setPosition(c.position() + r.length, c.KeepAnchor)
|
||||
return unicode(c.selectedText())
|
||||
|
||||
def spellcheck_locale_for_cursor(self, c):
|
||||
with store_locale:
|
||||
formats = self.highlighter.parse_single_block(c.block())[0]
|
||||
pos = c.positionInBlock()
|
||||
for i, num, fmt in formats:
|
||||
if i <= pos < i + num and fmt.property(SPELL_PROPERTY).toBool():
|
||||
return fmt.property(SPELL_LOCALE_PROPERTY).toPyObject()
|
||||
|
||||
def recheck_word(self, word, locale):
|
||||
c = self.textCursor()
|
||||
c.movePosition(c.Start)
|
||||
block = c.block()
|
||||
while block.isValid():
|
||||
for r in block.layout().additionalFormats():
|
||||
x = r.format.property(SPELL_PROPERTY).toPyObject()
|
||||
if x is not None and word == x[0]:
|
||||
if r.format.property(SPELL_PROPERTY).toBool() and self.text_for_range(block, r) == word:
|
||||
self.highlighter.reformat_block(block)
|
||||
break
|
||||
block = block.next()
|
||||
|
||||
# Tooltips {{{
|
||||
def syntax_format_for_cursor(self, cursor):
|
||||
def syntax_range_for_cursor(self, cursor):
|
||||
if cursor.isNull():
|
||||
return
|
||||
pos = cursor.positionInBlock()
|
||||
for r in cursor.block().layout().additionalFormats():
|
||||
if r.start <= pos < r.start + r.length and r.format.property(SYNTAX_PROPERTY).toBool():
|
||||
return r.format
|
||||
return r
|
||||
|
||||
def syntax_format_for_cursor(self, cursor):
|
||||
return getattr(self.syntax_range_for_cursor(cursor), 'format', None)
|
||||
|
||||
def show_tooltip(self, ev):
|
||||
c = self.cursorForPosition(ev.pos())
|
||||
|
@ -383,10 +383,10 @@ class Editor(QMainWindow):
|
||||
m = QMenu(self)
|
||||
a = m.addAction
|
||||
c = self.editor.cursorForPosition(pos)
|
||||
fmt = self.editor.syntax_format_for_cursor(c)
|
||||
spell = fmt.property(SPELL_PROPERTY).toPyObject() if fmt is not None else None
|
||||
if spell is not None:
|
||||
word, locale = spell
|
||||
r = self.editor.syntax_range_for_cursor(c)
|
||||
if r.format.property(SPELL_PROPERTY).toBool():
|
||||
word = self.editor.text_for_range(c.block(), r)
|
||||
locale = self.editor.spellcheck_locale_for_cursor(c)
|
||||
orig_pos = c.position()
|
||||
c.setPosition(orig_pos - utf16_length(word))
|
||||
found = False
|
||||
|
Loading…
x
Reference in New Issue
Block a user