/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: Szilveszter Farkas (Phanatic)
  • Date: 2007-03-15 16:23:15 UTC
  • mfrom: (170 trunk)
  • mto: (170.1.3 trunk)
  • mto: This revision was merged to the branch mainline in revision 172.
  • Revision ID: szilveszter.farkas@gmail.com-20070315162315-rs1sbxjh31n314zc
MergeĀ fromĀ trunk.

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 merge as _mod_merge, osutils, progress, workingtree
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.patches import parse_patches
37
 
from bzrlib.trace import warning
38
 
from bzrlib.plugins.gtk.window import Window
39
 
from dialog import error_dialog
40
 
 
41
 
 
42
 
class SelectCancelled(Exception):
43
 
 
44
 
    pass
45
 
 
46
 
 
47
 
class DiffFileView(gtk.ScrolledWindow):
 
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
    """
48
37
 
49
38
    def __init__(self):
50
 
        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)
 
49
 
51
50
        self.construct()
52
 
        self._diffs = {}
53
51
 
54
52
    def construct(self):
55
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
56
 
        self.set_shadow_type(gtk.SHADOW_IN)
57
 
 
58
 
        if have_gtksourceview:
59
 
            self.buffer = gtksourceview.SourceBuffer()
60
 
            slm = gtksourceview.SourceLanguagesManager()
61
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
62
 
            if have_gconf:
63
 
                self.apply_gedit_colors(gsl)
64
 
            self.apply_colordiff_colors(gsl)
65
 
            self.buffer.set_language(gsl)
66
 
            self.buffer.set_highlight(True)
67
 
 
68
 
            sourceview = gtksourceview.SourceView(self.buffer)
69
 
        else:
70
 
            self.buffer = gtk.TextBuffer()
71
 
            sourceview = gtk.TextView(self.buffer)
72
 
 
73
 
        sourceview.set_editable(False)
74
 
        sourceview.modify_font(pango.FontDescription("Monospace"))
75
 
        self.add(sourceview)
76
 
        sourceview.show()
77
 
 
78
 
    @staticmethod
79
 
    def apply_gedit_colors(lang):
80
 
        """Set style for lang to that specified in gedit configuration.
81
 
 
82
 
        This method needs the gconf module.
83
 
 
84
 
        :param lang: a gtksourceview.SourceLanguage object.
85
 
        """
86
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
87
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
88
 
 
89
 
        client = gconf.client_get_default()
90
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
91
 
 
92
 
        for tag in lang.get_tags():
93
 
            tag_id = tag.get_id()
94
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
95
 
            style_string = client.get_string(gconf_key)
96
 
 
97
 
            if style_string is None:
98
 
                continue
99
 
 
100
 
            # function to get a bool from a string that's either '0' or '1'
101
 
            string_bool = lambda x: bool(int(x))
102
 
 
103
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
104
 
            # values are: mask, fg, bg, italic, bold, underline, strike
105
 
            # this packs them into (str_value, attr_name, conv_func) tuples
106
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
107
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
108
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
109
 
                    string_bool, string_bool, string_bool ]
110
 
            )
111
 
 
112
 
            style = gtksourceview.SourceTagStyle()
113
 
 
114
 
            # XXX The mask attribute controls whether the present values of
115
 
            # foreground and background color should in fact be used. Ideally
116
 
            # (and that's what gedit does), one could set all three attributes,
117
 
            # and let the TagStyle object figure out which colors to use.
118
 
            # However, in the GtkSourceview python bindings, the mask attribute
119
 
            # is read-only, and it's derived instead from the colors being
120
 
            # set or not. This means that we have to sometimes refrain from
121
 
            # setting fg or bg colors, depending on the value of the mask.
122
 
            # This code could go away if mask were writable.
123
 
            mask = int(items[0][0])
124
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
125
 
                items[2:3] = []
126
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
127
 
                items[1:2] = []
128
 
            items[0:1] = [] # skip the mask unconditionally
129
 
 
130
 
            for value, attr, func in items:
131
 
                try:
132
 
                    value = func(value)
133
 
                except ValueError:
134
 
                    warning('gconf key %s contains an invalid value: %s'
135
 
                            % gconf_key, value)
136
 
                else:
137
 
                    setattr(style, attr, value)
138
 
 
139
 
            lang.set_tag_style(tag_id, style)
140
 
 
141
 
    @classmethod
142
 
    def apply_colordiff_colors(klass, lang):
143
 
        """Set style colors for lang using the colordiff configuration file.
