1
# -*- coding: UTF-8 -*-
2
1
"""Difference window.
4
3
This module contains the code to manage the diff window which shows
5
4
the changes made between two revisions on a branch.
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
7
__copyright__ = "Copyright 2005 Canonical Ltd."
9
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
12
11
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
24
26
have_gtksourceview = True
25
27
except ImportError:
26
28
have_gtksourceview = False
30
from gi.repository import GConf
30
32
except ImportError:
33
35
from bzrlib import (
34
37
merge as _mod_merge,
40
from bzrlib.diff import show_diff_trees, internal_diff
41
from bzrlib.errors import NoSuchFile
42
from bzrlib.diff import show_diff_trees
42
43
from bzrlib.patches import parse_patches
43
44
from bzrlib.trace import warning
44
from bzrlib.plugins.gtk import _i18n
45
from bzrlib.plugins.gtk.dialog import (
50
from bzrlib.plugins.gtk.i18n import _i18n
45
51
from bzrlib.plugins.gtk.window import Window
46
from dialog import error_dialog, info_dialog, warning_dialog
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():
49
62
class SelectCancelled(Exception):
54
class DiffFileView(gtk.ScrolledWindow):
67
class DiffFileView(Gtk.ScrolledWindow):
55
68
"""Window for displaying diffs from a diff file"""
57
70
def __init__(self):
58
gtk.ScrolledWindow.__init__(self)
71
GObject.GObject.__init__(self)
62
75
def construct(self):
63
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64
self.set_shadow_type(gtk.SHADOW_IN)
76
self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
77
self.set_shadow_type(Gtk.ShadowType.IN)
66
79
if have_gtksourceview:
67
self.buffer = gtksourceview.SourceBuffer()
68
slm = gtksourceview.SourceLanguagesManager()
69
gsl = slm.get_language_from_mime_type("text/x-patch")
80
self.buffer = GtkSource.Buffer()
81
slm = GtkSource.LanguageManager()
82
guess_language = getattr(GtkSource.LanguageManager,
83
"guess_language", fallback_guess_language)
84
gsl = guess_language(slm, content_type="text/x-patch")
71
self.apply_gedit_colors(gsl)
72
self.apply_colordiff_colors(gsl)
86
self.apply_gedit_colors(self.buffer)
87
self.apply_colordiff_colors(self.buffer)
73
88
self.buffer.set_language(gsl)
74
self.buffer.set_highlight(True)
89
self.buffer.set_highlight_syntax(True)
76
self.sourceview = gtksourceview.SourceView(self.buffer)
91
self.sourceview = GtkSource.View(self.buffer)
78
self.buffer = gtk.TextBuffer()
79
self.sourceview = gtk.TextView(self.buffer)
93
self.buffer = Gtk.TextBuffer()
94
self.sourceview = Gtk.TextView(self.buffer)
81
96
self.sourceview.set_editable(False)
82
self.sourceview.modify_font(pango.FontDescription("Monospace"))
97
self.sourceview.modify_font(Pango.FontDescription("Monospace"))
83
98
self.add(self.sourceview)
84
99
self.sourceview.show()
87
def apply_gedit_colors(lang):
88
"""Set style for lang to that specified in gedit configuration.
102
def apply_gedit_colors(buf):
103
"""Set style to that specified in gedit configuration.
90
105
This method needs the gconf module.
92
:param lang: a gtksourceview.SourceLanguage object.
107
:param buf: a GtkSource.Buffer object.
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)
109
GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
110
GEDIT_USER_STYLES_PATH = os.path.expanduser('~/.gnome2/gedit/styles')
112
client = GConf.Client.get_default()
113
style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
114
if style_scheme_name is not None:
115
style_scheme_mgr = GtkSource.StyleSchemeManager()
116
style_scheme_mgr.append_search_path(GEDIT_USER_STYLES_PATH)
118
style_scheme = style_scheme_mgr.get_scheme(style_scheme_name)
120
if style_scheme is not None:
121
buf.set_style_scheme(style_scheme)
150
def apply_colordiff_colors(klass, lang):
124
def apply_colordiff_colors(klass, buf):
151
125
"""Set style colors for lang using the colordiff configuration file.
153
127
Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
155
:param lang: a "Diff" gtksourceview.SourceLanguage object.
129
:param buf: a "Diff" GtkSource.Buffer object.
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
131
scheme_manager = GtkSource.StyleSchemeManager()
132
style_scheme = scheme_manager.get_scheme('colordiff')
134
# if style scheme not found, we'll generate it from colordiffrc
135
# TODO: reload if colordiffrc has changed.
136
if style_scheme is None:
139
for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
140
f = os.path.expanduser(f)
141
if os.path.exists(f):
145
warning('could not open file %s: %s' % (f, str(e)))
147
colors.update(klass.parse_colordiffrc(f))
151
# ~/.colordiffrc does not exist
155
# map GtkSourceView2 scheme styles to colordiff names
176
156
# since GSV is richer, accept new names for extra bits,
177
157
# defaulting to old names if they're not present
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)
158
'diff:added-line': ['newtext'],
159
'diff:removed-line': ['oldtext'],
160
'diff:location': ['location', 'diffstuff'],
161
'diff:file': ['file', 'diffstuff'],
162
'diff:special-case': ['specialcase', 'diffstuff'],
165
converted_colors = {}
166
for name, values in mapping.items():
169
color = colors.get(value, None)
170
if color is not None:
174
converted_colors[name] = color
176
# some xml magic to produce needed style scheme description
177
e_style_scheme = Element('style-scheme')
178
e_style_scheme.set('id', 'colordiff')
179
e_style_scheme.set('_name', 'ColorDiff')
180
e_style_scheme.set('version', '1.0')
181
for name, color in converted_colors.items():
182
style = SubElement(e_style_scheme, 'style')
183
style.set('name', name)
184
style.set('foreground', '#%s' % color)
186
scheme_xml = tostring(e_style_scheme, 'UTF-8')
187
if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
188
os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
189
file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
191
scheme_manager.force_rescan()
192
style_scheme = scheme_manager.get_scheme('colordiff')
194
buf.set_style_scheme(style_scheme)
207
197
def parse_colordiffrc(fileobj):