|
@@ -8,6 +8,8 @@ import difflib
|
8
|
8
|
|
9
|
9
|
COLORS = {
|
10
|
10
|
'reset' : '\x1b[0m',
|
|
11
|
+ 'underline' : '\x1b[4m',
|
|
12
|
+ 'reverse' : '\x1b[7m',
|
11
|
13
|
'red' : '\x1b[31m',
|
12
|
14
|
'green' : '\x1b[32m',
|
13
|
15
|
'yellow' : '\x1b[33m',
|
|
@@ -87,73 +89,87 @@ class Diff(object):
|
87
|
89
|
self._hunks = hunks
|
88
|
90
|
|
89
|
91
|
def markup_traditional(self):
|
90
|
|
- out = []
|
91
|
|
-
|
|
92
|
+ """Returns a generator"""
|
92
|
93
|
for line in self._headers:
|
93
|
|
- out.append(self._markup_header(line))
|
|
94
|
+ yield self._markup_header(line)
|
94
|
95
|
|
95
|
|
- out.append(self._markup_old_path(self._old_path))
|
96
|
|
- out.append(self._markup_new_path(self._new_path))
|
|
96
|
+ yield self._markup_old_path(self._old_path)
|
|
97
|
+ yield self._markup_new_path(self._new_path)
|
97
|
98
|
|
98
|
99
|
for hunk in self._hunks:
|
99
|
|
- out.append(self._markup_hunk_header(hunk.get_header()))
|
|
100
|
+ yield self._markup_hunk_header(hunk.get_header())
|
100
|
101
|
for old, new, changed in hunk.mdiff():
|
101
|
102
|
if changed:
|
102
|
103
|
if not old[0]:
|
103
|
104
|
# The '+' char after \x00 is kept
|
|
105
|
+ # DEBUG: yield 'NEW: %s %s\n' % (old, new)
|
104
|
106
|
line = new[1].strip('\x00\x01')
|
105
|
|
- out.append(self._markup_new(line))
|
|
107
|
+ yield self._markup_new(line)
|
106
|
108
|
elif not new[0]:
|
107
|
109
|
# The '-' char after \x00 is kept
|
|
110
|
+ # DEBUG: yield 'OLD: %s %s\n' % (old, new)
|
108
|
111
|
line = old[1].strip('\x00\x01')
|
109
|
|
- out.append(self._markup_old(line))
|
|
112
|
+ yield self._markup_old(line)
|
110
|
113
|
else:
|
111
|
|
- out.append(self._markup_old('-') +
|
112
|
|
- self._markup_old_mix(old[1]))
|
113
|
|
- out.append(self._markup_new('+') +
|
114
|
|
- self._markup_new_mix(new[1]))
|
|
114
|
+ # DEBUG: yield 'CHG: %s %s\n' % (old, new)
|
|
115
|
+ yield self._markup_old('-') + \
|
|
116
|
+ self._markup_old_mix(old[1])
|
|
117
|
+ yield self._markup_new('+') + \
|
|
118
|
+ self._markup_new_mix(new[1])
|
115
|
119
|
else:
|
116
|
|
- out.append(self._markup_common(' ' + old[1]))
|
117
|
|
- return ''.join(out)
|
|
120
|
+ yield self._markup_common(' ' + old[1])
|
118
|
121
|
|
119
|
122
|
def markup_side_by_side(self, show_number, width):
|
120
|
|
- """width of 0 means infinite width, None means auto detect"""
|
|
123
|
+ """width of 0 means infinite width, None means auto detect. Returns a
|
|
124
|
+ generator
|
|
125
|
+ """
|
121
|
126
|
def _normalize(line):
|
122
|
127
|
return line.replace('\t', ' ' * 8).replace('\n', '')
|
123
|
128
|
|
|
129
|
+ def _fit_width(markup, width):
|
|
130
|
+ """str len does not count correctly if left column contains ansi
|
|
131
|
+ color code
|
|
132
|
+ """
|
|
133
|
+ 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, '')
|
|
137
|
+ else:
|
|
138
|
+ # TODO
|
|
139
|
+ return markup
|
|
140
|
+
|
124
|
141
|
width = 80
|
125
|
|
- line_fmt = '%%-%ds | %%-%ds\n' % (width, width)
|
126
|
|
- out = []
|
|
142
|
+ line_fmt = '%%s %s %%s\n' % colorize('|', 'lightyellow')
|
127
|
143
|
|
128
|
144
|
for line in self._headers:
|
129
|
|
- out.append(self._markup_header(line))
|
|
145
|
+ yield self._markup_header(line)
|
130
|
146
|
|
131
|
|
- out.append(self._markup_old_path(self._old_path))
|
132
|
|
- out.append(self._markup_new_path(self._new_path))
|
|
147
|
+ yield self._markup_old_path(self._old_path)
|
|
148
|
+ yield self._markup_new_path(self._new_path)
|
133
|
149
|
|
134
|
150
|
for hunk in self._hunks:
|
135
|
|
- out.append(self._markup_hunk_header(hunk.get_header()))
|
|
151
|
+ yield self._markup_hunk_header(hunk.get_header())
|
136
|
152
|
for old, new, changed in hunk.mdiff():
|
137
|
153
|
left = _normalize(old[1])
|
138
|
154
|
right = _normalize(new[1])
|
139
|
155
|
if changed:
|
140
|
156
|
if not old[0]:
|
|
157
|
+ left = '%*s' % (width, '')
|
141
|
158
|
right = right.lstrip('\x00+').rstrip('\x01')
|
142
|
|
- out.append(line_fmt % (' ', self._markup_new(right)))
|
|
159
|
+ right = _fit_width(self._markup_new(right), width)
|
|
160
|
+ yield line_fmt % (left, right)
|
143
|
161
|
elif not new[0]:
|
144
|
162
|
left = left.lstrip('\x00-').rstrip('\x01')
|
145
|
|
- out.append(line_fmt % (self._markup_old(left), ' '))
|
|
163
|
+ left = _fit_width(self._markup_old(left), width)
|
|
164
|
+ yield line_fmt % (left, '')
|
146
|
165
|
else:
|
147
|
|
- out.append(line_fmt % (
|
148
|
|
- self._markup_old_mix(left),
|
149
|
|
- self._markup_new_mix(right)
|
150
|
|
- ))
|
|
166
|
+ left = _fit_width(self._markup_old_mix(left), width)
|
|
167
|
+ right = _fit_width(self._markup_new_mix(right), width)
|
|
168
|
+ yield line_fmt % (left, right)
|
151
|
169
|
else:
|
152
|
|
- out.append(line_fmt % (
|
153
|
|
- self._markup_common(left),
|
154
|
|
- self._markup_common(right)
|
155
|
|
- ))
|
156
|
|
- return ''.join(out)
|
|
170
|
+ left = _fit_width(self._markup_common(left), width)
|
|
171
|
+ right = _fit_width(self._markup_common(right), width)
|
|
172
|
+ yield line_fmt % (left, right)
|
157
|
173
|
|
158
|
174
|
def _markup_header(self, line):
|
159
|
175
|
return colorize(line, 'cyan')
|
|
@@ -176,20 +192,22 @@ class Diff(object):
|
176
|
192
|
def _markup_new(self, line):
|
177
|
193
|
return colorize(line, 'lightgreen')
|
178
|
194
|
|
179
|
|
- def _markup_mix(self, line, base_color, del_color, add_color, chg_color):
|
180
|
|
- line = line.replace('\x00-', ansi_code(del_color))
|
181
|
|
- line = line.replace('\x00+', ansi_code(add_color))
|
182
|
|
- line = line.replace('\x00^', ansi_code(chg_color))
|
183
|
|
- line = line.replace('\x01', ansi_code(base_color))
|
|
195
|
+ def _markup_mix(self, line, base_color):
|
|
196
|
+ del_code = ansi_code('reverse') + ansi_code(base_color)
|
|
197
|
+ add_code = ansi_code('reverse') + ansi_code(base_color)
|
|
198
|
+ chg_code = ansi_code('underline') + ansi_code(base_color)
|
|
199
|
+ rst_code = ansi_code('reset') + ansi_code(base_color)
|
|
200
|
+ line = line.replace('\x00-', del_code)
|
|
201
|
+ line = line.replace('\x00+', add_code)
|
|
202
|
+ line = line.replace('\x00^', chg_code)
|
|
203
|
+ line = line.replace('\x01', rst_code)
|
184
|
204
|
return colorize(line, base_color)
|
185
|
205
|
|
186
|
206
|
def _markup_old_mix(self, line):
|
187
|
|
- return self._markup_mix(line, 'cyan', 'lightred', 'lightgreen',
|
188
|
|
- 'yellow')
|
|
207
|
+ return self._markup_mix(line, 'red')
|
189
|
208
|
|
190
|
209
|
def _markup_new_mix(self, line):
|
191
|
|
- return self._markup_mix(line, 'lightcyan', 'lightred', 'lightgreen',
|
192
|
|
- 'lightyellow')
|
|
210
|
+ return self._markup_mix(line, 'green')
|
193
|
211
|
|
194
|
212
|
|
195
|
213
|
class Udiff(Diff):
|
|
@@ -344,22 +362,21 @@ class DiffMarkup(object):
|
344
|
362
|
self._diffs = DiffParser(stream).get_diffs()
|
345
|
363
|
|
346
|
364
|
def markup(self, side_by_side=False, show_number=False, width=0):
|
|
365
|
+ """Returns a generator"""
|
347
|
366
|
if side_by_side:
|
348
|
367
|
return self._markup_side_by_side(show_number, width)
|
349
|
368
|
else:
|
350
|
369
|
return self._markup_traditional()
|
351
|
370
|
|
352
|
371
|
def _markup_traditional(self):
|
353
|
|
- out = []
|
354
|
372
|
for diff in self._diffs:
|
355
|
|
- out.append(diff.markup_traditional())
|
356
|
|
- return out
|
|
373
|
+ for line in diff.markup_traditional():
|
|
374
|
+ yield line
|
357
|
375
|
|
358
|
376
|
def _markup_side_by_side(self, show_number, width):
|
359
|
|
- out = []
|
360
|
377
|
for diff in self._diffs:
|
361
|
|
- out.append(diff.markup_side_by_side(show_number, width))
|
362
|
|
- return out
|
|
378
|
+ for line in diff.markup_side_by_side(show_number, width):
|
|
379
|
+ yield line
|
363
|
380
|
|
364
|
381
|
|
365
|
382
|
if __name__ == '__main__':
|
|
@@ -392,6 +409,7 @@ if __name__ == '__main__':
|
392
|
409
|
else:
|
393
|
410
|
diff_hdl = sys.stdin
|
394
|
411
|
|
|
412
|
+ # FIXME: can't use generator for now due to current implementation in parser
|
395
|
413
|
stream = diff_hdl.readlines()
|
396
|
414
|
if diff_hdl is not sys.stdin:
|
397
|
415
|
diff_hdl.close()
|
|
@@ -404,7 +422,9 @@ if __name__ == '__main__':
|
404
|
422
|
# args stolen fron git source: github.com/git/git/blob/master/pager.c
|
405
|
423
|
pager = subprocess.Popen(['less', '-FRSXK'],
|
406
|
424
|
stdin=subprocess.PIPE, stdout=sys.stdout)
|
407
|
|
- pager.stdin.write(''.join(color_diff))
|
|
425
|
+ for line in color_diff:
|
|
426
|
+ pager.stdin.write(line)
|
|
427
|
+
|
408
|
428
|
pager.stdin.close()
|
409
|
429
|
pager.wait()
|
410
|
430
|
else:
|