144
 
 
145
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
146
 
 
147
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
148
 
        """
149
 
        colors = {}
150
 
 
151
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
152
 
            f = os.path.expanduser(f)
153
 
            if os.path.exists(f):
154
 
                try:
155
 
                    f = file(f)
156
 
                except IOError, e:
157
 
                    warning('could not open file %s: %s' % (f, str(e)))
158
 
                else:
159
 
                    colors.update(klass.parse_colordiffrc(f))
160
 
                    f.close()
161
 
 
162
 
        if not colors:
163
 
            # ~/.colordiffrc does not exist
164
 
            return
165
 
 
166
 
        mapping = {
167
 
                # map GtkSourceView tags to colordiff names
168
 
                # since GSV is richer, accept new names for extra bits,
169
 
                # defaulting to old names if they're not present
170
 
                'Added@32@line': ['newtext'],
171
 
                'Removed@32@line': ['oldtext'],
172
 
                'Location': ['location', 'diffstuff'],
173
 
                'Diff@32@file': ['file', 'diffstuff'],
174
 
                'Special@32@case': ['specialcase', 'diffstuff'],
175
 
        }
176
 
 
177
 
        for tag in lang.get_tags():
178
 
            tag_id = tag.get_id()
179
 
            keys = mapping.get(tag_id, [])
180
 
            color = None
181
 
 
182
 
            for key in keys:
183
 
                color = colors.get(key, None)
184
 
                if color is not None:
185
 
                    break
186
 
 
187
 
            if color is None:
188
 
                continue
189
 
 
190
 
            style = gtksourceview.SourceTagStyle()
191
 
            try:
192
 
                style.foreground = gtk.gdk.color_parse(color)
193
 
            except ValueError:
194
 
                warning('not a valid color: %s' % color)
195
 
            else:
196
 
                lang.set_tag_style(tag_id, style)
197
 
 
198
 
    @staticmethod
199
 
    def parse_colordiffrc(fileobj):
200
 
        """Parse fileobj as a colordiff configuration file.
201
 
 
202
 
        :return: A dict with the key -> value pairs.
