~debian-bazaar/qbzr/experimental

« back to all changes in this revision

Viewing changes to lib/diffwindow.py

  • Committer: Stefano Karapetsas
  • Date: 2012-02-09 10:16:03 UTC
  • mfrom: (1017.1.1 qbzr)
  • Revision ID: stefano@karapetsas.com-20120209101603-huac7q4h3ntsn4x9
Tags: 0.22-1
releasing version 0.22-1

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
    show_diff,
45
45
    has_ext_diff,
46
46
    ExtDiffMenu,
 
47
    DiffItem,
47
48
    )
48
49
 
49
50
from bzrlib.plugins.qbzr.lib.i18n import gettext, ngettext, N_
65
66
from bzrlib.plugins.qbzr.lib.trace import reports_exception
66
67
from bzrlib.plugins.qbzr.lib.encoding_selector import EncodingMenuSelector
67
68
from bzrlib.plugins.qbzr.lib.widgets.tab_width_selector import TabWidthMenuSelector
68
 
 
69
 
try:
70
 
    from bzrlib.errors import FileTimestampUnavailable
71
 
except ImportError:
72
 
    # FileTimestampUnavailable is available only in bzr 2.1.0rc1 and up
73
 
    from bzrlib.errors import BzrError
74
 
    class FileTimestampUnavailable(BzrError):
75
 
        """Fake FileTimestampUnavailable error for older bzr."""
76
 
        pass
77
 
 
78
 
 
79
 
def get_file_lines_from_tree(tree, file_id):
80
 
    try:
81
 
        return tree.get_file_lines(file_id)
82
 
    except AttributeError:
83
 
        return tree.get_file(file_id).readlines()
 
69
from bzrlib.plugins.qbzr.lib.widgets.texteditaccessory import setup_guidebar_for_find
 
70
 
 
71
 
84
72
 
85
73
def get_title_for_tree(tree, branch, other_branch):
86
74
    branch_title = ""
125
113
    # XXX I don't know what other cases we need to handle
126
114
    return 'Unknown tree'
127
115
 
128
 
 
129
 
class DiffItem(object):
130
 
    
131
 
    @classmethod
132
 
    def create(klass, trees, file_id, paths, changed_content, versioned, 
133
 
            parent, name, kind, executable, filter = None):
134
 
 
135
 
        if parent == (None, None): # filter out TREE_ROOT (?)
136
 
            return None
137
 
 
138
 
        # check for manually deleted files (w/o using bzr rm commands)
139
 
        if kind[1] is None:
140
 
            if versioned == (False, True):
141
 
                # added and missed
142
 
                return None
143
 
            if versioned == (True, True):
144
 
                versioned = (True, False)
145
 
                paths = (paths[0], None)
146
 
 
147
 
        renamed = (parent[0], name[0]) != (parent[1], name[1])
148
 
 
149
 
        dates = [None, None]
150
 
        for ix in range(2):
151
 
            if versioned[ix]:
152
 
                try:
153
 
                    dates[ix] = trees[ix].get_file_mtime(file_id, paths[ix])
154
 
                except OSError, e:
155
 
                    if not renamed or e.errno != errno.ENOENT:
156
 
                        raise
157
 
                    # If we get ENOENT error then probably we trigger
158
 
                    # bug #251532 in bzrlib. Take current time instead
159
 
                    dates[ix] = time.time()
160
 
                except FileTimestampUnavailable:
161
 
                    # ghosts around us (see Bug #513096)
162
 
                    dates[ix] = 0  # using 1970/1/1 instead
163
 
 
164
 
        properties_changed = [] 
165
 
        if bool(executable[0]) != bool(executable[1]):
166
 
            descr = {True: "+x", False: "-x", None: None}
167
 
            properties_changed.append((descr[executable[0]],
168
 
                                       descr[executable[1]]))
169
 
 
170
 
        if versioned == (True, False):
171
 
            status = N_('removed')
172
 
        elif versioned == (False, True):
173
 
            status = N_('added')
174
 
        elif renamed and changed_content:
175
 
            status = N_('renamed and modified')
176
 
        elif renamed:
177
 
            status = N_('renamed')
178
 
        else:
179
 
            status = N_('modified')
