Browse Source

side-by-side now fit width if len(line) < 80; FIXME: reset term after exception

Matthew Wang 11 years ago
parent
commit
0c672b1315
1 changed files with 68 additions and 48 deletions
  1. 68
    48
      src/cdiff.py

+ 68
- 48
src/cdiff.py View File

@@ -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: