PluginRefactoringRope.py

changeset 100
2bfe9e3fad8d
parent 99
e21a49043f31
child 101
5098ad8960ed
equal deleted inserted replaced
99:e21a49043f31 100:2bfe9e3fad8d
10 from __future__ import unicode_literals 10 from __future__ import unicode_literals
11 11
12 import os 12 import os
13 import sys 13 import sys
14 14
15 from PyQt5.QtCore import QObject, QTranslator 15 from PyQt5.QtCore import QObject, QTranslator, QCoreApplication, QTimer
16 16
17 from E5Gui.E5Application import e5App 17 from E5Gui.E5Application import e5App
18
19 import Preferences
20 import Utilities
18 21
19 from Preferences.Shortcuts import readShortcuts 22 from Preferences.Shortcuts import readShortcuts
20 23
21 # Start-Of-Header 24 # Start-Of-Header
22 name = "Refactoring Rope Plugin" 25 name = "Refactoring Rope Plugin"
23 author = "Detlev Offenbach <detlev@die-offenbachs.de>" 26 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
24 autoactivate = True 27 autoactivate = True
25 deactivateable = True 28 deactivateable = True
26 version = "3.1.0" 29 version = "4.0.0"
27 className = "RefactoringRopePlugin" 30 className = "RefactoringRopePlugin"
28 packageName = "RefactoringRope" 31 packageName = "RefactoringRope"
29 internalPackages = "rope" 32 internalPackages = "rope"
30 shortDescription = "Refactoring using the Rope library." 33 shortDescription = "Refactoring using the Rope library."
31 longDescription = """This plugin implements refactoring functionality""" \ 34 longDescription = """This plug-in implements refactoring functionality""" \
32 """ using the Rope refactoring library. Only refactoring in the same""" \ 35 """ using the Rope refactoring library. Additonally it implements an """ \
33 """ Python version as Eric is running is allowed.""" 36 """ alternative auto-completion and calltips provider. Only""" \
37 """ refactoring, completions and calltips in the same Python variant""" \
38 """ as Eric is running is allowed."""
34 pyqtApi = 2 39 pyqtApi = 2
35 doNotCompile = True 40 doNotCompile = True
36 python2Compatible = True 41 python2Compatible = True
37 # End-Of-Header 42 # End-Of-Header
38 43
39 error = "" 44 error = ""
40 45
46 refactoringRopePluginObject = None
47
48
49 def createAutoCompletionPage(configDlg):
50 """
51 Module function to create the autocompletion configuration page.
52
53 @param configDlg reference to the configuration dialog
54 @return reference to the configuration page
55 """
56 global refactoringRopePluginObject
57 from RefactoringRope.ConfigurationPage.AutoCompletionRopePage \
58 import AutoCompletionRopePage
59 page = AutoCompletionRopePage(refactoringRopePluginObject)
60 return page
61
62
63 def createCallTipsPage(configDlg):
64 """
65 Module function to create the calltips configuration page.
66
67 @param configDlg reference to the configuration dialog
68 @return reference to the configuration page
69 """
70 global refactoringRopePluginObject
71 from RefactoringRope.ConfigurationPage.CallTipsRopePage \
72 import CallTipsRopePage
73 page = CallTipsRopePage(refactoringRopePluginObject)
74 return page
75
76
77 def getConfigData():
78 """
79 Module function returning data as required by the configuration dialog.
80
81 @return dictionary containing the relevant data
82 """
83 return {
84 "ropeAutoCompletionPage" : [
85 QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
86 os.path.join("RefactoringRope", "ConfigurationPage",
87 "preferences-refactoring.png"),
88 createAutoCompletionPage, "editorAutocompletionPage", None],
89 "ropeCallTipsPage" : \
90 [QCoreApplication.translate("RefactoringRopePlugin", "Rope"),
91 os.path.join("RefactoringRope", "ConfigurationPage",
92 "preferences-refactoring.png"),
93 createCallTipsPage, "editorCalltipsPage", None],
94 }
95
96
97 def prepareUninstall():
98 """
99 Module function to prepare for an uninstallation.
100 """
101 Preferences.Prefs.settings.remove(RefactoringRopePlugin.PreferencesKey)
102
41 103
42 class RefactoringRopePlugin(QObject): 104 class RefactoringRopePlugin(QObject):
43 """ 105 """
44 Class implementing the Rope refactoring plugin. 106 Class implementing the Rope refactoring plugin.
45 """ 107 """
108 PreferencesKey = "RefactoringRope"
109
46 def __init__(self, ui): 110 def __init__(self, ui):
47 """ 111 """
48 Constructor 112 Constructor
49 113
50 @param ui reference to the user interface object (UI.UserInterface) 114 @param ui reference to the user interface object (UI.UserInterface)
51 """ 115 """
52 QObject.__init__(self, ui) 116 QObject.__init__(self, ui)
53 self.__ui = ui 117 self.__ui = ui
54 self.__initialize() 118 self.__initialize()
55 119
120 self.__defaults = {
121 "CodeAssistEnabled" : False,
122 "MaxFixes" : 10,
123 "CodeAssistTimeout" : 100,
124 "ShowQScintillaCompletions": True,
125
126 "CodeAssistCalltipsEnabled" : False,
127 "CalltipsMaxFixes" : 10,
128 }
129
56 self.__translator = None 130 self.__translator = None
57 self.__loadTranslator() 131 self.__loadTranslator()
132
133 self.__acTimer = QTimer(self)
134 self.__acTimer.setSingleShot(True)
135 self.__acTimer.setInterval(self.getPreferences("CodeAssistTimeout"))
136 self.__acTimer.timeout.connect(self.__codeAssist)
58 137
59 def __initialize(self): 138 def __initialize(self):
60 """ 139 """
61 Private slot to (re)initialize the plugin. 140 Private slot to (re)initialize the plugin.
62 """ 141 """
63 self.__object = None 142 self.__object = None
64 143
65 self.__mainAct = None 144 self.__mainAct = None
66 self.__mainMenu = None 145 self.__mainMenu = None
67 self.__projectIsOpen = False 146 self.__projectIsOpen = False
147
148 self.__editors = []
149
150 self.__currentEditor = None
151 self.__savedEditorName = None
152 self.__oldEditorText = ""
68 153
69 def __checkUiVersion(self): 154 def __checkUiVersion(self):
70 """ 155 """
71 Private method to check, if the IDE has a suitable version. 156 Private method to check, if the IDE has a suitable version.
72 157
88 global error 173 global error
89 error = "" # clear previous error 174 error = "" # clear previous error
90 175
91 if not self.__checkUiVersion(): 176 if not self.__checkUiVersion():
92 return None, False 177 return None, False
178
179 global refactoringRopePluginObject
180 refactoringRopePluginObject = self
93 181
94 from RefactoringRope.Refactoring import Refactoring 182 from RefactoringRope.Refactoring import Refactoring
95 183
96 self.__object = Refactoring(self, self.__ui) 184 self.__object = Refactoring(self, self.__ui)
97 self.__object.initActions() 185 self.__object.initActions()
111 self.__projectOpened() 199 self.__projectOpened()
112 200
113 if self.__projectIsOpen: 201 if self.__projectIsOpen:
114 self.__object.projectOpened() 202 self.__object.projectOpened()
115 203
204 e5App().getObject("ViewManager").editorOpenedEd.connect(
205 self.__editorOpened)
206 e5App().getObject("ViewManager").editorClosedEd.connect(
207 self.__editorClosed)
208
116 e5App().getObject("Project").projectOpened.connect( 209 e5App().getObject("Project").projectOpened.connect(
117 self.__object.projectOpened) 210 self.__object.projectOpened)
118 e5App().getObject("Project").projectPropertiesChanged.connect( 211 e5App().getObject("Project").projectPropertiesChanged.connect(
119 self.__object.projectOpened) 212 self.__object.projectOpened)
120 e5App().getObject("Project").projectClosed.connect( 213 e5App().getObject("Project").projectClosed.connect(
129 e5App().getObject("Project").projectClosed.connect( 222 e5App().getObject("Project").projectClosed.connect(
130 self.__projectClosed) 223 self.__projectClosed)
131 e5App().getObject("Project").newProject.connect( 224 e5App().getObject("Project").newProject.connect(
132 self.__projectOpened) 225 self.__projectOpened)
133 226
227 if e5App().getObject("Project").isOpen():
228 for editor in e5App().getObject("ViewManager").getOpenEditors():
229 self.__editorOpened(editor)
230
134 return None, True 231 return None, True
135 232
136 def deactivate(self): 233 def deactivate(self):
137 """ 234 """
138 Public method to deactivate this plugin. 235 Public method to deactivate this plugin.
139 """ 236 """
140 e5App().unregisterPluginObject("RefactoringRope") 237 e5App().unregisterPluginObject("RefactoringRope")
238
239 e5App().getObject("ViewManager").editorOpenedEd.disconnect(
240 self.__editorOpened)
241 e5App().getObject("ViewManager").editorClosedEd.disconnect(
242 self.__editorClosed)
141 243
142 e5App().getObject("Project").projectOpened.disconnect( 244 e5App().getObject("Project").projectOpened.disconnect(
143 self.__object.projectOpened) 245 self.__object.projectOpened)
144 e5App().getObject("Project").projectPropertiesChanged.disconnect( 246 e5App().getObject("Project").projectPropertiesChanged.disconnect(
145 self.__object.projectOpened) 247 self.__object.projectOpened)
156 self.__projectClosed) 258 self.__projectClosed)
157 e5App().getObject("Project").newProject.disconnect( 259 e5App().getObject("Project").newProject.disconnect(
158 self.__projectOpened) 260 self.__projectOpened)
159 261
160 self.__ui.menuBar().removeAction(self.__mainAct) 262 self.__ui.menuBar().removeAction(self.__mainAct)
263
264 for editor in self.__editors[:]:
265 self.__editorClosed(editor)
161 266
162 self.__initialize() 267 self.__initialize()
163 268
164 def __loadTranslator(self): 269 def __loadTranslator(self):
165 """ 270 """
180 else: 285 else:
181 print("Warning: translation file '{0}' could not" 286 print("Warning: translation file '{0}' could not"
182 " be loaded.".format(translation)) 287 " be loaded.".format(translation))
183 print("Using default.") 288 print("Using default.")
184 289
185 def __projectOpened(self): 290 def getPreferences(self, key):
186 """ 291 """
187 Private slot to handle the projectOpened signal. 292 Public method to retrieve the various refactoring settings.
293
294 @param key the key of the value to get
295 @param prefClass preferences class used as the storage area
296 @return the requested refactoring setting
297 """
298 if key in ["CodeAssistEnabled", "CodeAssistCalltipsEnabled",
299 "ShowQScintillaCompletions"]:
300 return Preferences.toBool(Preferences.Prefs.settings.value(
301 self.PreferencesKey + "/" + key, self.__defaults[key]))
302 else:
303 return int(Preferences.Prefs.settings.value(
304 self.PreferencesKey + "/" + key, self.__defaults[key]))
305
306 def setPreferences(self, key, value):
307 """
308 Public method to store the various refactoring settings.
309
310 @param key the key of the setting to be set (string)
311 @param value the value to be set
312 @param prefClass preferences class used as the storage area
313 """
314 Preferences.Prefs.settings.setValue(
315 self.PreferencesKey + "/" + key, value)
316
317 if key in ["CodeAssistEnabled", "CodeAssistCalltipsEnabled"]:
318 if value:
319 if e5App().getObject("Project").isOpen():
320 for editor in e5App().getObject("ViewManager")\
321 .getOpenEditors():
322 if editor not in self.__editors:
323 self.__editorOpened(editor)
324 else:
325 for editor in self.__editors[:]:
326 self.__editorClosed(editor)
327 elif key == "CodeAssistTimeout":
328 self.__acTimer.setInterval(value)
329
330 def __determineLanguage(self):
331 """
332 Private method to determine the valid language strings.
333
334 @return list of valid language strings (list of string)
188 """ 335 """
189 if sys.version_info[0] == 3: 336 if sys.version_info[0] == 3:
190 lang = ["Python3"] 337 lang = ["Python3"]
191 elif sys.version_info[0] == 2: 338 elif sys.version_info[0] == 2:
192 lang = ["Python", "Python2"] 339 lang = ["Python", "Python2"]
193 else: 340 else:
194 lang = [] 341 lang = []
195 342
343 return lang
344
345 def __projectOpened(self):
346 """
347 Private slot to handle the projectOpened signal.
348 """
349 lang = self.__determineLanguage()
350
196 enabled = e5App().getObject("Project").getProjectLanguage() in lang 351 enabled = e5App().getObject("Project").getProjectLanguage() in lang
197 self.__mainAct.setEnabled(enabled) 352 self.__mainAct.setEnabled(enabled)
198 self.__projectIsOpen = enabled 353 self.__projectIsOpen = enabled
354
355 for editor in self.__editors:
356 if editor.getLanguage() in lang and \
357 self.__projectIsOpen:
358 self.__connectEditorSignals(editor)
359 if self.getPreferences("CodeAssistEnabled"):
360 self.__setAutoCompletionHook(editor)
361 if self.getPreferences("CodeAssistCalltipsEnabled"):
362 self.__setCalltipsHook(editor)
199 363
200 def __projectClosed(self): 364 def __projectClosed(self):
201 """ 365 """
202 Private slot to handle the projectClosed signal. 366 Private slot to handle the projectClosed signal.
203 """ 367 """
204 self.__mainAct.setEnabled(False) 368 self.__mainAct.setEnabled(False)
205 self.__projectIsOpen = False 369 self.__projectIsOpen = False
370
371 for editor in self.__editors:
372 self.__disconnectEditorSignals(editor)
373 if editor.autoCompletionHook() == self.codeAssist:
374 self.__unsetAutoCompletionHook(editor)
375 if editor.callTipHook() == self.codeAssistCallTip:
376 self.__unsetCalltipsHook(editor)
377
378 def __editorOpened(self, editor):
379 """
380 Private slot called, when a new editor was opened.
381
382 @param editor reference to the new editor (QScintilla.Editor)
383 """
384 lang = self.__determineLanguage()
385
386 if editor.getLanguage() in lang and self.__projectIsOpen:
387 self.__connectEditorSignals(editor)
388 if self.getPreferences("CodeAssistEnabled"):
389 self.__setAutoCompletionHook(editor)
390 if self.getPreferences("CodeAssistCalltipsEnabled"):
391 self.__setCalltipsHook(editor)
392
393 editor.languageChanged.connect(self.__editorLanguageChanged)
394 self.__editors.append(editor)
395
396 def __editorClosed(self, editor):
397 """
398 Private slot called, when an editor was closed.
399
400 @param editor reference to the editor (QScintilla.Editor)
401 """
402 if editor in self.__editors:
403 editor.languageChanged.disconnect(self.__editorLanguageChanged)
404 self.__disconnectEditorSignals(editor)
405 self.__editors.remove(editor)
406 if editor.autoCompletionHook() == self.codeAssist:
407 self.__unsetAutoCompletionHook(editor)
408 if editor.callTipHook() == self.codeAssistCallTip:
409 self.__unsetCalltipsHook(editor)
410
411 def __editorLanguageChanged(self, language):
412 """
413 Private slot to handle the language change of an editor.
414
415 @param language programming language of the editor (string)
416 """
417 editor = self.sender()
418 lang = self.__determineLanguage()
419
420 if language in lang and self.__projectIsOpen:
421 self.__connectEditorSignals(editor)
422 if self.getPreferences("CodeAssistEnabled"):
423 self.__setAutoCompletionHook(editor)
424 if self.getPreferences("CodeAssistCalltipsEnabled"):
425 self.__setCalltipsHook(editor)
426 else:
427 self.__disconnectEditorSignals(editor)
428 if editor.autoCompletionHook() == self.codeAssist:
429 self.__unsetAutoCompletionHook(editor)
430 if editor.callTipHook() == self.codeAssistCallTip:
431 self.__unsetCalltipsHook(editor)
432
433 def __connectEditorSignals(self, editor):
434 """
435 Private method to connect to some signals of an editor.
436
437 @param editor reference to the editor (QScintilla.Editor)
438 """
439 editor.editorAboutToBeSaved.connect(self.__editorAboutToBeSaved)
440 editor.editorSaved.connect(self.__editorSaved)
441
442 def __disconnectEditorSignals(self, editor):
443 """
444 Private method to disconnect to some signals of an editor.
445
446 @param editor reference to the editor (QScintilla.Editor)
447 """
448 editor.editorAboutToBeSaved.disconnect(self.__editorAboutToBeSaved)
449 editor.editorSaved.disconnect(self.__editorSaved)
450
451 def __completionListSelected(self, id, txt):
452 """
453 Private slot to handle the selection from the completion list.
454
455 @param id the ID of the user list (should be 1) (integer)
456 @param txt the selected text (QString)
457 """
458 from QScintilla.Editor import EditorAutoCompletionListID
459
460 editor = self.sender()
461 if id == EditorAutoCompletionListID:
462 lst = txt.split()
463 if len(lst) > 1:
464 txt = lst[0]
465
466 if Preferences.getEditor("AutoCompletionReplaceWord"):
467 editor.selectCurrentWord()
468 editor.removeSelectedText()
469 line, col = editor.getCursorPosition()
470 else:
471 line, col = editor.getCursorPosition()
472 wLeft = editor.getWordLeft(line, col)
473 if not txt.startswith(wLeft):
474 editor.selectCurrentWord()
475 editor.removeSelectedText()
476 line, col = editor.getCursorPosition()
477 elif wLeft:
478 txt = txt[len(wLeft):]
479 editor.insert(txt)
480 editor.setCursorPosition(line, col + len(txt))
481
482 def __setAutoCompletionHook(self, editor):
483 """
484 Private method to set the autocompletion hook.
485
486 @param editor reference to the editor (QScintilla.Editor)
487 """
488 editor.userListActivated.connect(self.__completionListSelected)
489 editor.setAutoCompletionHook(self.codeAssist)
490
491 def __unsetAutoCompletionHook(self, editor):
492 """
493 Private method to unset the autocompletion hook.
494
495 @param editor reference to the editor (QScintilla.Editor)
496 """
497 editor.unsetAutoCompletionHook()
498 editor.userListActivated.disconnect(self.__completionListSelected)
499
500 def codeAssist(self, editor, context = False):
501 """
502 Public method to determine the autocompletion proposals.
503
504 @param editor reference to the editor object, that called this method
505 QScintilla.Editor)
506 @param context flag indicating to autocomplete a context (boolean)
507 """
508 self.__currentEditor = editor
509 if self.getPreferences("CodeAssistTimeout"):
510 self.__acTimer.stop()
511 self.__acTimer.start()
512 else:
513 self.__codeAssist()
514
515 def __codeAssist(self):
516 """
517 Private slot to show a list with completion proposals.
518 """
519 from RefactoringRope.CodeAssist import CodeAssist
520 from QScintilla.Editor import EditorAutoCompletionListID
521
522 if self.__currentEditor is not None:
523 if self.__currentEditor.isListActive():
524 self.__currentEditor.cancelList()
525 ca = CodeAssist(self.__object, self.__currentEditor, self, self)
526 completions = ca.getCompletions()
527 if len(completions) == 0 and \
528 self.getPreferences("ShowQScintillaCompletions"):
529 # try QScintilla autocompletion
530 self.__currentEditor.autoCompleteQScintilla()
531 else:
532 completions.sort()
533 self.__currentEditor.showUserList(EditorAutoCompletionListID,
534 completions)
535
536 def __editorAboutToBeSaved(self, filename):
537 """
538 Private slot to get the old contents of the named file.
539
540 @param filename name of the file about to be saved (string)
541 """
542 if filename and os.path.exists(filename):
543 try:
544 self.__oldEditorText = Utilities.readEncodedFile(filename)[0]
545 except IOError:
546 self.__oldEditorText = ""
547 self.__savedEditorName = filename
548 else:
549 self.__savedEditorName = ""
550 self.__oldEditorText = ""
551
552 def __editorSaved(self, filename):
553 """
554 Private slot to activate SOA.
555
556 @param filename name of the file that was saved (string)
557 """
558 import rope.base.libutils
559
560 if filename == self.__savedEditorName and self.__oldEditorText:
561 rope.base.libutils.report_change(self.__object.getProject(),
562 self.__savedEditorName, self.__oldEditorText)
563 elif self.__savedEditorName == "":
564 rope.base.libutils.report_change(self.__object.getProject(),
565 filename, "")
566
567 def __setCalltipsHook(self, editor):
568 """
569 Private method to set the calltip hook.
570
571 @param editor reference to the editor (QScintilla.Editor)
572 """
573 editor.setCallTipHook(self.codeAssistCallTip)
574
575 def __unsetCalltipsHook(self, editor):
576 """
577 Private method to unset the calltip hook.
578
579 @param editor reference to the editor (QScintilla.Editor)
580 """
581 editor.unsetCallTipHook()
582
583 def codeAssistCallTip(self, editor, pos, commas):
584 """
585 Public method to return a list of calltips.
586
587 @param editor reference to the editor (QScintilla.Editor)
588 @param pos position in the text for the calltip (integer)
589 @param commas minimum number of commas contained in the calltip
590 (integer)
591 @return list of possible calltips (list of strings)
592 """
593 from RefactoringRope.CodeAssist import CodeAssist
594 ca = CodeAssist(self.__object, editor, self, self)
595 cts = ca.getCallTips(pos)
596 return cts

eric ide

mercurial