/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: Toshio Kuratomi
  • Date: 2010-04-13 15:06:17 UTC
  • mto: This revision was merged to the branch mainline in revision 693.
  • Revision ID: toshio@fedoraproject.org-20100413150617-e37qx1qgdewh0n8o
Fix bzr-handle-patch to process patches with leading and trailing comments with
newer bzrlib.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
"""
6
6
 
7
7
__copyright__ = "Copyright 2005 Canonical Ltd."
8
 
__author__ = "Scott James Remnant <scott@ubuntu.com>"
 
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
9
 
10
10
 
11
11
from cStringIO import StringIO
12
12
 
 
13
import pygtk
 
14
pygtk.require("2.0")
 
15
import gtk
 
16
import pango
 
17
import os
 
18
import re
13
19
import sys
14
20
import inspect
 
21
try:
 
22
    from xml.etree.ElementTree import Element, SubElement, tostring
 
23
except ImportError:
 
24
    from elementtree.ElementTree import Element, SubElement, tostring
15
25
 
16
 
from gi.repository import Gtk
17
 
from gi.repository import Pango
18
26
try:
19
 
    from gi.repository import GtkSource
 
27
    import gtksourceview2
20
28
    have_gtksourceview = True
21
29
except ImportError:
22
30
    have_gtksourceview = False
 
31
try:
 
32
    import gconf
 
33
    have_gconf = True
 
34
except ImportError:
 
35
    have_gconf = False
23
36
 
24
37
from bzrlib import (
25
38
    errors,
27
40
    osutils,
28
41
    urlutils,
29
42
    workingtree,
30
 
    )
31
 
from bzrlib.diff import show_diff_trees
 
43
)
 
44
from bzrlib.diff import show_diff_trees, internal_diff
32
45
from bzrlib.patches import parse_patches
33
 
from bzrlib.plugins.gtk.dialog import (
34
 
    error_dialog,
35
 
    info_dialog,
36
 
    warning_dialog,
37
 
    )
38
 
from bzrlib.plugins.gtk.i18n import _i18n
 
46
from bzrlib.trace import warning
 
47
from bzrlib.plugins.gtk import _i18n
39
48
from bzrlib.plugins.gtk.window import Window
 
49
from dialog import error_dialog, info_dialog, warning_dialog
40
50
 
41
51
 
42
52
def fallback_guess_language(slm, content_type):
52
62
    pass
53
63
 
54
64
 
55
 
class DiffFileView(Gtk.ScrolledWindow):
 
65
class DiffFileView(gtk.ScrolledWindow):
56
66
    """Window for displaying diffs from a diff file"""
57
67
 
58
 
    SHOW_WIDGETS = True
59
 
 
60
68
    def __init__(self):
61
 
        super(DiffFileView, self).__init__()
 
69
        gtk.ScrolledWindow.__init__(self)
62
70
        self.construct()
63
71
        self._diffs = {}
64
72
 
65
73
    def construct(self):
66
 
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
67
 
        self.set_shadow_type(Gtk.ShadowType.IN)
 
74
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
75
        self.set_shadow_type(gtk.SHADOW_IN)
68
76
 
69
77
        if have_gtksourceview:
70
 
            self.buffer = GtkSource.Buffer()
71
 
            lang_manager = GtkSource.LanguageManager.get_default()
72
 
            language = lang_manager.guess_language(None, "text/x-patch")
73
 
            self.buffer.set_language(language)
 
78
            self.buffer = gtksourceview2.Buffer()
 
79
            slm = gtksourceview2.LanguageManager()
 
80
            guess_language = getattr(gtksourceview2.LanguageManager, 
 
81
                "guess_language", fallback_guess_language)
 
82
            gsl = guess_language(slm, content_type="text/x-patch")
 
83
            if have_gconf:
 
84
                self.apply_gedit_colors(self.buffer)
 
85
            self.apply_colordiff_colors(self.buffer)
 
86
            self.buffer.set_language(gsl)
74
87
            self.buffer.set_highlight_syntax(True)
75
 
            self.sourceview = GtkSource.View(buffer=self.buffer)
 
88
 
 
89
            self.sourceview = gtksourceview2.View(self.buffer)
76
90
        else:
77
 
            self.buffer = Gtk.TextBuffer()
78
 
            self.sourceview = Gtk.TextView(self.buffer)
 
91
            self.buffer = gtk.TextBuffer()
 
92
            self.sourceview = gtk.TextView(self.buffer)
79
93
 
80
94
        self.sourceview.set_editable(False)
81
 
        self.sourceview.override_font(Pango.FontDescription("Monospace"))
 
95
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
82
96
        self.add(self.sourceview)
83
 
        if self.SHOW_WIDGETS:
84
 
            self.sourceview.show()
 
97
        self.sourceview.show()
 
98
 
 
99
    @staticmethod
 
100
    def apply_gedit_colors(buf):
 
101
        """Set style to that specified in gedit configuration.
 
