Plugins/CheckerPlugins/Pep8/CodeStyleCheckerDialog.py

changeset 2973
284c7f4bc875
parent 2972
ae58d4b2562e
child 2978
9d63132a23e0
equal deleted inserted replaced
2972:ae58d4b2562e 2973:284c7f4bc875
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)

eric ide

mercurial