|
@@ -121,49 +121,7 @@ class Hunk(object):
|
121
|
121
|
return out
|
122
|
122
|
|
123
|
123
|
|
124
|
|
-class DiffOps(object): # pragma: no cover
|
125
|
|
- """Methods in this class are supposed to be overwritten by derived class.
|
126
|
|
- No is_header() anymore, all non-recognized lines are considered as headers
|
127
|
|
- """
|
128
|
|
- def is_old_path(self, line):
|
129
|
|
- return False
|
130
|
|
-
|
131
|
|
- def is_new_path(self, line):
|
132
|
|
- return False
|
133
|
|
-
|
134
|
|
- def is_hunk_meta(self, line):
|
135
|
|
- return False
|
136
|
|
-
|
137
|
|
- def parse_hunk_meta(self, line):
|
138
|
|
- """Returns a 2-element tuple, each is a tuple of (start, offset)"""
|
139
|
|
- return None
|
140
|
|
-
|
141
|
|
- def parse_hunk_line(self, line):
|
142
|
|
- """Returns a 2-element tuple: (attr, text), where attr is:
|
143
|
|
- '-': old, '+': new, ' ': common
|
144
|
|
- """
|
145
|
|
- return None
|
146
|
|
-
|
147
|
|
- def is_old(self, line):
|
148
|
|
- return False
|
149
|
|
-
|
150
|
|
- def is_new(self, line):
|
151
|
|
- return False
|
152
|
|
-
|
153
|
|
- def is_common(self, line):
|
154
|
|
- return False
|
155
|
|
-
|
156
|
|
- def is_eof(self, line):
|
157
|
|
- return False
|
158
|
|
-
|
159
|
|
- def is_only_in_dir(self, line):
|
160
|
|
- return False
|
161
|
|
-
|
162
|
|
- def is_binary_differ(self, line):
|
163
|
|
- return False
|
164
|
|
-
|
165
|
|
-
|
166
|
|
-class Diff(DiffOps):
|
|
124
|
+class UnifiedDiff(object):
|
167
|
125
|
|
168
|
126
|
def __init__(self, headers, old_path, new_path, hunks):
|
169
|
127
|
self._headers = headers
|
|
@@ -171,206 +129,6 @@ class Diff(DiffOps):
|
171
|
129
|
self._new_path = new_path
|
172
|
130
|
self._hunks = hunks
|
173
|
131
|
|
174
|
|
- def markup_traditional(self):
|
175
|
|
- """Returns a generator"""
|
176
|
|
- for line in self._headers:
|
177
|
|
- yield self._markup_header(line)
|
178
|
|
-
|
179
|
|
- yield self._markup_old_path(self._old_path)
|
180
|
|
- yield self._markup_new_path(self._new_path)
|
181
|
|
-
|
182
|
|
- for hunk in self._hunks:
|
183
|
|
- for hunk_header in hunk._hunk_headers:
|
184
|
|
- yield self._markup_hunk_header(hunk_header)
|
185
|
|
- yield self._markup_hunk_meta(hunk._hunk_meta)
|
186
|
|
- for old, new, changed in hunk.mdiff():
|
187
|
|
- if changed:
|
188
|
|
- if not old[0]:
|
189
|
|
- # The '+' char after \x00 is kept
|
190
|
|
- # DEBUG: yield 'NEW: %s %s\n' % (old, new)
|
191
|
|
- line = new[1].strip('\x00\x01')
|
192
|
|
- yield self._markup_new(line)
|
193
|
|
- elif not new[0]:
|
194
|
|
- # The '-' char after \x00 is kept
|
195
|
|
- # DEBUG: yield 'OLD: %s %s\n' % (old, new)
|
196
|
|
- line = old[1].strip('\x00\x01')
|
197
|
|
- yield self._markup_old(line)
|
198
|
|
- else:
|
199
|
|
- # DEBUG: yield 'CHG: %s %s\n' % (old, new)
|
200
|
|
- yield self._markup_old('-') + \
|
201
|
|
- self._markup_mix(old[1], 'red')
|
202
|
|
- yield self._markup_new('+') + \
|
203
|
|
- self._markup_mix(new[1], 'green')
|
204
|
|
- else:
|
205
|
|
- yield self._markup_common(' ' + old[1])
|
206
|
|
-
|
207
|
|
- def markup_side_by_side(self, width):
|
208
|
|
- """Returns a generator"""
|
209
|
|
- wrap_char = colorize('>', 'lightmagenta')
|
210
|
|
-
|
211
|
|
- def _normalize(line):
|
212
|
|
- return line.replace(
|
213
|
|
- '\t', ' ' * 8).replace('\n', '').replace('\r', '')
|
214
|
|
-
|
215
|
|
- def _fit_with_marker(text, markup_fn, width, pad=False):
|
216
|
|
- """Wrap or pad input pure text, then markup"""
|
217
|
|
- if len(text) > width:
|
218
|
|
- return markup_fn(text[:(width - 1)]) + wrap_char
|
219
|
|
- elif pad:
|
220
|
|
- pad_len = width - len(text)
|
221
|
|
- return '%s%*s' % (markup_fn(text), pad_len, '')
|
222
|
|
- else:
|
223
|
|
- return markup_fn(text)
|
224
|
|
-
|
225
|
|
- def _fit_with_marker_mix(text, base_color, width, pad=False):
|
226
|
|
- """Wrap or pad input text which contains mdiff tags, markup at the
|
227
|
|
- meantime, note only left side need to set `pad`
|
228
|
|
- """
|
229
|
|
- out = [COLORS[base_color]]
|
230
|
|
- count = 0
|
231
|
|
- tag_re = re.compile(r'\x00[+^-]|\x01')
|
232
|
|
-
|
233
|
|
- while text and count < width:
|
234
|
|
- if text.startswith('\x00-'): # del
|
235
|
|
- out.append(COLORS['reverse'] + COLORS[base_color])
|
236
|
|
- text = text[2:]
|
237
|
|
- elif text.startswith('\x00+'): # add
|
238
|
|
- out.append(COLORS['reverse'] + COLORS[base_color])
|
239
|
|
- text = text[2:]
|
240
|
|
- elif text.startswith('\x00^'): # change
|
241
|
|
- out.append(COLORS['underline'] + COLORS[base_color])
|
242
|
|
- text = text[2:]
|
243
|
|
- elif text.startswith('\x01'): # reset
|
244
|
|
- out.append(COLORS['reset'] + COLORS[base_color])
|
245
|
|
- text = text[1:]
|
246
|
|
- else:
|
247
|
|
- # FIXME: utf-8 wchar might break the rule here, e.g.
|
248
|
|
- # u'\u554a' takes double width of a single letter, also
|
249
|
|
- # this depends on your terminal font. I guess audience of
|
250
|
|
- # this tool never put that kind of symbol in their code :-)
|
251
|
|
- #
|
252
|
|
- out.append(text[0])
|
253
|
|
- count += 1
|
254
|
|
- text = text[1:]
|
255
|
|
-
|
256
|
|
- if count == width and tag_re.sub('', text):
|
257
|
|
- # Was stripped: output fulfil and still has normal char in text
|
258
|
|
- out[-1] = COLORS['reset'] + wrap_char
|
259
|
|
- elif count < width and pad:
|
260
|
|
- pad_len = width - count
|
261
|
|
- out.append('%s%*s' % (COLORS['reset'], pad_len, ''))
|
262
|
|
- else:
|
263
|
|
- out.append(COLORS['reset'])
|
264
|
|
-
|
265
|
|
- return ''.join(out)
|
266
|
|
-
|
267
|
|
- # Set up line width
|
268
|
|
- if width <= 0:
|
269
|
|
- width = 80
|
270
|
|
-
|
271
|
|
- # Set up number width, note last hunk might be empty
|
272
|
|
- try:
|
273
|
|
- (start, offset) = self._hunks[-1]._old_addr
|
274
|
|
- max1 = start + offset - 1
|
275
|
|
- (start, offset) = self._hunks[-1]._new_addr
|
276
|
|
- max2 = start + offset - 1
|
277
|
|
- except IndexError:
|
278
|
|
- max1 = max2 = 0
|
279
|
|
- num_width = max(len(str(max1)), len(str(max2)))
|
280
|
|
-
|
281
|
|
- # Setup lineno and line format
|
282
|
|
- left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
283
|
|
- right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
284
|
|
- line_fmt = left_num_fmt + ' %(left)s ' + COLORS['reset'] + \
|
285
|
|
- right_num_fmt + ' %(right)s\n'
|
286
|
|
-
|
287
|
|
- # yield header, old path and new path
|
288
|
|
- for line in self._headers:
|
289
|
|
- yield self._markup_header(line)
|
290
|
|
- yield self._markup_old_path(self._old_path)
|
291
|
|
- yield self._markup_new_path(self._new_path)
|
292
|
|
-
|
293
|
|
- # yield hunks
|
294
|
|
- for hunk in self._hunks:
|
295
|
|
- for hunk_header in hunk._hunk_headers:
|
296
|
|
- yield self._markup_hunk_header(hunk_header)
|
297
|
|
- yield self._markup_hunk_meta(hunk._hunk_meta)
|
298
|
|
- for old, new, changed in hunk.mdiff():
|
299
|
|
- if old[0]:
|
300
|
|
- left_num = str(hunk._old_addr[0] + int(old[0]) - 1)
|
301
|
|
- else:
|
302
|
|
- left_num = ' '
|
303
|
|
-
|
304
|
|
- if new[0]:
|
305
|
|
- right_num = str(hunk._new_addr[0] + int(new[0]) - 1)
|
306
|
|
- else:
|
307
|
|
- right_num = ' '
|
308
|
|
-
|
309
|
|
- left = _normalize(old[1])
|
310
|
|
- right = _normalize(new[1])
|
311
|
|
-
|
312
|
|
- if changed:
|
313
|
|
- if not old[0]:
|
314
|
|
- left = '%*s' % (width, ' ')
|
315
|
|
- right = right.lstrip('\x00+').rstrip('\x01')
|
316
|
|
- right = _fit_with_marker(
|
317
|
|
- right, self._markup_new, width)
|
318
|
|
- elif not new[0]:
|
319
|
|
- left = left.lstrip('\x00-').rstrip('\x01')
|
320
|
|
- left = _fit_with_marker(left, self._markup_old, width)
|
321
|
|
- right = ''
|
322
|
|
- else:
|
323
|
|
- left = _fit_with_marker_mix(left, 'red', width, 1)
|
324
|
|
- right = _fit_with_marker_mix(right, 'green', width)
|
325
|
|
- else:
|
326
|
|
- left = _fit_with_marker(
|
327
|
|
- left, self._markup_common, width, 1)
|
328
|
|
- right = _fit_with_marker(right, self._markup_common, width)
|
329
|
|
- yield line_fmt % {
|
330
|
|
- 'left_num': left_num,
|
331
|
|
- 'left': left,
|
332
|
|
- 'right_num': right_num,
|
333
|
|
- 'right': right
|
334
|
|
- }
|
335
|
|
-
|
336
|
|
- def _markup_header(self, line):
|
337
|
|
- return colorize(line, 'cyan')
|
338
|
|
-
|
339
|
|
- def _markup_old_path(self, line):
|
340
|
|
- return colorize(line, 'yellow')
|
341
|
|
-
|
342
|
|
- def _markup_new_path(self, line):
|
343
|
|
- return colorize(line, 'yellow')
|
344
|
|
-
|
345
|
|
- def _markup_hunk_header(self, line):
|
346
|
|
- return colorize(line, 'lightcyan')
|
347
|
|
-
|
348
|
|
- def _markup_hunk_meta(self, line):
|
349
|
|
- return colorize(line, 'lightblue')
|
350
|
|
-
|
351
|
|
- def _markup_common(self, line):
|
352
|
|
- return colorize(line, 'reset')
|
353
|
|
-
|
354
|
|
- def _markup_old(self, line):
|
355
|
|
- return colorize(line, 'lightred')
|
356
|
|
-
|
357
|
|
- def _markup_new(self, line):
|
358
|
|
- return colorize(line, 'lightgreen')
|
359
|
|
-
|
360
|
|
- def _markup_mix(self, line, base_color):
|
361
|
|
- del_code = COLORS['reverse'] + COLORS[base_color]
|
362
|
|
- add_code = COLORS['reverse'] + COLORS[base_color]
|
363
|
|
- chg_code = COLORS['underline'] + COLORS[base_color]
|
364
|
|
- rst_code = COLORS['reset'] + COLORS[base_color]
|
365
|
|
- line = line.replace('\x00-', del_code)
|
366
|
|
- line = line.replace('\x00+', add_code)
|
367
|
|
- line = line.replace('\x00^', chg_code)
|
368
|
|
- line = line.replace('\x01', rst_code)
|
369
|
|
- return colorize(line, base_color)
|
370
|
|
-
|
371
|
|
-
|
372
|
|
-class UnifiedDiff(Diff):
|
373
|
|
-
|
374
|
132
|
def is_old_path(self, line):
|
375
|
133
|
return line.startswith('--- ')
|
376
|
134
|
|
|
@@ -551,46 +309,45 @@ class DiffParser(object):
|
551
|
309
|
self._stream = stream
|
552
|
310
|
|
553
|
311
|
def get_diff_generator(self):
|
554
|
|
- """parse all diff lines, construct a list of Diff objects"""
|
555
|
|
- difflet = UnifiedDiff(None, None, None, None)
|
556
|
|
- diff = Diff([], None, None, [])
|
|
312
|
+ """parse all diff lines, construct a list of UnifiedDiff objects"""
|
|
313
|
+ diff = UnifiedDiff([], None, None, [])
|
557
|
314
|
headers = []
|
558
|
315
|
|
559
|
316
|
for line in self._stream:
|
560
|
317
|
line = decode(line)
|
561
|
318
|
|
562
|
|
- if difflet.is_old_path(line):
|
|
319
|
+ if diff.is_old_path(line):
|
563
|
320
|
# FIXME: '--- ' breaks here, better to probe next line
|
564
|
321
|
if diff._old_path and diff._new_path and diff._hunks:
|
565
|
322
|
# See a new diff, yield previous diff if exists
|
566
|
323
|
yield diff
|
567
|
|
- diff = Diff(headers, line, None, [])
|
|
324
|
+ diff = UnifiedDiff(headers, line, None, [])
|
568
|
325
|
headers = []
|
569
|
326
|
|
570
|
|
- elif difflet.is_new_path(line) and diff._old_path:
|
|
327
|
+ elif diff.is_new_path(line) and diff._old_path:
|
571
|
328
|
diff._new_path = line
|
572
|
329
|
|
573
|
|
- elif difflet.is_hunk_meta(line):
|
|
330
|
+ elif diff.is_hunk_meta(line):
|
574
|
331
|
hunk_meta = line
|
575
|
332
|
try:
|
576
|
|
- old_addr, new_addr = difflet.parse_hunk_meta(hunk_meta)
|
|
333
|
+ old_addr, new_addr = diff.parse_hunk_meta(hunk_meta)
|
577
|
334
|
except (IndexError, ValueError):
|
578
|
335
|
raise RuntimeError('invalid hunk meta: %s' % hunk_meta)
|
579
|
336
|
hunk = Hunk(headers, hunk_meta, old_addr, new_addr)
|
580
|
337
|
headers = []
|
581
|
338
|
diff._hunks.append(hunk)
|
582
|
339
|
|
583
|
|
- elif diff._hunks and not headers and (difflet.is_old(line) or
|
584
|
|
- difflet.is_new(line) or
|
585
|
|
- difflet.is_common(line)):
|
586
|
|
- diff._hunks[-1].append(difflet.parse_hunk_line(line))
|
|
340
|
+ elif diff._hunks and not headers and (diff.is_old(line) or
|
|
341
|
+ diff.is_new(line) or
|
|
342
|
+ diff.is_common(line)):
|
|
343
|
+ diff._hunks[-1].append(diff.parse_hunk_line(line))
|
587
|
344
|
|
588
|
|
- elif difflet.is_eof(line):
|
|
345
|
+ elif diff.is_eof(line):
|
589
|
346
|
# ignore
|
590
|
347
|
pass
|
591
|
348
|
|
592
|
|
- elif difflet.is_only_in_dir(line) or \
|
593
|
|
- difflet.is_binary_differ(line):
|
|
349
|
+ elif diff.is_only_in_dir(line) or \
|
|
350
|
+ diff.is_binary_differ(line):
|
594
|
351
|
# 'Only in foo:' and 'Binary files ... differ' are considered
|
595
|
352
|
# as separate diffs, so yield current diff, then this line
|
596
|
353
|
#
|
|
@@ -598,9 +355,9 @@ class DiffParser(object):
|
598
|
355
|
# Current diff is comppletely constructed
|
599
|
356
|
yield diff
|
600
|
357
|
headers.append(line)
|
601
|
|
- yield Diff(headers, '', '', [])
|
|
358
|
+ yield UnifiedDiff(headers, '', '', [])
|
602
|
359
|
headers = []
|
603
|
|
- diff = Diff([], None, None, [])
|
|
360
|
+ diff = UnifiedDiff([], None, None, [])
|
604
|
361
|
|
605
|
362
|
else:
|
606
|
363
|
# All other non-recognized lines are considered as headers or
|
|
@@ -617,38 +374,227 @@ class DiffParser(object):
|
617
|
374
|
yield diff
|
618
|
375
|
|
619
|
376
|
if headers:
|
620
|
|
- # Tolerate dangling headers, just yield a Diff object with only
|
621
|
|
- # header lines
|
|
377
|
+ # Tolerate dangling headers, just yield a UnifiedDiff object with
|
|
378
|
+ # only header lines
|
622
|
379
|
#
|
623
|
|
- yield Diff(headers, '', '', [])
|
|
380
|
+ yield UnifiedDiff(headers, '', '', [])
|
624
|
381
|
|
625
|
382
|
|
626
|
|
-class DiffMarkup(object):
|
|
383
|
+class DiffMarker(object):
|
627
|
384
|
|
628
|
|
- def __init__(self, stream):
|
629
|
|
- self._diffs = DiffParser(stream).get_diff_generator()
|
630
|
|
-
|
631
|
|
- def markup(self, side_by_side=False, width=0):
|
|
385
|
+ def markup(self, diffs, side_by_side=False, width=0):
|
632
|
386
|
"""Returns a generator"""
|
633
|
387
|
if side_by_side:
|
634
|
|
- return self._markup_side_by_side(width)
|
|
388
|
+ for diff in diffs:
|
|
389
|
+ for line in self._markup_side_by_side(diff, width):
|
|
390
|
+ yield line
|
635
|
391
|
else:
|
636
|
|
- return self._markup_traditional()
|
|
392
|
+ for diff in diffs:
|
|
393
|
+ for line in self._markup_traditional(diff):
|
|
394
|
+ yield line
|
637
|
395
|
|
638
|
|
- def _markup_traditional(self):
|
639
|
|
- for diff in self._diffs:
|
640
|
|
- for line in diff.markup_traditional():
|
641
|
|
- yield line
|
|
396
|
+ def _markup_traditional(self, diff):
|
|
397
|
+ """Returns a generator"""
|
|
398
|
+ for line in diff._headers:
|
|
399
|
+ yield self._markup_header(line)
|
642
|
400
|
|
643
|
|
- def _markup_side_by_side(self, width):
|
644
|
|
- for diff in self._diffs:
|
645
|
|
- for line in diff.markup_side_by_side(width):
|
646
|
|
- yield line
|
|
401
|
+ yield self._markup_old_path(diff._old_path)
|
|
402
|
+ yield self._markup_new_path(diff._new_path)
|
|
403
|
+
|
|
404
|
+ for hunk in diff._hunks:
|
|
405
|
+ for hunk_header in hunk._hunk_headers:
|
|
406
|
+ yield self._markup_hunk_header(hunk_header)
|
|
407
|
+ yield self._markup_hunk_meta(hunk._hunk_meta)
|
|
408
|
+ for old, new, changed in hunk.mdiff():
|
|
409
|
+ if changed:
|
|
410
|
+ if not old[0]:
|
|
411
|
+ # The '+' char after \x00 is kept
|
|
412
|
+ # DEBUG: yield 'NEW: %s %s\n' % (old, new)
|
|
413
|
+ line = new[1].strip('\x00\x01')
|
|
414
|
+ yield self._markup_new(line)
|
|
415
|
+ elif not new[0]:
|
|
416
|
+ # The '-' char after \x00 is kept
|
|
417
|
+ # DEBUG: yield 'OLD: %s %s\n' % (old, new)
|
|
418
|
+ line = old[1].strip('\x00\x01')
|
|
419
|
+ yield self._markup_old(line)
|
|
420
|
+ else:
|
|
421
|
+ # DEBUG: yield 'CHG: %s %s\n' % (old, new)
|
|
422
|
+ yield self._markup_old('-') + \
|
|
423
|
+ self._markup_mix(old[1], 'red')
|
|
424
|
+ yield self._markup_new('+') + \
|
|
425
|
+ self._markup_mix(new[1], 'green')
|
|
426
|
+ else:
|
|
427
|
+ yield self._markup_common(' ' + old[1])
|
|
428
|
+
|
|
429
|
+ def _markup_side_by_side(self, diff, width):
|
|
430
|
+ """Returns a generator"""
|
|
431
|
+ wrap_char = colorize('>', 'lightmagenta')
|
|
432
|
+
|
|
433
|
+ def _normalize(line):
|
|
434
|
+ return line.replace(
|
|
435
|
+ '\t', ' ' * 8).replace('\n', '').replace('\r', '')
|
|
436
|
+
|
|
437
|
+ def _fit_with_marker(text, markup_fn, width, pad=False):
|
|
438
|
+ """Wrap or pad input pure text, then markup"""
|
|
439
|
+ if len(text) > width:
|
|
440
|
+ return markup_fn(text[:(width - 1)]) + wrap_char
|
|
441
|
+ elif pad:
|
|
442
|
+ pad_len = width - len(text)
|
|
443
|
+ return '%s%*s' % (markup_fn(text), pad_len, '')
|
|
444
|
+ else:
|
|
445
|
+ return markup_fn(text)
|
|
446
|
+
|
|
447
|
+ def _fit_with_marker_mix(text, base_color, width, pad=False):
|
|
448
|
+ """Wrap or pad input text which contains mdiff tags, markup at the
|
|
449
|
+ meantime, note only left side need to set `pad`
|
|
450
|
+ """
|
|
451
|
+ out = [COLORS[base_color]]
|
|
452
|
+ count = 0
|
|
453
|
+ tag_re = re.compile(r'\x00[+^-]|\x01')
|
|
454
|
+
|
|
455
|
+ while text and count < width:
|
|
456
|
+ if text.startswith('\x00-'): # del
|
|
457
|
+ out.append(COLORS['reverse'] + COLORS[base_color])
|
|
458
|
+ text = text[2:]
|
|
459
|
+ elif text.startswith('\x00+'): # add
|
|
460
|
+ out.append(COLORS['reverse'] + COLORS[base_color])
|
|
461
|
+ text = text[2:]
|
|
462
|
+ elif text.startswith('\x00^'): # change
|
|
463
|
+ out.append(COLORS['underline'] + COLORS[base_color])
|
|
464
|
+ text = text[2:]
|
|
465
|
+ elif text.startswith('\x01'): # reset
|
|
466
|
+ out.append(COLORS['reset'] + COLORS[base_color])
|
|
467
|
+ text = text[1:]
|
|
468
|
+ else:
|
|
469
|
+ # FIXME: utf-8 wchar might break the rule here, e.g.
|
|
470
|
+ # u'\u554a' takes double width of a single letter, also
|
|
471
|
+ # this depends on your terminal font. I guess audience of
|
|
472
|
+ # this tool never put that kind of symbol in their code :-)
|
|
473
|
+ #
|
|
474
|
+ out.append(text[0])
|
|
475
|
+ count += 1
|
|
476
|
+ text = text[1:]
|
|
477
|
+
|
|
478
|
+ if count == width and tag_re.sub('', text):
|
|
479
|
+ # Was stripped: output fulfil and still has normal char in text
|
|
480
|
+ out[-1] = COLORS['reset'] + wrap_char
|
|
481
|
+ elif count < width and pad:
|
|
482
|
+ pad_len = width - count
|
|
483
|
+ out.append('%s%*s' % (COLORS['reset'], pad_len, ''))
|
|
484
|
+ else:
|
|
485
|
+ out.append(COLORS['reset'])
|
|
486
|
+
|
|
487
|
+ return ''.join(out)
|
|
488
|
+
|
|
489
|
+ # Set up line width
|
|
490
|
+ if width <= 0:
|
|
491
|
+ width = 80
|
|
492
|
+
|
|
493
|
+ # Set up number width, note last hunk might be empty
|
|
494
|
+ try:
|
|
495
|
+ (start, offset) = diff._hunks[-1]._old_addr
|
|
496
|
+ max1 = start + offset - 1
|
|
497
|
+ (start, offset) = diff._hunks[-1]._new_addr
|
|
498
|
+ max2 = start + offset - 1
|
|
499
|
+ except IndexError:
|
|
500
|
+ max1 = max2 = 0
|
|
501
|
+ num_width = max(len(str(max1)), len(str(max2)))
|
|
502
|
+
|
|
503
|
+ # Setup lineno and line format
|
|
504
|
+ left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
|
505
|
+ right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
|
506
|
+ line_fmt = left_num_fmt + ' %(left)s ' + COLORS['reset'] + \
|
|
507
|
+ right_num_fmt + ' %(right)s\n'
|
|
508
|
+
|
|
509
|
+ # yield header, old path and new path
|
|
510
|
+ for line in diff._headers:
|
|
511
|
+ yield self._markup_header(line)
|
|
512
|
+ yield self._markup_old_path(diff._old_path)
|
|
513
|
+ yield self._markup_new_path(diff._new_path)
|
|
514
|
+
|
|
515
|
+ # yield hunks
|
|
516
|
+ for hunk in diff._hunks:
|
|
517
|
+ for hunk_header in hunk._hunk_headers:
|
|
518
|
+ yield self._markup_hunk_header(hunk_header)
|
|
519
|
+ yield self._markup_hunk_meta(hunk._hunk_meta)
|
|
520
|
+ for old, new, changed in hunk.mdiff():
|
|
521
|
+ if old[0]:
|
|
522
|
+ left_num = str(hunk._old_addr[0] + int(old[0]) - 1)
|
|
523
|
+ else:
|
|
524
|
+ left_num = ' '
|
|
525
|
+
|
|
526
|
+ if new[0]:
|
|
527
|
+ right_num = str(hunk._new_addr[0] + int(new[0]) - 1)
|
|
528
|
+ else:
|
|
529
|
+ right_num = ' '
|
|
530
|
+
|
|
531
|
+ left = _normalize(old[1])
|
|
532
|
+ right = _normalize(new[1])
|
|
533
|
+
|
|
534
|
+ if changed:
|
|
535
|
+ if not old[0]:
|
|
536
|
+ left = '%*s' % (width, ' ')
|
|
537
|
+ right = right.lstrip('\x00+').rstrip('\x01')
|
|
538
|
+ right = _fit_with_marker(
|
|
539
|
+ right, self._markup_new, width)
|
|
540
|
+ elif not new[0]:
|
|
541
|
+ left = left.lstrip('\x00-').rstrip('\x01')
|
|
542
|
+ left = _fit_with_marker(left, self._markup_old, width)
|
|
543
|
+ right = ''
|
|
544
|
+ else:
|
|
545
|
+ left = _fit_with_marker_mix(left, 'red', width, 1)
|
|
546
|
+ right = _fit_with_marker_mix(right, 'green', width)
|
|
547
|
+ else:
|
|
548
|
+ left = _fit_with_marker(
|
|
549
|
+ left, self._markup_common, width, 1)
|
|
550
|
+ right = _fit_with_marker(right, self._markup_common, width)
|
|
551
|
+ yield line_fmt % {
|
|
552
|
+ 'left_num': left_num,
|
|
553
|
+ 'left': left,
|
|
554
|
+ 'right_num': right_num,
|
|
555
|
+ 'right': right
|
|
556
|
+ }
|
|
557
|
+
|
|
558
|
+ def _markup_header(self, line):
|
|
559
|
+ return colorize(line, 'cyan')
|
|
560
|
+
|
|
561
|
+ def _markup_old_path(self, line):
|
|
562
|
+ return colorize(line, 'yellow')
|
|
563
|
+
|
|
564
|
+ def _markup_new_path(self, line):
|
|
565
|
+ return colorize(line, 'yellow')
|
|
566
|
+
|
|
567
|
+ def _markup_hunk_header(self, line):
|
|
568
|
+ return colorize(line, 'lightcyan')
|
|
569
|
+
|
|
570
|
+ def _markup_hunk_meta(self, line):
|
|
571
|
+ return colorize(line, 'lightblue')
|
|
572
|
+
|
|
573
|
+ def _markup_common(self, line):
|
|
574
|
+ return colorize(line, 'reset')
|
|
575
|
+
|
|
576
|
+ def _markup_old(self, line):
|
|
577
|
+ return colorize(line, 'lightred')
|
|
578
|
+
|
|
579
|
+ def _markup_new(self, line):
|
|
580
|
+ return colorize(line, 'lightgreen')
|
|
581
|
+
|
|
582
|
+ def _markup_mix(self, line, base_color):
|
|
583
|
+ del_code = COLORS['reverse'] + COLORS[base_color]
|
|
584
|
+ add_code = COLORS['reverse'] + COLORS[base_color]
|
|
585
|
+ chg_code = COLORS['underline'] + COLORS[base_color]
|
|
586
|
+ rst_code = COLORS['reset'] + COLORS[base_color]
|
|
587
|
+ line = line.replace('\x00-', del_code)
|
|
588
|
+ line = line.replace('\x00+', add_code)
|
|
589
|
+ line = line.replace('\x00^', chg_code)
|
|
590
|
+ line = line.replace('\x01', rst_code)
|
|
591
|
+ return colorize(line, base_color)
|
647
|
592
|
|
648
|
593
|
|
649
|
594
|
def markup_to_pager(stream, opts):
|
650
|
|
- markup = DiffMarkup(stream)
|
651
|
|
- color_diff = markup.markup(side_by_side=opts.side_by_side,
|
|
595
|
+ diffs = DiffParser(stream).get_diff_generator()
|
|
596
|
+ marker = DiffMarker()
|
|
597
|
+ color_diff = marker.markup(diffs, side_by_side=opts.side_by_side,
|
652
|
598
|
width=opts.width)
|
653
|
599
|
|
654
|
600
|
# Args stolen from git source: github.com/git/git/blob/master/pager.c
|