102
 
 
103
        This method needs the gconf module.
 
104
 
 
105
        :param buf: a gtksourceview2.Buffer object.
 
106
        """
 
107
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
 
108
 
 
109
        client = gconf.client_get_default()
 
110
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
 
111
        if style_scheme_name is not None:
 
112
            style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
 
113
            
 
114
            buf.set_style_scheme(style_scheme)
 
115
 
 
116
    @classmethod
 
117
    def apply_colordiff_colors(klass, buf):
 
118
        """Set style colors for lang using the colordiff configuration file.
 
119
 
 
120
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
 
121
 
 
122
        :param buf: a "Diff" gtksourceview2.Buffer object.
 
123
        """
 
124
        scheme_manager = gtksourceview2.StyleSchemeManager()
 
125
        style_scheme = scheme_manager.get_scheme('colordiff')
 
126
        
 
127
        # if style scheme not found, we'll generate it from colordiffrc
 
128
        # TODO: reload if colordiffrc has changed.
 
129
        if style_scheme is None:
 
130
            colors = {}
 
131
 
 
132
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
 
133
                f = os.path.expanduser(f)
 
134
                if os.path.exists(f):
 
135
                    try:
 
136
                        f = file(f)
 
137
                    except IOError, e:
 
138
                        warning('could not open file %s: %s' % (f, str(e)))
 
139
                    else:
 
140
                        colors.update(klass.parse_colordiffrc(f))
 
141
                        f.close()
 
142
 
 
143
            if not colors:
 
144
                # ~/.colordiffrc does not exist
 
145
                return
 
146
            
 
147
            mapping = {
 
148
                # map GtkSourceView2 scheme styles to colordiff names
 
149
                # since GSV is richer, accept new names for extra bits,
 
150
                # defaulting to old names if they're not present
 
151
                'diff:added-line': ['newtext'],
 
152
                'diff:removed-line': ['oldtext'],
 
153
                'diff:location': ['location', 'diffstuff'],
 
154
                'diff:file': ['file', 'diffstuff'],
 
155
                'diff:special-case': ['specialcase', 'diffstuff'],
 
156
            }
 
157
            
 
158
            converted_colors = {}
 
159
            for name, values in mapping.items():
 
160
                color = None
 
161
                for value in values:
 
162
                    color = colors.get(value, None)
 
163
                    if color is not None:
 
164
                        break
 
165
                if color is None:
 
166
                    continue
 
167
                converted_colors[name] = color
 
168
            
 
169
            # some xml magic to produce needed style scheme description
 
170
            e_style_scheme = Element('style-scheme')
 
171
            e_style_scheme.set('id', 'colordiff')
 
172
            e_style_scheme.set('_name', 'ColorDiff')
 
173
            e_style_scheme.set('version', '1.0')
 
174
            for name, color in converted_colors.items():
 
175
                style = SubElement(e_style_scheme, 'style')
 
176
                style.set('name', name)
 
177
                style.set('foreground', '#%s' % color)
 
178
            
 
179
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
 
180
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
 
181
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
 
182
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
 
183
            
 
184
            scheme_manager.force_rescan()
 
185
            style_scheme = scheme_manager.get_scheme('colordiff')
 
186
        
 
187
        buf.set_style_scheme(style_scheme)
 
188
 
 
189
    @staticmethod
 
190
    def parse_colordiffrc(fileobj):
 
191
        """Parse fileobj as a colordiff configuration file.
 
192
 
 
193
        :return: A dict with the key -> value pairs.
 