180
 
        # check filter options
181
 
        if filter and not filter(status):
182
 
            return None
183
 
 
184
 
        return klass(trees, file_id, paths, changed_content, versioned, kind, 
185
 
                        properties_changed, dates, status)
186
 
 
187
 
    def __init__(self, trees, file_id, paths, changed_content, versioned, kind,
188
 
                        properties_changed, dates, status):
189
 
        self.trees = trees
190
 
        self.file_id = file_id
191
 
        self.paths = paths
192
 
        self.changed_content = changed_content
193
 
        self.versioned = versioned
194
 
        self.kind = kind
195
 
        self.properties_changed = properties_changed
196
 
        self.dates = dates
197
 
        self.status = status
198
 
 
199
 
        self._lines = None
200
 
        self._binary = None
201
 
        self._group_cache = {}
202
 
        self._encodings = [None, None]
203
 
        self._ulines = [None, None]
204
 
 
205
 
    def load(self):
206
 
        if self._lines is None:
207
 
            self._load_lines()
208
 
 
209
 
    def _load_lines(self):
210
 
        if ((self.versioned[0] != self.versioned[1] or self.changed_content)
211
 
            and (self.kind[0] == 'file' or self.kind[1] == 'file')):
212
 
            lines = []
213
 
            binary = False
214
 
            for ix, tree in enumerate(self.trees):
215
 
                content = ()
216
 
                if self.versioned[ix] and self.kind[ix] == 'file':
217
 
                    content = get_file_lines_from_tree(tree, self.file_id)
218
 
                lines.append(content)
219
 
                binary = binary or is_binary_content(content)
220
 
            self._lines = lines
221
 
            self._binary = binary
222
 
        else:
223
 
            self._lines = ((),())
224
 
            self._binary = False
225
 
 
226
 
    @property
227
 
    def lines(self):
228
 
        if self._lines is None:
229
 
            self._load_lines()
230
 
        return self._lines
231
 
 
232
 
    @property
233
 
    def binary(self):
234
 
        if self._binary is None:
235
 
            self._load_lines()
236
 
        return self._binary
237
 
 
238
 
    def groups(self, complete, ignore_whitespace):
239
 
        key = (complete, ignore_whitespace)
240
 
        groups = self._group_cache.get(key)
241
 
        if groups is not None:
242
 
            return groups
243
 
 
244
 
        lines = self.lines
245
 
 
246
 
        if not self.binary:
247
 
            if self.versioned == (True, False):
248
 
                groups = [[('delete', 0, len(lines[0]), 0, 0)]]
249
 
            elif self.versioned == (False, True):
250
 
                groups = [[('insert', 0, 0, 0, len(lines[1]))]]
251
 
            else:
252
 
                groups = self.difference_groups(lines, complete, ignore_whitespace)
253
 
        else:
254
 
            groups = []
255
 
 
256
 
        self._group_cache[key] = groups
257
 
        return groups
258
 
 
259
 
    def difference_groups(self, lines, complete, ignore_whitespace):
260
 
        left, right = lines
261
 
        if ignore_whitespace:
262
 
            re_whitespaces = re.compile("\s+")
263
 
            left  = (re_whitespaces.sub(" ", line) for line in left)
264
 
            right = (re_whitespaces.sub(" ", line) for line in right)
265
 
        matcher = SequenceMatcher(None, left, right)
266
 
        if complete:
267
 
            groups = list([matcher.get_opcodes()])
268
 
        else:
269
 
            groups = list(matcher.get_grouped_opcodes())
270
 
 
271
 
        return groups
272
 
 
273
 
    def encode(self, encodings):
274
 
        lines = self.lines
275
 
        ulines = self._ulines
276
 
        for i in range(2):
277
 
            if encodings[i] != self._encodings[i]:
278
 
                self._encodings[i] = encodings[i]
279
 
                if self.binary:
280
 
                    ulines[i] = lines[i][:]
281
 
                else:
282
 
                    try:
283
 
                        ulines[i] = [l.decode(encodings[i]) for l in lines[i]]
284
 
                    except UnicodeDecodeError, e:
285
 
                        trace.note('Failed to decode using %s, falling back to latin1', e.encoding)
