%PDF- %PDF-
Direktori : /proc/thread-self/root/usr/lib/calibre/calibre/gui2/tweak_book/editor/syntax/ |
Current File : //proc/thread-self/root/usr/lib/calibre/calibre/gui2/tweak_book/editor/syntax/base.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' from collections import defaultdict, deque from qt.core import QTextCursor, QTextBlockUserData, QTextLayout, QTimer from ..themes import highlight_to_char_format from calibre.gui2.tweak_book.widgets import BusyCursor from calibre.utils.icu import utf16_length from polyglot.builtins import iteritems def run_loop(user_data, state_map, formats, text): state = user_data.state i = 0 fix_offsets = utf16_length(text) != len(text) seen_states = defaultdict(set) while i < len(text): orig_i = i seen_states[i].add(state.parse) fmt = state_map[state.parse](state, text, i, formats, user_data) for num, f in fmt: if num > 0: if fix_offsets: # We need to map offsets/lengths from UCS-4 to UTF-16 in # which non-BMP characters are two code points wide yield utf16_length(text[:i]), utf16_length(text[i:i+num]), f else: yield i, num, f i += num if orig_i == i and state.parse in seen_states[i]: # Something went wrong in the syntax highlighter print('Syntax highlighter returned a zero length format, parse state:', state.parse) break class SimpleState: __slots__ = ('parse',) def __init__(self): self.parse = 0 def copy(self): s = SimpleState() s.parse = self.parse return s class SimpleUserData(QTextBlockUserData): def __init__(self): QTextBlockUserData.__init__(self) self.state = SimpleState() self.doc_name = None def clear(self, state=None, doc_name=None): self.state = SimpleState() if state is None else state self.doc_name = doc_name class SyntaxHighlighter: create_formats_func = lambda highlighter: {} spell_attributes = () tag_ok_for_spell = lambda x: False user_data_factory = SimpleUserData def __init__(self): self.doc = None self.doc_name = None self.requests = deque() self.ignore_requests = False @property def has_requests(self): return bool(self.requests) def apply_theme(self, theme): self.theme = {k:highlight_to_char_format(v) for k, v in iteritems(theme)} self.create_formats() self.rehighlight() def create_formats(self): self.formats = self.create_formats_func() def set_document(self, doc, doc_name=None): old_doc = self.doc if old_doc is not None: old_doc.contentsChange.disconnect(self.reformat_blocks) c = QTextCursor(old_doc) c.beginEditBlock() blk = old_doc.begin() while blk.isValid(): blk.layout().clearAdditionalFormats() blk = blk.next() c.endEditBlock() self.doc = self.doc_name = None if doc is not None: self.doc = doc self.doc_name = doc_name doc.contentsChange.connect(self.reformat_blocks) self.rehighlight() def rehighlight(self): doc = self.doc if doc is None: return lb = doc.lastBlock() with BusyCursor(): self.reformat_blocks(0, 0, lb.position() + lb.length()) def get_user_data(self, block): ud = block.userData() new_data = False if ud is None: ud = self.user_data_factory() block.setUserData(ud) new_data = True return ud, new_data def reformat_blocks(self, position, removed, added): doc = self.doc if doc is None or self.ignore_requests or not hasattr(self, 'state_map'): return block = doc.findBlock(position) if not block.isValid(): return start_cursor = QTextCursor(block) last_block = doc.findBlock(position + added + (1 if removed > 0 else 0)) if not last_block.isValid(): last_block = doc.lastBlock() end_cursor = QTextCursor(last_block) end_cursor.movePosition(QTextCursor.MoveOperation.EndOfBlock) self.requests.append((start_cursor, end_cursor)) QTimer.singleShot(0, self.do_one_block) def do_one_block(self): try: start_cursor, end_cursor = self.requests[0] except IndexError: return self.ignore_requests = True try: block = start_cursor.block() if not block.isValid(): self.requests.popleft() return formats, force_next_highlight = self.parse_single_block(block) self.apply_format_changes(block, formats) try: self.doc.markContentsDirty(block.position(), block.length()) except AttributeError: self.requests.clear() return ok = start_cursor.movePosition(QTextCursor.MoveOperation.NextBlock) if not ok: self.requests.popleft() return next_block = start_cursor.block() if next_block.position() > end_cursor.position(): if force_next_highlight: end_cursor.setPosition(next_block.position() + 1) else: self.requests.popleft() return finally: self.ignore_requests = False QTimer.singleShot(0, self.do_one_block) def join(self): ''' Blocks until all pending highlighting requests are handled ''' doc = self.doc if doc is None: self.requests.clear() return self.ignore_requests = True try: while self.requests: start_cursor, end_cursor = self.requests.popleft() block = start_cursor.block() last_block = end_cursor.block() if not last_block.isValid(): last_block = doc.lastBlock() end_pos = last_block.position() + last_block.length() force_next_highlight = False while block.isValid() and (force_next_highlight or block.position() < end_pos): formats, force_next_highlight = self.parse_single_block(block) self.apply_format_changes(block, formats) doc.markContentsDirty(block.position(), block.length()) block = block.next() finally: self.ignore_requests = False @property def is_working(self): return bool(self.requests) def parse_single_block(self, block): ud, is_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, doc_name=self.doc_name) # Ensure no stale user data lingers formats = [] for i, num, fmt in run_loop(ud, self.state_map, self.formats, str(block.text())): if fmt is not None: r = QTextLayout.FormatRange() r.start, r.length, r.format = i, num, fmt formats.append(r) force_next_highlight = is_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) def apply_format_changes(self, block, formats): layout = block.layout() preedit_start = layout.preeditAreaPosition() preedit_length = len(layout.preeditAreaText()) if preedit_length != 0 and preedit_start != 0: for r in formats: # Adjust range by pre-edit text, if any if r.start >= preedit_start: r.start += preedit_length elif r.start + r.length >= preedit_start: r.length += preedit_length layout.setAdditionalFormats(formats)