194
        """
 
195
        colors = {}
 
196
        for line in fileobj:
 
197
            if re.match(r'^\s*#', line):
 
198
                continue
 
199
            if '=' not in line:
 
200
                continue
 
201
            key, val = line.split('=', 1)
 
202
            colors[key.strip()] = val.strip()
 
203
        return colors
85
204
 
86
205
    def set_trees(self, rev_tree, parent_tree):
87
206
        self.rev_tree = rev_tree
92
211
#        self.parent_tree.lock_read()
93
212
#        self.rev_tree.lock_read()
94
213
#        try:
95
 
#            self.delta = iter_changes_to_status(
96
 
#               self.parent_tree, self.rev_tree)
 
214
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
97
215
#            self.path_to_status = {}
98
216
#            self.path_to_diff = {}
99
217
#            source_inv = self.parent_tree.inventory
100
218
#            target_inv = self.rev_tree.inventory
101
219
#            for (file_id, real_path, change_type, display_path) in self.delta:
102
 
#                self.path_to_status[real_path] = u'=== %s %s' % (
103
 
#                    change_type, display_path)
 
220
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
104
221
#                if change_type in ('modified', 'renamed and modified'):
105
222
#                    source_ie = source_inv[file_id]
106
223
#                    target_ie = target_inv[file_id]
108
225
#                    source_ie.diff(internal_diff, *old path, *old_tree,
109
226
#                                   *new_path, target_ie, self.rev_tree,
110
227
#                                   sio)
111
 
#                    self.path_to_diff[real_path] =
 
228
#                    self.path_to_diff[real_path] = 
112
229
#
113
230
#        finally:
114
231
#            self.rev_tree.unlock()
128
245
    """This is the soft and chewy filling for a DiffWindow."""
129
246
 
130
247
    def __init__(self):
131
 
        super(DiffView, self).__init__()
 
248
        DiffFileView.__init__(self)
132
249
        self.rev_tree = None
133
250
        self.parent_tree = None
134
251
 
158
275
        self.buffer.set_text(decoded.encode('UTF-8'))
159
276
 
160
277
 
161
 
class DiffWidget(Gtk.HPaned):
 
278
class DiffWidget(gtk.HPaned):
162
279
    """Diff widget
163
280
 
