--- a/src/eric7/UI/CompareDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/UI/CompareDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -30,7 +30,7 @@ """ Compare two sequences of lines; generate the delta for display side by side. - + @param a first sequence of lines (list of strings) @param b second sequence of lines (list of strings) @param linenumberwidth width (in characters) of the linenumbers (integer) @@ -49,37 +49,49 @@ def removeMarkers(line): """ Internal function to remove all diff markers. - + @param line line to work on (string) @return line without diff markers (string) """ return ( - line - .replace('\0+', "") - .replace('\0-', "") - .replace('\0^', "") - .replace('\1', "") + line.replace("\0+", "") + .replace("\0-", "") + .replace("\0^", "") + .replace("\1", "") ) linenumberformat = "{{0:{0:d}d}}".format(linenumberwidth) - emptylineno = ' ' * linenumberwidth - - for (ln1, l1), (ln2, l2), flag in _mdiff(a, b, None, None, - IS_CHARACTER_JUNK): + emptylineno = " " * linenumberwidth + + for (ln1, l1), (ln2, l2), flag in _mdiff(a, b, None, None, IS_CHARACTER_JUNK): if not flag: - yield ('e', linenumberformat.format(ln1), l1, - linenumberformat.format(ln2), l2) + yield ( + "e", + linenumberformat.format(ln1), + l1, + linenumberformat.format(ln2), + l2, + ) continue if ln2 == "" and l2 in ("\r\n", "\n", "\r"): - yield ('d', linenumberformat.format(ln1), removeMarkers(l1), - emptylineno, l2) + yield ( + "d", + linenumberformat.format(ln1), + removeMarkers(l1), + emptylineno, + l2, + ) continue if ln1 == "" and l1 in ("\r\n", "\n", "\r"): - yield ('i', emptylineno, l1, - linenumberformat.format(ln2), removeMarkers(l2)) + yield ( + "i", + emptylineno, + l1, + linenumberformat.format(ln2), + removeMarkers(l2), + ) continue - yield ('r', linenumberformat.format(ln1), l1, - linenumberformat.format(ln2), l2) + yield ("r", linenumberformat.format(ln1), l1, linenumberformat.format(ln2), l2) class CompareDialog(QWidget, Ui_CompareDialog): @@ -87,63 +99,66 @@ Class implementing a dialog to compare two files and show the result side by side. """ + def __init__(self, files=None, parent=None): """ Constructor - + @param files list of files to compare and their label (list of two tuples of two strings) @param parent parent widget (QWidget) """ super().__init__(parent) self.setupUi(self) - + if files is None: files = [] - + self.file1Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) self.file2Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) - + self.diffButton = self.buttonBox.addButton( - self.tr("Compare"), QDialogButtonBox.ButtonRole.ActionRole) + self.tr("Compare"), QDialogButtonBox.ButtonRole.ActionRole + ) self.diffButton.setToolTip( - self.tr("Press to perform the comparison of the two files")) + self.tr("Press to perform the comparison of the two files") + ) self.diffButton.setEnabled(False) self.diffButton.setDefault(True) - + self.firstButton.setIcon(UI.PixmapCache.getIcon("2uparrow")) self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow")) self.lastButton.setIcon(UI.PixmapCache.getIcon("2downarrow")) - - self.totalLabel.setText(self.tr('Total: {0}').format(0)) - self.changedLabel.setText(self.tr('Changed: {0}').format(0)) - self.addedLabel.setText(self.tr('Added: {0}').format(0)) - self.deletedLabel.setText(self.tr('Deleted: {0}').format(0)) - - self.updateInterval = 20 # update every 20 lines - + + self.totalLabel.setText(self.tr("Total: {0}").format(0)) + self.changedLabel.setText(self.tr("Changed: {0}").format(0)) + self.addedLabel.setText(self.tr("Added: {0}").format(0)) + self.deletedLabel.setText(self.tr("Deleted: {0}").format(0)) + + self.updateInterval = 20 # update every 20 lines + self.vsb1 = self.contents_1.verticalScrollBar() self.hsb1 = self.contents_1.horizontalScrollBar() self.vsb2 = self.contents_2.verticalScrollBar() self.hsb2 = self.contents_2.horizontalScrollBar() - + self.on_synchronizeCheckBox_toggled(True) - + self.__generateFormats() - + # connect some of our widgets explicitly self.file1Picker.textChanged.connect(self.__fileChanged) self.file2Picker.textChanged.connect(self.__fileChanged) self.vsb1.valueChanged.connect(self.__scrollBarMoved) self.vsb1.valueChanged.connect(self.vsb2.setValue) self.vsb2.valueChanged.connect(self.vsb1.setValue) - + self.diffParas = [] self.currentDiffPos = -1 - + self.markerPattern = r"\0\+|\0\^|\0\-" - + if len(files) == 2: self.filesGroup.hide() self.file1Picker.setText(files[0][1]) @@ -155,7 +170,7 @@ else: self.file1Label.hide() self.file2Label.hide() - + def __generateFormats(self): """ Private method to generate the various text formats. @@ -166,33 +181,35 @@ self.contents_2.setFontFamily(font.family()) self.contents_2.setFontPointSize(font.pointSize()) self.fontHeight = QFontMetrics(self.contents_1.currentFont()).height() - + self.cNormalFormat = self.contents_1.currentCharFormat() self.cInsertedFormat = self.contents_1.currentCharFormat() self.cInsertedFormat.setBackground( - QBrush(Preferences.getDiffColour("AddedColor"))) + QBrush(Preferences.getDiffColour("AddedColor")) + ) self.cDeletedFormat = self.contents_1.currentCharFormat() self.cDeletedFormat.setBackground( - QBrush(Preferences.getDiffColour("RemovedColor"))) + QBrush(Preferences.getDiffColour("RemovedColor")) + ) self.cReplacedFormat = self.contents_1.currentCharFormat() self.cReplacedFormat.setBackground( - QBrush(Preferences.getDiffColour("ReplacedColor"))) - + QBrush(Preferences.getDiffColour("ReplacedColor")) + ) + def show(self, filename=None): """ Public slot to show the dialog. - + @param filename name of a file to use as the first file (string) """ if filename: self.file1Picker.setText(filename) super().show() - - def __appendText(self, pane, linenumber, line, charFormat, - interLine=False): + + def __appendText(self, pane, linenumber, line, charFormat, interLine=False): """ Private method to append text to the end of the contents pane. - + @param pane text edit widget to append text to (QTextedit) @param linenumber number of line to insert (string) @param line text to insert (string) @@ -207,8 +224,8 @@ pane.insertPlainText("{0} ".format(linenumber)) for txt in re.split(self.markerPattern, line): if txt: - if txt.count('\1'): - txt1, txt = txt.split('\1', 1) + if txt.count("\1"): + txt1, txt = txt.split("\1", 1) tc = pane.textCursor() tc.movePosition(QTextCursor.MoveOperation.End) pane.setTextCursor(tc) @@ -225,12 +242,12 @@ def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. - + @param button button that was clicked (QAbstractButton) """ if button == self.diffButton: self.on_diffButton_clicked() - + @pyqtSlot() def on_diffButton_clicked(self): """ @@ -244,9 +261,10 @@ EricMessageBox.critical( self, self.tr("Compare Files"), - self.tr( - """<p>The file <b>{0}</b> could not be read.</p>""") - .format(filename1)) + self.tr("""<p>The file <b>{0}</b> could not be read.</p>""").format( + filename1 + ), + ) return filename2 = self.file2Picker.text() @@ -257,17 +275,18 @@ EricMessageBox.critical( self, self.tr("Compare Files"), - self.tr( - """<p>The file <b>{0}</b> could not be read.</p>""") - .format(filename2)) + self.tr("""<p>The file <b>{0}</b> could not be read.</p>""").format( + filename2 + ), + ) return - + self.__compare(lines1, lines2) - + def compare(self, lines1, lines2, name1="", name2=""): """ Public method to compare two lists of text. - + @param lines1 text to compare against (string or list of strings) @param lines2 text to compare (string or list of strings) @param name1 name to be shown for the first text (string) @@ -282,55 +301,55 @@ self.file2Picker.setReadOnly(True) self.diffButton.setEnabled(False) self.diffButton.hide() - + if isinstance(lines1, str): lines1 = lines1.splitlines(True) if isinstance(lines2, str): lines2 = lines2.splitlines(True) - + self.__compare(lines1, lines2) - + def __compare(self, lines1, lines2): """ Private method to compare two lists of text. - + @param lines1 text to compare against (list of strings) @param lines2 text to compare (list of strings) """ self.contents_1.clear() self.contents_2.clear() - + self.__generateFormats() - + # counters for changes added = 0 deleted = 0 changed = 0 - + self.diffParas = [] self.currentDiffPos = -1 - oldOpcode = '' + oldOpcode = "" for paras, (opcode, ln1, l1, ln2, l2) in enumerate( sbsdiff(lines1, lines2), start=1 ): - if opcode in 'idr': + if opcode in "idr": if oldOpcode != opcode: oldOpcode = opcode self.diffParas.append(paras) # update counters - if opcode == 'i': + if opcode == "i": added += 1 - elif opcode == 'd': + elif opcode == "d": deleted += 1 - elif opcode == 'r': + elif opcode == "r": changed += 1 - if opcode == 'i': + if opcode == "i": format1 = self.cNormalFormat format2 = self.cInsertedFormat - elif opcode == 'd': + elif opcode == "d": format1 = self.cDeletedFormat format2 = self.cNormalFormat - elif opcode == 'r': + elif opcode == "r": if ln1.strip(): format1 = self.cReplacedFormat else: @@ -340,31 +359,30 @@ else: format2 = self.cNormalFormat else: - oldOpcode = '' + oldOpcode = "" format1 = self.cNormalFormat format2 = self.cNormalFormat - self.__appendText(self.contents_1, ln1, l1, format1, opcode == 'r') - self.__appendText(self.contents_2, ln2, l2, format2, opcode == 'r') + self.__appendText(self.contents_1, ln1, l1, format1, opcode == "r") + self.__appendText(self.contents_2, ln2, l2, format2, opcode == "r") if not (paras % self.updateInterval): QApplication.processEvents() - + self.vsb1.setValue(0) self.vsb2.setValue(0) self.firstButton.setEnabled(False) self.upButton.setEnabled(False) self.downButton.setEnabled( - len(self.diffParas) > 0 and - (self.vsb1.isVisible() or self.vsb2.isVisible())) + len(self.diffParas) > 0 and (self.vsb1.isVisible() or self.vsb2.isVisible()) + ) self.lastButton.setEnabled( - len(self.diffParas) > 0 and - (self.vsb1.isVisible() or self.vsb2.isVisible())) - - self.totalLabel.setText(self.tr('Total: {0}') - .format(added + deleted + changed)) - self.changedLabel.setText(self.tr('Changed: {0}').format(changed)) - self.addedLabel.setText(self.tr('Added: {0}').format(added)) - self.deletedLabel.setText(self.tr('Deleted: {0}').format(deleted)) - + len(self.diffParas) > 0 and (self.vsb1.isVisible() or self.vsb2.isVisible()) + ) + + self.totalLabel.setText(self.tr("Total: {0}").format(added + deleted + changed)) + self.changedLabel.setText(self.tr("Changed: {0}").format(changed)) + self.addedLabel.setText(self.tr("Added: {0}").format(added)) + self.deletedLabel.setText(self.tr("Deleted: {0}").format(deleted)) + # move to the first difference self.downButton.click() @@ -376,31 +394,31 @@ value = (self.diffParas[self.currentDiffPos] - 1) * self.fontHeight self.vsb1.setValue(value) self.vsb2.setValue(value) - + def __scrollBarMoved(self, value): """ Private slot to enable the buttons and set the current diff position depending on scrollbar position. - + @param value scrollbar position (integer) """ tPos = value / self.fontHeight + 1 bPos = (value + self.vsb1.pageStep()) / self.fontHeight + 1 - + self.currentDiffPos = -1 - + if self.diffParas: self.firstButton.setEnabled(tPos > self.diffParas[0]) self.upButton.setEnabled(tPos > self.diffParas[0]) self.downButton.setEnabled(bPos < self.diffParas[-1]) self.lastButton.setEnabled(bPos < self.diffParas[-1]) - + if tPos >= self.diffParas[0]: for diffPos in self.diffParas: self.currentDiffPos += 1 if tPos <= diffPos: break - + @pyqtSlot() def on_upButton_clicked(self): """ @@ -408,7 +426,7 @@ """ self.currentDiffPos -= 1 self.__moveTextToCurrentDiffPos() - + @pyqtSlot() def on_downButton_clicked(self): """ @@ -416,7 +434,7 @@ """ self.currentDiffPos += 1 self.__moveTextToCurrentDiffPos() - + @pyqtSlot() def on_firstButton_clicked(self): """ @@ -424,7 +442,7 @@ """ self.currentDiffPos = 0 self.__moveTextToCurrentDiffPos() - + @pyqtSlot() def on_lastButton_clicked(self): """ @@ -432,15 +450,12 @@ """ self.currentDiffPos = len(self.diffParas) - 1 self.__moveTextToCurrentDiffPos() - + def __fileChanged(self): """ Private slot to enable/disable the Compare button. """ - if ( - not self.file1Picker.text() or - not self.file2Picker.text() - ): + if not self.file1Picker.text() or not self.file2Picker.text(): self.diffButton.setEnabled(False) else: self.diffButton.setEnabled(True) @@ -449,7 +464,7 @@ def on_synchronizeCheckBox_toggled(self, sync): """ Private slot to connect or disconnect the scrollbars of the displays. - + @param sync flag indicating synchronisation status (boolean) """ if sync: @@ -465,29 +480,29 @@ """ Main window class for the standalone dialog. """ + def __init__(self, files=None, parent=None): """ Constructor - + @param files list of files to compare and their label (list of two tuples of two strings) @param parent reference to the parent widget (QWidget) """ super().__init__(parent) - - self.setStyle(Preferences.getUI("Style"), - Preferences.getUI("StyleSheet")) - + + self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) + self.cw = CompareDialog(files, self) self.cw.installEventFilter(self) size = self.cw.size() self.setCentralWidget(self.cw) self.resize(size) - + def eventFilter(self, obj, event): """ Public method to filter events. - + @param obj reference to the object the event is meant for (QObject) @param event reference to the event object (QEvent) @return flag indicating, whether the event was handled (boolean) @@ -495,5 +510,5 @@ if event.type() == QEvent.Type.Close: QApplication.exit() return True - + return False