Refactor to support multiple diffs in the same view

This commit is contained in:
Kovid Goyal 2014-01-20 17:08:03 +05:30
parent 2eccbae35f
commit 20295489cd

View File

@ -14,7 +14,7 @@ from difflib import SequenceMatcher
from PyQt4.Qt import ( from PyQt4.Qt import (
QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument, QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument,
QTextCursor, QTextCharFormat, Qt, QRect, QPainter, QPalette, QPen, QTextCursor, QTextCharFormat, Qt, QRect, QPainter, QPalette, QPen,
QBrush, QColor, QTextLayout) QBrush, QColor, QTextLayout, QCursor)
from calibre.gui2.tweak_book import tprefs from calibre.gui2.tweak_book import tprefs
from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family, LineNumbers from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family, LineNumbers
@ -23,6 +23,14 @@ from calibre.utils.diff import get_sequence_matcher
Change = namedtuple('Change', 'ltop lbot rtop rbot kind') Change = namedtuple('Change', 'ltop lbot rtop rbot kind')
class BusyCursor(object):
def __enter__(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
def __exit__(self, *args):
QApplication.restoreOverrideCursor()
def get_theme(): def get_theme():
theme = THEMES.get(tprefs['editor_theme'], None) theme = THEMES.get(tprefs['editor_theme'], None)
if theme is None: if theme is None:
@ -222,36 +230,52 @@ class TextDiffView(QSplitter):
self.left, self.right = TextBrowser(parent=self), TextBrowser(right=True, parent=self) self.left, self.right = TextBrowser(parent=self), TextBrowser(right=True, parent=self)
self.addWidget(self.left), self.addWidget(self.right) self.addWidget(self.left), self.addWidget(self.right)
self.split_words = re.compile(r"\w+|\W", re.UNICODE) self.split_words = re.compile(r"\w+|\W", re.UNICODE)
self.clear()
def __call__(self, left_text, right_text, context=None, syntax=None): def clear(self):
self.left.clear(), self.right.clear()
self.changes = []
def finalize(self):
for v in (self.left, self.right):
c = v.textCursor()
c.movePosition(c.Start)
v.setTextCursor(c)
self.update()
def add_diff(self, left_text, right_text, context=None, syntax=None):
is_text = isinstance(left_text, type('')) or isinstance(right_text, type(''))
with BusyCursor():
if is_text:
self.add_text_diff(left_text, right_text, context, syntax)
def add_text_diff(self, left_text, right_text, context, syntax):
left_text = unicodedata.normalize('NFC', left_text) left_text = unicodedata.normalize('NFC', left_text)
right_text = unicodedata.normalize('NFC', right_text) right_text = unicodedata.normalize('NFC', right_text)
left_lines = self.left_lines = left_text.splitlines() left_lines = self.left_lines = left_text.splitlines()
right_lines = self.right_lines = right_text.splitlines() right_lines = self.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.cruncher = get_sequence_matcher()(None, left_lines, right_lines) cruncher = get_sequence_matcher()(None, left_lines, right_lines)
self.do_layout(context)
def refresh(self): left_highlight, right_highlight = Highlight(self, left_text, syntax), Highlight(self, right_text, syntax)
self.do_layout(self.context)
def do_layout(self, context=None):
self.left.clear(), self.right.clear()
cl, cr = self.left_cursor, self.right_cursor = self.left.textCursor(), self.right.textCursor() cl, cr = self.left_cursor, self.right_cursor = self.left.textCursor(), self.right.textCursor()
cl.beginEditBlock(), cr.beginEditBlock() cl.beginEditBlock(), cr.beginEditBlock()
cl.movePosition(cl.End), cr.movePosition(cr.End)
self.left_insert = partial(self.do_insert, cl, left_highlight, self.left.line_number_map)
self.right_insert = partial(self.do_insert, cr, right_highlight, self.right.line_number_map)
ochanges = []
self.changes = [] self.changes = []
self.left_insert = partial(self.do_insert, cl, self.left_highlight, self.left.line_number_map)
self.right_insert = partial(self.do_insert, cr, self.right_highlight, self.right.line_number_map)
if context is None: if context is None:
for tag, alo, ahi, blo, bhi in self.cruncher.get_opcodes(): for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
getattr(self, tag)(alo, ahi, blo, bhi) getattr(self, tag)(alo, ahi, blo, bhi)
QApplication.processEvents()
else: else:
for group in self.cruncher.get_grouped_opcodes(context): for group in cruncher.get_grouped_opcodes(context):
for tag, alo, ahi, blo, bhi in group: for tag, alo, ahi, blo, bhi in group:
getattr(self, tag)(alo, ahi, blo, bhi) getattr(self, tag)(alo, ahi, blo, bhi)
QApplication.processEvents()
cl.insertBlock(), cr.insertBlock() cl.insertBlock(), cr.insertBlock()
self.changes.append(Change( self.changes.append(Change(
ltop=cl.block().blockNumber()-1, lbot=cl.block().blockNumber(), ltop=cl.block().blockNumber()-1, lbot=cl.block().blockNumber(),
@ -259,13 +283,11 @@ class TextDiffView(QSplitter):
self.left.line_number_map[self.changes[-1].ltop] = '-' self.left.line_number_map[self.changes[-1].ltop] = '-'
self.right.line_number_map[self.changes[-1].rtop] = '-' self.right.line_number_map[self.changes[-1].rtop] = '-'
cl.endEditBlock(), cr.endEditBlock() cl.endEditBlock(), cr.endEditBlock()
self.equalize_block_counts()
del self.left_lines del self.left_lines
del self.right_lines del self.right_lines
del self.left_insert
for v in (self.left, self.right): del self.right_insert
c = v.textCursor()
c.movePosition(c.Start)
v.setTextCursor(c)
self.coalesce_changes() self.coalesce_changes()
@ -274,7 +296,13 @@ class TextDiffView(QSplitter):
self.left.changes.append((ltop, lbot, kind)) self.left.changes.append((ltop, lbot, kind))
self.right.changes.append((rtop, rbot, kind)) self.right.changes.append((rtop, rbot, kind))
self.update() self.changes = ochanges + self.changes
def equalize_block_counts(self):
l, r = self.left.blockCount(), self.right.blockCount()
c = (self.left if l < r else self.right).textCursor()
c.movePosition(c.End)
c.insertText('\n' * (abs(l - r)))
def coalesce_changes(self): def coalesce_changes(self):
'Merge neighboring changes of the same kind, if any' 'Merge neighboring changes of the same kind, if any'
@ -429,7 +457,8 @@ if __name__ == '__main__':
raw2 = open(sys.argv[-1], 'rb').read().decode('utf-8') raw2 = open(sys.argv[-1], 'rb').read().decode('utf-8')
w = TextDiffView() w = TextDiffView()
w.show() w.show()
w(raw1, raw2, syntax='html', context=3) w.add_diff(raw1, raw2, syntax='html', context=3)
w.finalize()
app.exec_() app.exec_()
# TODO: Add diff colors for other color schemes # TODO: Add diff colors for other color schemes