~ubuntu-branches/debian/sid/calibre/sid

« back to all changes in this revision

Viewing changes to src/calibre/gui2/tweak_book/diff/view.py

  • Committer: Package Import Robot
  • Author(s): Martin Pitt
  • Date: 2014-02-27 07:48:06 UTC
  • mto: This revision was merged to the branch mainline in revision 74.
  • Revision ID: package-import@ubuntu.com-20140227074806-64wdebb3ptosxhhx
Tags: upstream-1.25.0+dfsg
ImportĀ upstreamĀ versionĀ 1.25.0+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import regex
18
18
from PyQt4.Qt import (
19
 
    QSplitter, QApplication, QPlainTextDocumentLayout, QTextDocument, QTimer,
 
19
    QSplitter, QApplication, QTimer,
20
20
    QTextCursor, QTextCharFormat, Qt, QRect, QPainter, QPalette, QPen, QBrush,
21
21
    QColor, QTextLayout, QCursor, QFont, QSplitterHandle, QPainterPath,
22
22
    QHBoxLayout, QWidget, QScrollBar, QEventLoop, pyqtSignal, QImage, QPixmap,
25
25
from calibre import human_readable, fit_image
26
26
from calibre.gui2 import info_dialog
27
27
from calibre.gui2.tweak_book import tprefs
28
 
from calibre.gui2.tweak_book.editor.text import PlainTextEdit, get_highlighter, default_font_family, LineNumbers
29
 
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
 
28
from calibre.gui2.tweak_book.editor.text import PlainTextEdit, default_font_family, LineNumbers
 
29
from calibre.gui2.tweak_book.editor.themes import theme_color
30
30
from calibre.gui2.tweak_book.diff import get_sequence_matcher
 
31
from calibre.gui2.tweak_book.diff.highlight import get_theme, get_highlighter
31
32
 
32
33
Change = namedtuple('Change', 'ltop lbot rtop rbot kind')
33
34
 
39
40
    def __exit__(self, *args):
40
41
        QApplication.restoreOverrideCursor()
41
42
 
42
 
def get_theme():
43
 
    theme = THEMES.get(tprefs['editor_theme'], None)
44
 
    if theme is None:
45
 
        theme = THEMES[default_theme()]
46
 
    return theme
47
 
 
48
43
def beautify_text(raw, syntax):
49
44
    from lxml import etree
50
45
    from calibre.ebooks.oeb.polish.parsing import parse
165
160
    def show_context_menu(self, pos):
166
161
        m = QMenu(self)
167
162
        a = m.addAction
168
 
        i = unicode(self.textCursor().selectedText())
 
163
        i = unicode(self.textCursor().selectedText()).rstrip('\0')
169
164
        if i:
170
165
            a(QIcon(I('edit-copy.png')), _('Copy to clipboard'), self.copy).setShortcut(QKeySequence.Copy)
171
166
 
198
193
        for i, (num, text) in enumerate(self.headers):
199
194
            if num > block_number:
200
195
                name = text if i == 0 else self.headers[i - 1][1]
 
196
                break
201
197
        else:
202
 
            name = self.headers[0][1]
 
198
            name = self.headers[-1][1]
203
199
        self.line_activated.emit(name, lnum, bool(self.right))
204
200
 
205
201
    def search(self, query, reverse=False):
392
388
 
393
389
# }}}
394
390
 
395
 
class Highlight(QTextDocument):  # {{{
396
 
 
397
 
    def __init__(self, parent, text, syntax):
398
 
        QTextDocument.__init__(self, parent)
399
 
        self.l = QPlainTextDocumentLayout(self)
400
 
        self.setDocumentLayout(self.l)
401
 
        self.highlighter = get_highlighter(syntax)(self)
402
 
        self.highlighter.apply_theme(get_theme())
403
 
        self.highlighter.setDocument(self)
404
 
        self.setPlainText(text)
405
 
 
406
 
    def copy_lines(self, lo, hi, cursor):
407
 
        ''' Copy specified lines from the syntax highlighted buffer into the
408
 
        destination cursor, preserving all formatting created by the syntax
409
 
        highlighter. '''
410
 
        num = hi - lo
411
 
        if num > 0:
412
 
            block = self.findBlockByNumber(lo)
413
 
            while num > 0:
414
 
                num -= 1
415
 
                cursor.insertText(block.text())
416
 
                dest_block = cursor.block()
417
 
                c = QTextCursor(dest_block)
418
 
                for af in block.layout().additionalFormats():
419
 
                    start = dest_block.position() + af.start
420
 
                    c.setPosition(start), c.setPosition(start + af.length, c.KeepAnchor)
421
 
                    c.setCharFormat(af.format)
422
 
                cursor.insertBlock()
423
 
                cursor.setCharFormat(QTextCharFormat())
424
 
                block = block.next()
425
 
# }}}
426
 
 
427
391
class DiffSplitHandle(QSplitterHandle):  # {{{
428
392
 
429
393
    WIDTH = 30  # px
692
656
        right_text = unicodedata.normalize('NFC', right_text)
693
657
        if beautify and syntax in {'xml', 'html', 'css'}:
694
658
            left_text, right_text = beautify_text(left_text, syntax), beautify_text(right_text, syntax)
 
659
            if len(left_text) == len(right_text) and left_text == right_text:
 
660
                for v in (self.left, self.right):
 
661
                    c = v.textCursor()
 
662
                    c.movePosition(c.End)
 
663
                    c.insertText('[%s]\n\n' % _('The files are identical after beautifying'))
 
664
                return
 
665
 
695
666
        left_lines = self.left_lines = left_text.splitlines()
696
667
        right_lines = self.right_lines = right_text.splitlines()
697
668
 
698
669
        cruncher = get_sequence_matcher()(None, left_lines, right_lines)
699
670
 
700
 
        left_highlight, right_highlight = Highlight(self, left_text, syntax), Highlight(self, right_text, syntax)
 
671
        left_highlight, right_highlight = get_highlighter(self.left, left_text, syntax), get_highlighter(self.right, right_text, syntax)
701
672
        cl, cr = self.left_cursor, self.right_cursor = self.left.textCursor(), self.right.textCursor()
702
673
        cl.beginEditBlock(), cr.beginEditBlock()
703
674
        cl.movePosition(cl.End), cr.movePosition(cr.End)
718
689
                self.left.line_number_map[self.changes[-1].ltop] = '-'
719
690
                self.right.line_number_map[self.changes[-1].rtop] = '-'
720
691
 
 
692
            ahi = bhi = 0
721
693
            for i, group in enumerate(cruncher.get_grouped_opcodes(context)):
722
694
                for j, (tag, alo, ahi, blo, bhi) in enumerate(group):
723
695
                    if j == 0 and (i > 0 or min(alo, blo) > 0):
778
750
        self.changes.append(Change(
779
751
            rtop=start_block, rbot=current_block, ltop=l, lbot=l, kind='insert'))
780
752
 
 
753
    def trim_identical_leading_lines(self, alo, ahi, blo, bhi):
 
754
        ''' The patience diff algorithm sometimes results in a block of replace
 
755
        lines with identical leading lines. Remove these. This can cause extra
 
756
        lines of context, but that is better than having extra lines of diff
 
757
        with no actual changes. '''
 
758
        a, b = self.left_lines, self.right_lines
 
759
        leading = 0
 
760
        while alo < ahi and blo < bhi and a[alo] == b[blo]:
 
761
            leading += 1
 
762
            alo += 1
 
763
            blo += 1
 
764
        if leading > 0:
 
765
            self.equal(alo - leading, alo, blo - leading, blo)
 
766
        return alo, ahi, blo, bhi
 
767
 
781
768
    def replace(self, alo, ahi, blo, bhi):
782
769
        ''' When replacing one block of lines with another, search the blocks
783
770
        for *similar* lines; the best-matching pair (if any) is used as a synch
784
771
        point, and intraline difference marking is done on the similar pair.
785
772
        Lots of work, but often worth it.  '''
 
773
        alo, ahi, blo, bhi = self.trim_identical_leading_lines(alo, ahi, blo, bhi)
 
774
        if alo == ahi and blo == bhi:
 
775
            return
786
776
        if ahi + bhi - alo - blo > 100:
787
777
            # Too many lines, this will be too slow
788
778
            # http://bugs.python.org/issue6931
968
958
        changes = self.changes[which]
969
959
        bar = self.bars[which]
970
960
        syncpos = self.syncpos + bar.value()
971
 
        prev = (0, 0, None)
 
961
        prev = 0
972
962
        for i, (top, bot, kind) in enumerate(changes):
973
963
            if syncpos <= bot:
974
964
                if top <= syncpos:
979
969
                        ratio = 0
980
970
                    return 'in', i, ratio
981
971
                else:
982
 
                    # syncpos is after the change
983
 
                    offset = syncpos - prev[1]
 
972
                    # syncpos is after the previous change
 
973
                    offset = syncpos - prev
984
974
                    return 'after', i - 1, offset
985
 
                break
986
975
            else:
987
 
                prev = (top, bot, kind)
988
 
        else:
989
 
            offset = syncpos - prev[1]
990
 
            return 'after', len(self.changes) - 1, offset
 
976
                # syncpos is after the current change
 
977
                prev = bot
 
978
        offset = syncpos - prev
 
979
        return 'after', len(changes) - 1, offset
991
980
 
992
981
    def scroll_to(self, which, position):
993
982
        changes = self.changes[which]