203
 
        """
204
 
        colors = {}
205
 
        for line in fileobj:
206
 
            if re.match(r'^\s*#', line):
207
 
                continue
208
 
            if '=' not in line:
209
 
                continue
210
 
            key, val = line.split('=', 1)
211
 
            colors[key.strip()] = val.strip()
212
 
        return colors
213
 
 
214
 
    def set_trees(self, rev_tree, parent_tree):
215
 
        self.rev_tree = rev_tree
216
 
        self.parent_tree = parent_tree
217
 
#        self._build_delta()
218
 
 
219
 
#    def _build_delta(self):
220
 
#        self.parent_tree.lock_read()
221
 
#        self.rev_tree.lock_read()
222
 
#        try:
223
 
#            self.delta = _iter_changes_to_status(self.parent_tree, self.rev_tree)
224
 
#            self.path_to_status = {}
225
 
#            self.path_to_diff = {}
226
 
#            source_inv = self.parent_tree.inventory
227
 
#            target_inv = self.rev_tree.inventory
228
 
#            for (file_id, real_path, change_type, display_path) in self.delta:
229
 
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
230
 
#                if change_type in ('modified', 'renamed and modified'):
231
 
#                    source_ie = source_inv[file_id]
232
 
#                    target_ie = target_inv[file_id]
233
 
#                    sio = StringIO()
234
 
#                    source_ie.diff(internal_diff, *old path, *old_tree,
235
 
#                                   *new_path, target_ie, self.rev_tree,
236
 
#                                   sio)
237
 
#                    self.path_to_diff[real_path] = 
238
 
#
239
 
#        finally:
240
 
#            self.rev_tree.unlock()
241
 
#            self.parent_tree.unlock()
242
 
 
243
 
    def show_diff(self, specific_files):
244
 
        sections = []
245
 
        if specific_files is None:
246
 
            self.buffer.set_text(self._diffs[None])
247
 
        else:
248
 
            for specific_file in specific_files:
249
 
                sections.append(self._diffs[specific_file])
250
 
            self.buffer.set_text(''.join(sections))
251
 
 
252
 
 
253
 
class DiffView(DiffFileView):
254
 
    """This is the soft and chewy filling for a DiffWindow."""
255
 
 
256
 
    def __init__(self):
257
 
        DiffFileView.__init__(self)
258
 
        self.rev_tree = None
259
 
        self.parent_tree = None
260
 
 
261
 
    def show_diff(self, specific_files):
262
 
        s = StringIO()
263
 
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
264
 
                        old_label='', new_label='',
265
 
                        # path_encoding=sys.getdefaultencoding()
266
 
                        # The default is utf-8, but we interpret the file
267
 
                        # contents as getdefaultencoding(), so we should
268
 
                        # probably try to make the paths in the same encoding.
269
 
                        )
270
 
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
271
 
        # character is not valid in 'encoding' there is nothing to replace, the
272
 
        # 'replace' is for 'str.encode()'
273
 
        try:
274
 
            decoded = s.getvalue().decode(sys.getdefaultencoding())
275
 
        except UnicodeDecodeError:
276
 
            try:
277
 
                decoded = s.getvalue().decode('UTF-8')
278
 
            except UnicodeDecodeError:
279
 
                decoded = s.getvalue().decode('iso-8859-1')
280
 
                # This always works, because every byte has a valid
281
 
                # mapping from iso-8859-1 to Unicode
282
 
        # TextBuffer must contain pure UTF-8 data
283
 
        self.buffer.set_text(decoded.encode('UTF-8'))
284
 
 
285
 
 
286
 
class DiffWidget(gtk.HPaned):
287
 
    """Diff widget
288
 
 
289
 
    """
290
 
    def __init__(self):
291
 
        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()
292
60
 
293
61
        # The file hierarchy: a scrollable treeview
294
62
        scrollwin = gtk.ScrolledWindow()
295
63
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
296
64
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
297
 
        self.pack1(scrollwin)
 
65
        pane.pack1(scrollwin)
298
66
        scrollwin.show()
299
67
 
300
68
        self.model = gtk.TreeStore(str, str)
312
80
        column.add_attribute(cell, "text", 0)
313
81
        self.treeview.append_column(column)
314
82
 
315
 
    def set_diff_text(self, lines):
316
83
        # The diffs of the  selected file: a scrollable source or
317
84
        # text view
318
 
        self.diff_view = DiffFileView()
319
 
        self.diff_view.show()
320
 
        self.pack2(self.diff_view)
321
 
        self.model.append(None, [ "Complete Diff", "" ])
322
 
        self.diff_view._diffs[None] = ''.join(lines)
323
 
        for patch in parse_patches(lines):
324
 
            oldname = patch.oldname.split('\t')[0]
325
 
            newname = patch.newname.split('\t')[0]
326
 
            self.model.append(None, [oldname, newname])
327
 
            self.diff_view._diffs[newname] = str(patch)
328
 
        self.diff_view.show_diff(None)
329
 
 
330
 
    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):
331
109
        """Set the differences showed by this window.
332
110
 
333
111
        Compares the two trees and populates the window with the
334
112
        differences.
