src/eric7/Plugins/VcsPlugins/vcsMercurial/HgDiffDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of the hg diff command process.
8 """
9
10 import pathlib
11
12 from PyQt6.QtCore import pyqtSlot, Qt
13 from PyQt6.QtGui import QTextCursor
14 from PyQt6.QtWidgets import QWidget, QDialogButtonBox
15
16 from EricWidgets import EricMessageBox, EricFileDialog
17 from EricWidgets.EricApplication import ericApp
18
19 from .Ui_HgDiffDialog import Ui_HgDiffDialog
20 from .HgDiffHighlighter import HgDiffHighlighter
21 from .HgDiffGenerator import HgDiffGenerator
22
23 import Preferences
24
25
26 class HgDiffDialog(QWidget, Ui_HgDiffDialog):
27 """
28 Class implementing a dialog to show the output of the hg diff command
29 process.
30 """
31 def __init__(self, vcs, parent=None):
32 """
33 Constructor
34
35 @param vcs reference to the vcs object
36 @param parent parent widget (QWidget)
37 """
38 super().__init__(parent)
39 self.setupUi(self)
40
41 self.refreshButton = self.buttonBox.addButton(
42 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole)
43 self.refreshButton.setToolTip(
44 self.tr("Press to refresh the display"))
45 self.refreshButton.setEnabled(False)
46 self.buttonBox.button(
47 QDialogButtonBox.StandardButton.Save).setEnabled(False)
48 self.buttonBox.button(
49 QDialogButtonBox.StandardButton.Close).setDefault(True)
50
51 self.searchWidget.attachTextEdit(self.contents)
52
53 self.vcs = vcs
54
55 font = Preferences.getEditorOtherFonts("MonospacedFont")
56 self.contents.document().setDefaultFont(font)
57
58 self.highlighter = HgDiffHighlighter(self.contents.document())
59
60 self.__diffGenerator = HgDiffGenerator(vcs, self)
61 self.__diffGenerator.finished.connect(self.__generatorFinished)
62
63 def closeEvent(self, e):
64 """
65 Protected slot implementing a close event handler.
66
67 @param e close event (QCloseEvent)
68 """
69 self.__diffGenerator.stopProcess()
70 e.accept()
71
72 def start(self, fn, versions=None, bundle=None, qdiff=False,
73 refreshable=False):
74 """
75 Public slot to start the hg diff command.
76
77 @param fn filename to be diffed (string)
78 @param versions list of versions to be diffed (list of up to
79 2 strings or None)
80 @param bundle name of a bundle file (string)
81 @param qdiff flag indicating qdiff command shall be used (boolean)
82 @param refreshable flag indicating a refreshable diff (boolean)
83 """
84 self.refreshButton.setVisible(refreshable)
85
86 self.errorGroup.hide()
87 self.filename = fn
88
89 self.contents.clear()
90 self.filesCombo.clear()
91 self.highlighter.regenerateRules()
92
93 if qdiff:
94 self.setWindowTitle(self.tr("Patch Contents"))
95
96 self.raise_()
97 self.activateWindow()
98
99 procStarted = self.__diffGenerator.start(
100 fn, versions=versions, bundle=bundle, qdiff=qdiff)
101 if not procStarted:
102 EricMessageBox.critical(
103 self,
104 self.tr('Process Generation Error'),
105 self.tr(
106 'The process {0} could not be started. '
107 'Ensure, that it is in the search path.'
108 ).format('hg'))
109
110 def __generatorFinished(self):
111 """
112 Private slot connected to the finished signal.
113 """
114 self.refreshButton.setEnabled(True)
115
116 diff, errors, fileSeparators = self.__diffGenerator.getResult()
117
118 if diff:
119 self.contents.setPlainText("".join(diff))
120 else:
121 self.contents.setPlainText(
122 self.tr('There is no difference.'))
123
124 if errors:
125 self.errorGroup.show()
126 self.errors.setPlainText("".join(errors))
127 self.errors.ensureCursorVisible()
128
129 self.buttonBox.button(
130 QDialogButtonBox.StandardButton.Save).setEnabled(bool(diff))
131 self.buttonBox.button(
132 QDialogButtonBox.StandardButton.Close).setEnabled(True)
133 self.buttonBox.button(
134 QDialogButtonBox.StandardButton.Close).setDefault(True)
135 self.buttonBox.button(
136 QDialogButtonBox.StandardButton.Close).setFocus(
137 Qt.FocusReason.OtherFocusReason)
138
139 tc = self.contents.textCursor()
140 tc.movePosition(QTextCursor.MoveOperation.Start)
141 self.contents.setTextCursor(tc)
142 self.contents.ensureCursorVisible()
143
144 self.filesCombo.addItem(self.tr("<Start>"), 0)
145 self.filesCombo.addItem(self.tr("<End>"), -1)
146 for oldFile, newFile, pos in sorted(fileSeparators):
147 if not oldFile:
148 self.filesCombo.addItem(newFile, pos)
149 elif oldFile != newFile:
150 self.filesCombo.addItem(
151 "{0}\n{1}".format(oldFile, newFile), pos)
152 else:
153 self.filesCombo.addItem(oldFile, pos)
154
155 def on_buttonBox_clicked(self, button):
156 """
157 Private slot called by a button of the button box clicked.
158
159 @param button button that was clicked (QAbstractButton)
160 """
161 if button == self.buttonBox.button(
162 QDialogButtonBox.StandardButton.Save
163 ):
164 self.on_saveButton_clicked()
165 elif button == self.refreshButton:
166 self.on_refreshButton_clicked()
167
168 @pyqtSlot(int)
169 def on_filesCombo_activated(self, index):
170 """
171 Private slot to handle the selection of a file.
172
173 @param index activated row (integer)
174 """
175 para = self.filesCombo.itemData(index)
176
177 if para == 0:
178 tc = self.contents.textCursor()
179 tc.movePosition(QTextCursor.MoveOperation.Start)
180 self.contents.setTextCursor(tc)
181 self.contents.ensureCursorVisible()
182 elif para == -1:
183 tc = self.contents.textCursor()
184 tc.movePosition(QTextCursor.MoveOperation.End)
185 self.contents.setTextCursor(tc)
186 self.contents.ensureCursorVisible()
187 else:
188 # step 1: move cursor to end
189 tc = self.contents.textCursor()
190 tc.movePosition(QTextCursor.MoveOperation.End)
191 self.contents.setTextCursor(tc)
192 self.contents.ensureCursorVisible()
193
194 # step 2: move cursor to desired line
195 tc = self.contents.textCursor()
196 delta = tc.blockNumber() - para
197 tc.movePosition(QTextCursor.MoveOperation.PreviousBlock,
198 QTextCursor.MoveMode.MoveAnchor,
199 delta)
200 self.contents.setTextCursor(tc)
201 self.contents.ensureCursorVisible()
202
203 @pyqtSlot()
204 def on_saveButton_clicked(self):
205 """
206 Private slot to handle the Save button press.
207
208 It saves the diff shown in the dialog to a file in the local
209 filesystem.
210 """
211 if isinstance(self.filename, list):
212 if len(self.filename) > 1:
213 fname = self.vcs.splitPathList(self.filename)[0]
214 else:
215 dname, fname = self.vcs.splitPath(self.filename[0])
216 if fname != '.':
217 fname = "{0}.diff".format(self.filename[0])
218 else:
219 fname = dname
220 else:
221 fname = self.vcs.splitPath(self.filename)[0]
222
223 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
224 self,
225 self.tr("Save Diff"),
226 fname,
227 self.tr("Patch Files (*.diff)"),
228 None,
229 EricFileDialog.DontConfirmOverwrite)
230
231 if not fname:
232 return # user aborted
233
234 fpath = pathlib.Path(fname)
235 if not fpath.suffix:
236 ex = selectedFilter.split("(*")[1].split(")")[0]
237 if ex:
238 fpath = fpath.with_suffix(ex)
239 if fpath.exists():
240 res = EricMessageBox.yesNo(
241 self,
242 self.tr("Save Diff"),
243 self.tr("<p>The patch file <b>{0}</b> already exists."
244 " Overwrite it?</p>").format(fpath),
245 icon=EricMessageBox.Warning)
246 if not res:
247 return
248
249 eol = ericApp().getObject("Project").getEolString()
250 try:
251 with fpath.open("w", encoding="utf-8", newline="") as f:
252 f.write(eol.join(self.contents.toPlainText().splitlines()))
253 except OSError as why:
254 EricMessageBox.critical(
255 self, self.tr('Save Diff'),
256 self.tr(
257 '<p>The patch file <b>{0}</b> could not be saved.'
258 '<br>Reason: {1}</p>')
259 .format(fpath, str(why)))
260
261 @pyqtSlot()
262 def on_refreshButton_clicked(self):
263 """
264 Private slot to refresh the display.
265 """
266 self.buttonBox.button(
267 QDialogButtonBox.StandardButton.Close).setEnabled(False)
268
269 self.buttonBox.button(
270 QDialogButtonBox.StandardButton.Save).setEnabled(False)
271 self.refreshButton.setEnabled(False)
272
273 self.start(self.filename, refreshable=True)

eric ide

mercurial