diff --git a/src/calibre/gui2/tweak_book/diff.py b/src/calibre/gui2/tweak_book/diff.py index 23df2713da..3ea89b7df8 100644 --- a/src/calibre/gui2/tweak_book/diff.py +++ b/src/calibre/gui2/tweak_book/diff.py @@ -9,19 +9,76 @@ __copyright__ = '2014, Kovid Goyal ' import sys from functools import partial -from PyQt4.Qt import (QSplitter, QApplication) +from PyQt4.Qt import (QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument, QTextCursor, QTextCharFormat) -from calibre.gui2.tweak_book.editor.text import PlainTextEdit +from calibre.gui2.tweak_book import tprefs +from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family +from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color from calibre.utils.diff import get_sequence_matcher +def get_theme(): + theme = THEMES.get(tprefs['editor_theme'], None) + if theme is None: + theme = THEMES[default_theme()] + return theme + class TextBrowser(PlainTextEdit): def __init__(self, parent=None): PlainTextEdit.__init__(self, parent) self.setReadOnly(True) - self.setLineWrapMode(self.NoWrap) + w = self.fontMetrics() + self.space_width = w.width(' ') + self.setLineWrapMode(self.WidgetWidth if tprefs['editor_line_wrap'] else self.NoWrap) + self.setTabStopWidth(tprefs['editor_tab_stop_width'] * self.space_width) + font = self.font() + ff = tprefs['editor_font_family'] + if ff is None: + ff = default_font_family() + font.setFamily(ff) + font.setPointSize(tprefs['editor_font_size']) + self.setFont(font) + theme = get_theme() + pal = self.palette() + pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg')) + pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg')) + pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg')) + pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg')) + pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg')) + self.setPalette(pal) -class DiffView(QSplitter): +class Highlight(QTextDocument): + + def __init__(self, parent, text, syntax): + QTextDocument.__init__(self, parent) + self.l = QPlainTextDocumentLayout(self) + self.setDocumentLayout(self.l) + self.highlighter = get_highlighter(syntax)(self) + self.highlighter.apply_theme(get_theme()) + self.highlighter.setDocument(self) + self.setPlainText(text) + + def copy_lines(self, lo, hi, cursor): + ''' Copy specified lines from the syntax highlighted buffer into the + destination cursor, preserving all formatting created by the syntax + highlighter. ''' + num = hi - lo + if num > 0: + block = self.findBlockByNumber(lo + 1) + while num > 0: + num -= 1 + cursor.insertText(block.text()) + dest_block = cursor.block() + c = QTextCursor(dest_block) + for af in block.layout().additionalFormats(): + start = dest_block.position() + af.start + c.setPosition(start), c.setPosition(start + af.length, c.KeepAnchor) + c.setCharFormat(af.format) + cursor.insertBlock() + cursor.setCharFormat(QTextCharFormat()) + block = block.next() + +class TextDiffView(QSplitter): def __init__(self, parent=None): QSplitter.__init__(self, parent) @@ -32,10 +89,11 @@ class DiffView(QSplitter): def __call__(self, left_text, right_text, context=None, syntax=None): left_lines = left_text.splitlines() right_lines = right_text.splitlines() + self.left_highlight, self.right_highlight = Highlight(self, left_text, syntax), Highlight(self, right_text, syntax) self.context = context - self.left_insert = partial(self.do_insert, self.left.textCursor(), left_lines) - self.right_insert = partial(self.do_insert, self.right.textCursor(), right_lines) + self.left_insert = partial(self.do_insert, self.left.textCursor(), self.left_highlight) + self.right_insert = partial(self.do_insert, self.right.textCursor(), self.right_highlight) self.cruncher = get_sequence_matcher()(None, left_lines, right_lines) self.do_layout(context) @@ -44,19 +102,22 @@ class DiffView(QSplitter): def do_layout(self, context=None): self.left.clear(), self.right.clear() - opcodes = self.cruncher.get_opcodes() if context is None else self.cruncher.get_grouped_opcodes(context) - for tag, alo, ahi, blo, bhi in opcodes: - getattr(self, tag)(alo, ahi, blo, bhi) + if context is None: + for tag, alo, ahi, blo, bhi in self.cruncher.get_opcodes(): + getattr(self, tag)(alo, ahi, blo, bhi) + else: + for group in self.cruncher.get_grouped_opcodes(): + for tag, alo, ahi, blo, bhi in group: + getattr(self, tag)(alo, ahi, blo, bhi) + for v in (self.left, self.right): c = v.textCursor() c.movePosition(c.Start) v.setTextCursor(c) - def do_insert(self, cursor, lines, lo, hi): + def do_insert(self, cursor, highlighter, lo, hi): start_block = cursor.blockNumber() - if lo != hi: - cursor.insertText('\n'.join(lines[lo:hi])) - cursor.insertBlock() + highlighter.copy_lines(lo, hi, cursor) return start_block, cursor.blockNumber() def equal(self, alo, ahi, blo, bhi): @@ -76,8 +137,8 @@ if __name__ == '__main__': app = QApplication([]) raw1 = open(sys.argv[-2], 'rb').read().decode('utf-8') raw2 = open(sys.argv[-1], 'rb').read().decode('utf-8') - w = DiffView() - w(raw1, raw2) + w = TextDiffView() + w(raw1, raw2, syntax='html', context=None) w.show() app.exec_() diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index cd5e3ef240..de775ff52b 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -27,6 +27,9 @@ from calibre.gui2.tweak_book.editor.syntax.css import CSSHighlighter PARAGRAPH_SEPARATOR = '\u2029' entity_pat = re.compile(r'&(#{0,1}[a-zA-Z0-9]{1,8});') +def get_highlighter(syntax): + return {'html':HTMLHighlighter, 'css':CSSHighlighter, 'xml':XMLHighlighter}.get(syntax, SyntaxHighlighter) + _dff = None def default_font_family(): global _dff @@ -180,7 +183,7 @@ class TextEdit(PlainTextEdit): def load_text(self, text, syntax='html', process_template=False): self.syntax = syntax - self.highlighter = {'html':HTMLHighlighter, 'css':CSSHighlighter, 'xml':XMLHighlighter}.get(syntax, SyntaxHighlighter)(self) + self.highlighter = get_highlighter(syntax)(self) self.highlighter.apply_theme(self.theme) self.highlighter.setDocument(self.document()) self.setPlainText(unicodedata.normalize('NFC', text))