Plugins/CheckerPlugins/Pep8/Pep8Dialog.py

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

eric ide

mercurial