|
@@ -33,13 +33,21 @@ def colorize(text, start_color, end_color='reset'):
|
33
|
33
|
|
34
|
34
|
class Hunk(object):
|
35
|
35
|
|
36
|
|
- def __init__(self, hunk_header):
|
|
36
|
+ def __init__(self, hunk_header, old_addr, new_addr):
|
37
|
37
|
self._hunk_header = hunk_header
|
38
|
|
- self._hunk_list = [] # 2-element group (attr, line)
|
|
38
|
+ self._old_addr = old_addr # tuple (start, offset)
|
|
39
|
+ self._new_addr = new_addr # tuple group (start, offset)
|
|
40
|
+ self._hunk_list = [] # list of tuple (attr, line)
|
39
|
41
|
|
40
|
42
|
def get_header(self):
|
41
|
43
|
return self._hunk_header
|
42
|
44
|
|
|
45
|
+ def get_old_addr(self):
|
|
46
|
+ return self._old_addr
|
|
47
|
+
|
|
48
|
+ def get_new_addr(self):
|
|
49
|
+ return self._new_addr
|
|
50
|
+
|
43
|
51
|
def append(self, attr, line):
|
44
|
52
|
"""attr: '-': old, '+': new, ' ': common"""
|
45
|
53
|
self._hunk_list.append((attr, line))
|
|
@@ -119,57 +127,81 @@ class Diff(object):
|
119
|
127
|
else:
|
120
|
128
|
yield self._markup_common(' ' + old[1])
|
121
|
129
|
|
122
|
|
- def markup_side_by_side(self, show_number, width):
|
|
130
|
+ def markup_side_by_side(self, width):
|
123
|
131
|
"""width of 0 means infinite width, None means auto detect. Returns a
|
124
|
132
|
generator
|
125
|
133
|
"""
|
126
|
134
|
def _normalize(line):
|
127
|
135
|
return line.replace('\t', ' ' * 8).replace('\n', '')
|
128
|
136
|
|
129
|
|
- def _fit_width(markup, width):
|
|
137
|
+ def _fit_width(markup, width, pad=False):
|
130
|
138
|
"""str len does not count correctly if left column contains ansi
|
131
|
|
- color code
|
|
139
|
+ color code. Only left side need to set `pad`
|
132
|
140
|
"""
|
133
|
141
|
line = re.sub(r'\x1b\[(1;)?\d{1,2}m', '', markup)
|
134
|
|
- if len(line) < width:
|
135
|
|
- pad = width - len(line)
|
136
|
|
- return '%s%*s' % (markup, pad, '')
|
|
142
|
+ if pad and len(line) < width:
|
|
143
|
+ pad_len = width - len(line)
|
|
144
|
+ return '%s%*s' % (markup, pad_len, '')
|
137
|
145
|
else:
|
138
|
146
|
# TODO
|
139
|
147
|
return markup
|
140
|
148
|
|
141
|
|
- width = 80
|
142
|
|
- line_fmt = '%%s %s %%s\n' % colorize('|', 'lightyellow')
|
143
|
|
-
|
|
149
|
+ # Setup line width and number width
|
|
150
|
+ if not width: width = 80
|
|
151
|
+ (start, offset) = self._hunks[-1].get_old_addr()
|
|
152
|
+ max1 = start + offset - 1
|
|
153
|
+ (start, offset) = self._hunks[-1].get_new_addr()
|
|
154
|
+ max2 = start + offset - 1
|
|
155
|
+ num_width = max(len(str(max1)), len(str(max2)))
|
|
156
|
+ left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
|
157
|
+ right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
|
158
|
+ line_fmt = left_num_fmt + ' %(left)s ' + right_num_fmt + \
|
|
159
|
+ ' %(right)s\n'
|
|
160
|
+
|
|
161
|
+ # yield header, old path and new path
|
144
|
162
|
for line in self._headers:
|
145
|
163
|
yield self._markup_header(line)
|
146
|
|
-
|
147
|
164
|
yield self._markup_old_path(self._old_path)
|
148
|
165
|
yield self._markup_new_path(self._new_path)
|
149
|
166
|
|
|
167
|
+ # yield hunks
|
150
|
168
|
for hunk in self._hunks:
|
151
|
169
|
yield self._markup_hunk_header(hunk.get_header())
|
152
|
170
|
for old, new, changed in hunk.mdiff():
|
|
171
|
+ if old[0]:
|
|
172
|
+ left_num = str(hunk.get_old_addr()[0] + int(old[0]) - 1)
|
|
173
|
+ else:
|
|
174
|
+ left_num = ' '
|
|
175
|
+
|
|
176
|
+ if new[0]:
|
|
177
|
+ right_num = str(hunk.get_new_addr()[0] + int(new[0]) - 1)
|
|
178
|
+ else:
|
|
179
|
+ right_num = ' '
|
|
180
|
+
|
153
|
181
|
left = _normalize(old[1])
|
154
|
182
|
right = _normalize(new[1])
|
|
183
|
+
|
155
|
184
|
if changed:
|
156
|
185
|
if not old[0]:
|
157
|
|
- left = '%*s' % (width, '')
|
|
186
|
+ left = '%*s' % (width, ' ')
|
158
|
187
|
right = right.lstrip('\x00+').rstrip('\x01')
|
159
|
188
|
right = _fit_width(self._markup_new(right), width)
|
160
|
|
- yield line_fmt % (left, right)
|
161
|
189
|
elif not new[0]:
|
162
|
190
|
left = left.lstrip('\x00-').rstrip('\x01')
|
163
|
191
|
left = _fit_width(self._markup_old(left), width)
|
164
|
|
- yield line_fmt % (left, '')
|
|
192
|
+ right = ''
|
165
|
193
|
else:
|
166
|
|
- left = _fit_width(self._markup_old_mix(left), width)
|
|
194
|
+ left = _fit_width(self._markup_old_mix(left), width, 1)
|
167
|
195
|
right = _fit_width(self._markup_new_mix(right), width)
|
168
|
|
- yield line_fmt % (left, right)
|
169
|
196
|
else:
|
170
|
|
- left = _fit_width(self._markup_common(left), width)
|
|
197
|
+ left = _fit_width(self._markup_common(left), width, 1)
|
171
|
198
|
right = _fit_width(self._markup_common(right), width)
|
172
|
|
- yield line_fmt % (left, right)
|
|
199
|
+ yield line_fmt % {
|
|
200
|
+ 'left_num': left_num,
|
|
201
|
+ 'left': left,
|
|
202
|
+ 'right_num': right_num,
|
|
203
|
+ 'right': right
|
|
204
|
+ }
|
173
|
205
|
|
174
|
206
|
def _markup_header(self, line):
|
175
|
207
|
return colorize(line, 'cyan')
|
|
@@ -324,7 +356,13 @@ class DiffParser(object):
|
324
|
356
|
hunks.append(hunk)
|
325
|
357
|
hunk = None
|
326
|
358
|
else:
|
327
|
|
- hunk = Hunk(stream.pop(0))
|
|
359
|
+ # @@ -3,7 +3,6 @@
|
|
360
|
+ hunk_header = stream.pop(0)
|
|
361
|
+ a = hunk_header.split()[1].split(',') # -3 7
|
|
362
|
+ old_addr = (int(a[0][1:]), int(a[1]))
|
|
363
|
+ b = hunk_header.split()[2].split(',') # +3 6
|
|
364
|
+ new_addr = (int(b[0][1:]), int(b[1]))
|
|
365
|
+ hunk = Hunk(hunk_header, old_addr, new_addr)
|
328
|
366
|
|
329
|
367
|
elif Udiff.is_old(stream[0]) or Udiff.is_new(stream[0]) or \
|
330
|
368
|
Udiff.is_common(stream[0]):
|
|
@@ -361,10 +399,10 @@ class DiffMarkup(object):
|
361
|
399
|
def __init__(self, stream):
|
362
|
400
|
self._diffs = DiffParser(stream).get_diffs()
|
363
|
401
|
|
364
|
|
- def markup(self, side_by_side=False, show_number=False, width=0):
|
|
402
|
+ def markup(self, side_by_side=False, width=0):
|
365
|
403
|
"""Returns a generator"""
|
366
|
404
|
if side_by_side:
|
367
|
|
- return self._markup_side_by_side(show_number, width)
|
|
405
|
+ return self._markup_side_by_side(width)
|
368
|
406
|
else:
|
369
|
407
|
return self._markup_traditional()
|
370
|
408
|
|
|
@@ -373,9 +411,9 @@ class DiffMarkup(object):
|
373
|
411
|
for line in diff.markup_traditional():
|
374
|
412
|
yield line
|
375
|
413
|
|
376
|
|
- def _markup_side_by_side(self, show_number, width):
|
|
414
|
+ def _markup_side_by_side(self, width):
|
377
|
415
|
for diff in self._diffs:
|
378
|
|
- for line in diff.markup_side_by_side(show_number, width):
|
|
416
|
+ for line in diff.markup_side_by_side(width):
|
379
|
417
|
yield line
|
380
|
418
|
|
381
|
419
|
|
|
@@ -392,8 +430,6 @@ if __name__ == '__main__':
|
392
|
430
|
parser = optparse.OptionParser(usage)
|
393
|
431
|
parser.add_option('-s', '--side-by-side', action='store_true',
|
394
|
432
|
help=('show in side-by-side mode'))
|
395
|
|
- parser.add_option('-n', '--number', action='store_true',
|
396
|
|
- help='show line number')
|
397
|
433
|
parser.add_option('-w', '--width', type='int', default=None,
|
398
|
434
|
help='set line width (side-by-side mode only)')
|
399
|
435
|
opts, args = parser.parse_args()
|
|
@@ -417,7 +453,7 @@ if __name__ == '__main__':
|
417
|
453
|
if sys.stdout.isatty():
|
418
|
454
|
markup = DiffMarkup(stream)
|
419
|
455
|
color_diff = markup.markup(side_by_side=opts.side_by_side,
|
420
|
|
- show_number=opts.number, width=opts.width)
|
|
456
|
+ width=opts.width)
|
421
|
457
|
|
422
|
458
|
# args stolen fron git source: github.com/git/git/blob/master/pager.c
|
423
|
459
|
pager = subprocess.Popen(['less', '-FRSXK'],
|