164
281
    """
165
 
 
166
 
    SHOW_WIDGETS = True
167
 
 
168
282
    def __init__(self):
169
283
        super(DiffWidget, self).__init__()
170
284
 
171
285
        # The file hierarchy: a scrollable treeview
172
 
        scrollwin = Gtk.ScrolledWindow()
173
 
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
174
 
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
 
286
        scrollwin = gtk.ScrolledWindow()
 
287
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
288
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
175
289
        self.pack1(scrollwin)
176
 
        if self.SHOW_WIDGETS:
177
 
            scrollwin.show()
178
 
 
179
 
        self.model = Gtk.TreeStore(str, str)
180
 
        self.treeview = Gtk.TreeView(model=self.model)
 
290
        scrollwin.show()
 
291
        
 
292
        self.model = gtk.TreeStore(str, str)
 
293
        self.treeview = gtk.TreeView(self.model)
181
294
        self.treeview.set_headers_visible(False)
182
295
        self.treeview.set_search_column(1)
183
296
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
184
297
        scrollwin.add(self.treeview)
185
 
        if self.SHOW_WIDGETS:
186
 
            self.treeview.show()
 
298
        self.treeview.show()
187
299
 
188
 
        cell = Gtk.CellRendererText()
 
300
        cell = gtk.CellRendererText()
189
301
        cell.set_property("width-chars", 20)
190
 
        column = Gtk.TreeViewColumn()
191
 
        column.pack_start(cell, True)
 
302
        column = gtk.TreeViewColumn()
 
303
        column.pack_start(cell, expand=True)
192
304
        column.add_attribute(cell, "text", 0)
193
305
        self.treeview.append_column(column)
194
306
 
204
316
        if getattr(self, 'diff_view', None) is None:
205
317
            self.diff_view = DiffFileView()
206
318
            self.pack2(self.diff_view)
207
 
        if self.SHOW_WIDGETS:
208
 
            self.diff_view.show()
 
319
        self.diff_view.show()
209
320
        for oldname, newname, patch in sections:
210
321
            self.diff_view._diffs[newname] = str(patch)
211
322
            if newname is None:
222
333
        if getattr(self, 'diff_view', None) is None:
223
334
            self.diff_view = DiffView()
224
335
            self.pack2(self.diff_view)
225
 
        if self.SHOW_WIDGETS:
226
 
            self.diff_view.show()
 
336
        self.diff_view.show()
227
337
        self.diff_view.set_trees(rev_tree, parent_tree)
228
338
        self.rev_tree = rev_tree
229
339
        self.parent_tree = parent_tree
231
341
        self.model.clear()
232
342
        delta = self.rev_tree.changes_from(self.parent_tree)
233
343
 
234
 
        self.model.append(None, ["Complete Diff", ""])
 
344
        self.model.append(None, [ "Complete Diff", "" ])
235
345
 
236
346
        if len(delta.added):
237
 
            titer = self.model.append(None, ["Added", None])
 
347
            titer = self.model.append(None, [ "Added", None ])
238
348
            for path, id, kind in delta.added:
239
 
                self.model.append(titer, [path, path])
 
349
                self.model.append(titer, [ path, path ])
240
350
 
241
351
        if len(delta.removed):
242
 
            titer = self.model.append(None, ["Removed", None])
 
352
            titer = self.model.append(None, [ "Removed", None ])
243
353
            for path, id, kind in delta.removed:
244
 
                self.model.append(titer, [path, path])
 
354
                self.model.append(titer, [ path, path ])
245
355
 
246
356
        if len(delta.renamed):
247
 
            titer = self.model.append(None, ["Renamed", None])
 
357
            titer = self.model.append(None, [ "Renamed", None ])
248
358
            for oldpath, newpath, id, kind, text_modified, meta_modified \
249
359
                    in delta.renamed:
250
 
                self.model.append(titer, [oldpath, newpath])
 
360
                self.model.append(titer, [ oldpath, newpath ])
251
361
 
252
362
        if len(delta.modified):
253
 
            titer = self.model.append(None, ["Modified", None])
 
363
            titer = self.model.append(None, [ "Modified", None ])
254
364
            for path, id, kind, text_modified, meta_modified in delta.modified:
255
 
                self.model.append(titer, [path, path])
 
365
                self.model.append(titer, [ path, path ])
256
366
 
257
367
        self.treeview.expand_all()
258
368
        self.diff_view.show_diff(None)
267
377
                    break
268
378
        if tv_path is None:
269
379
            raise errors.NoSuchFile(file_path)
270
 
        self.treeview.set_cursor(tv_path, None, False)
 
380
        self.treeview.set_cursor(tv_path)
271
381
        self.treeview.scroll_to_cell(tv_path)
272
382
 
273
383
    def _treeview_cursor_cb(self, *args):
274
384
        """Callback for when the treeview cursor changes."""
275
385
        (path, col) = self.treeview.get_cursor()
276
 
        if path is None:
277
 
            return
278
 
        specific_files = [self.model[path][1]]
279
 
        if specific_files == [None]:
280
 
            return
281
 
        elif specific_files == [""]:
 
386
        specific_files = [ self.model[path][1] ]
 
387
        if specific_files == [ None ]:
 
388
            return
 
389
        elif specific_files == [ "" ]:
282
390
            specific_files = None
283
 
 
 
391
        
284
392
        self.diff_view.show_diff(specific_files)
285
 
 
 
393
    
286
394
    def _on_wraplines_toggled(self, widget=None, wrap=False):
287
395
        """Callback for when the wrap lines checkbutton is toggled"""
288
396
        if wrap or widget.get_active():
289
 
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
 
397
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
290
398
        else:
291
 
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
292
 
 
 
399
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
293
400
 
294
401
class DiffWindow(Window):
295
402
    """Diff window.
298
405
    differences between two revisions on a branch.
