eric6/Plugins/PluginTabnanny.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7178
43e994af5ee0
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Tabnanny plugin.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13
14 from PyQt5.QtCore import QObject, pyqtSignal
15
16 from E5Gui.E5Application import e5App
17 from E5Gui.E5Action import E5Action
18 from Project.ProjectBrowserModel import ProjectBrowserFileItem
19 from Utilities import determinePythonVersion
20
21 import Preferences
22 import UI.Info
23
24 # Start-Of-Header
25 name = "Tabnanny Plugin"
26 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
27 autoactivate = True
28 deactivateable = True
29 version = UI.Info.VersionOnly
30 className = "TabnannyPlugin"
31 packageName = "__core__"
32 shortDescription = "Show the Tabnanny dialog."
33 longDescription = """This plugin implements the Tabnanny dialog.""" \
34 """ Tabnanny is used to check Python source files for correct""" \
35 """ indentations."""
36 pyqtApi = 2
37 python2Compatible = True
38 # End-Of-Header
39
40 error = ""
41
42
43 class TabnannyPlugin(QObject):
44 """
45 Class implementing the Tabnanny plugin.
46
47 @signal indentChecked(str, bool, str, str) emitted when the indent
48 check was done.
49 @signal batchFinished() emitted when a style check batch is done
50 @signal error(str, str) emitted in case of an error
51 """
52 indentChecked = pyqtSignal(str, bool, str, str)
53 batchFinished = pyqtSignal()
54 error = pyqtSignal(str, str)
55
56 def __init__(self, ui):
57 """
58 Constructor
59
60 @param ui reference to the user interface object (UI.UserInterface)
61 """
62 super(TabnannyPlugin, self).__init__(ui)
63 self.__ui = ui
64 self.__initialize()
65
66 self.backgroundService = e5App().getObject("BackgroundService")
67
68 path = os.path.join(
69 os.path.dirname(__file__), 'CheckerPlugins', 'Tabnanny')
70 self.backgroundService.serviceConnect(
71 'indent', 'Python2', path, 'Tabnanny',
72 lambda *args: self.indentChecked.emit(*args),
73 onErrorCallback=self.serviceErrorPy2,
74 onBatchDone=self.batchJobDone)
75 self.backgroundService.serviceConnect(
76 'indent', 'Python3', path, 'Tabnanny',
77 lambda *args: self.indentChecked.emit(*args),
78 onErrorCallback=self.serviceErrorPy3,
79 onBatchDone=self.batchJobDone)
80
81 self.queuedBatches = []
82 self.batchesFinished = True
83
84 def __serviceError(self, fn, msg):
85 """
86 Private slot handling service errors.
87
88 @param fn file name (string)
89 @param msg message text (string)
90 """
91 self.error.emit(fn, msg)
92
93 def serviceErrorPy2(self, fx, lang, fn, msg):
94 """
95 Public slot handling service errors for Python 2.
96
97 @param fx service name (string)
98 @param lang language (string)
99 @param fn file name (string)
100 @param msg message text (string)
101 """
102 if fx in ['indent', 'batch_indent'] and lang == 'Python2':
103 if fx == 'indent':
104 self.__serviceError(fn, msg)
105 else:
106 self.__serviceError(self.tr("Python 2 batch check"), msg)
107 self.batchJobDone(fx, lang)
108
109 def serviceErrorPy3(self, fx, lang, fn, msg):
110 """
111 Public slot handling service errors for Python 2.
112
113 @param fx service name (string)
114 @param lang language (string)
115 @param fn file name (string)
116 @param msg message text (string)
117 """
118 if fx in ['indent', 'batch_indent'] and lang == 'Python3':
119 if fx == 'indent':
120 self.__serviceError(fn, msg)
121 else:
122 self.__serviceError(self.tr("Python 3 batch check"), msg)
123 self.batchJobDone(fx, lang)
124
125 def batchJobDone(self, fx, lang):
126 """
127 Public slot handling the completion of a batch job.
128
129 @param fx service name (string)
130 @param lang language (string)
131 """
132 if fx in ['indent', 'batch_indent']:
133 if lang in self.queuedBatches:
134 self.queuedBatches.remove(lang)
135 # prevent sending the signal multiple times
136 if len(self.queuedBatches) == 0 and not self.batchesFinished:
137 self.batchFinished.emit()
138 self.batchesFinished = True
139
140 def __initialize(self):
141 """
142 Private slot to (re)initialize the plugin.
143 """
144 self.__projectAct = None
145 self.__projectTabnannyDialog = None
146
147 self.__projectBrowserAct = None
148 self.__projectBrowserMenu = None
149 self.__projectBrowserTabnannyDialog = None
150
151 self.__editors = []
152 self.__editorAct = None
153 self.__editorTabnannyDialog = None
154
155 def indentCheck(self, lang, filename, source):
156 """
157 Public method to prepare an indentation check on one Python source
158 file.
159
160 @param lang language of the file or None to determine by internal
161 algorithm (str or None)
162 @param filename source filename (string)
163 @param source string containing the code to check (string)
164 """
165 if lang is None:
166 lang = 'Python{0}'.format(determinePythonVersion(filename, source))
167 if lang not in ['Python2', 'Python3']:
168 return
169
170 self.backgroundService.enqueueRequest(
171 'indent', lang, filename, [source])
172
173 def indentBatchCheck(self, argumentsList):
174 """
175 Public method to prepare an indentation check on multiple Python
176 source files.
177
178 @param argumentsList list of arguments tuples with each tuple
179 containing filename and source (string, string)
180 """
181 data = {
182 "Python2": [],
183 "Python3": [],
184 }
185 for filename, source in argumentsList:
186 lang = 'Python{0}'.format(determinePythonVersion(filename, source))
187 if lang not in ['Python2', 'Python3']:
188 continue
189 else:
190 data[lang].append((filename, source))
191
192 self.queuedBatches = []
193 for lang in ['Python2', 'Python3']:
194 if data[lang]:
195 self.queuedBatches.append(lang)
196 self.backgroundService.enqueueRequest('batch_indent', lang, "",
197 data[lang])
198 self.batchesFinished = False
199
200 def cancelIndentBatchCheck(self):
201 """
202 Public method to cancel all batch jobs.
203 """
204 for lang in ['Python2', 'Python3']:
205 self.backgroundService.requestCancel('batch_style', lang)
206
207 def activate(self):
208 """
209 Public method to activate this plugin.
210
211 @return tuple of None and activation status (boolean)
212 """
213 menu = e5App().getObject("Project").getMenu("Checks")
214 if menu:
215 self.__projectAct = E5Action(
216 self.tr('Check Indentations'),
217 self.tr('&Indentations...'), 0, 0,
218 self, 'project_check_indentations')
219 self.__projectAct.setStatusTip(
220 self.tr('Check indentations using tabnanny.'))
221 self.__projectAct.setWhatsThis(self.tr(
222 """<b>Check Indentations...</b>"""
223 """<p>This checks Python files"""
224 """ for bad indentations using tabnanny.</p>"""
225 ))
226 self.__projectAct.triggered.connect(self.__projectTabnanny)
227 e5App().getObject("Project").addE5Actions([self.__projectAct])
228 menu.addAction(self.__projectAct)
229
230 self.__editorAct = E5Action(
231 self.tr('Check Indentations'),
232 self.tr('&Indentations...'), 0, 0,
233 self, "")
234 self.__editorAct.setWhatsThis(self.tr(
235 """<b>Check Indentations...</b>"""
236 """<p>This checks Python files"""
237 """ for bad indentations using tabnanny.</p>"""
238 ))
239 self.__editorAct.triggered.connect(self.__editorTabnanny)
240
241 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu)
242 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\
243 .showMenu.connect(self.__projectBrowserShowMenu)
244 e5App().getObject("ViewManager").editorOpenedEd.connect(
245 self.__editorOpened)
246 e5App().getObject("ViewManager").editorClosedEd.connect(
247 self.__editorClosed)
248
249 for editor in e5App().getObject("ViewManager").getOpenEditors():
250 self.__editorOpened(editor)
251
252 return None, True
253
254 def deactivate(self):
255 """
256 Public method to deactivate this plugin.
257 """
258 e5App().getObject("Project").showMenu.disconnect(
259 self.__projectShowMenu)
260 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\
261 .showMenu.disconnect(self.__projectBrowserShowMenu)
262 e5App().getObject("ViewManager").editorOpenedEd.disconnect(
263 self.__editorOpened)
264 e5App().getObject("ViewManager").editorClosedEd.disconnect(
265 self.__editorClosed)
266
267 menu = e5App().getObject("Project").getMenu("Checks")
268 if menu:
269 menu.removeAction(self.__projectAct)
270
271 if self.__projectBrowserMenu:
272 if self.__projectBrowserAct:
273 self.__projectBrowserMenu.removeAction(
274 self.__projectBrowserAct)
275
276 for editor in self.__editors:
277 editor.showMenu.disconnect(self.__editorShowMenu)
278 menu = editor.getMenu("Checks")
279 if menu is not None:
280 menu.removeAction(self.__editorAct)
281
282 self.__initialize()
283
284 def __projectShowMenu(self, menuName, menu):
285 """
286 Private slot called, when the the project menu or a submenu is
287 about to be shown.
288
289 @param menuName name of the menu to be shown (string)
290 @param menu reference to the menu (QMenu)
291 """
292 if menuName == "Checks" and self.__projectAct is not None:
293 self.__projectAct.setEnabled(
294 e5App().getObject("Project").getProjectLanguage() in
295 ["Python3", "Python2", "Python"])
296
297 def __projectBrowserShowMenu(self, menuName, menu):
298 """
299 Private slot called, when the the project browser context menu or a
300 submenu is about to be shown.
301
302 @param menuName name of the menu to be shown (string)
303 @param menu reference to the menu (QMenu)
304 """
305 if menuName == "Checks" and \
306 e5App().getObject("Project").getProjectLanguage() in \
307 ["Python3", "Python2", "Python"]:
308 self.__projectBrowserMenu = menu
309 if self.__projectBrowserAct is None:
310 self.__projectBrowserAct = E5Action(
311 self.tr('Check Indentations'),
312 self.tr('&Indentations...'), 0, 0,
313 self, "")
314 self.__projectBrowserAct.setWhatsThis(self.tr(
315 """<b>Check Indentations...</b>"""
316 """<p>This checks Python files"""
317 """ for bad indentations using tabnanny.</p>"""
318 ))
319 self.__projectBrowserAct.triggered.connect(
320 self.__projectBrowserTabnanny)
321 if self.__projectBrowserAct not in menu.actions():
322 menu.addAction(self.__projectBrowserAct)
323
324 def __projectTabnanny(self):
325 """
326 Private slot used to check the project files for bad indentations.
327 """
328 project = e5App().getObject("Project")
329 project.saveAllScripts()
330 ppath = project.getProjectPath()
331 files = [os.path.join(ppath, file)
332 for file in project.pdata["SOURCES"]
333 if file.endswith(
334 tuple(Preferences.getPython("Python3Extensions")) +
335 tuple(Preferences.getPython("PythonExtensions")))]
336
337 from CheckerPlugins.Tabnanny.TabnannyDialog import TabnannyDialog
338 self.__projectTabnannyDialog = TabnannyDialog(self)
339 self.__projectTabnannyDialog.show()
340 self.__projectTabnannyDialog.prepare(files, project)
341
342 def __projectBrowserTabnanny(self):
343 """
344 Private method to handle the tabnanny context menu action of the
345 project sources browser.
346 """
347 browser = e5App().getObject("ProjectBrowser").getProjectBrowser(
348 "sources")
349 if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
350 fn = []
351 for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
352 fn.append(itm.fileName())
353 else:
354 itm = browser.model().item(browser.currentIndex())
355 try:
356 fn = itm.fileName()
357 except AttributeError:
358 fn = itm.dirName()
359
360 from CheckerPlugins.Tabnanny.TabnannyDialog import TabnannyDialog
361 self.__projectBrowserTabnannyDialog = TabnannyDialog(self)
362 self.__projectBrowserTabnannyDialog.show()
363 self.__projectBrowserTabnannyDialog.start(fn)
364
365 def __editorOpened(self, editor):
366 """
367 Private slot called, when a new editor was opened.
368
369 @param editor reference to the new editor (QScintilla.Editor)
370 """
371 menu = editor.getMenu("Checks")
372 if menu is not None:
373 menu.addAction(self.__editorAct)
374 editor.showMenu.connect(self.__editorShowMenu)
375 self.__editors.append(editor)
376
377 def __editorClosed(self, editor):
378 """
379 Private slot called, when an editor was closed.
380
381 @param editor reference to the editor (QScintilla.Editor)
382 """
383 try:
384 self.__editors.remove(editor)
385 except ValueError:
386 pass
387
388 def __editorShowMenu(self, menuName, menu, editor):
389 """
390 Private slot called, when the the editor context menu or a submenu is
391 about to be shown.
392
393 @param menuName name of the menu to be shown (string)
394 @param menu reference to the menu (QMenu)
395 @param editor reference to the editor
396 """
397 if menuName == "Checks":
398 if self.__editorAct not in menu.actions():
399 menu.addAction(self.__editorAct)
400 self.__editorAct.setEnabled(editor.isPyFile())
401
402 def __editorTabnanny(self):
403 """
404 Private slot to handle the tabnanny context menu action of the editors.
405 """
406 editor = e5App().getObject("ViewManager").activeWindow()
407 if editor is not None:
408 if editor.checkDirty() and editor.getFileName() is not None:
409 from CheckerPlugins.Tabnanny.TabnannyDialog import \
410 TabnannyDialog
411 self.__editorTabnannyDialog = TabnannyDialog(self)
412 self.__editorTabnannyDialog.show()
413 self.__editorTabnannyDialog.start(editor.getFileName())

eric ide

mercurial