|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Syntax Checker plugin. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 |
|
14 from PyQt5.QtCore import QObject |
|
15 |
|
16 from E5Gui.E5Action import E5Action |
|
17 from E5Gui.E5Application import e5App |
|
18 from eric6config import getConfig |
|
19 |
|
20 from Project.ProjectBrowserModel import ProjectBrowserFileItem |
|
21 |
|
22 import Preferences |
|
23 import UI.Info |
|
24 |
|
25 # Start-Of-Header |
|
26 name = "Syntax Checker Plugin" |
|
27 author = "Detlev Offenbach <detlev@die-offenbachs.de>" |
|
28 autoactivate = True |
|
29 deactivateable = True |
|
30 version = UI.Info.VersionOnly |
|
31 className = "SyntaxCheckerPlugin" |
|
32 packageName = "__core__" |
|
33 shortDescription = "Show the Syntax Checker dialog." |
|
34 longDescription = """This plugin implements the Syntax Checker dialog.""" \ |
|
35 """ Syntax Checker is used to check Python source files for correct""" \ |
|
36 """ syntax.""" |
|
37 pyqtApi = 2 |
|
38 python2Compatible = True |
|
39 # End-Of-Header |
|
40 |
|
41 error = "" |
|
42 |
|
43 |
|
44 class SyntaxCheckerPlugin(QObject): |
|
45 """ |
|
46 Class implementing the Syntax Checker plugin. |
|
47 """ |
|
48 def __init__(self, ui): |
|
49 """ |
|
50 Constructor |
|
51 |
|
52 @param ui reference to the user interface object (UI.UserInterface) |
|
53 """ |
|
54 super(SyntaxCheckerPlugin, self).__init__(ui) |
|
55 self.__ui = ui |
|
56 self.__initialize() |
|
57 |
|
58 from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheckService import \ |
|
59 SyntaxCheckService |
|
60 self.syntaxCheckService = SyntaxCheckService() |
|
61 e5App().registerObject("SyntaxCheckService", self.syntaxCheckService) |
|
62 |
|
63 ericPath = getConfig('ericDir') |
|
64 path = os.path.join(ericPath, 'Plugins', 'CheckerPlugins', |
|
65 'SyntaxChecker') |
|
66 |
|
67 self.syntaxCheckService.addLanguage( |
|
68 'Python2', 'Python2', path, 'SyntaxCheck', |
|
69 self.__getPythonOptions, |
|
70 lambda: Preferences.getPython("PythonExtensions"), |
|
71 self.__translateSyntaxCheck, |
|
72 self.syntaxCheckService.serviceErrorPy2) |
|
73 |
|
74 self.syntaxCheckService.addLanguage( |
|
75 'Python3', 'Python3', path, 'SyntaxCheck', |
|
76 self.__getPythonOptions, |
|
77 lambda: Preferences.getPython("Python3Extensions"), |
|
78 self.__translateSyntaxCheck, |
|
79 self.syntaxCheckService.serviceErrorPy3) |
|
80 |
|
81 # Jasy isn't yet compatible to Python2 |
|
82 self.syntaxCheckService.addLanguage( |
|
83 'JavaScript', 'Python3', path, |
|
84 'jsCheckSyntax', |
|
85 lambda: [], # No options |
|
86 lambda: ['.js'], |
|
87 lambda fn, problems: |
|
88 self.syntaxCheckService.syntaxChecked.emit(fn, problems), |
|
89 self.syntaxCheckService.serviceErrorJavaScript) |
|
90 |
|
91 def __initialize(self): |
|
92 """ |
|
93 Private slot to (re)initialize the plugin. |
|
94 """ |
|
95 self.__projectAct = None |
|
96 self.__projectSyntaxCheckerDialog = None |
|
97 |
|
98 self.__projectBrowserAct = None |
|
99 self.__projectBrowserMenu = None |
|
100 self.__projectBrowserSyntaxCheckerDialog = None |
|
101 |
|
102 self.__editors = [] |
|
103 self.__editorAct = None |
|
104 self.__editorSyntaxCheckerDialog = None |
|
105 |
|
106 def __getPythonOptions(self): |
|
107 """ |
|
108 Private methode to determine the syntax check options. |
|
109 |
|
110 @return state of checkFlakes and ignoreStarImportWarnings (bool, bool) |
|
111 """ |
|
112 checkFlakes = Preferences.getFlakes("IncludeInSyntaxCheck") |
|
113 ignoreStarImportWarnings = Preferences.getFlakes( |
|
114 "IgnoreStarImportWarnings") |
|
115 return checkFlakes, ignoreStarImportWarnings |
|
116 |
|
117 def __translateSyntaxCheck(self, fn, problems): |
|
118 """ |
|
119 Private slot to translate the resulting messages. |
|
120 |
|
121 If checkFlakes is True, warnings contains a list of strings containing |
|
122 the warnings (marker, file name, line number, message) |
|
123 The values are only valid, if nok is False. |
|
124 |
|
125 @param fn filename of the checked file (str) |
|
126 @param problems dictionary with the keys 'error' and 'warnings' which |
|
127 hold a list containing details about the error/ warnings |
|
128 (file name, line number, column, codestring (only at syntax |
|
129 errors), the message, a list with arguments for the message) |
|
130 """ |
|
131 from CheckerPlugins.SyntaxChecker.pyflakes.translations import \ |
|
132 getTranslatedFlakesMessage |
|
133 warnings = problems.get('warnings', []) |
|
134 for warning in warnings: |
|
135 # Translate messages |
|
136 msg_args = warning.pop() |
|
137 warning[4] = getTranslatedFlakesMessage(warning[4], msg_args) |
|
138 |
|
139 problems['warnings'] = warnings |
|
140 self.syntaxCheckService.syntaxChecked.emit(fn, problems) |
|
141 |
|
142 def activate(self): |
|
143 """ |
|
144 Public method to activate this plugin. |
|
145 |
|
146 @return tuple of None and activation status (boolean) |
|
147 """ |
|
148 menu = e5App().getObject("Project").getMenu("Checks") |
|
149 if menu: |
|
150 self.__projectAct = E5Action( |
|
151 self.tr('Check Syntax'), |
|
152 self.tr('&Syntax...'), 0, 0, |
|
153 self, 'project_check_syntax') |
|
154 self.__projectAct.setStatusTip( |
|
155 self.tr('Check syntax.')) |
|
156 self.__projectAct.setWhatsThis(self.tr( |
|
157 """<b>Check Syntax...</b>""" |
|
158 """<p>This checks Python files for syntax errors.</p>""" |
|
159 )) |
|
160 self.__projectAct.triggered.connect(self.__projectSyntaxCheck) |
|
161 e5App().getObject("Project").addE5Actions([self.__projectAct]) |
|
162 menu.addAction(self.__projectAct) |
|
163 |
|
164 self.__editorAct = E5Action( |
|
165 self.tr('Check Syntax'), |
|
166 self.tr('&Syntax...'), 0, 0, |
|
167 self, "") |
|
168 self.__editorAct.setWhatsThis(self.tr( |
|
169 """<b>Check Syntax...</b>""" |
|
170 """<p>This checks Python files for syntax errors.</p>""" |
|
171 )) |
|
172 self.__editorAct.triggered.connect(self.__editorSyntaxCheck) |
|
173 |
|
174 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu) |
|
175 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ |
|
176 .showMenu.connect(self.__projectBrowserShowMenu) |
|
177 e5App().getObject("ViewManager").editorOpenedEd.connect( |
|
178 self.__editorOpened) |
|
179 e5App().getObject("ViewManager").editorClosedEd.connect( |
|
180 self.__editorClosed) |
|
181 |
|
182 for editor in e5App().getObject("ViewManager").getOpenEditors(): |
|
183 self.__editorOpened(editor) |
|
184 |
|
185 return None, True |
|
186 |
|
187 def deactivate(self): |
|
188 """ |
|
189 Public method to deactivate this plugin. |
|
190 """ |
|
191 e5App().getObject("Project").showMenu.disconnect( |
|
192 self.__projectShowMenu) |
|
193 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ |
|
194 .showMenu.disconnect(self.__projectBrowserShowMenu) |
|
195 e5App().getObject("ViewManager").editorOpenedEd.disconnect( |
|
196 self.__editorOpened) |
|
197 e5App().getObject("ViewManager").editorClosedEd.disconnect( |
|
198 self.__editorClosed) |
|
199 |
|
200 menu = e5App().getObject("Project").getMenu("Checks") |
|
201 if menu: |
|
202 menu.removeAction(self.__projectAct) |
|
203 |
|
204 if self.__projectBrowserMenu: |
|
205 if self.__projectBrowserAct: |
|
206 self.__projectBrowserMenu.removeAction( |
|
207 self.__projectBrowserAct) |
|
208 |
|
209 for editor in self.__editors: |
|
210 editor.showMenu.disconnect(self.__editorShowMenu) |
|
211 menu = editor.getMenu("Checks") |
|
212 if menu is not None: |
|
213 menu.removeAction(self.__editorAct) |
|
214 |
|
215 self.__initialize() |
|
216 |
|
217 def __projectShowMenu(self, menuName, menu): |
|
218 """ |
|
219 Private slot called, when the the project menu or a submenu is |
|
220 about to be shown. |
|
221 |
|
222 @param menuName name of the menu to be shown (string) |
|
223 @param menu reference to the menu (QMenu) |
|
224 """ |
|
225 if menuName == "Checks" and self.__projectAct is not None: |
|
226 self.__projectAct.setEnabled( |
|
227 e5App().getObject("Project").getProjectLanguage() in |
|
228 self.syntaxCheckService.getLanguages()) |
|
229 |
|
230 def __projectBrowserShowMenu(self, menuName, menu): |
|
231 """ |
|
232 Private slot called, when the the project browser menu or a submenu is |
|
233 about to be shown. |
|
234 |
|
235 @param menuName name of the menu to be shown (string) |
|
236 @param menu reference to the menu (QMenu) |
|
237 """ |
|
238 if menuName == "Checks" and \ |
|
239 e5App().getObject("Project").getProjectLanguage() in \ |
|
240 self.syntaxCheckService.getLanguages(): |
|
241 self.__projectBrowserMenu = menu |
|
242 if self.__projectBrowserAct is None: |
|
243 self.__projectBrowserAct = E5Action( |
|
244 self.tr('Check Syntax'), |
|
245 self.tr('&Syntax...'), 0, 0, |
|
246 self, "") |
|
247 self.__projectBrowserAct.setWhatsThis(self.tr( |
|
248 """<b>Check Syntax...</b>""" |
|
249 """<p>This checks Python files for syntax errors.</p>""" |
|
250 )) |
|
251 self.__projectBrowserAct.triggered.connect( |
|
252 self.__projectBrowserSyntaxCheck) |
|
253 if self.__projectBrowserAct not in menu.actions(): |
|
254 menu.addAction(self.__projectBrowserAct) |
|
255 |
|
256 def __projectSyntaxCheck(self): |
|
257 """ |
|
258 Private slot used to check the project files for syntax errors. |
|
259 """ |
|
260 project = e5App().getObject("Project") |
|
261 project.saveAllScripts() |
|
262 ppath = project.getProjectPath() |
|
263 extensions = tuple(self.syntaxCheckService.getExtensions()) |
|
264 files = [os.path.join(ppath, file) |
|
265 for file in project.pdata["SOURCES"] |
|
266 if file.endswith(extensions)] |
|
267 |
|
268 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import \ |
|
269 SyntaxCheckerDialog |
|
270 self.__projectSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
271 self.__projectSyntaxCheckerDialog.show() |
|
272 self.__projectSyntaxCheckerDialog.prepare(files, project) |
|
273 |
|
274 def __projectBrowserSyntaxCheck(self): |
|
275 """ |
|
276 Private method to handle the syntax check context menu action of the |
|
277 project sources browser. |
|
278 """ |
|
279 browser = e5App().getObject("ProjectBrowser").getProjectBrowser( |
|
280 "sources") |
|
281 if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: |
|
282 fn = [] |
|
283 for itm in browser.getSelectedItems([ProjectBrowserFileItem]): |
|
284 fn.append(itm.fileName()) |
|
285 else: |
|
286 itm = browser.model().item(browser.currentIndex()) |
|
287 try: |
|
288 fn = itm.fileName() |
|
289 except AttributeError: |
|
290 fn = itm.dirName() |
|
291 |
|
292 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import \ |
|
293 SyntaxCheckerDialog |
|
294 self.__projectBrowserSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
295 self.__projectBrowserSyntaxCheckerDialog.show() |
|
296 self.__projectBrowserSyntaxCheckerDialog.start(fn) |
|
297 |
|
298 def __editorOpened(self, editor): |
|
299 """ |
|
300 Private slot called, when a new editor was opened. |
|
301 |
|
302 @param editor reference to the new editor (QScintilla.Editor) |
|
303 """ |
|
304 menu = editor.getMenu("Checks") |
|
305 if menu is not None: |
|
306 menu.addAction(self.__editorAct) |
|
307 editor.showMenu.connect(self.__editorShowMenu) |
|
308 self.__editors.append(editor) |
|
309 |
|
310 def __editorClosed(self, editor): |
|
311 """ |
|
312 Private slot called, when an editor was closed. |
|
313 |
|
314 @param editor reference to the editor (QScintilla.Editor) |
|
315 """ |
|
316 try: |
|
317 self.__editors.remove(editor) |
|
318 except ValueError: |
|
319 pass |
|
320 |
|
321 def __editorShowMenu(self, menuName, menu, editor): |
|
322 """ |
|
323 Private slot called, when the the editor context menu or a submenu is |
|
324 about to be shown. |
|
325 |
|
326 @param menuName name of the menu to be shown (string) |
|
327 @param menu reference to the menu (QMenu) |
|
328 @param editor reference to the editor |
|
329 """ |
|
330 if menuName == "Checks": |
|
331 if self.__editorAct not in menu.actions(): |
|
332 menu.addAction(self.__editorAct) |
|
333 self.__editorAct.setEnabled( |
|
334 editor.getLanguage() in self.syntaxCheckService.getLanguages()) |
|
335 |
|
336 def __editorSyntaxCheck(self): |
|
337 """ |
|
338 Private slot to handle the syntax check context menu action of the |
|
339 editors. |
|
340 """ |
|
341 editor = e5App().getObject("ViewManager").activeWindow() |
|
342 if editor is not None: |
|
343 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import \ |
|
344 SyntaxCheckerDialog |
|
345 self.__editorSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
346 self.__editorSyntaxCheckerDialog.show() |
|
347 if editor.isJavascriptFile(): |
|
348 unnamed = "Unnamed.js" |
|
349 else: |
|
350 unnamed = "Unnamed.py" |
|
351 self.__editorSyntaxCheckerDialog.start( |
|
352 editor.getFileName() or unnamed, editor.text()) |