335
113
        """
336
 
        self.diff_view = DiffView()
337
 
        self.pack2(self.diff_view)
338
 
        self.diff_view.show()
339
 
        self.diff_view.set_trees(rev_tree, parent_tree)
340
114
        self.rev_tree = rev_tree
341
115
        self.parent_tree = parent_tree
342
116
 
367
141
                self.model.append(titer, [ path, path ])
368
142
 
369
143
        self.treeview.expand_all()
 
144
        self.set_title(description + " - bzrk diff")
370
145
 
371
146
    def set_file(self, file_path):
372
147
        tv_path = None
387
162
        if specific_files == [ None ]:
388
163
            return
389
164
        elif specific_files == [ "" ]:
390
 
            specific_files = None
391
 
 
392
 
        self.diff_view.show_diff(specific_files)
393
 
 
394
 
 
395
 
class DiffWindow(Window):
396
 
    """Diff window.
397
 
 
398
 
    This object represents and manages a single window containing the
399
 
    differences between two revisions on a branch.
400
 
    """
401
 
 
402
 
    def __init__(self, parent=None):
403
 
        Window.__init__(self, parent)
404
 
        self.set_border_width(0)
405
 
        self.set_title("bzrk diff")
406
 
 
407
 
        # Use two thirds of the screen by default
408
 
        screen = self.get_screen()
409
 
        monitor = screen.get_monitor_geometry(0)
410
 
        width = int(monitor.width * 0.66)
411
 
        height = int(monitor.height * 0.66)
412
 
        self.set_default_size(width, height)
413
 
 
414
 
        self.construct()
415
 
 
416
 
    def construct(self):
417
 
        """Construct the window contents."""
418
 
        self.vbox = gtk.VBox()
419
 
        self.add(self.vbox)
420
 
        self.vbox.show()
421
 
        hbox = self._get_button_bar()
422
 
        if hbox is not None:
423
 
            self.vbox.pack_start(hbox, expand=False, fill=True)
424
 
        self.diff = DiffWidget()
425
 
        self.vbox.add(self.diff)
426
 
        self.diff.show_all()
427
 
 
428
 
    def _get_button_bar(self):
429
 
        return None
430
 
 
431
 
    def set_diff_text(self, description, lines):
432
 
        self.diff.set_diff_text(lines)
433
 
        self.set_title(description + " - bzrk diff")
434
 
 
435
 
    def set_diff(self, description, rev_tree, parent_tree):
436
 
        """Set the differences showed by this window.
437
 
 
438
 
        Compares the two trees and populates the window with the
439
 
        differences.
440
 
        """
441
 
        self.diff.set_diff(rev_tree, parent_tree)
442
 
        self.set_title(description + " - bzrk diff")
443
 
 
444
 
    def set_file(self, file_path):
445
 
        self.diff.set_file(file_path)
446
 
 
447
 
 
448
 
class MergeDirectiveWindow(DiffWindow):
449
 
 
450
 
    def __init__(self, directive, parent=None):
451
 
        DiffWindow.__init__(self, parent)
452
 
        self._merge_target = None
453
 
        self.directive = directive
454
 
 
455
 
    def _get_button_bar(self):
456
 
        merge_button = gtk.Button('Merge')
457
 
        merge_button.show()
458
 
        merge_button.set_relief(gtk.RELIEF_NONE)
459
 
        merge_button.connect("clicked", self.perform_merge)
460
 
 
461
 
        hbox = gtk.HButtonBox()
462
 
        hbox.set_layout(gtk.BUTTONBOX_START)
463
 
        hbox.pack_start(merge_button, expand=False, fill=True)
464
 
        hbox.show()
465
 
        return hbox
466
 
 
467
 
    def perform_merge(self, window):
468
 
        try:
469
 
            tree = self._get_merge_target()
470
 
        except SelectCancelled:
471
 
            return
472
 
        tree.lock_write()
473
 
        try:
474
 
            try:
475
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
476
 
                    self.directive, progress.DummyProgress())
477
 
                merger.check_basis(True)
478
 
                merger.merge_type = _mod_merge.Merge3Merger
479
 
                merger.set_pending()
480
 
                conflict_count = merger.do_merge()
481
 
                self.destroy()
482
 
            except Exception, e:
483
 
                error_dialog('Error', str(e))
484
 
        finally:
485
 
            tree.unlock()
486
 
 
487
 
    def _get_merge_target(self):
488
 
        if self._merge_target is not None:
489
 
            return self._merge_target
490
 
        d = gtk.FileChooserDialog('Merge branch', self,
491
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
492
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
493
 
                                           gtk.STOCK_CANCEL,
494
 
                                           gtk.RESPONSE_CANCEL,))
495
 
        try:
496
 
            result = d.run()
497
 
            if result == gtk.RESPONSE_OK:
498
 
                uri = d.get_current_folder_uri()
499
 
                return workingtree.WorkingTree.open(uri)
500
 
            else:
501
 
                raise SelectCancelled()
502
 
        finally:
503
 
            d.destroy()
504
 
 
505
 
 
506
 
def _iter_changes_to_status(source, target):
507
 
    """Determine the differences between trees.
