/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to diff.py

  • Committer: Jelmer Vernooij
  • Date: 2007-03-09 17:47:28 UTC
  • Revision ID: jelmer@samba.org-20070309174728-gljlmt9b7fu0rrn9
Add simple test for tortoise_bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
1
2
# -*- coding: UTF-8 -*-
2
3
"""Difference window.
3
4
 
11
12
 
12
13
from cStringIO import StringIO
13
14
 
14
 
import pygtk
15
 
pygtk.require("2.0")
16
15
import gtk
17
16
import pango
18
 
import os
19
 
import re
20
17
import sys
21
18
 
22
19
try:
24
21
    have_gtksourceview = True
25
22
except ImportError:
26
23
    have_gtksourceview = False
27
 
try:
28
 
    import gconf
29
 
    have_gconf = True
30
 
except ImportError:
31
 
    have_gconf = False
32
 
 
33
 
from bzrlib import osutils
34
 
from bzrlib.diff import show_diff_trees, internal_diff
 
24
 
 
25
import bzrlib
 
26
 
 
27
from bzrlib.diff import show_diff_trees
35
28
from bzrlib.errors import NoSuchFile
36
 
from bzrlib.trace import warning
37
 
from bzrlib.plugins.gtk.window import Window
38
 
 
39
 
 
40
 
class DiffView(gtk.ScrolledWindow):
41
 
    """This is the soft and chewy filling for a DiffWindow."""
 
29
 
 
30
 
 
31
class DiffWindow(gtk.Window):
 
32
    """Diff window.
 
33
 
 
34
    This object represents and manages a single window containing the
 
35
    differences between two revisions on a branch.
 
36
    """
42
37
 
43
38
    def __init__(self):
44
 
        gtk.ScrolledWindow.__init__(self)
 
39
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
40
        self.set_border_width(0)
 
41
        self.set_title("bzrk diff")
 
42
 
 
43
        # Use two thirds of the screen by default
 
44
        screen = self.get_screen()
 
45
        monitor = screen.get_monitor_geometry(0)
 
46
        width = int(monitor.width * 0.66)
 
47
        height = int(monitor.height * 0.66)
 
48
        self.set_default_size(width, height)
45
49
 
46
50
        self.construct()
47
 
        self.rev_tree = None
48
 
        self.parent_tree = None
49
51
 
50
52
    def construct(self):
51
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
52
 
        self.set_shadow_type(gtk.SHADOW_IN)
53
 
 
54
 
        if have_gtksourceview:
55
 
            self.buffer = gtksourceview.SourceBuffer()
56
 
            slm = gtksourceview.SourceLanguagesManager()
57
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
58
 
            if have_gconf:
59
 
                self.apply_gedit_colors(gsl)
60
 
            self.apply_colordiff_colors(gsl)
61
 
            self.buffer.set_language(gsl)
62
 
            self.buffer.set_highlight(True)
63
 
 
64
 
            sourceview = gtksourceview.SourceView(self.buffer)
65
 
        else:
66
 
            self.buffer = gtk.TextBuffer()
67
 
            sourceview = gtk.TextView(self.buffer)
68
 
 
69
 
        sourceview.set_editable(False)
70
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
71
 
        self.add(sourceview)
72
 
        sourceview.show()
73
 
 
74
 
    @staticmethod
75
 
    def apply_gedit_colors(lang):
76
 
        """Set style for lang to that specified in gedit configuration.
77
 
 
78
 
        This method needs the gconf module.
79
 
 
80
 
        :param lang: a gtksourceview.SourceLanguage object.
81
 
        """
82
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
83
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
84
 
 
85
 
        client = gconf.client_get_default()
86
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
87
 
 
88
 
        for tag in lang.get_tags():
89
 
            tag_id = tag.get_id()
90
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
91
 
            style_string = client.get_string(gconf_key)
92
 
 
93
 
            if style_string is None:
94
 
                continue
95
 
 
96
 
            # function to get a bool from a string that's either '0' or '1'
97
 
            string_bool = lambda x: bool(int(x))
98
 
 
99
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
100
 
            # values are: mask, fg, bg, italic, bold, underline, strike
101
 
            # this packs them into (str_value, attr_name, conv_func) tuples
102
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
103
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
104
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
105
 
                    string_bool, string_bool, string_bool ]
106
 
            )
107
 
 
108
 
            style = gtksourceview.SourceTagStyle()
109
 
 
110
 
            # XXX The mask attribute controls whether the present values of
111
 
            # foreground and background color should in fact be used. Ideally
112
 
            # (and that's what gedit does), one could set all three attributes,
113
 
            # and let the TagStyle object figure out which colors to use.
114
 
            # However, in the GtkSourceview python bindings, the mask attribute
115
 
            # is read-only, and it's derived instead from the colors being
116
 
            # set or not. This means that we have to sometimes refrain from
117
 
            # setting fg or bg colors, depending on the value of the mask.
118
 
            # This code could go away if mask were writable.
119
 
            mask = int(items[0][0])
120
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
121
 
                items[2:3] = []
122
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
123
 
                items[1:2] = []
124
 
            items[0:1] = [] # skip the mask unconditionally
125
 
 
126
 
            for value, attr, func in items:
127
 
                try:
128
 
                    value = func(value)
129
 
                except ValueError:
130
 
                    warning('gconf key %s contains an invalid value: %s'
131
 
                            % gconf_key, value)
132
 
                else:
133
 
                    setattr(style, attr, value)
134
 
 
135
 
            lang.set_tag_style(tag_id, style)
136
 
 
137
 
    @staticmethod
138
 
    def apply_colordiff_colors(lang):
139
 
        """Set style colors for lang using the colordiff configuration file.
