mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor to support multiple diffs in the same view
This commit is contained in:
parent
2eccbae35f
commit
20295489cd
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user