AssistantEric/Assistant.py

changeset 2
89cbc07f4bf0
child 5
e1a56c9a9c9d
equal deleted inserted replaced
1:3a4123edc944 2:89cbc07f4bf0
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2010 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the eric assistant, an alternative autocompletion and
8 calltips system.
9 """
10
11 from PyQt4.QtCore import *
12 from PyQt4.Qsci import QsciScintilla
13
14 from E5Gui.E5Application import e5App
15
16 from .APIsManager import APIsManager, ApisNameProject
17
18 from QScintilla.Editor import Editor
19
20 import Preferences
21
22 AcsAPIs = 0x0001
23 AcsDocument = 0x0002
24 AcsProject = 0x0004
25 AcsOther = 0x1000
26
27 class Assistant(QObject):
28 """
29 Class implementing the autocompletion and calltips system.
30 """
31 def __init__(self, plugin, parent = None):
32 """
33 Constructor
34
35 @param plugin reference to the plugin object
36 @param parent parent (QObject)
37 """
38 QObject.__init__(self, parent)
39
40 self.__plugin = plugin
41 self.__ui = parent
42 self.__project = e5App().getObject("Project")
43 self.__viewmanager = e5App().getObject("ViewManager")
44 self.__pluginManager = e5App().getObject("PluginManager")
45
46 self.__apisManager = APIsManager(self)
47
48 self.__editors = []
49 self.__completingContext = False
50 self.__lastContext = None
51 self.__lastFullContext = None
52
53 self.__fromDocumentID = Editor.FromDocumentID
54
55 def activate(self):
56 """
57 Public method to perform actions upon activation.
58 """
59 self.connect(self.__pluginManager, SIGNAL("shutdown()"),
60 self.__shutdown)
61
62 self.connect(self.__ui, SIGNAL('preferencesChanged'),
63 self.__preferencesChanged)
64
65 self.connect(self.__viewmanager, SIGNAL("editorOpenedEd"),
66 self.__editorOpened)
67 self.connect(self.__viewmanager, SIGNAL("editorClosedEd"),
68 self.__editorClosed)
69
70 # preload the project APIs object
71 self.__apisManager.getAPIs(ApisNameProject)
72
73 for editor in self.__viewmanager.getOpenEditors():
74 self.__editorOpened(editor)
75
76 def deactivate(self):
77 """
78 Public method to perform actions upon deactivation.
79 """
80 self.disconnect(self.__pluginManager, SIGNAL("shutdown()"),
81 self.__shutdown)
82
83 self.disconnect(self.__ui, SIGNAL('preferencesChanged'),
84 self.__preferencesChanged)
85
86 self.disconnect(self.__viewmanager, SIGNAL("editorOpenedEd"),
87 self.__editorOpened)
88 self.disconnect(self.__viewmanager, SIGNAL("editorClosedEd"),
89 self.__editorClosed)
90
91 self.__shutdown()
92
93 def __shutdown(self):
94 """
95 Private slot to handle the shutdown signal.
96 """
97 for editor in self.__editors[:]:
98 self.__editorClosed(editor)
99
100 self.__apisManager.deactivate()
101
102 def setEnabled(self, key, enabled):
103 """
104 Public method to enable or disable a feature.
105
106 @param key feature to set (string)
107 @param enabled flag indicating the status (boolean)
108 """
109 for editor in self.__editors[:]:
110 self.__editorClosed(editor)
111 if enabled:
112 for editor in self.__viewmanager.getOpenEditors():
113 self.__editorOpened(editor)
114
115 def __editorOpened(self, editor):
116 """
117 Private slot called, when a new editor was opened.
118
119 @param editor reference to the new editor (QScintilla.Editor)
120 """
121 if self.__plugin.getPreferences("AutoCompletionEnabled"):
122 self.__setAutoCompletionHook(editor)
123 if self.__plugin.getPreferences("CalltipsEnabled"):
124 self.__setCalltipsHook(editor)
125 self.connect(editor, SIGNAL("editorSaved"),
126 self.__apisManager.getAPIs(ApisNameProject).editorSaved)
127 self.__editors.append(editor)
128
129 # preload the api to give the manager a chance to prepare the database
130 language = editor.getLanguage()
131 if language == "":
132 return
133 self.__apisManager.getAPIs(language)
134
135 def __editorClosed(self, editor):
136 """
137 Private slot called, when an editor was closed.
138
139 @param editor reference to the editor (QScintilla.Editor)
140 """
141 if editor in self.__editors:
142 self.disconnect(editor, SIGNAL("editorSaved"),
143 self.__apisManager.getAPIs(ApisNameProject).editorSaved)
144 self.__editors.remove(editor)
145 if editor.autoCompletionHook() == self.autocomplete:
146 self.__unsetAutoCompletionHook(editor)
147 if editor.callTipHook() == self.calltips:
148 self.__unsetCalltipsHook(editor)
149
150 def __preferencesChanged(self):
151 """
152 Private method to handle a change of the global configuration.
153 """
154 self.__apisManager.reloadAPIs()
155
156 def __getCharacter(self, pos, editor):
157 """
158 Private method to get the character to the left of the current position
159 in the current line.
160
161 @param pos position to get character at (integer)
162 @param editor reference to the editor object to work with (QScintilla.Editor)
163 @return requested character or "", if there are no more (string) and
164 the next position (i.e. pos - 1)
165 """
166 if pos <= 0:
167 return "", pos
168
169 pos -= 1
170 ch = editor.charAt(pos)
171
172 # Don't go past the end of the previous line
173 if ch == '\n' or ch == '\r':
174 return "", pos
175
176 return ch, pos
177
178 #################################
179 ## autocompletion methods below
180 #################################
181
182 def __completionListSelected(self, id, txt):
183 """
184 Private slot to handle the selection from the completion list.
185
186 @param id the ID of the user list (should be 1) (integer)
187 @param txt the selected text (string)
188 """
189 from QScintilla.Editor import EditorAutoCompletionListID
190
191 editor = self.sender()
192 if id == EditorAutoCompletionListID:
193 lst = txt.split()
194 if len(lst) > 1:
195 txt = lst[0]
196 self.__lastFullContext = lst[1][1:].split(")")[0]
197 else:
198 self.__lastFullContext = None
199
200 if Preferences.getEditor("AutoCompletionReplaceWord"):
201 editor.selectCurrentWord()
202 editor.removeSelectedText()
203 line, col = editor.getCursorPosition()
204 else:
205 line, col = editor.getCursorPosition()
206 wLeft = editor.getWordLeft(line, col)
207 if not txt.startswith(wLeft):
208 editor.selectCurrentWord()
209 editor.removeSelectedText()
210 line, col = editor.getCursorPosition()
211 elif wLeft:
212 txt = txt[len(wLeft):]
213 editor.insert(txt)
214 editor.setCursorPosition(line, col + len(txt))
215
216 def __setAutoCompletionHook(self, editor):
217 """
218 Private method to set the autocompletion hook.
219
220 @param editor reference to the editor (QScintilla.Editor)
221 """
222 self.connect(editor, SIGNAL('userListActivated(int, const QString)'),
223 self.__completionListSelected)
224 editor.setAutoCompletionHook(self.autocomplete)
225
226 def __unsetAutoCompletionHook(self, editor):
227 """
228 Private method to unset the autocompletion hook.
229
230 @param editor reference to the editor (QScintilla.Editor)
231 """
232 editor.unsetAutoCompletionHook()
233 self.disconnect(editor, SIGNAL('userListActivated(int, const QString)'),
234 self.__completionListSelected)
235
236 def autocomplete(self, editor, context):
237 """
238 Public method to determine the autocompletion proposals.
239
240 @param editor reference to the editor object, that called this method
241 (QScintilla.Editor)
242 @param context flag indicating to autocomplete a context (boolean)
243 """
244 from QScintilla.Editor import EditorAutoCompletionListID
245
246 if editor.isListActive():
247 editor.cancelList()
248
249 language = editor.getLanguage()
250 if language == "":
251 return
252
253 line, col = editor.getCursorPosition()
254 self.__completingContext = context
255 apiCompletionsList = []
256 docCompletionsList = []
257 projectCompletionList = []
258 sep = ""
259 if context:
260 wc = editor.wordCharacters()
261 text = editor.text(line)
262
263 beg = text[:col]
264 for wsep in editor.getLexer().autoCompletionWordSeparators():
265 if beg.endswith(wsep):
266 sep = wsep
267 break
268
269 depth = 0
270 while col > 0 and text[col - 1] not in wc:
271 ch = text[col - 1]
272 if ch == ')':
273 depth = 1
274
275 # ignore everything back to the start of the
276 # corresponding parenthesis
277 col -= 1
278 while col > 0:
279 ch = text[col - 1]
280 if ch == ')':
281 depth += 1
282 elif ch == '(':
283 depth -= 1
284 if depth == 0:
285 break
286 col -= 1
287 elif ch == '(':
288 break
289 col -= 1
290
291 word = editor.getWordLeft(line, col)
292 if context:
293 self.__lastContext = word
294 else:
295 self.__lastContext = None
296
297 if word:
298 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
299 api = self.__apisManager.getAPIs(language)
300 apiCompletionsList = self.__getApiCompletions(api, word, context)
301
302 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
303 api = self.__apisManager.getAPIs(ApisNameProject)
304 projectCompletionList = self.__getApiCompletions(api, word, context)
305
306 if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument:
307 docCompletionsList = \
308 self.getCompletionsFromDocument(editor, word, context, sep)
309
310 completionsList = list(
311 set(apiCompletionsList)
312 .union(set(docCompletionsList))
313 .union(set(projectCompletionList))
314 )
315
316 if len(completionsList) > 0:
317 completionsList.sort()
318 editor.showUserList(EditorAutoCompletionListID, completionsList)
319
320 def __getApiCompletions(self, api, word, context):
321 """
322 Private method to determine a list of completions from an API object.
323
324 @param api reference to the API object to be used (APIsManager.DbAPIs)
325 @param word word (or wordpart) to complete (string)
326 @param context flag indicating to autocomplete a context (boolean)
327 @return list of possible completions (list of strings)
328 """
329 completionsList = []
330 if api is not None:
331 if context:
332 completions = api.getCompletions(context = word)
333 for completion in completions:
334 entry = completion["completion"]
335 if completion["pictureId"]:
336 entry += "?{0}".format(completion["pictureId"])
337 if entry not in completionsList:
338 completionsList.append(entry)
339 else:
340 completions = api.getCompletions(start = word)
341 for completion in completions:
342 if not completion["context"]:
343 entry = completion["completion"]
344 else:
345 entry = "{0} ({1})".format(
346 completion["completion"],
347 completion["context"]
348 )
349 if entry in completionsList:
350 completionsList.remove(entry)
351 if completion["pictureId"]:
352 entry += "?{0}".format(completion["pictureId"])
353 else:
354 cont = False
355 re = QRegExp(QRegExp.escape(entry) + "\?\d{,2}")
356 for comp in completionsList:
357 if re.exactMatch(comp):
358 cont = True
359 break
360 if cont:
361 continue
362 if entry not in completionsList:
363 completionsList.append(entry)
364 return completionsList
365
366 def getCompletionsFromDocument(self, editor, word, context, sep):
367 """
368 Public method to determine autocompletion proposals from the document.
369
370 @param editor reference to the editor object (QScintilla.Editor)
371 @param word string to be completed (string)
372 @param context flag indicating to autocomplete a context (boolean)
373 @param sep separator string (string)
374 @return list of possible completions (list of strings)
375 """
376 currentPos = editor.currentPosition()
377 completionsList = []
378 if context:
379 word += sep
380
381 res = editor.findFirstTarget(word, False, editor.autoCompletionCaseSensitivity(),
382 False, begline = 0, begindex = 0, ws_ = True)
383 while res:
384 start, length = editor.getFoundTarget()
385 pos = start + length
386 if pos != currentPos:
387 if context:
388 completion = ""
389 else:
390 completion = word
391 ch = editor.charAt(pos)
392 while editor.isWordCharacter(ch):
393 completion += ch
394 pos += 1
395 ch = editor.charAt(pos)
396 if completion and completion not in completionsList:
397 completionsList.append(
398 "{0}?{1}".format(completion, self.__fromDocumentID))
399
400 res = editor.findNextTarget()
401
402 completionsList.sort()
403 return completionsList
404
405 ###########################
406 ## calltips methods below
407 ###########################
408
409 def __setCalltipsHook(self, editor):
410 """
411 Private method to set the calltip hook.
412
413 @param editor reference to the editor (QScintilla.Editor)
414 """
415 editor.setCallTipHook(self.calltips)
416
417 def __unsetCalltipsHook(self, editor):
418 """
419 Private method to unset the calltip hook.
420
421 @param editor reference to the editor (QScintilla.Editor)
422 """
423 editor.unsetCallTipHook()
424
425 def calltips(self, editor, pos, commas):
426 """
427 Public method to return a list of calltips.
428
429 @param editor reference to the editor (QScintilla.Editor)
430 @param pos position in the text for the calltip (integer)
431 @param commas minimum number of commas contained in the calltip (integer)
432 @return list of possible calltips (list of strings)
433 """
434 language = editor.getLanguage()
435 if language == "":
436 return
437
438 line, col = editor.lineIndexFromPosition(pos)
439 wc = editor.wordCharacters()
440 text = editor.text(line)
441 while col > 0 and text[col - 1] not in wc:
442 col -= 1
443 word = editor.getWordLeft(line, col)
444
445 apiCalltips = []
446 projectCalltips = []
447
448 if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs:
449 api = self.__apisManager.getAPIs(language)
450 if api is not None:
451 apiCalltips = api.getCalltips(word, commas, self.__lastContext,
452 self.__lastFullContext,
453 self.__plugin.getPreferences("CallTipsContextShown"))
454
455 if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject:
456 api = self.__apisManager.getAPIs(ApisNameProject)
457 projectCalltips = api.getCalltips(word, commas, self.__lastContext,
458 self.__lastFullContext,
459 self.__plugin.getPreferences("CallTipsContextShown"))
460
461 return sorted(set(apiCalltips).union(set(projectCalltips)))

eric ide

mercurial