eric6/Plugins/PluginTabnanny.py

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

eric ide

mercurial