|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to show the results of the PEP 8 check. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import fnmatch |
|
12 |
|
13 from PyQt4.QtCore import pyqtSlot, Qt |
|
14 from PyQt4.QtGui import QDialog, QTreeWidgetItem, QAbstractButton, \ |
|
15 QDialogButtonBox, QApplication, QHeaderView |
|
16 |
|
17 from . import pep8 |
|
18 |
|
19 from E5Gui.E5Application import e5App |
|
20 |
|
21 from .Pep8Checker import Pep8Checker |
|
22 from .Pep8CodeSelectionDialog import Pep8CodeSelectionDialog |
|
23 |
|
24 from .Ui_Pep8Dialog import Ui_Pep8Dialog |
|
25 |
|
26 import UI.PixmapCache |
|
27 import Preferences |
|
28 import Utilities |
|
29 |
|
30 class Pep8Dialog(QDialog, Ui_Pep8Dialog): |
|
31 """ |
|
32 Class implementing a dialog to show the results of the PEP 8 check. |
|
33 """ |
|
34 filenameRole = Qt.UserRole + 1 |
|
35 lineRole = Qt.UserRole + 2 |
|
36 positionRole = Qt.UserRole + 3 |
|
37 messageRole = Qt.UserRole + 4 |
|
38 |
|
39 settingsKey = "PEP8/" |
|
40 |
|
41 def __init__(self, parent = None): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param parent reference to the parent widget (QWidget) |
|
46 """ |
|
47 QDialog.__init__(self, parent) |
|
48 self.setupUi(self) |
|
49 |
|
50 self.showButton = self.buttonBox.addButton( |
|
51 self.trUtf8("Show"), QDialogButtonBox.ActionRole) |
|
52 self.showButton.setToolTip( |
|
53 self.trUtf8("Press to show all files containing an issue")) |
|
54 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) |
|
55 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) |
|
56 |
|
57 self.resultList.headerItem().setText(self.resultList.columnCount(), "") |
|
58 self.resultList.header().setSortIndicator(0, Qt.AscendingOrder) |
|
59 |
|
60 self.noResults = True |
|
61 self.cancelled = False |
|
62 self.__lastFileItem = None |
|
63 |
|
64 self.__fileOrFileList = "" |
|
65 self.__project = None |
|
66 self.__forProject = False |
|
67 self.__data = {} |
|
68 |
|
69 self.clearButton.setIcon( |
|
70 UI.PixmapCache.getIcon("clearLeft.png")) |
|
71 self.clearButtonExcludeMessages.setIcon( |
|
72 UI.PixmapCache.getIcon("clearLeft.png")) |
|
73 self.clearButtonIncludeMessages.setIcon( |
|
74 UI.PixmapCache.getIcon("clearLeft.png")) |
|
75 self.on_loadDefaultButton_clicked() |
|
76 |
|
77 def __resort(self): |
|
78 """ |
|
79 Private method to resort the tree. |
|
80 """ |
|
81 self.resultList.sortItems(self.resultList.sortColumn(), |
|
82 self.resultList.header().sortIndicatorOrder() |
|
83 ) |
|
84 |
|
85 def __createResultItem(self, file, line, pos, message): |
|
86 """ |
|
87 Private method to create an entry in the result list. |
|
88 |
|
89 @param file file name of the file (string) |
|
90 @param line line number of issue (integer or string) |
|
91 @param pos character position of issue (integer or string) |
|
92 @param message message text (string) |
|
93 """ |
|
94 if self.__lastFileItem is None: |
|
95 # It's a new file |
|
96 self.__lastFileItem = QTreeWidgetItem(self.resultList, [file]) |
|
97 self.__lastFileItem.setFirstColumnSpanned(True) |
|
98 self.__lastFileItem.setExpanded(True) |
|
99 self.__lastFileItem.setData(0, self.filenameRole, file) |
|
100 |
|
101 code, message = message.split(None, 1) |
|
102 itm = QTreeWidgetItem(self.__lastFileItem, |
|
103 ["{0:6}".format(line), code, message]) |
|
104 if code.startswith("W"): |
|
105 itm.setIcon(0, UI.PixmapCache.getIcon("warning.png")) |
|
106 else: |
|
107 itm.setIcon(0, UI.PixmapCache.getIcon("syntaxError.png")) |
|
108 itm.setData(0, self.filenameRole, file) |
|
109 itm.setData(0, self.lineRole, int(line)) |
|
110 itm.setData(0, self.positionRole, int(pos)) |
|
111 itm.setData(0, self.messageRole, message) |
|
112 |
|
113 def prepare(self, fileList, project): |
|
114 """ |
|
115 Public method to prepare the dialog with a list of filenames. |
|
116 |
|
117 @param fileList list of filenames (list of strings) |
|
118 @param project reference to the project object (Project) |
|
119 """ |
|
120 self.__fileOrFileList = fileList[:] |
|
121 self.__project = project |
|
122 self.__forProject = True |
|
123 |
|
124 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) |
|
125 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) |
|
126 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
127 |
|
128 self.__data = self.__project.getData("CHECKERSPARMS", "Pep8Checker") |
|
129 if self.__data is None or "ExcludeFiles" not in self.__data: |
|
130 # initialize the data structure |
|
131 self.__data = { |
|
132 "ExcludeFiles" : "", |
|
133 "ExcludeMessages" : pep8.DEFAULT_IGNORE, |
|
134 "IncludeMessages" : "", |
|
135 "RepeatMessages" : False, |
|
136 } |
|
137 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) |
|
138 self.excludeMessagesEdit.setText(self.__data["ExcludeMessages"]) |
|
139 self.includeMessagesEdit.setText(self.__data["IncludeMessages"]) |
|
140 self.repeatCheckBox.setChecked(self.__data["RepeatMessages"]) |
|
141 |
|
142 def start(self, fn, codestring = "", save = False, repeat = None): |
|
143 """ |
|
144 Public slot to start the PEP 8 check. |
|
145 |
|
146 @param fn file or list of files or directory to be checked |
|
147 (string or list of strings) |
|
148 @keyparam codestring string containing the code to be checked (string). |
|
149 If this is given, file must be a single file name. |
|
150 @keyparam save flag indicating to save the given |
|
151 file/file list/directory (boolean) |
|
152 @keyparam repeat state of the repeat check box if it is not None |
|
153 (None or boolean) |
|
154 """ |
|
155 if self.__project is None: |
|
156 self.__project = e5App().getObject("Project") |
|
157 |
|
158 self.cancelled = False |
|
159 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) |
|
160 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) |
|
161 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) |
|
162 if repeat is not None: |
|
163 self.repeatCheckBox.setChecked(repeat) |
|
164 QApplication.processEvents() |
|
165 |
|
166 if save: |
|
167 self.__fileOrFileList = fn |
|
168 |
|
169 if isinstance(fn, list): |
|
170 files = fn[:] |
|
171 elif os.path.isdir(fn): |
|
172 files = [] |
|
173 for ext in Preferences.getPython("Python3Extensions"): |
|
174 files.extend( |
|
175 Utilities.direntries(fn, 1, '*{0}'.format(ext), 0)) |
|
176 ## for ext in Preferences.getPython("PythonExtensions"): |
|
177 ## files.extend( |
|
178 ## Utilities.direntries(fn, 1, '*{0}'.format(ext), 0)) |
|
179 else: |
|
180 files = [fn] |
|
181 |
|
182 # filter the list depending on the filter string |
|
183 if files: |
|
184 filterString = self.excludeFilesEdit.text() |
|
185 filterList = [f.strip() for f in filterString.split(",") |
|
186 if f.strip()] |
|
187 for filter in filterList: |
|
188 files = \ |
|
189 [f for f in files |
|
190 if not fnmatch.fnmatch(f, filter.strip())] |
|
191 |
|
192 py3files = [f for f in files \ |
|
193 if f.endswith( |
|
194 tuple(Preferences.getPython("Python3Extensions")))] |
|
195 py2files = [] |
|
196 ## py2files = [f for f in files \ |
|
197 ## if f.endswith( |
|
198 ## tuple(Preferences.getPython("PythonExtensions")))] |
|
199 |
|
200 if (codestring and len(py3files) == 1) or \ |
|
201 (codestring and len(py2files) == 1) or \ |
|
202 (not codestring and len(py3files) + len(py2files) > 0): |
|
203 self.checkProgress.setMaximum(len(py3files) + len(py2files)) |
|
204 QApplication.processEvents() |
|
205 |
|
206 # extract the configuration values |
|
207 excludeMessages = self.excludeMessagesEdit.text() |
|
208 includeMessages = self.includeMessagesEdit.text() |
|
209 repeatMessages = self.repeatCheckBox.isChecked() |
|
210 |
|
211 # now go through all the files |
|
212 progress = 0 |
|
213 for file in py3files + py2files: |
|
214 self.checkProgress.setValue(progress) |
|
215 QApplication.processEvents() |
|
216 self.__resort() |
|
217 |
|
218 if self.cancelled: |
|
219 return |
|
220 |
|
221 self.__lastFileItem = None |
|
222 |
|
223 if codestring: |
|
224 source = codestring.splitlines(True) |
|
225 else: |
|
226 try: |
|
227 source = Utilities.readEncodedFile(file)[0] |
|
228 # convert eols |
|
229 source = Utilities.convertLineEnds(source, "\n") |
|
230 source = source.splitlines(True) |
|
231 except (UnicodeError, IOError) as msg: |
|
232 self.noResults = False |
|
233 self.__createResultItem(file, "1", "1", |
|
234 self.trUtf8("Error: {0}").format(str(msg))\ |
|
235 .rstrip()[1:-1]) |
|
236 progress += 1 |
|
237 continue |
|
238 |
|
239 flags = Utilities.extractFlags(source) |
|
240 ext = os.path.splitext(file)[1] |
|
241 if ("FileType" in flags and |
|
242 flags["FileType"] in ["Python", "Python2"]) or \ |
|
243 file in py2files or \ |
|
244 (ext in [".py", ".pyw"] and \ |
|
245 Preferences.getProject("DeterminePyFromProject") and \ |
|
246 self.__project.isOpen() and \ |
|
247 self.__project.isProjectFile(file) and \ |
|
248 self.__project.getProjectLanguage() in ["Python", |
|
249 "Python2"]): |
|
250 # TODO: include PEP 8 check for python 2 |
|
251 pass |
|
252 else: |
|
253 checker = Pep8Checker(file, source, |
|
254 repeat = repeatMessages, |
|
255 select = includeMessages, |
|
256 ignore = excludeMessages) |
|
257 checker.check_all() |
|
258 checker.messages.sort(key = lambda a: a[1]) |
|
259 for message in checker.messages: |
|
260 fname, lineno, position, text = message |
|
261 if not source[lineno - 1].strip()\ |
|
262 .endswith("__IGNORE_WARNING__"): |
|
263 self.noResults = False |
|
264 self.__createResultItem( |
|
265 fname, lineno, position, text) |
|
266 progress += 1 |
|
267 self.checkProgress.setValue(progress) |
|
268 QApplication.processEvents() |
|
269 self.__resort() |
|
270 else: |
|
271 self.checkProgress.setMaximum(1) |
|
272 self.checkProgress.setValue(1) |
|
273 self.__finish() |
|
274 |
|
275 def __finish(self): |
|
276 """ |
|
277 Private slot called when the PEP 8 check finished or the user |
|
278 pressed the cancel button. |
|
279 """ |
|
280 self.cancelled = True |
|
281 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) |
|
282 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) |
|
283 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
284 |
|
285 if self.noResults: |
|
286 QTreeWidgetItem(self.resultList, [self.trUtf8('No issues found.')]) |
|
287 QApplication.processEvents() |
|
288 self.showButton.setEnabled(False) |
|
289 self.__clearErrors() |
|
290 else: |
|
291 self.showButton.setEnabled(True) |
|
292 self.resultList.header().resizeSections(QHeaderView.ResizeToContents) |
|
293 self.resultList.header().setStretchLastSection(True) |
|
294 |
|
295 @pyqtSlot() |
|
296 def on_startButton_clicked(self): |
|
297 """ |
|
298 Private slot to start a PEP 8 check run. |
|
299 """ |
|
300 if self.__forProject: |
|
301 data = { |
|
302 "ExcludeFiles" : self.excludeFilesEdit.text(), |
|
303 "ExcludeMessages" : self.excludeMessagesEdit.text(), |
|
304 "IncludeMessages" : self.includeMessagesEdit.text(), |
|
305 "RepeatMessages" : self.repeatCheckBox.isChecked(), |
|
306 } |
|
307 if data != self.__data: |
|
308 self.__data = data |
|
309 self.__project.setData("CHECKERSPARMS", "Pep8Checker", |
|
310 self.__data) |
|
311 |
|
312 self.resultList.clear() |
|
313 self.noResults = True |
|
314 self.cancelled = False |
|
315 self.start(self.__fileOrFileList) |
|
316 |
|
317 @pyqtSlot() |
|
318 def on_excludeMessagesSelectButton_clicked(self): |
|
319 """ |
|
320 Private slot to select the message codes to be excluded via a |
|
321 selection dialog. |
|
322 """ |
|
323 dlg = Pep8CodeSelectionDialog(self.excludeMessagesEdit.text(), self) |
|
324 if dlg.exec_() == QDialog.Accepted: |
|
325 self.excludeMessagesEdit.setText(dlg.getSelectedCodes()) |
|
326 |
|
327 @pyqtSlot() |
|
328 def on_includeMessagesSelectButton_clicked(self): |
|
329 """ |
|
330 Private slot to select the message codes to be included via a |
|
331 selection dialog. |
|
332 """ |
|
333 dlg = Pep8CodeSelectionDialog(self.includeMessagesEdit.text(), self) |
|
334 if dlg.exec_() == QDialog.Accepted: |
|
335 self.includeMessagesEdit.setText(dlg.getSelectedCodes()) |
|
336 |
|
337 @pyqtSlot(QTreeWidgetItem, int) |
|
338 def on_resultList_itemActivated(self, item, column): |
|
339 """ |
|
340 Private slot to handle the activation of an item. |
|
341 |
|
342 @param item reference to the activated item (QTreeWidgetItem) |
|
343 @param column column the item was activated in (integer) |
|
344 """ |
|
345 if self.noResults: |
|
346 return |
|
347 |
|
348 if item.parent(): |
|
349 fn = Utilities.normabspath(item.data(0, self.filenameRole)) |
|
350 lineno = item.data(0, self.lineRole) |
|
351 position = item.data(0, self.positionRole) |
|
352 message = item.data(0, self.messageRole) |
|
353 |
|
354 vm = e5App().getObject("ViewManager") |
|
355 vm.openSourceFile(fn, lineno = lineno, pos = position) |
|
356 editor = vm.getOpenEditor(fn) |
|
357 |
|
358 editor.toggleFlakesWarning(lineno, True, message) |
|
359 |
|
360 @pyqtSlot() |
|
361 def on_showButton_clicked(self): |
|
362 """ |
|
363 Private slot to handle the "Show" button press. |
|
364 """ |
|
365 vm = e5App().getObject("ViewManager") |
|
366 |
|
367 selectedIndexes = [] |
|
368 for index in range(self.resultList.topLevelItemCount()): |
|
369 if self.resultList.topLevelItem(index).isSelected(): |
|
370 selectedIndexes.append(index) |
|
371 if len(selectedIndexes) == 0: |
|
372 selectedIndexes = list(range(self.resultList.topLevelItemCount())) |
|
373 for index in selectedIndexes: |
|
374 itm = self.resultList.topLevelItem(index) |
|
375 fn = Utilities.normabspath(itm.data(0, self.filenameRole)) |
|
376 vm.openSourceFile(fn, 1) |
|
377 editor = vm.getOpenEditor(fn) |
|
378 editor.clearFlakesWarnings() |
|
379 for cindex in range(itm.childCount()): |
|
380 citm = itm.child(cindex) |
|
381 lineno = citm.data(0, self.lineRole) |
|
382 message = citm.data(0, self.messageRole) |
|
383 editor.toggleFlakesWarning(lineno, True, message) |
|
384 |
|
385 # go through the list again to clear warning markers for files, |
|
386 # that are ok |
|
387 openFiles = vm.getOpenFilenames() |
|
388 errorFiles = [] |
|
389 for index in range(self.resultList.topLevelItemCount()): |
|
390 itm = self.resultList.topLevelItem(index) |
|
391 errorFiles.append( |
|
392 Utilities.normabspath(itm.data(0, self.filenameRole))) |
|
393 for file in openFiles: |
|
394 if not file in errorFiles: |
|
395 editor = vm.getOpenEditor(file) |
|
396 editor.clearFlakesWarnings() |
|
397 |
|
398 @pyqtSlot() |
|
399 def on_loadDefaultButton_clicked(self): |
|
400 """ |
|
401 Private slot to load the default configuration values. |
|
402 """ |
|
403 self.excludeFilesEdit.setText(Preferences.Prefs.settings.value( |
|
404 "PEP8/ExcludeFilePatterns")) |
|
405 self.excludeMessagesEdit.setText(Preferences.Prefs.settings.value( |
|
406 "PEP8/ExcludeMessages", pep8.DEFAULT_IGNORE)) |
|
407 self.includeMessagesEdit.setText(Preferences.Prefs.settings.value( |
|
408 "PEP8/IncludeMessages")) |
|
409 ## self.repeatCheckBox.setChecked(Preferences.toBool( |
|
410 ## Preferences.Prefs.settings.value("PEP8/RepeatMessages"))) |
|
411 |
|
412 @pyqtSlot() |
|
413 def on_storeDefaultButton_clicked(self): |
|
414 """ |
|
415 Private slot to store the current configuration values as |
|
416 default values. |
|
417 """ |
|
418 Preferences.Prefs.settings.setValue("PEP8/ExcludeFilePatterns", |
|
419 self.excludeFilesEdit.text()) |
|
420 Preferences.Prefs.settings.setValue("PEP8/ExcludeMessages", |
|
421 self.excludeMessagesEdit.text()) |
|
422 Preferences.Prefs.settings.setValue("PEP8/IncludeMessages", |
|
423 self.includeMessagesEdit.text()) |
|
424 ## Preferences.Prefs.settings.setValue("PEP8/RepeatMessages", |
|
425 ## self.repeatCheckBox.isChecked()) |
|
426 |
|
427 @pyqtSlot(QAbstractButton) |
|
428 def on_buttonBox_clicked(self, button): |
|
429 """ |
|
430 Private slot called by a button of the button box clicked. |
|
431 |
|
432 @param button button that was clicked (QAbstractButton) |
|
433 """ |
|
434 if button == self.buttonBox.button(QDialogButtonBox.Close): |
|
435 self.close() |
|
436 elif button == self.buttonBox.button(QDialogButtonBox.Cancel): |
|
437 self.__finish() |
|
438 elif button == self.showButton: |
|
439 self.on_showButton_clicked() |
|
440 |
|
441 def __clearErrors(self): |
|
442 """ |
|
443 Private method to clear all warning markers of open editors. |
|
444 """ |
|
445 vm = e5App().getObject("ViewManager") |
|
446 openFiles = vm.getOpenFilenames() |
|
447 for file in openFiles: |
|
448 editor = vm.getOpenEditor(file) |
|
449 editor.clearFlakesWarnings() |