108 # The header lines contain a tab after the filename. |
108 # The header lines contain a tab after the filename. |
109 |
109 |
110 |
110 |
111 def context_diff(a, b, fromfile='', tofile='', |
111 def context_diff(a, b, fromfile='', tofile='', |
112 fromfiledate='', tofiledate='', n=3, lineterm='\n'): |
112 fromfiledate='', tofiledate='', n=3, lineterm='\n'): |
113 """ |
113 r""" |
114 Compare two sequences of lines; generate the delta as a context diff. |
114 Compare two sequences of lines; generate the delta as a context diff. |
115 |
115 |
116 Context diffs are a compact way of showing line changes and a few |
116 Context diffs are a compact way of showing line changes and a few |
117 lines of context. The number of context lines is set by 'n' which |
117 lines of context. The number of context lines is set by 'n' which |
118 defaults to three. |
118 defaults to three. |
133 by time.ctime(). If not specified, the strings default to blanks. |
133 by time.ctime(). If not specified, the strings default to blanks. |
134 |
134 |
135 Example: |
135 Example: |
136 |
136 |
137 <pre> |
137 <pre> |
138 >>> print ''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(1), |
138 >>> print ''.join( |
|
139 ... context_diff('one\ntwo\nthree\nfour\n'.splitlines(1), |
139 ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current', |
140 ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current', |
140 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:22:46 2003')), |
141 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:22:46 2003')), |
141 *** Original Sat Jan 26 23:30:50 1991 |
142 *** Original Sat Jan 26 23:30:50 1991 |
142 --- Current Fri Jun 06 10:22:46 2003 |
143 --- Current Fri Jun 06 10:22:46 2003 |
143 *************** |
144 *************** |
161 @param tofiledate modification time of the second file (string) |
162 @param tofiledate modification time of the second file (string) |
162 @param n number of lines of context (integer) |
163 @param n number of lines of context (integer) |
163 @param lineterm line termination string (string) |
164 @param lineterm line termination string (string) |
164 @return a generator yielding lines of differences |
165 @return a generator yielding lines of differences |
165 """ |
166 """ |
166 |
|
167 started = False |
167 started = False |
168 prefixmap = {'insert': '+ ', 'delete': '- ', 'replace': '! ', 'equal': ' '} |
168 prefixmap = {'insert': '+ ', 'delete': '- ', 'replace': '! ', |
|
169 'equal': ' '} |
169 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n): |
170 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n): |
170 if not started: |
171 if not started: |
171 yield '*** {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm) |
172 yield '*** {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm) |
172 yield '--- {0}\t{1}{2}'.format(tofile, tofiledate, lineterm) |
173 yield '--- {0}\t{1}{2}'.format(tofile, tofiledate, lineterm) |
173 started = True |
174 started = True |
174 |
175 |
175 yield '***************{0}'.format(lineterm) |
176 yield '***************{0}'.format(lineterm) |
176 if group[-1][2] - group[0][1] >= 2: |
177 if group[-1][2] - group[0][1] >= 2: |
177 yield '*** {0:d},{1:d} ****{2}'.format(group[0][1] + 1, group[-1][2], lineterm) |
178 yield '*** {0:d},{1:d} ****{2}'.format( |
|
179 group[0][1] + 1, group[-1][2], lineterm) |
178 else: |
180 else: |
179 yield '*** {0:d} ****{1}'.format(group[-1][2], lineterm) |
181 yield '*** {0:d} ****{1}'.format(group[-1][2], lineterm) |
180 visiblechanges = [e for e in group if e[0] in ('replace', 'delete')] |
182 visiblechanges = [e for e in group if e[0] in ('replace', 'delete')] |
181 if visiblechanges: |
183 if visiblechanges: |
182 for tag, i1, i2, _, _ in group: |
184 for tag, i1, i2, _, _ in group: |
183 if tag != 'insert': |
185 if tag != 'insert': |
184 for line in a[i1:i2]: |
186 for line in a[i1:i2]: |
185 yield prefixmap[tag] + line |
187 yield prefixmap[tag] + line |
186 |
188 |
187 if group[-1][4] - group[0][3] >= 2: |
189 if group[-1][4] - group[0][3] >= 2: |
188 yield '--- {0:d},{1:d} ----{2}'.format(group[0][3] + 1, group[-1][4], lineterm) |
190 yield '--- {0:d},{1:d} ----{2}'.format( |
|
191 group[0][3] + 1, group[-1][4], lineterm) |
189 else: |
192 else: |
190 yield '--- {0:d} ----{1}'.format(group[-1][4], lineterm) |
193 yield '--- {0:d} ----{1}'.format(group[-1][4], lineterm) |
191 visiblechanges = [e for e in group if e[0] in ('replace', 'insert')] |
194 visiblechanges = [e for e in group if e[0] in ('replace', 'insert')] |
192 if visiblechanges: |
195 if visiblechanges: |
193 for tag, _, _, j1, j2 in group: |
196 for tag, _, _, j1, j2 in group: |
201 Class implementing a dialog to compare two files. |
204 Class implementing a dialog to compare two files. |
202 """ |
205 """ |
203 def __init__(self, parent=None): |
206 def __init__(self, parent=None): |
204 """ |
207 """ |
205 Constructor |
208 Constructor |
|
209 |
|
210 @param parent reference to the parent widget (QWidget) |
206 """ |
211 """ |
207 super(DiffDialog, self).__init__(parent) |
212 super(DiffDialog, self).__init__(parent) |
208 self.setupUi(self) |
213 self.setupUi(self) |
209 |
214 |
210 self.file1Completer = E5FileCompleter(self.file1Edit) |
215 self.file1Completer = E5FileCompleter(self.file1Edit) |
211 self.file2Completer = E5FileCompleter(self.file2Edit) |
216 self.file2Completer = E5FileCompleter(self.file2Edit) |
212 |
217 |
213 self.diffButton = \ |
218 self.diffButton = self.buttonBox.addButton( |
214 self.buttonBox.addButton(self.trUtf8("Compare"), QDialogButtonBox.ActionRole) |
219 self.trUtf8("Compare"), QDialogButtonBox.ActionRole) |
215 self.diffButton.setToolTip( |
220 self.diffButton.setToolTip( |
216 self.trUtf8("Press to perform the comparison of the two files")) |
221 self.trUtf8("Press to perform the comparison of the two files")) |
217 self.saveButton = \ |
222 self.saveButton = self.buttonBox.addButton( |
218 self.buttonBox.addButton(self.trUtf8("Save"), QDialogButtonBox.ActionRole) |
223 self.trUtf8("Save"), QDialogButtonBox.ActionRole) |
219 self.saveButton.setToolTip(self.trUtf8("Save the output to a patch file")) |
224 self.saveButton.setToolTip( |
|
225 self.trUtf8("Save the output to a patch file")) |
220 self.diffButton.setEnabled(False) |
226 self.diffButton.setEnabled(False) |
221 self.saveButton.setEnabled(False) |
227 self.saveButton.setEnabled(False) |
222 self.diffButton.setDefault(True) |
228 self.diffButton.setDefault(True) |
223 |
229 |
224 self.filename1 = '' |
230 self.filename1 = '' |
312 f.write(txt) |
318 f.write(txt) |
313 except UnicodeError: |
319 except UnicodeError: |
314 pass |
320 pass |
315 f.close() |
321 f.close() |
316 except IOError as why: |
322 except IOError as why: |
317 E5MessageBox.critical(self, self.trUtf8('Save Diff'), |
323 E5MessageBox.critical( |
318 self.trUtf8('<p>The patch file <b>{0}</b> could not be saved.<br />' |
324 self, self.trUtf8('Save Diff'), |
319 'Reason: {1}</p>') |
325 self.trUtf8( |
320 .format(fname, str(why))) |
326 '<p>The patch file <b>{0}</b> could not be saved.<br />' |
|
327 'Reason: {1}</p>').format(fname, str(why))) |
321 |
328 |
322 @pyqtSlot() |
329 @pyqtSlot() |
323 def on_diffButton_clicked(self): |
330 def on_diffButton_clicked(self): |
324 """ |
331 """ |
325 Private slot to handle the Compare button press. |
332 Private slot to handle the Compare button press. |
350 lines2 = f2.readlines() |
358 lines2 = f2.readlines() |
351 f2.close() |
359 f2.close() |
352 except IOError: |
360 except IOError: |
353 E5MessageBox.critical(self, |
361 E5MessageBox.critical(self, |
354 self.trUtf8("Compare Files"), |
362 self.trUtf8("Compare Files"), |
355 self.trUtf8("""<p>The file <b>{0}</b> could not be read.</p>""") |
363 self.trUtf8( |
|
364 """<p>The file <b>{0}</b> could not be read.</p>""") |
356 .format(self.filename2)) |
365 .format(self.filename2)) |
357 return |
366 return |
358 |
367 |
359 self.contents.clear() |
368 self.contents.clear() |
360 self.saveButton.setEnabled(False) |
369 self.saveButton.setEnabled(False) |
361 |
370 |
362 if self.unifiedRadioButton.isChecked(): |
371 if self.unifiedRadioButton.isChecked(): |
363 self.__generateUnifiedDiff(lines1, lines2, self.filename1, self.filename2, |
372 self.__generateUnifiedDiff( |
364 filemtime1, filemtime2) |
373 lines1, lines2, self.filename1, self.filename2, |
|
374 filemtime1, filemtime2) |
365 else: |
375 else: |
366 self.__generateContextDiff(lines1, lines2, self.filename1, self.filename2, |
376 self.__generateContextDiff( |
367 filemtime1, filemtime2) |
377 lines1, lines2, self.filename1, self.filename2, |
|
378 filemtime1, filemtime2) |
368 |
379 |
369 tc = self.contents.textCursor() |
380 tc = self.contents.textCursor() |
370 tc.movePosition(QTextCursor.Start) |
381 tc.movePosition(QTextCursor.Start) |
371 self.contents.setTextCursor(tc) |
382 self.contents.setTextCursor(tc) |
372 self.contents.ensureCursorVisible() |
383 self.contents.ensureCursorVisible() |
413 paras += 1 |
424 paras += 1 |
414 if not (paras % self.updateInterval): |
425 if not (paras % self.updateInterval): |
415 QApplication.processEvents() |
426 QApplication.processEvents() |
416 |
427 |
417 if paras == 0: |
428 if paras == 0: |
418 self.__appendText(self.trUtf8('There is no difference.'), self.cNormalFormat) |
429 self.__appendText( |
|
430 self.trUtf8('There is no difference.'), self.cNormalFormat) |
419 |
431 |
420 def __generateContextDiff(self, a, b, fromfile, tofile, |
432 def __generateContextDiff(self, a, b, fromfile, tofile, |
421 fromfiledate, tofiledate): |
433 fromfiledate, tofiledate): |
422 """ |
434 """ |
423 Private slot to generate a context diff output. |
435 Private slot to generate a context diff output. |
436 format = self.cAddedFormat |
448 format = self.cAddedFormat |
437 elif line.startswith('- '): |
449 elif line.startswith('- '): |
438 format = self.cRemovedFormat |
450 format = self.cRemovedFormat |
439 elif line.startswith('! '): |
451 elif line.startswith('! '): |
440 format = self.cReplacedFormat |
452 format = self.cReplacedFormat |
441 elif (line.startswith('*** ') or line.startswith('--- ')) and paras > 1: |
453 elif (line.startswith('*** ') or line.startswith('--- ')) and \ |
|
454 paras > 1: |
442 format = self.cLineNoFormat |
455 format = self.cLineNoFormat |
443 else: |
456 else: |
444 format = self.cNormalFormat |
457 format = self.cNormalFormat |
445 self.__appendText(line, format) |
458 self.__appendText(line, format) |
446 paras += 1 |
459 paras += 1 |
447 if not (paras % self.updateInterval): |
460 if not (paras % self.updateInterval): |
448 QApplication.processEvents() |
461 QApplication.processEvents() |
449 |
462 |
450 if paras == 0: |
463 if paras == 0: |
451 self.__appendText(self.trUtf8('There is no difference.'), self.cNormalFormat) |
464 self.__appendText( |
|
465 self.trUtf8('There is no difference.'), self.cNormalFormat) |
452 |
466 |
453 def __fileChanged(self): |
467 def __fileChanged(self): |
454 """ |
468 """ |
455 Private slot to enable/disable the Compare button. |
469 Private slot to enable/disable the Compare button. |
456 """ |
470 """ |
501 |
515 |
502 @param parent reference to the parent widget (QWidget) |
516 @param parent reference to the parent widget (QWidget) |
503 """ |
517 """ |
504 super(DiffWindow, self).__init__(parent) |
518 super(DiffWindow, self).__init__(parent) |
505 |
519 |
506 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
520 self.setStyle(Preferences.getUI("Style"), |
|
521 Preferences.getUI("StyleSheet")) |
507 |
522 |
508 self.cw = DiffDialog(self) |
523 self.cw = DiffDialog(self) |
509 self.cw.installEventFilter(self) |
524 self.cw.installEventFilter(self) |
510 size = self.cw.size() |
525 size = self.cw.size() |
511 self.setCentralWidget(self.cw) |
526 self.setCentralWidget(self.cw) |