140
 
 
141
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
142
 
 
143
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
144
 
        """
145
 
        colors = {}
146
 
 
147
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
148
 
            f = os.path.expanduser(f)
149
 
            if os.path.exists(f):
150
 
                try:
151
 
                    f = file(f)
152
 
                except IOError, e:
153
 
                    warning('could not open file %s: %s' % (f, str(e)))
154
 
                else:
155
 
                    colors.update(DiffView.parse_colordiffrc(f))
156
 
                    f.close()
157
 
 
158
 
        if not colors:
159
 
            # ~/.colordiffrc does not exist
160
 
            return
161
 
 
162
 
        mapping = {
163
 
                # map GtkSourceView tags to colordiff names
164
 
                # since GSV is richer, accept new names for extra bits,
165
 
                # defaulting to old names if they're not present
166
 
                'Added@32@line': ['newtext'],
167
 
                'Removed@32@line': ['oldtext'],
168
 
                'Location': ['location', 'diffstuff'],
169
 
                'Diff@32@file': ['file', 'diffstuff'],
170
 
                'Special@32@case': ['specialcase', 'diffstuff'],
171
 
        }
172
 
 
173
 
        for tag in lang.get_tags():
174
 
            tag_id = tag.get_id()
175
 
            keys = mapping.get(tag_id, [])
176
 
            color = None
177
 
 
178
 
            for key in keys:
179
 
                color = colors.get(key, None)
180
 
                if color is not None:
181
 
                    break
182
 
 
183
 
            if color is None:
184
 
                continue
185
 
 
186
 
            style = gtksourceview.SourceTagStyle()
187
 
            try:
188
 
                style.foreground = gtk.gdk.color_parse(color)
189
 
            except ValueError:
190
 
                warning('not a valid color: %s' % color)
191
 
            else:
192
 
                lang.set_tag_style(tag_id, style)
193
 
 
194
 
    @staticmethod
195
 
    def parse_colordiffrc(fileobj):
196
 
        """Parse fileobj as a colordiff configuration file.
197
 
 
198
 
        :return: A dict with the key -> value pairs.
