|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2013 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, QIcon |
|
16 |
|
17 from E5Gui.E5Application import e5App |
|
18 |
|
19 from .Ui_CodeStyleCheckerDialog import Ui_CodeStyleCheckerDialog |
|
20 |
|
21 import UI.PixmapCache |
|
22 import Preferences |
|
23 import Utilities |
|
24 |
|
25 from . import pep8 |
|
26 from .Pep8NamingChecker import Pep8NamingChecker |
|
27 |
|
28 # register the name checker |
|
29 pep8.register_check(Pep8NamingChecker, Pep8NamingChecker.Codes) |
|
30 |
|
31 from .DocStyleChecker import DocStyleChecker |
|
32 |
|
33 |
|
34 class CodeStyleCheckerReport(pep8.BaseReport): |
|
35 """ |
|
36 Class implementing a special report to be used with our dialog. |
|
37 """ |
|
38 def __init__(self, options): |
|
39 """ |
|
40 Constructor |
|
41 |
|
42 @param options options for the report (optparse.Values) |
|
43 """ |
|
44 super().__init__(options) |
|
45 |
|
46 self.__repeat = options.repeat |
|
47 self.errors = [] |
|
48 |
|
49 def error_args(self, line_number, offset, code, check, *args): |
|
50 """ |
|
51 Public method to collect the error messages. |
|
52 |
|
53 @param line_number line number of the issue (integer) |
|
54 @param offset position within line of the issue (integer) |
|
55 @param code message code (string) |
|
56 @param check reference to the checker function (function) |
|
57 @param args arguments for the message (list) |
|
58 @return error code (string) |
|
59 """ |
|
60 code = super().error_args(line_number, offset, code, check, *args) |
|
61 if code and (self.counters[code] == 1 or self.__repeat): |
|
62 if code in Pep8NamingChecker.Codes: |
|
63 text = Pep8NamingChecker.getMessage(code, *args) |
|
64 else: |
|
65 text = pep8.getMessage(code, *args) |
|
66 self.errors.append( |
|
67 (self.filename, line_number, offset, text) |
|
68 ) |
|
69 return code |
|
70 |
|
71 |
|
72 class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog): |
|
73 """ |
|
74 Class implementing a dialog to show the results of the PEP 8 check. |
|
75 """ |
|
76 filenameRole = Qt.UserRole + 1 |
|
77 lineRole = Qt.UserRole + 2 |
|
78 positionRole = Qt.UserRole + 3 |
|
79 messageRole = Qt.UserRole + 4 |
|
80 fixableRole = Qt.UserRole + 5 |
|
81 codeRole = Qt.UserRole + 6 |
|
82 |
|
83 def __init__(self, parent=None): |
|
84 """ |
|
85 Constructor |
|
86 |
|
87 @param parent reference to the parent widget (QWidget) |
|
88 """ |
|
89 super().__init__(parent) |
|
90 self.setupUi(self) |
|
91 |
|
92 self.docTypeComboBox.addItem(self.trUtf8("PEP-257"), "pep257") |
|
93 self.docTypeComboBox.addItem(self.trUtf8("Eric"), "eric") |
|
94 |
|
95 self.statisticsButton = self.buttonBox.addButton( |
|
96 self.trUtf8("Statistics..."), QDialogButtonBox.ActionRole) |
|
97 self.statisticsButton.setToolTip( |
|
98 self.trUtf8("Press to show some statistics for the last run")) |
|
99 self.statisticsButton.setEnabled(False) |
|
100 self.showButton = self.buttonBox.addButton( |
|
101 self.trUtf8("Show"), QDialogButtonBox.ActionRole) |
|
102 self.showButton.setToolTip( |
|
103 self.trUtf8("Press to show all files containing an issue")) |
|
104 self.showButton.setEnabled(False) |
|
105 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) |
|
106 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) |
|
107 |
|
108 self.resultList.headerItem().setText(self.resultList.columnCount(), "") |
|
109 self.resultList.header().setSortIndicator(0, Qt.AscendingOrder) |
|
110 |
|
111 self.checkProgress.setVisible(False) |
|
112 self.checkProgressLabel.setVisible(False) |
|
113 self.checkProgressLabel.setMaximumWidth(600) |
|
114 |
|
115 self.noResults = True |
|
116 self.cancelled = False |
|
117 self.__lastFileItem = None |
|
118 |
|
119 self.__fileOrFileList = "" |
|
120 self.__project = None |
|
121 self.__forProject = False |
|
122 self.__data = {} |
|
123 self.__statistics = {} |
|
124 |
|
125 self.on_loadDefaultButton_clicked() |
|
126 |
|
127 def __resort(self): |
|
128 """ |
|
129 Private method to resort the tree. |
|
130 """ |
|
131 self.resultList.sortItems(self.resultList.sortColumn(), |
|
132 self.resultList.header().sortIndicatorOrder() |
|
133 ) |
|
134 |
|
135 def __createResultItem(self, file, line, pos, message, fixed, autofixing): |
|
136 """ |
|
137 Private method to create an entry in the result list. |
|
138 |
|
139 @param file file name of the file (string) |
|
140 @param line line number of issue (integer or string) |
|
141 @param pos character position of issue (integer or string) |
|
142 @param message message text (string) |
|
143 @param fixed flag indicating a fixed issue (boolean) |
|
144 @param autofixing flag indicating, that we are fixing issues |
|
145 automatically (boolean) |
|
146 @return reference to the created item (QTreeWidgetItem) |
|
147 """ |
|
148 from .Pep8Fixer import Pep8FixableIssues |
|
149 |
|
150 if self.__lastFileItem is None: |
|
151 # It's a new file |
|
152 self.__lastFileItem = QTreeWidgetItem(self.resultList, [file]) |
|
153 self.__lastFileItem.setFirstColumnSpanned(True) |
|
154 self.__lastFileItem.setExpanded(True) |
|
155 self.__lastFileItem.setData(0, self.filenameRole, file) |
|
156 |
|
157 fixable = False |
|
158 code, message = message.split(None, 1) |
|
159 itm = QTreeWidgetItem(self.__lastFileItem, |
|
160 ["{0:6}".format(line), code, message]) |
|
161 if code.startswith("W"): |
|
162 itm.setIcon(1, UI.PixmapCache.getIcon("warning.png")) |
|
163 elif code.startswith("N"): |
|
164 itm.setIcon(1, UI.PixmapCache.getIcon("namingError.png")) |
|
165 elif code.startswith("D"): |
|
166 itm.setIcon(1, UI.PixmapCache.getIcon("docstringError.png")) |
|
167 else: |
|
168 itm.setIcon(1, UI.PixmapCache.getIcon("syntaxError.png")) |
|
169 if fixed: |
|
170 itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed.png")) |
|
171 elif code in Pep8FixableIssues and not autofixing: |
|
172 itm.setIcon(0, UI.PixmapCache.getIcon("issueFixable.png")) |
|
173 fixable = True |
|
174 |
|
175 itm.setTextAlignment(0, Qt.AlignRight) |
|
176 itm.setTextAlignment(1, Qt.AlignHCenter) |
|
177 |
|
178 itm.setTextAlignment(0, Qt.AlignVCenter) |
|
179 itm.setTextAlignment(1, Qt.AlignVCenter) |
|
180 itm.setTextAlignment(2, Qt.AlignVCenter) |
|
181 |
|
182 itm.setData(0, self.filenameRole, file) |
|
183 itm.setData(0, self.lineRole, int(line)) |
|
184 itm.setData(0, self.positionRole, int(pos)) |
|
185 itm.setData(0, self.messageRole, message) |
|
186 itm.setData(0, self.fixableRole, fixable) |
|
187 itm.setData(0, self.codeRole, code) |
|
188 |
|
189 return itm |
|
190 |
|
191 def __modifyFixedResultItem(self, itm, text, fixed): |
|
192 """ |
|
193 Private method to modify a result list entry to show its |
|
194 positive fixed state. |
|
195 |
|
196 @param itm reference to the item to modify (QTreeWidgetItem) |
|
197 @param text text to be appended (string) |
|
198 @param fixed flag indicating a fixed issue (boolean) |
|
199 """ |
|
200 if fixed: |
|
201 message = itm.data(0, self.messageRole) + text |
|
202 itm.setText(2, message) |
|
203 itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed.png")) |
|
204 |
|
205 itm.setData(0, self.messageRole, message) |
|
206 else: |
|
207 itm.setIcon(0, QIcon()) |
|
208 itm.setData(0, self.fixableRole, False) |
|
209 |
|
210 def __updateStatistics(self, statistics, fixer): |
|
211 """ |
|
212 Private method to update the collected statistics. |
|
213 |
|
214 @param statistics dictionary of statistical data with |
|
215 message code as key and message count as value |
|
216 @param fixer reference to the PEP 8 fixer (Pep8Fixer) |
|
217 """ |
|
218 self.__statistics["_FilesCount"] += 1 |
|
219 stats = {v: k for v, k in statistics.items() if v[0].isupper()} |
|
220 if stats: |
|
221 self.__statistics["_FilesIssues"] += 1 |
|
222 for key in statistics: |
|
223 if key in self.__statistics: |
|
224 self.__statistics[key] += statistics[key] |
|
225 else: |
|
226 self.__statistics[key] = statistics[key] |
|
227 if fixer: |
|
228 self.__statistics["_IssuesFixed"] += fixer.fixed |
|
229 |
|
230 def __updateFixerStatistics(self, fixer): |
|
231 """ |
|
232 Private method to update the collected fixer related statistics. |
|
233 |
|
234 @param fixer reference to the PEP 8 fixer (Pep8Fixer) |
|
235 """ |
|
236 self.__statistics["_IssuesFixed"] += fixer.fixed |
|
237 |
|
238 def __resetStatistics(self): |
|
239 """ |
|
240 Private slot to reset the statistics data. |
|
241 """ |
|
242 self.__statistics = {} |
|
243 self.__statistics["_FilesCount"] = 0 |
|
244 self.__statistics["_FilesIssues"] = 0 |
|
245 self.__statistics["_IssuesFixed"] = 0 |
|
246 |
|
247 def prepare(self, fileList, project): |
|
248 """ |
|
249 Public method to prepare the dialog with a list of filenames. |
|
250 |
|
251 @param fileList list of filenames (list of strings) |
|
252 @param project reference to the project object (Project) |
|
253 """ |
|
254 self.__fileOrFileList = fileList[:] |
|
255 self.__project = project |
|
256 self.__forProject = True |
|
257 |
|
258 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) |
|
259 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) |
|
260 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
261 |
|
262 self.__data = self.__project.getData("CHECKERSPARMS", "Pep8Checker") |
|
263 if self.__data is None or \ |
|
264 len(self.__data) < 6: |
|
265 # initialize the data structure |
|
266 self.__data = { |
|
267 "ExcludeFiles": "", |
|
268 "ExcludeMessages": pep8.DEFAULT_IGNORE, |
|
269 "IncludeMessages": "", |
|
270 "RepeatMessages": False, |
|
271 "FixCodes": "", |
|
272 "FixIssues": False, |
|
273 } |
|
274 if "MaxLineLength" not in self.__data: |
|
275 self.__data["MaxLineLength"] = pep8.MAX_LINE_LENGTH |
|
276 if "HangClosing" not in self.__data: |
|
277 self.__data["HangClosing"] = False |
|
278 if "NoFixCodes" not in self.__data: |
|
279 self.__data["NoFixCodes"] = "E501" |
|
280 if "DocstringType" not in self.__data: |
|
281 self.__data["DocstringType"] = "pep257" |
|
282 |
|
283 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) |
|
284 self.excludeMessagesEdit.setText(self.__data["ExcludeMessages"]) |
|
285 self.includeMessagesEdit.setText(self.__data["IncludeMessages"]) |
|
286 self.repeatCheckBox.setChecked(self.__data["RepeatMessages"]) |
|
287 self.fixIssuesEdit.setText(self.__data["FixCodes"]) |
|
288 self.noFixIssuesEdit.setText(self.__data["NoFixCodes"]) |
|
289 self.fixIssuesCheckBox.setChecked(self.__data["FixIssues"]) |
|
290 self.lineLengthSpinBox.setValue(self.__data["MaxLineLength"]) |
|
291 self.hangClosingCheckBox.setChecked(self.__data["HangClosing"]) |
|
292 self.docTypeComboBox.setCurrentIndex( |
|
293 self.docTypeComboBox.findData(self.__data["DocstringType"])) |
|
294 |
|
295 def start(self, fn, save=False, repeat=None): |
|
296 """ |
|
297 Public slot to start the PEP 8 check. |
|
298 |
|
299 @param fn file or list of files or directory to be checked |
|
300 (string or list of strings) |
|
301 @keyparam save flag indicating to save the given |
|
302 file/file list/directory (boolean) |
|
303 @keyparam repeat state of the repeat check box if it is not None |
|
304 (None or boolean) |
|
305 """ |
|
306 if self.__project is None: |
|
307 self.__project = e5App().getObject("Project") |
|
308 |
|
309 self.cancelled = False |
|
310 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) |
|
311 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) |
|
312 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) |
|
313 self.statisticsButton.setEnabled(False) |
|
314 self.showButton.setEnabled(False) |
|
315 self.fixButton.setEnabled(False) |
|
316 self.startButton.setEnabled(False) |
|
317 if repeat is not None: |
|
318 self.repeatCheckBox.setChecked(repeat) |
|
319 self.checkProgress.setVisible(True) |
|
320 QApplication.processEvents() |
|
321 |
|
322 self.__resetStatistics() |
|
323 |
|
324 if save: |
|
325 self.__fileOrFileList = fn |
|
326 |
|
327 if isinstance(fn, list): |
|
328 files = fn[:] |
|
329 elif os.path.isdir(fn): |
|
330 files = [] |
|
331 for ext in Preferences.getPython("Python3Extensions"): |
|
332 files.extend( |
|
333 Utilities.direntries(fn, 1, '*{0}'.format(ext), 0)) |
|
334 for ext in Preferences.getPython("PythonExtensions"): |
|
335 files.extend( |
|
336 Utilities.direntries(fn, 1, '*{0}'.format(ext), 0)) |
|
337 else: |
|
338 files = [fn] |
|
339 |
|
340 # filter the list depending on the filter string |
|
341 if files: |
|
342 filterString = self.excludeFilesEdit.text() |
|
343 filterList = [f.strip() for f in filterString.split(",") |
|
344 if f.strip()] |
|
345 for filter in filterList: |
|
346 files = \ |
|
347 [f for f in files |
|
348 if not fnmatch.fnmatch(f, filter.strip())] |
|
349 |
|
350 py3files = [f for f in files \ |
|
351 if f.endswith( |
|
352 tuple(Preferences.getPython("Python3Extensions")))] |
|
353 py2files = [f for f in files \ |
|
354 if f.endswith( |
|
355 tuple(Preferences.getPython("PythonExtensions")))] |
|
356 |
|
357 if len(py3files) + len(py2files) > 0: |
|
358 self.checkProgress.setMaximum(len(py3files) + len(py2files)) |
|
359 self.checkProgressLabel.setVisible( |
|
360 len(py3files) + len(py2files) > 1) |
|
361 QApplication.processEvents() |
|
362 |
|
363 # extract the configuration values |
|
364 excludeMessages = self.excludeMessagesEdit.text() |
|
365 includeMessages = self.includeMessagesEdit.text() |
|
366 repeatMessages = self.repeatCheckBox.isChecked() |
|
367 fixCodes = self.fixIssuesEdit.text() |
|
368 noFixCodes = self.noFixIssuesEdit.text() |
|
369 fixIssues = self.fixIssuesCheckBox.isChecked() and repeatMessages |
|
370 maxLineLength = self.lineLengthSpinBox.value() |
|
371 hangClosing = self.hangClosingCheckBox.isChecked() |
|
372 docType = self.docTypeComboBox.itemData( |
|
373 self.docTypeComboBox.currentIndex()) |
|
374 |
|
375 try: |
|
376 # disable updates of the list for speed |
|
377 self.resultList.setUpdatesEnabled(False) |
|
378 self.resultList.setSortingEnabled(False) |
|
379 |
|
380 # now go through all the files |
|
381 progress = 0 |
|
382 for file in sorted(py3files + py2files): |
|
383 self.checkProgress.setValue(progress) |
|
384 self.checkProgressLabel.setPath(file) |
|
385 QApplication.processEvents() |
|
386 |
|
387 if self.cancelled: |
|
388 self.__resort() |
|
389 return |
|
390 |
|
391 self.__lastFileItem = None |
|
392 |
|
393 try: |
|
394 source, encoding = Utilities.readEncodedFile(file) |
|
395 source = source.splitlines(True) |
|
396 except (UnicodeError, IOError) as msg: |
|
397 self.noResults = False |
|
398 self.__createResultItem(file, "1", "1", |
|
399 self.trUtf8("Error: {0}").format(str(msg))\ |
|
400 .rstrip()[1:-1], False, False) |
|
401 progress += 1 |
|
402 continue |
|
403 |
|
404 stats = {} |
|
405 flags = Utilities.extractFlags(source) |
|
406 ext = os.path.splitext(file)[1] |
|
407 if fixIssues: |
|
408 from .Pep8Fixer import Pep8Fixer |
|
409 fixer = Pep8Fixer(self.__project, file, source, |
|
410 fixCodes, noFixCodes, maxLineLength, |
|
411 True) # always fix in place |
|
412 else: |
|
413 fixer = None |
|
414 if ("FileType" in flags and |
|
415 flags["FileType"] in ["Python", "Python2"]) or \ |
|
416 file in py2files or \ |
|
417 (ext in [".py", ".pyw"] and \ |
|
418 Preferences.getProject("DeterminePyFromProject") and \ |
|
419 self.__project.isOpen() and \ |
|
420 self.__project.isProjectFile(file) and \ |
|
421 self.__project.getProjectLanguage() in ["Python", |
|
422 "Python2"]): |
|
423 from .CodeStyleChecker import CodeStyleCheckerPy2 |
|
424 report = CodeStyleCheckerPy2(file, [], |
|
425 repeat=repeatMessages, |
|
426 select=includeMessages, |
|
427 ignore=excludeMessages, |
|
428 max_line_length=maxLineLength, |
|
429 hang_closing=hangClosing, |
|
430 docType=docType, |
|
431 ) |
|
432 errors = report.errors[:] |
|
433 stats.update(report.counters) |
|
434 else: |
|
435 if includeMessages: |
|
436 select = [s.strip() for s in includeMessages.split(',') |
|
437 if s.strip()] |
|
438 else: |
|
439 select = [] |
|
440 if excludeMessages: |
|
441 ignore = [i.strip() for i in excludeMessages.split(',') |
|
442 if i.strip()] |
|
443 else: |
|
444 ignore = [] |
|
445 |
|
446 # check PEP-8 |
|
447 styleGuide = pep8.StyleGuide( |
|
448 reporter=CodeStyleCheckerReport, |
|
449 repeat=repeatMessages, |
|
450 select=select, |
|
451 ignore=ignore, |
|
452 max_line_length=maxLineLength, |
|
453 hang_closing=hangClosing, |
|
454 ) |
|
455 report = styleGuide.check_files([file]) |
|
456 stats.update(report.counters) |
|
457 |
|
458 # check PEP-257 |
|
459 pep257Checker = DocStyleChecker( |
|
460 source, file, select, ignore, [], repeatMessages, |
|
461 maxLineLength=maxLineLength, docType=docType) |
|
462 pep257Checker.run() |
|
463 stats.update(pep257Checker.counters) |
|
464 |
|
465 errors = report.errors + pep257Checker.errors |
|
466 |
|
467 deferredFixes = {} |
|
468 for error in errors: |
|
469 fname, lineno, position, text = error |
|
470 if lineno > len(source): |
|
471 lineno = len(source) |
|
472 if "__IGNORE_WARNING__" not in Utilities.extractLineFlags( |
|
473 source[lineno - 1].strip()): |
|
474 self.noResults = False |
|
475 if fixer: |
|
476 res, msg, id_ = fixer.fixIssue(lineno, position, text) |
|
477 if res == 1: |
|
478 text += "\n" + \ |
|
479 self.trUtf8("Fix: {0}").format(msg) |
|
480 self.__createResultItem( |
|
481 fname, lineno, position, text, True, True) |
|
482 elif res == 0: |
|
483 self.__createResultItem( |
|
484 fname, lineno, position, text, False, True) |
|
485 else: |
|
486 itm = self.__createResultItem( |
|
487 fname, lineno, position, |
|
488 text, False, False) |
|
489 deferredFixes[id_] = itm |
|
490 else: |
|
491 self.__createResultItem( |
|
492 fname, lineno, position, text, False, False) |
|
493 if fixer: |
|
494 deferredResults = fixer.finalize() |
|
495 for id_ in deferredResults: |
|
496 fixed, msg = deferredResults[id_] |
|
497 itm = deferredFixes[id_] |
|
498 if fixed == 1: |
|
499 text = "\n" + self.trUtf8("Fix: {0}").format(msg) |
|
500 self.__modifyFixedResultItem(itm, text, True) |
|
501 else: |
|
502 self.__modifyFixedResultItem(itm, "", False) |
|
503 fixer.saveFile(encoding) |
|
504 self.__updateStatistics(stats, fixer) |
|
505 progress += 1 |
|
506 finally: |
|
507 # reenable updates of the list |
|
508 self.resultList.setSortingEnabled(True) |
|
509 self.resultList.setUpdatesEnabled(True) |
|
510 self.checkProgress.setValue(progress) |
|
511 self.checkProgressLabel.setPath("") |
|
512 QApplication.processEvents() |
|
513 self.__resort() |
|
514 else: |
|
515 self.checkProgress.setMaximum(1) |
|
516 self.checkProgress.setValue(1) |
|
517 self.__finish() |
|
518 |
|
519 def __finish(self): |
|
520 """ |
|
521 Private slot called when the PEP 8 check finished or the user |
|
522 pressed the cancel button. |
|
523 """ |
|
524 self.cancelled = True |
|
525 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) |
|
526 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) |
|
527 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
528 self.statisticsButton.setEnabled(True) |
|
529 self.showButton.setEnabled(True) |
|
530 self.startButton.setEnabled(True) |
|
531 |
|
532 if self.noResults: |
|
533 QTreeWidgetItem(self.resultList, [self.trUtf8('No issues found.')]) |
|
534 QApplication.processEvents() |
|
535 self.statisticsButton.setEnabled(False) |
|
536 self.showButton.setEnabled(False) |
|
537 self.__clearErrors() |
|
538 else: |
|
539 self.statisticsButton.setEnabled(True) |
|
540 self.showButton.setEnabled(True) |
|
541 self.resultList.header().resizeSections(QHeaderView.ResizeToContents) |
|
542 self.resultList.header().setStretchLastSection(True) |
|
543 |
|
544 self.checkProgress.setVisible(False) |
|
545 self.checkProgressLabel.setVisible(False) |
|
546 |
|
547 @pyqtSlot() |
|
548 def on_startButton_clicked(self): |
|
549 """ |
|
550 Private slot to start a PEP 8 check run. |
|
551 """ |
|
552 if self.__forProject: |
|
553 data = { |
|
554 "ExcludeFiles": self.excludeFilesEdit.text(), |
|
555 "ExcludeMessages": self.excludeMessagesEdit.text(), |
|
556 "IncludeMessages": self.includeMessagesEdit.text(), |
|
557 "RepeatMessages": self.repeatCheckBox.isChecked(), |
|
558 "FixCodes": self.fixIssuesEdit.text(), |
|
559 "NoFixCodes": self.noFixIssuesEdit.text(), |
|
560 "FixIssues": self.fixIssuesCheckBox.isChecked(), |
|
561 "MaxLineLength": self.lineLengthSpinBox.value(), |
|
562 "HangClosing": self.hangClosingCheckBox.isChecked(), |
|
563 "DocstringType": self.docTypeComboBox.itemData( |
|
564 self.docTypeComboBox.currentIndex()), |
|
565 } |
|
566 if data != self.__data: |
|
567 self.__data = data |
|
568 self.__project.setData("CHECKERSPARMS", "Pep8Checker", |
|
569 self.__data) |
|
570 |
|
571 self.resultList.clear() |
|
572 self.noResults = True |
|
573 self.cancelled = False |
|
574 self.start(self.__fileOrFileList) |
|
575 |
|
576 def __selectCodes(self, edit, showFixCodes): |
|
577 """ |
|
578 Private method to select message codes via a selection dialog. |
|
579 |
|
580 @param edit reference of the line edit to be populated (QLineEdit) |
|
581 @param showFixCodes flag indicating to show a list of fixable |
|
582 issues (boolean) |
|
583 """ |
|
584 from .CodeStyleCodeSelectionDialog import CodeStyleCodeSelectionDialog |
|
585 dlg = CodeStyleCodeSelectionDialog(edit.text(), showFixCodes, self) |
|
586 if dlg.exec_() == QDialog.Accepted: |
|
587 edit.setText(dlg.getSelectedCodes()) |
|
588 |
|
589 @pyqtSlot() |
|
590 def on_excludeMessagesSelectButton_clicked(self): |
|
591 """ |
|
592 Private slot to select the message codes to be excluded via a |
|
593 selection dialog. |
|
594 """ |
|
595 self.__selectCodes(self.excludeMessagesEdit, False) |
|
596 |
|
597 @pyqtSlot() |
|
598 def on_includeMessagesSelectButton_clicked(self): |
|
599 """ |
|
600 Private slot to select the message codes to be included via a |
|
601 selection dialog. |
|
602 """ |
|
603 self.__selectCodes(self.includeMessagesEdit, False) |
|
604 |
|
605 @pyqtSlot() |
|
606 def on_fixIssuesSelectButton_clicked(self): |
|
607 """ |
|
608 Private slot to select the issue codes to be fixed via a |
|
609 selection dialog. |
|
610 """ |
|
611 self.__selectCodes(self.fixIssuesEdit, True) |
|
612 |
|
613 @pyqtSlot() |
|
614 def on_noFixIssuesSelectButton_clicked(self): |
|
615 """ |
|
616 Private slot to select the issue codes not to be fixed via a |
|
617 selection dialog. |
|
618 """ |
|
619 self.__selectCodes(self.noFixIssuesEdit, True) |
|
620 |
|
621 @pyqtSlot(QTreeWidgetItem, int) |
|
622 def on_resultList_itemActivated(self, item, column): |
|
623 """ |
|
624 Private slot to handle the activation of an item. |
|
625 |
|
626 @param item reference to the activated item (QTreeWidgetItem) |
|
627 @param column column the item was activated in (integer) |
|
628 """ |
|
629 if self.noResults: |
|
630 return |
|
631 |
|
632 if item.parent(): |
|
633 fn = Utilities.normabspath(item.data(0, self.filenameRole)) |
|
634 lineno = item.data(0, self.lineRole) |
|
635 position = item.data(0, self.positionRole) |
|
636 message = item.data(0, self.messageRole) |
|
637 code = item.data(0, self.codeRole) |
|
638 |
|
639 vm = e5App().getObject("ViewManager") |
|
640 vm.openSourceFile(fn, lineno=lineno, pos=position + 1) |
|
641 editor = vm.getOpenEditor(fn) |
|
642 |
|
643 if code == "E901": |
|
644 editor.toggleSyntaxError(lineno, 0, True, message, True) |
|
645 else: |
|
646 editor.toggleFlakesWarning( |
|
647 lineno, True, message, warningType=editor.WarningStyle) |
|
648 |
|
649 @pyqtSlot() |
|
650 def on_resultList_itemSelectionChanged(self): |
|
651 """ |
|
652 Private slot to change the dialog state depending on the selection. |
|
653 """ |
|
654 self.fixButton.setEnabled(len(self.__getSelectedFixableItems()) > 0) |
|
655 |
|
656 @pyqtSlot() |
|
657 def on_showButton_clicked(self): |
|
658 """ |
|
659 Private slot to handle the "Show" button press. |
|
660 """ |
|
661 vm = e5App().getObject("ViewManager") |
|
662 |
|
663 selectedIndexes = [] |
|
664 for index in range(self.resultList.topLevelItemCount()): |
|
665 if self.resultList.topLevelItem(index).isSelected(): |
|
666 selectedIndexes.append(index) |
|
667 if len(selectedIndexes) == 0: |
|
668 selectedIndexes = list(range(self.resultList.topLevelItemCount())) |
|
669 for index in selectedIndexes: |
|
670 itm = self.resultList.topLevelItem(index) |
|
671 fn = Utilities.normabspath(itm.data(0, self.filenameRole)) |
|
672 vm.openSourceFile(fn, 1) |
|
673 editor = vm.getOpenEditor(fn) |
|
674 editor.clearFlakesWarnings() |
|
675 for cindex in range(itm.childCount()): |
|
676 citm = itm.child(cindex) |
|
677 lineno = citm.data(0, self.lineRole) |
|
678 message = citm.data(0, self.messageRole) |
|
679 editor.toggleFlakesWarning(lineno, True, message) |
|
680 |
|
681 # go through the list again to clear warning markers for files, |
|
682 # that are ok |
|
683 openFiles = vm.getOpenFilenames() |
|
684 errorFiles = [] |
|
685 for index in range(self.resultList.topLevelItemCount()): |
|
686 itm = self.resultList.topLevelItem(index) |
|
687 errorFiles.append( |
|
688 Utilities.normabspath(itm.data(0, self.filenameRole))) |
|
689 for file in openFiles: |
|
690 if not file in errorFiles: |
|
691 editor = vm.getOpenEditor(file) |
|
692 editor.clearFlakesWarnings() |
|
693 |
|
694 @pyqtSlot() |
|
695 def on_statisticsButton_clicked(self): |
|
696 """ |
|
697 Private slot to show the statistics dialog. |
|
698 """ |
|
699 from .CodeStyleStatisticsDialog import CodeStyleStatisticsDialog |
|
700 dlg = CodeStyleStatisticsDialog(self.__statistics, self) |
|
701 dlg.exec_() |
|
702 |
|
703 @pyqtSlot() |
|
704 def on_loadDefaultButton_clicked(self): |
|
705 """ |
|
706 Private slot to load the default configuration values. |
|
707 """ |
|
708 self.excludeFilesEdit.setText(Preferences.Prefs.settings.value( |
|
709 "PEP8/ExcludeFilePatterns")) |
|
710 self.excludeMessagesEdit.setText(Preferences.Prefs.settings.value( |
|
711 "PEP8/ExcludeMessages", pep8.DEFAULT_IGNORE)) |
|
712 self.includeMessagesEdit.setText(Preferences.Prefs.settings.value( |
|
713 "PEP8/IncludeMessages")) |
|
714 self.repeatCheckBox.setChecked(Preferences.toBool( |
|
715 Preferences.Prefs.settings.value("PEP8/RepeatMessages"))) |
|
716 self.fixIssuesEdit.setText(Preferences.Prefs.settings.value( |
|
717 "PEP8/FixCodes")) |
|
718 self.noFixIssuesEdit.setText(Preferences.Prefs.settings.value( |
|
719 "PEP8/NoFixCodes", "E501")) |
|
720 self.fixIssuesCheckBox.setChecked(Preferences.toBool( |
|
721 Preferences.Prefs.settings.value("PEP8/FixIssues"))) |
|
722 self.lineLengthSpinBox.setValue(int(Preferences.Prefs.settings.value( |
|
723 "PEP8/MaxLineLength", pep8.MAX_LINE_LENGTH))) |
|
724 self.hangClosingCheckBox.setChecked(Preferences.toBool( |
|
725 Preferences.Prefs.settings.value("PEP8/HangClosing"))) |
|
726 self.docTypeComboBox.setCurrentIndex(self.docTypeComboBox.findData( |
|
727 Preferences.Prefs.settings.value("PEP8/DocstringType", "pep257"))) |
|
728 |
|
729 @pyqtSlot() |
|
730 def on_storeDefaultButton_clicked(self): |
|
731 """ |
|
732 Private slot to store the current configuration values as |
|
733 default values. |
|
734 """ |
|
735 Preferences.Prefs.settings.setValue("PEP8/ExcludeFilePatterns", |
|
736 self.excludeFilesEdit.text()) |
|
737 Preferences.Prefs.settings.setValue("PEP8/ExcludeMessages", |
|
738 self.excludeMessagesEdit.text()) |
|
739 Preferences.Prefs.settings.setValue("PEP8/IncludeMessages", |
|
740 self.includeMessagesEdit.text()) |
|
741 Preferences.Prefs.settings.setValue("PEP8/RepeatMessages", |
|
742 self.repeatCheckBox.isChecked()) |
|
743 Preferences.Prefs.settings.setValue("PEP8/FixCodes", |
|
744 self.fixIssuesEdit.text()) |
|
745 Preferences.Prefs.settings.setValue("PEP8/NoFixCodes", |
|
746 self.noFixIssuesEdit.text()) |
|
747 Preferences.Prefs.settings.setValue("PEP8/FixIssues", |
|
748 self.fixIssuesCheckBox.isChecked()) |
|
749 Preferences.Prefs.settings.setValue("PEP8/MaxLineLength", |
|
750 self.lineLengthSpinBox.value()) |
|
751 Preferences.Prefs.settings.setValue("PEP8/HangClosing", |
|
752 self.hangClosingCheckBox.isChecked()) |
|
753 Preferences.Prefs.settings.setValue("PEP8/DocstringType", |
|
754 self.docTypeComboBox.itemData( |
|
755 self.docTypeComboBox.currentIndex())) |
|
756 |
|
757 @pyqtSlot() |
|
758 def on_resetDefaultButton_clicked(self): |
|
759 """ |
|
760 Private slot to reset the configuration values to their default values. |
|
761 """ |
|
762 Preferences.Prefs.settings.setValue("PEP8/ExcludeFilePatterns", "") |
|
763 Preferences.Prefs.settings.setValue("PEP8/ExcludeMessages", |
|
764 pep8.DEFAULT_IGNORE) |
|
765 Preferences.Prefs.settings.setValue("PEP8/IncludeMessages", "") |
|
766 Preferences.Prefs.settings.setValue("PEP8/RepeatMessages", False) |
|
767 Preferences.Prefs.settings.setValue("PEP8/FixCodes", "") |
|
768 Preferences.Prefs.settings.setValue("PEP8/NoFixCodes", "E501") |
|
769 Preferences.Prefs.settings.setValue("PEP8/FixIssues", False) |
|
770 Preferences.Prefs.settings.setValue("PEP8/MaxLineLength", |
|
771 pep8.MAX_LINE_LENGTH) |
|
772 Preferences.Prefs.settings.setValue("PEP8/HangClosing", False) |
|
773 Preferences.Prefs.settings.setValue("PEP8/DocstringType", "pep257") |
|
774 |
|
775 @pyqtSlot(QAbstractButton) |
|
776 def on_buttonBox_clicked(self, button): |
|
777 """ |
|
778 Private slot called by a button of the button box clicked. |
|
779 |
|
780 @param button button that was clicked (QAbstractButton) |
|
781 """ |
|
782 if button == self.buttonBox.button(QDialogButtonBox.Close): |
|
783 self.close() |
|
784 elif button == self.buttonBox.button(QDialogButtonBox.Cancel): |
|
785 self.__finish() |
|
786 elif button == self.showButton: |
|
787 self.on_showButton_clicked() |
|
788 elif button == self.statisticsButton: |
|
789 self.on_statisticsButton_clicked() |
|
790 |
|
791 def __clearErrors(self): |
|
792 """ |
|
793 Private method to clear all warning markers of open editors. |
|
794 """ |
|
795 vm = e5App().getObject("ViewManager") |
|
796 openFiles = vm.getOpenFilenames() |
|
797 for file in openFiles: |
|
798 editor = vm.getOpenEditor(file) |
|
799 editor.clearFlakesWarnings() |
|
800 |
|
801 @pyqtSlot() |
|
802 def on_fixButton_clicked(self): |
|
803 """ |
|
804 Private slot to fix selected issues. |
|
805 """ |
|
806 from .Pep8Fixer import Pep8Fixer |
|
807 |
|
808 # build a dictionary of issues to fix |
|
809 fixableItems = self.__getSelectedFixableItems() |
|
810 fixesDict = {} # dictionary of lists of tuples containing |
|
811 # the issue and the item |
|
812 for itm in fixableItems: |
|
813 filename = itm.data(0, self.filenameRole) |
|
814 if filename not in fixesDict: |
|
815 fixesDict[filename] = [] |
|
816 fixesDict[filename].append(( |
|
817 (itm.data(0, self.lineRole), |
|
818 itm.data(0, self.positionRole), |
|
819 "{0} {1}".format(itm.data(0, self.codeRole), |
|
820 itm.data(0, self.messageRole))), |
|
821 itm |
|
822 )) |
|
823 |
|
824 # extract the configuration values |
|
825 fixCodes = self.fixIssuesEdit.text() |
|
826 noFixCodes = self.noFixIssuesEdit.text() |
|
827 maxLineLength = self.lineLengthSpinBox.value() |
|
828 |
|
829 # now go through all the files |
|
830 if fixesDict: |
|
831 self.checkProgress.setMaximum(len(fixesDict)) |
|
832 progress = 0 |
|
833 for file in fixesDict: |
|
834 self.checkProgress.setValue(progress) |
|
835 QApplication.processEvents() |
|
836 |
|
837 try: |
|
838 source, encoding = Utilities.readEncodedFile(file) |
|
839 source = source.splitlines(True) |
|
840 except (UnicodeError, IOError) as msg: |
|
841 # skip silently because that should not happen |
|
842 progress += 1 |
|
843 continue |
|
844 |
|
845 deferredFixes = {} |
|
846 fixer = Pep8Fixer(self.__project, file, source, |
|
847 fixCodes, noFixCodes, maxLineLength, |
|
848 True) # always fix in place |
|
849 errors = fixesDict[file] |
|
850 errors.sort(key=lambda a: a[0][0]) |
|
851 for error in errors: |
|
852 (lineno, position, text), itm = error |
|
853 if lineno > len(source): |
|
854 lineno = len(source) |
|
855 fixed, msg, id_ = fixer.fixIssue(lineno, position, text) |
|
856 if fixed == 1: |
|
857 text = "\n" + self.trUtf8("Fix: {0}").format(msg) |
|
858 self.__modifyFixedResultItem(itm, text, True) |
|
859 elif fixed == 0: |
|
860 self.__modifyFixedResultItem(itm, "", False) |
|
861 else: |
|
862 # remember item for the deferred fix |
|
863 deferredFixes[id_] = itm |
|
864 deferredResults = fixer.finalize() |
|
865 for id_ in deferredResults: |
|
866 fixed, msg = deferredResults[id_] |
|
867 itm = deferredFixes[id_] |
|
868 if fixed == 1: |
|
869 text = "\n" + self.trUtf8("Fix: {0}").format(msg) |
|
870 self.__modifyFixedResultItem(itm, text, True) |
|
871 else: |
|
872 self.__modifyFixedResultItem(itm, "", False) |
|
873 fixer.saveFile(encoding) |
|
874 |
|
875 self.__updateFixerStatistics(fixer) |
|
876 progress += 1 |
|
877 |
|
878 self.checkProgress.setValue(progress) |
|
879 QApplication.processEvents() |
|
880 |
|
881 def __getSelectedFixableItems(self): |
|
882 """ |
|
883 Private method to extract all selected items for fixable issues. |
|
884 |
|
885 @return selected items for fixable issues (list of QTreeWidgetItem) |
|
886 """ |
|
887 fixableItems = [] |
|
888 for itm in self.resultList.selectedItems(): |
|
889 if itm.childCount() > 0: |
|
890 for index in range(itm.childCount()): |
|
891 citm = itm.child(index) |
|
892 if self.__itemFixable(citm) and not citm in fixableItems: |
|
893 fixableItems.append(citm) |
|
894 elif self.__itemFixable(itm) and not itm in fixableItems: |
|
895 fixableItems.append(itm) |
|
896 |
|
897 return fixableItems |
|
898 |
|
899 def __itemFixable(self, itm): |
|
900 """ |
|
901 Private method to check, if an item has a fixable issue. |
|
902 |
|
903 @param itm item to be checked (QTreeWidgetItem) |
|
904 @return flag indicating a fixable issue (boolean) |
|
905 """ |
|
906 return itm.data(0, self.fixableRole) |