508
 
 
509
 
    This is a wrapper around _iter_changes which just yields more
510
 
    understandable results.
511
 
 
512
 
    :param source: The source tree (basis tree)
513
 
    :param target: The target tree
514
 
    :return: A list of (file_id, real_path, change_type, display_path)
515
 
    """
516
 
    added = 'added'
517
 
    removed = 'removed'
518
 
    renamed = 'renamed'
519
 
    renamed_and_modified = 'renamed and modified'
520
 
    modified = 'modified'
521
 
    kind_changed = 'kind changed'
522
 
 
523
 
    # TODO: Handle metadata changes
524
 
 
525
 
    status = []
526
 
    target.lock_read()
527
 
    try:
528
 
        source.lock_read()
529
 
        try:
530
 
            for (file_id, paths, changed_content, versioned, parent_ids, names,
531
 
                 kinds, executables) in target._iter_changes(source):
532
 
 
533
 
                # Skip the root entry if it isn't very interesting
534
 
                if parent_ids == (None, None):
535
 
                    continue
536
 
 
537
 
                change_type = None
538
 
                if kinds[0] is None:
539
 
                    source_marker = ''
540
 
                else:
541
 
                    source_marker = osutils.kind_marker(kinds[0])
542
 
                if kinds[1] is None:
543
 
                    assert kinds[0] is not None
544
 
                    marker = osutils.kind_marker(kinds[0])
545
 
                else:
546
 
                    marker = osutils.kind_marker(kinds[1])
547
 
 
548
 
                real_path = paths[1]
549
 
                if real_path is None:
550
 
                    real_path = paths[0]
551
 
                assert real_path is not None
552
 
                display_path = real_path + marker
553
 
 
554
 
                present_source = versioned[0] and kinds[0] is not None
555
 
                present_target = versioned[1] and kinds[1] is not None
556
 
 
557
 
                if present_source != present_target:
558
 
                    if present_target:
559
 
                        change_type = added
560
 
                    else:
561
 
                        assert present_source
562
 
                        change_type = removed
563
 
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
564
 
                    # Renamed
565
 
                    if changed_content or executables[0] != executables[1]:
566
 
                        # and modified
567
 
                        change_type = renamed_and_modified
568
 
                    else:
569
 
                        change_type = renamed
570
 
                    display_path = (paths[0] + source_marker
571
 
                                    + ' => ' + paths[1] + marker)
572
 
                elif kinds[0] != kinds[1]:
573
 
                    change_type = kind_changed
574
 
                    display_path = (paths[0] + source_marker
575
 
                                    + ' => ' + paths[1] + marker)
576
 
                elif changed_content is True or executables[0] != executables[1]:
577
 
                    change_type = modified
578
 
                else:
579
 
                    assert False, "How did we get here?"
580
 
 
581
 
                status.append((file_id, real_path, change_type, display_path))
582
 
        finally:
583
 
            source.unlock()
584
 
    finally:
585
 
        target.unlock()
586
 
 
587
 
    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'))