|
|
|
|
70
|
}
|
70
|
}
|
71
|
|
71
|
|
72
|
|
72
|
|
73
|
-def ansi_code(color):
|
|
|
74
|
- return COLORS.get(color, '')
|
|
|
75
|
-
|
|
|
76
|
def colorize(text, start_color, end_color='reset'):
|
73
|
def colorize(text, start_color, end_color='reset'):
|
77
|
- return ansi_code(start_color) + text + ansi_code(end_color)
|
|
|
|
|
74
|
+ return COLORS[start_color] + text + COLORS[end_color]
|
78
|
|
75
|
|
79
|
|
76
|
|
80
|
class Hunk(object):
|
77
|
class Hunk(object):
|
|
|
|
|
217
|
|
214
|
|
218
|
def markup_side_by_side(self, width):
|
215
|
def markup_side_by_side(self, width):
|
219
|
"""Returns a generator"""
|
216
|
"""Returns a generator"""
|
|
|
217
|
+ wrap_char = colorize('>', 'lightmagenta')
|
220
|
def _normalize(line):
|
218
|
def _normalize(line):
|
221
|
return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
|
219
|
return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
|
222
|
|
220
|
|
223
|
- def _fit_width(markup, width, pad=False):
|
|
|
|
|
221
|
+ def _fit_with_marker(text, markup_fn, width, pad=False):
|
|
|
222
|
+ """Wrap or pad input pure text, then markup"""
|
|
|
223
|
+ if len(text) > width:
|
|
|
224
|
+ return markup_fn(text[:width-1]) + wrap_char
|
|
|
225
|
+ elif pad:
|
|
|
226
|
+ pad_len = width - len(text)
|
|
|
227
|
+ return '%s%*s' % (markup_fn(text), pad_len, '')
|
|
|
228
|
+ else:
|
|
|
229
|
+ return markup_fn(text)
|
|
|
230
|
+
|
|
|
231
|
+ def _fit_markup(markup, width, pad=False):
|
224
|
"""Fit input markup to given width, pad or wrap accordingly, str len
|
232
|
"""Fit input markup to given width, pad or wrap accordingly, str len
|
225
|
- does not count correctly if line contains ansi color code. Only
|
|
|
|
|
233
|
+ does not count correctly if string contains ansi color code. Only
|
226
|
left side need to set `pad`
|
234
|
left side need to set `pad`
|
227
|
"""
|
235
|
"""
|
228
|
out = []
|
236
|
out = []
|
229
|
count = 0
|
237
|
count = 0
|
230
|
ansi_color_regex = r'\x1b\[(1;)?\d{1,2}m'
|
238
|
ansi_color_regex = r'\x1b\[(1;)?\d{1,2}m'
|
231
|
- patt = re.compile('^(%s)(.*)' % ansi_color_regex)
|
|
|
|
|
239
|
+ patt = re.compile('^((%s)+)(.*)' % ansi_color_regex)
|
232
|
repl = re.compile(ansi_color_regex)
|
240
|
repl = re.compile(ansi_color_regex)
|
233
|
|
241
|
|
234
|
while markup and count < width:
|
242
|
while markup and count < width:
|
235
|
if patt.match(markup):
|
243
|
if patt.match(markup):
|
236
|
- # Extract the ansi color code seq to target output and
|
|
|
237
|
- # remove the seq from input markup, no update on counter
|
|
|
|
|
244
|
+ # Extract longest ansi color code seq to target output and
|
|
|
245
|
+ # remove the seq from input markup, no update on counter
|
238
|
#
|
246
|
#
|
239
|
out.append(patt.sub(r'\1', markup))
|
247
|
out.append(patt.sub(r'\1', markup))
|
240
|
- markup = patt.sub(r'\3', markup)
|
|
|
|
|
248
|
+ markup = patt.sub(r'\4', markup)
|
241
|
else:
|
249
|
else:
|
242
|
# FIXME: utf-8 wchar might break the rule here, e.g.
|
250
|
# FIXME: utf-8 wchar might break the rule here, e.g.
|
243
|
# u'\u554a' takes double width of a single letter, also this
|
251
|
# u'\u554a' takes double width of a single letter, also this
|
|
|
|
|
250
|
|
258
|
|
251
|
if count == width and repl.sub('', markup):
|
259
|
if count == width and repl.sub('', markup):
|
252
|
# Was stripped: output fulfil and still has ascii in markup
|
260
|
# Was stripped: output fulfil and still has ascii in markup
|
253
|
- out[-1] = ansi_code('reset') + colorize('>', 'lightmagenta')
|
|
|
|
|
261
|
+ out[-1] = COLORS['reset'] + wrap_char
|
254
|
elif count < width and pad:
|
262
|
elif count < width and pad:
|
255
|
pad_len = width - count
|
263
|
pad_len = width - count
|
256
|
out.append('%*s' % (pad_len, ''))
|
264
|
out.append('%*s' % (pad_len, ''))
|
|
|
|
|
260
|
# Setup line width and number width
|
268
|
# Setup line width and number width
|
261
|
if width <= 0:
|
269
|
if width <= 0:
|
262
|
width = 80
|
270
|
width = 80
|
|
|
271
|
+
|
263
|
(start, offset) = self._hunks[-1].get_old_addr()
|
272
|
(start, offset) = self._hunks[-1].get_old_addr()
|
264
|
max1 = start + offset - 1
|
273
|
max1 = start + offset - 1
|
265
|
(start, offset) = self._hunks[-1].get_new_addr()
|
274
|
(start, offset) = self._hunks[-1].get_new_addr()
|
|
|
|
|
267
|
num_width = max(len(str(max1)), len(str(max2)))
|
276
|
num_width = max(len(str(max1)), len(str(max2)))
|
268
|
left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
277
|
left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
269
|
right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
278
|
right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
270
|
- line_fmt = left_num_fmt + ' %(left)s ' + ansi_code('reset') + \
|
|
|
|
|
279
|
+ line_fmt = left_num_fmt + ' %(left)s ' + COLORS['reset'] + \
|
271
|
right_num_fmt + ' %(right)s\n'
|
280
|
right_num_fmt + ' %(right)s\n'
|
272
|
|
281
|
|
273
|
# yield header, old path and new path
|
282
|
# yield header, old path and new path
|
|
|
|
|
299
|
if not old[0]:
|
308
|
if not old[0]:
|
300
|
left = '%*s' % (width, ' ')
|
309
|
left = '%*s' % (width, ' ')
|
301
|
right = right.lstrip('\x00+').rstrip('\x01')
|
310
|
right = right.lstrip('\x00+').rstrip('\x01')
|
302
|
- right = _fit_width(self._markup_new(right), width)
|
|
|
|
|
311
|
+ right = _fit_with_marker(right, self._markup_new, width)
|
303
|
elif not new[0]:
|
312
|
elif not new[0]:
|
304
|
left = left.lstrip('\x00-').rstrip('\x01')
|
313
|
left = left.lstrip('\x00-').rstrip('\x01')
|
305
|
- left = _fit_width(self._markup_old(left), width)
|
|
|
|
|
314
|
+ left = _fit_with_marker(left, self._markup_old, width)
|
306
|
right = ''
|
315
|
right = ''
|
307
|
else:
|
316
|
else:
|
308
|
- left = _fit_width(self._markup_old_mix(left), width, 1)
|
|
|
309
|
- right = _fit_width(self._markup_new_mix(right), width)
|
|
|
|
|
317
|
+ left = _fit_markup(self._markup_old_mix(left), width, 1)
|
|
|
318
|
+ right = _fit_markup(self._markup_new_mix(right), width)
|
310
|
else:
|
319
|
else:
|
311
|
- left = _fit_width(self._markup_common(left), width, 1)
|
|
|
312
|
- right = _fit_width(self._markup_common(right), width)
|
|
|
|
|
320
|
+ left = _fit_with_marker(left, self._markup_common, width, 1)
|
|
|
321
|
+ right = _fit_with_marker(right, self._markup_common, width)
|
313
|
yield line_fmt % {
|
322
|
yield line_fmt % {
|
314
|
'left_num': left_num,
|
323
|
'left_num': left_num,
|
315
|
'left': left,
|
324
|
'left': left,
|
|
|
|
|
342
|
return colorize(line, 'lightgreen')
|
351
|
return colorize(line, 'lightgreen')
|
343
|
|
352
|
|
344
|
def _markup_mix(self, line, base_color):
|
353
|
def _markup_mix(self, line, base_color):
|
345
|
- del_code = ansi_code('reverse') + ansi_code(base_color)
|
|
|
346
|
- add_code = ansi_code('reverse') + ansi_code(base_color)
|
|
|
347
|
- chg_code = ansi_code('underline') + ansi_code(base_color)
|
|
|
348
|
- rst_code = ansi_code('reset') + ansi_code(base_color)
|
|
|
|
|
354
|
+ del_code = COLORS['reverse'] + COLORS[base_color]
|
|
|
355
|
+ add_code = COLORS['reverse'] + COLORS[base_color]
|
|
|
356
|
+ chg_code = COLORS['underline'] + COLORS[base_color]
|
|
|
357
|
+ rst_code = COLORS['reset'] + COLORS[base_color]
|
349
|
line = line.replace('\x00-', del_code)
|
358
|
line = line.replace('\x00-', del_code)
|
350
|
line = line.replace('\x00+', add_code)
|
359
|
line = line.replace('\x00+', add_code)
|
351
|
line = line.replace('\x00^', chg_code)
|
360
|
line = line.replace('\x00^', chg_code)
|