299
406
    """
300
407
 
301
 
    SHOW_WIDGETS = True
302
 
 
303
408
    def __init__(self, parent=None, operations=None):
304
 
        super(DiffWindow, self).__init__(parent=parent)
 
409
        Window.__init__(self, parent)
305
410
        self.set_border_width(0)
306
 
        self.set_title("bzr diff")
 
411
        self.set_title("bzrk diff")
307
412
 
308
413
        # Use two thirds of the screen by default
309
414
        screen = self.get_screen()
315
420
 
316
421
    def construct(self, operations):
317
422
        """Construct the window contents."""
318
 
        self.vbox = Gtk.VBox()
 
423
        self.vbox = gtk.VBox()
319
424
        self.add(self.vbox)
320
 
        if self.SHOW_WIDGETS:
321
 
            self.vbox.show()
 
425
        self.vbox.show()
322
426
        self.diff = DiffWidget()
323
427
        self.vbox.pack_end(self.diff, True, True, 0)
324
 
        if self.SHOW_WIDGETS:
325
 
            self.diff.show_all()
 
428
        self.diff.show_all()
326
429
        # Build after DiffWidget to connect signals
327
430
        menubar = self._get_menu_bar()
328
431
        self.vbox.pack_start(menubar, False, False, 0)
329
432
        hbox = self._get_button_bar(operations)
330
433
        if hbox is not None:
331
434
            self.vbox.pack_start(hbox, False, True, 0)
332
 
 
 
435
        
 
436
    
333
437
    def _get_menu_bar(self):
334
 
        menubar = Gtk.MenuBar()
 
438
        menubar = gtk.MenuBar()
335
439
        # View menu
336
 
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
337
 
        mb_view_menu = Gtk.Menu()
338
 
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
339
 
            _i18n("Wrap _Long Lines"))
 
440
        mb_view = gtk.MenuItem(_i18n("_View"))
 
441
        mb_view_menu = gtk.Menu()
 
442
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
340
443
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
 
444
        mb_view_wrapsource.show()
341
445
        mb_view_menu.append(mb_view_wrapsource)
 
446
        mb_view.show()
342
447
        mb_view.set_submenu(mb_view_menu)
 
448
        mb_view.show()
343
449
        menubar.append(mb_view)
344
 
        if self.SHOW_WIDGETS:
345
 
            menubar.show_all()
 
450
        menubar.show()
346
451
        return menubar
347
 
 
 
452
    
348
453
    def _get_button_bar(self, operations):
349
454
        """Return a button bar to use.
350
455
 
352
457
        """
353
458
        if operations is None:
354
459
            return None
355
 
        hbox = Gtk.HButtonBox()
356
 
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
 
460
        hbox = gtk.HButtonBox()
 
461
        hbox.set_layout(gtk.BUTTONBOX_START)
357
462
        for title, method in operations:
358
 
            merge_button = Gtk.Button(title)
359
 
            if self.SHOW_WIDGETS:
360
 
                merge_button.show()
361
 
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
 
463
            merge_button = gtk.Button(title)
 
464
            merge_button.show()
 
465
            merge_button.set_relief(gtk.RELIEF_NONE)
362
466
            merge_button.connect("clicked", method)
363
 
            hbox.pack_start(merge_button, False, True, 0)
364
 
        if self.SHOW_WIDGETS:
365
 
            hbox.show()
 
467
            hbox.pack_start(merge_button, expand=False, fill=True)
 
468
        hbox.show()
366
469
        return hbox
367
470
 
368
471
    def _get_merge_target(self):
369
 
        d = Gtk.FileChooserDialog('Merge branch', self,
370
 
                                  Gtk.FileChooserAction.SELECT_FOLDER,
371
 
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
372
 
                                           Gtk.STOCK_CANCEL,
373
 
                                           Gtk.ResponseType.CANCEL,))
 
472
        d = gtk.FileChooserDialog('Merge branch', self,
 
473
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
 
474
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
 
475
                                           gtk.STOCK_CANCEL,
 
476
                                           gtk.RESPONSE_CANCEL,))
374
477
        try:
375
478
            result = d.run()
376
 
            if result != Gtk.ResponseType.OK:
 
479
            if result != gtk.RESPONSE_OK:
377
480
                raise SelectCancelled()
378
481
            return d.get_current_folder_uri()
379
482
        finally:
393
496
        error_dialog('Error', str(e))
394
497
 
395
498
    def _get_save_path(self, basename):
396
 
        d = Gtk.FileChooserDialog('Save As', self,
397
 
                                  Gtk.FileChooserAction.SAVE,
398
 
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
399
 
                                           Gtk.STOCK_CANCEL,
400
 
                                           Gtk.ResponseType.CANCEL,))
 
499
        d = gtk.FileChooserDialog('Save As', self,
 
500
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
 
501
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
 
502
                                           gtk.STOCK_CANCEL,
 
503
                                           gtk.RESPONSE_CANCEL,))
401
504
        d.set_current_name(basename)
402
505
        try:
403
506
            result = d.run()
404
 
            if result != Gtk.ResponseType.OK:
 
507
            if result != gtk.RESPONSE_OK:
405
508
                raise SelectCancelled()
406
509
            return urlutils.local_path_from_url(d.get_uri())
407
510
        finally:
469
572
class MergeDirectiveController(DiffController):
470
573
 
471
574
    def __init__(self, path, directive, window=None):
472
 
        super(MergeDirectiveController, self).__init__(
473
 
            path, directive.patch.splitlines(True), window)
 
575
        DiffController.__init__(self, path, directive.patch.splitlines(True),
 
576
                                window)
474
577
        self.directive = directive
475
578
        self.merge_target = None
476
579