/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: Daniel Schierbeck
  • Date: 2008-01-23 16:36:21 UTC
  • mto: (423.1.8 trunk)
  • mto: This revision was merged to the branch mainline in revision 429.
  • Revision ID: daniel.schierbeck@gmail.com-20080123163621-x8kublc38ojipnly
Made the revision popup menu correctly add tags.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
1
# -*- coding: UTF-8 -*-
3
2
"""Difference window.
4
3
 
12
11
 
13
12
from cStringIO import StringIO
14
13
 
 
14
import pygtk
 
15
pygtk.require("2.0")
15
16
import gtk
16
17
import pango
 
18
import os
 
19
import re
17
20
import sys
18
21
 
19
22
try:
21
24
    have_gtksourceview = True
22
25
except ImportError:
23
26
    have_gtksourceview = False
24
 
 
25
 
import bzrlib
26
 
 
27
 
from bzrlib.diff import show_diff_trees
 
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
28
35
from bzrlib.errors import NoSuchFile
29
 
 
30
 
 
31
 
class DiffWindow(gtk.Window):
 
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."""
 
42
 
 
43
    def __init__(self):
 
44
        gtk.ScrolledWindow.__init__(self)
 
45
 
 
46
        self.construct()
 
47
        self.rev_tree = None
 
48
        self.parent_tree = None
 
49
 
 
50
    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 DiffWindow(Window):
32
265
    """Diff window.
33
266
 
34
267
    This object represents and manages a single window containing the
35
268
    differences between two revisions on a branch.
36
269
    """
37
270
 
38
 
    def __init__(self):
39
 
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
271
    def __init__(self, parent=None):
 
272
        Window.__init__(self, parent)
40
273
        self.set_border_width(0)
41
274
        self.set_title("bzrk diff")
42
275
 
82
315
 
83
316
        # The diffs of the  selected file: a scrollable source or
84
317
        # text view
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()
 
318
        self.diff_view = DiffView()
 
319
        pane.pack2(self.diff_view)
 
320
        self.diff_view.show()
107
321
 
108
322
    def set_diff(self, description, rev_tree, parent_tree):
109
323
        """Set the differences showed by this window.
111
325
        Compares the two trees and populates the window with the
112
326
        differences.
113
327
        """
 
328
        self.diff_view.set_trees(rev_tree, parent_tree)
114
329
        self.rev_tree = rev_tree
115
330
        self.parent_tree = parent_tree
116
331
 
162
377
        if specific_files == [ None ]:
163
378
            return
164
379
        elif specific_files == [ "" ]:
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'))
 
380
            specific_files = None
 
381
 
 
382
        self.diff_view.show_diff(specific_files)
 
383
 
 
384
 
 
385
def _iter_changes_to_status(source, target):
 
386
    """Determine the differences between trees.
 
387
 
 
388
    This is a wrapper around _iter_changes which just yields more
 
389
    understandable results.
 
390
 
 
391
    :param source: The source tree (basis tree)
 
392
    :param target: The target tree
 
393
    :return: A list of (file_id, real_path, change_type, display_path)
 
394
    """
 
395
    added = 'added'
 
396
    removed = 'removed'
 
397
    renamed = 'renamed'
 
398
    renamed_and_modified = 'renamed and modified'
 
399
    modified = 'modified'
 
400
    kind_changed = 'kind changed'
 
401
 
 
402
    # TODO: Handle metadata changes
 
403
 
 
404
    status = []
 
405
    target.lock_read()
 
406
    try:
 
407
        source.lock_read()
 
408
        try:
 
409
            for (file_id, paths, changed_content, versioned, parent_ids, names,
 
410
                 kinds, executables) in target._iter_changes(source):
 
411
 
 
412
                # Skip the root entry if it isn't very interesting
 
413
                if parent_ids == (None, None):
 
414
                    continue
 
415
 
 
416
                change_type = None
 
417
                if kinds[0] is None:
 
418
                    source_marker = ''
 
419
                else:
 
420
                    source_marker = osutils.kind_marker(kinds[0])
 
421
                if kinds[1] is None:
 
422
                    assert kinds[0] is not None
 
423
                    marker = osutils.kind_marker(kinds[0])
 
424
                else:
 
425
                    marker = osutils.kind_marker(kinds[1])
 
426
 
 
427
                real_path = paths[1]
 
428
                if real_path is None:
 
429
                    real_path = paths[0]
 
430
                assert real_path is not None
 
431
                display_path = real_path + marker
 
432
 
 
433
                present_source = versioned[0] and kinds[0] is not None
 
434
                present_target = versioned[1] and kinds[1] is not None
 
435
 
 
436
                if present_source != present_target:
 
437
                    if present_target:
 
438
                        change_type = added
 
439
                    else:
 
440
                        assert present_source
 
441
                        change_type = removed
 
442
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
 
443
                    # Renamed
 
444
                    if changed_content or executables[0] != executables[1]:
 
445
                        # and modified
 
446
                        change_type = renamed_and_modified
 
447
                    else:
 
448
                        change_type = renamed
 
449
                    display_path = (paths[0] + source_marker
 
450
                                    + ' => ' + paths[1] + marker)
 
451
                elif kinds[0] != kinds[1]:
 
452
                    change_type = kind_changed
 
453
                    display_path = (paths[0] + source_marker
 
454
                                    + ' => ' + paths[1] + marker)
 
455
                elif changed_content is True or executables[0] != executables[1]:
 
456
                    change_type = modified
 
457
                else:
 
458
                    assert False, "How did we get here?"
 
459
 
 
460
                status.append((file_id, real_path, change_type, display_path))
 
461
        finally:
 
462
            source.unlock()
 
463
    finally:
 
464
        target.unlock()
 
465
 
 
466
    return status