286
 
                        ulines[i] = [l.decode('latin1') for l in lines[i]]
287
 
        return ulines
288
 
 
289
 
 
290
116
class DiffWindow(QBzrWindow):
291
117
 
292
118
    def __init__(self, arg_provider, parent=None,
310
136
        self.diffview = SidebySideDiffView(self)
311
137
        self.sdiffview = SimpleDiffView(self)
312
138
        self.views = (self.diffview, self.sdiffview)
 
139
        for view in self.views:
 
140
            view.set_complete(complete)
313
141
 
314
142
        self.stack = QtGui.QStackedWidget(self.centralwidget)
315
143
        self.stack.addWidget(self.diffview)
327
155
 
328
156
        self.create_main_toolbar(allow_refresh)
329
157
        self.addToolBarBreak()
330
 
        self.find_toolbar = FindToolbar(self, self.diffview.browsers[0],
 
158
        self.find_toolbar = FindToolbar(self, self.diffview.browsers,
331
159
                self.show_find)
332
160
        self.find_toolbar.hide()
333
161
        self.addToolBar(self.find_toolbar)
 
162
        setup_guidebar_for_find(self.sdiffview, self.find_toolbar, 1)
 
163
        for gb in self.diffview.guidebar_panels:
 
164
            setup_guidebar_for_find(gb, self.find_toolbar, 1)
334
165
 
335
166
    def connect_later(self, *args, **kwargs):
336
167
        """Schedules a signal to be connected after loading CLI arguments.
510
341
    def eventFilter(self, object, event):
511
342
        if event.type() == QtCore.QEvent.FocusIn:
512
343
            if object in self.diffview.browsers:
513
 
                self.find_toolbar.text_edit = object
 
344
                self.find_toolbar.set_text_edit(object)
514
345
        return QBzrWindow.eventFilter(self, object, event)
515
346
        # Why doesn't this work?
516
347
        #return super(DiffWindow, self).eventFilter(object, event)
630
461
                    self.processEvents()
631
462
                    groups = di.groups(self.complete, self.ignore_whitespace)
632
463
                    self.processEvents()
633
 
                    ulines = di.encode([self.encoding_selector_left.encoding,
634
 
                                        self.encoding_selector_right.encoding])
 
464
                    ulines = di.get_unicode_lines(
 
465
                        (self.encoding_selector_left.encoding,
 
466
                         self.encoding_selector_right.encoding))
635
467
                    data = [''.join(l) for l in ulines]
636
468
 
637
469
                    for view in self.views:
652
484
            QtGui.QMessageBox.information(self, gettext('Diff'),
653
485
                gettext('No changes found.'),
654
486
                gettext('&OK'))
 
487
        for t in self.views[0].browsers + (self.views[1],):
 
488
            t.emit(QtCore.SIGNAL("documentChangeFinished()"))
655
489
        self.view_refresh.setEnabled(self.can_refresh())
656
490
 
657
491
    def click_toggle_view_mode(self, checked):
658
492
        if checked:
659
493
            view = self.sdiffview
660
 
            self.find_toolbar.text_edit = view
 
494
            self.find_toolbar.set_text_edits([view.view])
661
495
            self.tab_width_selector_left.menuAction().setVisible(False)
662
496
            self.tab_width_selector_right.menuAction().setVisible(False)
663
497
            self.tab_width_selector_unidiff.menuAction().setVisible(True)
664
498
        else:
665
499
            view = self.diffview
666
 
            self.find_toolbar.text_edit = view.browsers[0]
 
500
            self.find_toolbar.set_text_edits(view.browsers)
667
501
            self.tab_width_selector_left.menuAction().setVisible(True)
668
502
            self.tab_width_selector_right.menuAction().setVisible(True)
669
503
            self.tab_width_selector_unidiff.menuAction().setVisible(False)
674
508
    def click_complete(self, checked ):
675
509
        self.complete = checked
676
510
        #Has the side effect of refreshing...
 
511
        self.diffview.set_complete(checked)
 
512
        self.sdiffview.set_complete(checked)
677
513
        self.diffview.clear()
678
514
        self.sdiffview.clear()
679
515
        run_in_loading_queue(self.load_diff)