| 
				
			 | 
			
			
				@@ -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'], 
			 |