199
 
        """
200
 
        colors = {}
201
 
        for line in fileobj:
202
 
            if re.match(r'^\s*#', line):
203
 
                continue
204
 
            if '=' not in line:
205
 
                continue
206
 
            key, val = line.split('=', 1)
207
 
            colors[key.strip()] = val.strip()
208
 
        return colors
209
 
 
210
 
    def set_trees(self, rev_tree, parent_tree):
211
 
        self.rev_tree = rev_tree
212
 
        self.parent_tree = parent_tree
213
 
#        self._build_delta()
214
 
 
215
 
#    def _build_delta(self):
216
 
#        self.parent_tree.lock_read()
217
 
#        self.rev_tree.lock_read()
218
 
#        try:
219
 
#            self.delta = _iter_changes_to_status(self.parent_tree, self.rev_tree)
220
 
#            self.path_to_status = {}
221
 
#            self.path_to_diff = {}
222
 
#            source_inv = self.parent_tree.inventory
223
 
#            target_inv = self.rev_tree.inventory
224
 
#            for (file_id, real_path, change_type, display_path) in self.delta:
225
 
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
226
 
#                if change_type in ('modified', 'renamed and modified'):
227
 
#                    source_ie = source_inv[file_id]
228
 
#                    target_ie = target_inv[file_id]
229
 
#                    sio = StringIO()
230
 
#                    source_ie.diff(internal_diff, *old path, *old_tree,
231
 
#                                   *new_path, target_ie, self.rev_tree,
232
 
#                                   sio)
233
 
#                    self.path_to_diff[real_path] = 
234
 
#
235
 
#        finally:
236
 
#            self.rev_tree.unlock()
237
 
#            self.parent_tree.unlock()
238
 
 
239
 
    def show_diff(self, specific_files):
240
 
        s = StringIO()
241
 
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
242
 
                        old_label='', new_label='',
243
 
                        # path_encoding=sys.getdefaultencoding()
244
 
                        # The default is utf-8, but we interpret the file
245
 
                        # contents as getdefaultencoding(), so we should
246
 
                        # probably try to make the paths in the same encoding.
247
 
                        )
248
 
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
249
 
        # character is not valid in 'encoding' there is nothing to replace, the
250
 
        # 'replace' is for 'str.encode()'
251
 
        try:
252
 
            decoded = s.getvalue().decode(sys.getdefaultencoding())
253
 
        except UnicodeDecodeError:
254
 
            try:
255
 
                decoded = s.getvalue().decode('UTF-8')
256
 
            except UnicodeDecodeError:
257
 
                decoded = s.getvalue().decode('iso-8859-1')
258
 
                # This always works, because every byte has a valid
259
 
                # mapping from iso-8859-1 to Unicode
260
 
        # TextBuffer must contain pure UTF-8 data
261
 
        self.buffer.set_text(decoded.encode('UTF-8'))
262
 
 
263
 
 
264
 
class DiffWidget(gtk.HPaned):
265
 
    """Diff widget
266
 
 
267
 
    """
268
 
    def __init__(self):
269
 
        super(DiffWidget, self).__init__()
 
53
        """Construct the window contents."""
 
54
        # The   window  consists  of   a  pane   containing:  the
 
55
        # hierarchical list  of files on  the left, and  the diff
 
56
        # for the currently selected file on the right.
 
57
        pane = gtk.HPaned()
 
58
        self.add(pane)
 
59
        pane.show()
270
60
 
271
61
        # The file hierarchy: a scrollable treeview
272
62
        scrollwin = gtk.ScrolledWindow()
273
63
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
274
64
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
275
 
        self.pack1(scrollwin)
 
65
        pane.pack1(scrollwin)
276
66
        scrollwin.show()
277
67
 
278
68
        self.model = gtk.TreeStore(str, str)
292
82
 
293
83
        # The diffs of the  selected file: a scrollable source or
294
84
        # text view
295
 
        self.diff_view = DiffView()
296
 
        self.pack2(self.diff_view)
297
 
        self.diff_view.show()
298
 
 
299
 
    def set_diff(self, rev_tree, parent_tree):
 
85
        scrollwin = gtk.ScrolledWindow()
 
86
        scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
87
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
88
        pane.pack2(scrollwin)
 
89
        scrollwin.show()
 
90
 
 
91
        if have_gtksourceview:
 
92
            self.buffer = gtksourceview.SourceBuffer()
 
93
            slm = gtksourceview.SourceLanguagesManager()
 
94
            gsl = slm.get_language_from_mime_type("text/x-patch")
 
95
            self.buffer.set_language(gsl)
 
96
            self.buffer.set_highlight(True)
 
97
 
 
98
            sourceview = gtksourceview.SourceView(self.buffer)
 
99
        else:
 
100
            self.buffer = gtk.TextBuffer()
 
101
            sourceview = gtk.TextView(self.buffer)
 
102
 
 
103
        sourceview.set_editable(False)
 
104
        sourceview.modify_font(pango.FontDescription("Monospace"))
 
105
        scrollwin.add(sourceview)
 
106
        sourceview.show()
 
107
 
 
108
    def set_diff(self, description, rev_tree, parent_tree):
300
109
        """Set the differences showed by this window.
301
110
 
302
111
        Compares the two trees and populates the window with the
303
112
        differences.
304
113
        """
305
 
        self.diff_view.set_trees(rev_tree, parent_tree)
306
114
        self.rev_tree = rev_tree
307
115
        self.parent_tree = parent_tree
308
116
 
333
141
                self.model.append(titer, [ path, path ])
334
142
 
335
143
        self.treeview.expand_all()
 
144
        self.set_title(description + " - bzrk diff")
336
145
 
337
146
    def set_file(self, file_path):
338
147
        tv_path = None
353
162
        if specific_files == [ None ]:
354
163
            return
355
164
        elif specific_files == [ "" ]:
356
 
            specific_files = None
357
 
 
358
 
        self.diff_view.show_diff(specific_files)
359
 
 
360
 
 
361
 
class DiffWindow(Window):
362
 
    """Diff window.
363
 
 
364
 
    This object represents and manages a single window containing the
365
 
    differences between two revisions on a branch.
366
 
    """
367
 
 
368
 
    def __init__(self, parent=None):
369
 
        Window.__init__(self, parent)
370
 
        self.set_border_width(0)
371
 
        self.set_title("bzrk diff")
372
 
 
373
 
        # Use two thirds of the screen by default
374
 
        screen = self.get_screen()
375
 
        monitor = screen.get_monitor_geometry(0)
376
 
        width = int(monitor.width * 0.66)
377
 
        height = int(monitor.height * 0.66)
378
 
        self.set_default_size(width, height)
379
 
 
380
 
        self.construct()
381
 
 
382
 
    def construct(self):
383
 
        """Construct the window contents."""
384
 
        self.diff = DiffWidget()
385
 
        self.add(self.diff)
386
 
        self.diff.show_all()
387
 
 
388
 
    def set_diff(self, description, rev_tree, parent_tree):
389
 
        """Set the differences showed by this window.
390
 
 
391
 
        Compares the two trees and populates the window with the
392
 
        differences.
393
 
        """
394
 
        self.diff.set_diff(rev_tree, parent_tree)
395
 
        self.set_title(description + " - bzrk diff")
396
 
 
397
 
    def set_file(self, file_path):
398
 
        self.diff.set_file(file_path)
399
 
 
400
 
 
401
 
def _iter_changes_to_status(source, target):
402
 
    """Determine the differences between trees.
403
 
 
404
 
    This is a wrapper around _iter_changes which just yields more
405
 
    understandable results.
406
 
 
407
 
    :param source: The source tree (basis tree)
408
 
    :param target: The target tree
409
 
    :return: A list of (file_id, real_path, change_type, display_path)
410
 
    """
411
 
    added = 'added'
412
 
    removed = 'removed'
413
 
    renamed = 'renamed'
414
 
    renamed_and_modified = 'renamed and modified'
415
 
    modified = 'modified'
416
 
    kind_changed = 'kind changed'
417
 
 
418
 
    # TODO: Handle metadata changes
419
 
 
420
 
    status = []
421
 
    target.lock_read()
422
 
    try:
423
 
        source.lock_read()
424
 
        try:
425
 
            for (file_id, paths, changed_content, versioned, parent_ids, names,
426
 
                 kinds, executables) in target._iter_changes(source):
427
 
 
428
 
                # Skip the root entry if it isn't very interesting
429
 
                if parent_ids == (None, None):
430
 
                    continue
431
 
 
432
 
                change_type = None
433
 
                if kinds[0] is None:
434
 
                    source_marker = ''
435
 
                else:
436
 
                    source_marker = osutils.kind_marker(kinds[0])
437
 
                if kinds[1] is None:
438
 
                    assert kinds[0] is not None
439
 
                    marker = osutils.kind_marker(kinds[0])
440
 
                else:
441
 
                    marker = osutils.kind_marker(kinds[1])
442
 
 
443
 
                real_path = paths[1]
444
 
                if real_path is None:
445
 
                    real_path = paths[0]
446
 
                assert real_path is not None
447
 
                display_path = real_path + marker
448
 
 
449
 
                present_source = versioned[0] and kinds[0] is not None
450
 
                present_target = versioned[1] and kinds[1] is not None
451
 
 
452
 
                if present_source != present_target:
453
 
                    if present_target:
454
 
                        change_type = added
455
 
                    else:
456
 
                        assert present_source
457
 
                        change_type = removed
458
 
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
459
 
                    # Renamed
460
 
                    if changed_content or executables[0] != executables[1]:
461
 
                        # and modified
462
 
                        change_type = renamed_and_modified
463
 
                    else:
464
 
                        change_type = renamed
465
 
                    display_path = (paths[0] + source_marker
466
 
                                    + ' => ' + paths[1] + marker)
467
 
                elif kinds[0] != kinds[1]:
468
 
                    change_type = kind_changed
469
 
                    display_path = (paths[0] + source_marker
470
 
                                    + ' => ' + paths[1] + marker)
471
 
                elif changed_content is True or executables[0] != executables[1]:
472
 
                    change_type = modified
473
 
                else:
474
 
                    assert False, "How did we get here?"
475
 
 
476
 
                status.append((file_id, real_path, change_type, display_path))
477
 
        finally:
478
 
            source.unlock()
479
 
    finally:
480
 
        target.unlock()
481
 
 
482
 
    return status
 
165
            specific_files = []
 
166
 
 
167
        s = StringIO()
 
168
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files)
 
169
        self.buffer.set_text(s.getvalue().decode(sys.getdefaultencoding(), 'replace'))