Plugins/VcsPlugins/vcsGit/GitBlameDialog.py

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

eric ide

mercurial