Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py

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

eric ide

mercurial