eric7/Plugins/VcsPlugins/vcsGit/GitBlameDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of git blame.
8 """
9
10 import os
11 import re
12
13 from PyQt5.QtCore import pyqtSlot, QProcess, QTimer, Qt, QCoreApplication
14 from PyQt5.QtWidgets import (
15 QDialog, QDialogButtonBox, QHeaderView, QLineEdit, QTreeWidgetItem
16 )
17
18 from E5Gui import E5MessageBox
19
20 from .Ui_GitBlameDialog import Ui_GitBlameDialog
21
22 import Preferences
23 from Globals import strToQByteArray
24
25
26 class GitBlameDialog(QDialog, Ui_GitBlameDialog):
27 """
28 Class implementing a dialog to show the output of git blame.
29 """
30 def __init__(self, vcs, parent=None):
31 """
32 Constructor
33
34 @param vcs reference to the vcs object
35 @param parent reference to the parent widget (QWidget)
36 """
37 super().__init__(parent)
38 self.setupUi(self)
39 self.setWindowFlags(Qt.WindowType.Window)
40
41 self.buttonBox.button(
42 QDialogButtonBox.StandardButton.Close).setEnabled(False)
43 self.buttonBox.button(
44 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
45
46 self.vcs = vcs
47
48 self.__blameRe = re.compile(
49 r"""\^?([0-9a-fA-F]+)\s+\((.+)\s+(\d{4}-\d{2}-\d{2})\s+"""
50 r"""(\d{2}:\d{2}):\d{2}\s+[+-]\d{4}\s+(\d+)\)\s?(.*)""")
51 # commit - author - date - time - lineno. - text
52
53 self.blameList.headerItem().setText(
54 self.blameList.columnCount(), "")
55 font = Preferences.getEditorOtherFonts("MonospacedFont")
56 self.blameList.setFont(font)
57
58 self.process = QProcess()
59 self.process.finished.connect(self.__procFinished)
60 self.process.readyReadStandardOutput.connect(self.__readStdout)
61 self.process.readyReadStandardError.connect(self.__readStderr)
62
63 self.show()
64 QCoreApplication.processEvents()
65
66 def closeEvent(self, e):
67 """
68 Protected slot implementing a close event handler.
69
70 @param e close event (QCloseEvent)
71 """
72 if (
73 self.process is not None and
74 self.process.state() != QProcess.ProcessState.NotRunning
75 ):
76 self.process.terminate()
77 QTimer.singleShot(2000, self.process.kill)
78 self.process.waitForFinished(3000)
79
80 e.accept()
81
82 def start(self, fn):
83 """
84 Public slot to start the blame command.
85
86 @param fn filename to show the blame for (string)
87 """
88 self.blameList.clear()
89
90 self.errorGroup.hide()
91 self.intercept = False
92 self.activateWindow()
93
94 dname, fname = self.vcs.splitPath(fn)
95
96 # find the root of the repo
97 repodir = dname
98 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
99 repodir = os.path.dirname(repodir)
100 if os.path.splitdrive(repodir)[1] == os.sep:
101 return
102
103 args = self.vcs.initCommand("blame")
104 args.append('--abbrev={0}'.format(
105 self.vcs.getPlugin().getPreferences("CommitIdLength")))
106 args.append('--date=iso')
107 args.append(fn)
108
109 self.process.kill()
110 self.process.setWorkingDirectory(repodir)
111
112 self.process.start('git', args)
113 procStarted = self.process.waitForStarted(5000)
114 if not procStarted:
115 self.inputGroup.setEnabled(False)
116 self.inputGroup.hide()
117 E5MessageBox.critical(
118 self,
119 self.tr('Process Generation Error'),
120 self.tr(
121 'The process {0} could not be started. '
122 'Ensure, that it is in the search path.'
123 ).format('git'))
124 else:
125 self.inputGroup.setEnabled(True)
126 self.inputGroup.show()
127
128 def __finish(self):
129 """
130 Private slot called when the process finished or the user pressed
131 the button.
132 """
133 if (
134 self.process is not None and
135 self.process.state() != QProcess.ProcessState.NotRunning
136 ):
137 self.process.terminate()
138 QTimer.singleShot(2000, self.process.kill)
139 self.process.waitForFinished(3000)
140
141 self.inputGroup.setEnabled(False)
142 self.inputGroup.hide()
143
144 self.buttonBox.button(
145 QDialogButtonBox.StandardButton.Close).setEnabled(True)
146 self.buttonBox.button(
147 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
148 self.buttonBox.button(
149 QDialogButtonBox.StandardButton.Close).setDefault(True)
150 self.buttonBox.button(
151 QDialogButtonBox.StandardButton.Close).setFocus(
152 Qt.FocusReason.OtherFocusReason)
153
154 self.__resizeColumns()
155
156 def on_buttonBox_clicked(self, button):
157 """
158 Private slot called by a button of the button box clicked.
159
160 @param button button that was clicked (QAbstractButton)
161 """
162 if button == self.buttonBox.button(
163 QDialogButtonBox.StandardButton.Close
164 ):
165 self.close()
166 elif button == self.buttonBox.button(
167 QDialogButtonBox.StandardButton.Cancel
168 ):
169 self.__finish()
170
171 def __procFinished(self, exitCode, exitStatus):
172 """
173 Private slot connected to the finished signal.
174
175 @param exitCode exit code of the process (integer)
176 @param exitStatus exit status of the process (QProcess.ExitStatus)
177 """
178 self.__finish()
179
180 def __resizeColumns(self):
181 """
182 Private method to resize the list columns.
183 """
184 self.blameList.header().resizeSections(
185 QHeaderView.ResizeMode.ResizeToContents)
186
187 def __generateItem(self, commitId, author, date, time, lineno, text):
188 """
189 Private method to generate a blame item in the annotation list.
190
191 @param commitId commit identifier (string)
192 @param author author of the change (string)
193 @param date date of the change (string)
194 @param time time of the change (string)
195 @param lineno line number of the change (string)
196 @param text name (path) of the tag (string)
197 """
198 itm = QTreeWidgetItem(
199 self.blameList,
200 [commitId, author, date, time, lineno, text])
201 itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight)
202 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignRight)
203
204 def __readStdout(self):
205 """
206 Private slot to handle the readyReadStdout signal.
207
208 It reads the output of the process, formats it and inserts it into
209 the annotation list.
210 """
211 self.process.setReadChannel(QProcess.ProcessChannel.StandardOutput)
212
213 while self.process.canReadLine():
214 line = str(self.process.readLine(),
215 Preferences.getSystem("IOEncoding"),
216 'replace').strip()
217 match = self.__blameRe.match(line)
218 commitId, author, date, time, lineno, text = match.groups()
219 self.__generateItem(commitId, author, date, time, lineno, text)
220
221 def __readStderr(self):
222 """
223 Private slot to handle the readyReadStderr signal.
224
225 It reads the error output of the process and inserts it into the
226 error pane.
227 """
228 if self.process is not None:
229 s = str(self.process.readAllStandardError(),
230 Preferences.getSystem("IOEncoding"),
231 'replace')
232
233 self.errorGroup.show()
234 self.errors.insertPlainText(s)
235 self.errors.ensureCursorVisible()
236
237 @pyqtSlot()
238 def on_sendButton_clicked(self):
239 """
240 Private slot to send the input to the git process.
241 """
242 inputTxt = self.input.text()
243 inputTxt += os.linesep
244
245 if self.passwordCheckBox.isChecked():
246 self.errors.insertPlainText(os.linesep)
247 self.errors.ensureCursorVisible()
248 else:
249 self.errors.insertPlainText(inputTxt)
250 self.errors.ensureCursorVisible()
251
252 self.process.write(strToQByteArray(inputTxt))
253
254 self.passwordCheckBox.setChecked(False)
255 self.input.clear()
256
257 @pyqtSlot()
258 def on_input_returnPressed(self):
259 """
260 Private slot to handle the press of the return key in the input field.
261 """
262 self.intercept = True
263 self.on_sendButton_clicked()
264
265 @pyqtSlot(bool)
266 def on_passwordCheckBox_toggled(self, checked):
267 """
268 Private slot to handle the password checkbox toggled.
269
270 @param checked flag indicating the status of the check box (boolean)
271 """
272 if checked:
273 self.input.setEchoMode(QLineEdit.EchoMode.Password)
274 else:
275 self.input.setEchoMode(QLineEdit.EchoMode.Normal)
276
277 def keyPressEvent(self, evt):
278 """
279 Protected slot to handle a key press event.
280
281 @param evt the key press event (QKeyEvent)
282 """
283 if self.intercept:
284 self.intercept = False
285 evt.accept()
286 return
287 super().keyPressEvent(evt)

eric ide

mercurial