1
# -*- coding: UTF-8 -*-
1
2
"""Difference window.
3
4
This module contains the code to manage the diff window which shows
4
5
the changes made between two revisions on a branch.
7
__copyright__ = "Copyright 2005 Canonical Ltd."
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
9
__author__ = "Scott James Remnant <scott@ubuntu.com>"
11
12
from cStringIO import StringIO
13
from gi.repository import Gtk
14
from gi.repository import Pango
20
from xml.etree.ElementTree import Element, SubElement, tostring
22
from elementtree.ElementTree import Element, SubElement, tostring
25
from gi.repository import GtkSource
26
24
have_gtksourceview = True
27
25
except ImportError:
28
26
have_gtksourceview = False
30
from gi.repository import GConf
32
30
except ImportError:
35
33
from bzrlib import (
37
34
merge as _mod_merge,
42
from bzrlib.diff import show_diff_trees
40
from bzrlib.diff import show_diff_trees, internal_diff
41
from bzrlib.errors import NoSuchFile
43
42
from bzrlib.patches import parse_patches
44
43
from bzrlib.trace import warning
45
from bzrlib.plugins.gtk.dialog import (
50
from bzrlib.plugins.gtk.i18n import _i18n
44
from bzrlib.plugins.gtk import _i18n
51
45
from bzrlib.plugins.gtk.window import Window
54
def fallback_guess_language(slm, content_type):
55
for lang_id in slm.get_language_ids():
56
lang = slm.get_language(lang_id)
57
if "text/x-patch" in lang.get_mime_types():
46
from dialog import error_dialog, info_dialog, warning_dialog
62
49
class SelectCancelled(Exception):
67
class DiffFileView(Gtk.ScrolledWindow):
54
class DiffFileView(gtk.ScrolledWindow):
68
55
"""Window for displaying diffs from a diff file"""
70
57
def __init__(self):
71
Gtk.ScrolledWindow.__init__(self)
58
gtk.ScrolledWindow.__init__(self)
75
62
def construct(self):
76
self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
77
self.set_shadow_type(Gtk.ShadowType.IN)
63
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64
self.set_shadow_type(gtk.SHADOW_IN)
79
66
if have_gtksourceview:
80
self.buffer = GtkSource.Buffer()
81
lang_manager = GtkSource.LanguageManager.get_default()
82
language = lang_manager.guess_language(None, "text/x-patch")
67
self.buffer = gtksourceview.SourceBuffer()
68
slm = gtksourceview.SourceLanguagesManager()
69
gsl = slm.get_language_from_mime_type("text/x-patch")
84
self.apply_gedit_colors(self.buffer)
85
self.apply_colordiff_colors(self.buffer)
86
self.buffer.set_language(language)
87
self.buffer.set_highlight_syntax(True)
71
self.apply_gedit_colors(gsl)
72
self.apply_colordiff_colors(gsl)
73
self.buffer.set_language(gsl)
74
self.buffer.set_highlight(True)
89
self.sourceview = GtkSource.View(buffer=self.buffer)
76
sourceview = gtksourceview.SourceView(self.buffer)
91
self.buffer = Gtk.TextBuffer()
92
self.sourceview = Gtk.TextView(self.buffer)
78
self.buffer = gtk.TextBuffer()
79
sourceview = gtk.TextView(self.buffer)
94
self.sourceview.set_editable(False)
95
self.sourceview.modify_font(Pango.FontDescription("Monospace"))
96
self.add(self.sourceview)
97
self.sourceview.show()
81
sourceview.set_editable(False)
82
sourceview.modify_font(pango.FontDescription("Monospace"))
100
def apply_gedit_colors(buf):
101
"""Set style to that specified in gedit configuration.
87
def apply_gedit_colors(lang):
88
"""Set style for lang to that specified in gedit configuration.
103
90
This method needs the gconf module.
105
:param buf: a GtkSource.Buffer object.
92
:param lang: a gtksourceview.SourceLanguage object.
107
GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
108
GEDIT_USER_STYLES_PATH = os.path.expanduser('~/.gnome2/gedit/styles')
110
client = GConf.Client.get_default()
111
style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
112
if style_scheme_name is not None:
113
style_scheme_mgr = GtkSource.StyleSchemeManager()
114
style_scheme_mgr.append_search_path(GEDIT_USER_STYLES_PATH)
116
style_scheme = style_scheme_mgr.get_scheme(style_scheme_name)
118
if style_scheme is not None:
119
buf.set_style_scheme(style_scheme)
94
GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
95
GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
97
client = gconf.client_get_default()
98
client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
100
for tag in lang.get_tags():
101
tag_id = tag.get_id()
102
gconf_key = GEDIT_LANG_PATH + '/' + tag_id
103
style_string = client.get_string(gconf_key)
105
if style_string is None:
108
# function to get a bool from a string that's either '0' or '1'
109
string_bool = lambda x: bool(int(x))
111
# style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
112
# values are: mask, fg, bg, italic, bold, underline, strike
113
# this packs them into (str_value, attr_name, conv_func) tuples
114
items = zip(style_string.split('/'), ['mask', 'foreground',
115
'background', 'italic', 'bold', 'underline', 'strikethrough' ],
116
[ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
117
string_bool, string_bool, string_bool ]
120
style = gtksourceview.SourceTagStyle()
122
# XXX The mask attribute controls whether the present values of
123
# foreground and background color should in fact be used. Ideally
124
# (and that's what gedit does), one could set all three attributes,
125
# and let the TagStyle object figure out which colors to use.
126
# However, in the GtkSourceview python bindings, the mask attribute
127
# is read-only, and it's derived instead from the colors being
128
# set or not. This means that we have to sometimes refrain from
129
# setting fg or bg colors, depending on the value of the mask.
130
# This code could go away if mask were writable.
131
mask = int(items[0][0])
132
if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
134
if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
136
items[0:1] = [] # skip the mask unconditionally
138
for value, attr, func in items:
142
warning('gconf key %s contains an invalid value: %s'
145
setattr(style, attr, value)
147
lang.set_tag_style(tag_id, style)
122
def apply_colordiff_colors(klass, buf):
150
def apply_colordiff_colors(klass, lang):
123
151
"""Set style colors for lang using the colordiff configuration file.
125
153
Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
127
:param buf: a "Diff" GtkSource.Buffer object.
155
:param lang: a "Diff" gtksourceview.SourceLanguage object.
129
scheme_manager = GtkSource.StyleSchemeManager()
130
style_scheme = scheme_manager.get_scheme('colordiff')
132
# if style scheme not found, we'll generate it from colordiffrc
133
# TODO: reload if colordiffrc has changed.
134
if style_scheme is None:
137
for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
138
f = os.path.expanduser(f)
139
if os.path.exists(f):
143
warning('could not open file %s: %s' % (f, str(e)))
145
colors.update(klass.parse_colordiffrc(f))
149
# ~/.colordiffrc does not exist
153
# map GtkSourceView2 scheme styles to colordiff names
159
for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
160
f = os.path.expanduser(f)
161
if os.path.exists(f):
165
warning('could not open file %s: %s' % (f, str(e)))
167
colors.update(klass.parse_colordiffrc(f))
171
# ~/.colordiffrc does not exist
175
# map GtkSourceView tags to colordiff names
154
176
# since GSV is richer, accept new names for extra bits,
155
177
# defaulting to old names if they're not present
156
'diff:added-line': ['newtext'],
157
'diff:removed-line': ['oldtext'],
158
'diff:location': ['location', 'diffstuff'],
159
'diff:file': ['file', 'diffstuff'],
160
'diff:special-case': ['specialcase', 'diffstuff'],
163
converted_colors = {}
164
for name, values in mapping.items():
167
color = colors.get(value, None)
168
if color is not None:
172
converted_colors[name] = color
174
# some xml magic to produce needed style scheme description
175
e_style_scheme = Element('style-scheme')
176
e_style_scheme.set('id', 'colordiff')
177
e_style_scheme.set('_name', 'ColorDiff')
178
e_style_scheme.set('version', '1.0')
179
for name, color in converted_colors.items():
180
style = SubElement(e_style_scheme, 'style')
181
style.set('name', name)
182
style.set('foreground', '#%s' % color)
184
scheme_xml = tostring(e_style_scheme, 'UTF-8')
185
if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
186
os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
187
file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
189
scheme_manager.force_rescan()
190
style_scheme = scheme_manager.get_scheme('colordiff')
192
buf.set_style_scheme(style_scheme)
178
'Added@32@line': ['newtext'],
179
'Removed@32@line': ['oldtext'],
180
'Location': ['location', 'diffstuff'],
181
'Diff@32@file': ['file', 'diffstuff'],
182
'Special@32@case': ['specialcase', 'diffstuff'],
185
for tag in lang.get_tags():
186
tag_id = tag.get_id()
187
keys = mapping.get(tag_id, [])
191
color = colors.get(key, None)
192
if color is not None:
198
style = gtksourceview.SourceTagStyle()
200
style.foreground = gtk.gdk.color_parse(color)
202
warning('not a valid color: %s' % color)
204
lang.set_tag_style(tag_id, style)
195
207
def parse_colordiffrc(fileobj):