|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the APIsManager. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt4.QtCore import * |
|
13 from PyQt4.QtSql import QSqlDatabase, QSqlQuery |
|
14 |
|
15 from E5Gui.E5Application import e5App |
|
16 |
|
17 import QScintilla.Lexers |
|
18 |
|
19 from DocumentationTools.APIGenerator import APIGenerator |
|
20 import Utilities.ModuleParser |
|
21 import Utilities |
|
22 import Preferences |
|
23 |
|
24 WorkerStarted = QEvent.User + 2001 |
|
25 WorkerFinished = QEvent.User + 2002 |
|
26 WorkerAborted = QEvent.User + 2003 |
|
27 |
|
28 ApisNameProject = "__Project__" |
|
29 |
|
30 class DbAPIsWorker(QThread): |
|
31 """ |
|
32 Class implementing a worker thread to prepare the API database. |
|
33 """ |
|
34 populate_api_stmt = """ |
|
35 INSERT INTO api (acWord, context, fullContext, signature, fileId, pictureId) |
|
36 VALUES (:acWord, :context, :fullContext, :signature, :fileId, :pictureId) |
|
37 """ |
|
38 populate_del_api_stmt = """ |
|
39 DELETE FROM api WHERE fileId = :fileId |
|
40 """ |
|
41 populate_file_stmt = """ |
|
42 INSERT INTO file (file) VALUES (:file) |
|
43 """ |
|
44 update_file_stmt = """ |
|
45 UPDATE file SET lastRead = :lastRead WHERE file = :file |
|
46 """ |
|
47 |
|
48 file_loaded_stmt = """ |
|
49 SELECT lastRead from file WHERE file = :file |
|
50 """ |
|
51 file_id_stmt = """ |
|
52 SELECT id FROM file WHERE file = :file |
|
53 """ |
|
54 file_delete_id_stmt = """ |
|
55 DELETE FROM file WHERE id = :id |
|
56 """ |
|
57 |
|
58 def __init__(self, proxy, language, apiFiles, projectPath = ""): |
|
59 """ |
|
60 Constructor |
|
61 |
|
62 @param proxy reference to the object that is proxied (DbAPIs) |
|
63 @param language language of the APIs object (string) |
|
64 @param apiFiles list of API files to process (list of strings) |
|
65 @param projectPath path of the project. Only needed, if the APIs |
|
66 are extracted out of the sources of a project. (string) |
|
67 """ |
|
68 QThread.__init__(self) |
|
69 |
|
70 self.setTerminationEnabled(True) |
|
71 |
|
72 # Get the AC word separators for all of the languages that the editor supports. |
|
73 # This has to be before we create a new thread, because access to GUI elements |
|
74 # is not allowed from non-gui threads. |
|
75 self.__wseps = {} |
|
76 for lang in QScintilla.Lexers.getSupportedLanguages(): |
|
77 lexer = QScintilla.Lexers.getLexer(lang) |
|
78 if lexer is not None: |
|
79 self.__wseps[lang] = lexer.autoCompletionWordSeparators() |
|
80 |
|
81 self.__proxy = proxy |
|
82 self.__language = language |
|
83 self.__apiFiles = apiFiles[:] |
|
84 self.__aborted = False |
|
85 self.__projectPath = projectPath |
|
86 |
|
87 def __autoCompletionWordSeparators(self, language): |
|
88 """ |
|
89 Private method to get the word separator characters for a language. |
|
90 |
|
91 @param language language of the APIs object (string) |
|
92 @return word separator characters (list of strings) |
|
93 """ |
|
94 return self.__wseps.get(language, None) |
|
95 |
|
96 def abort(self): |
|
97 """ |
|
98 Public method to ask the thread to stop. |
|
99 """ |
|
100 self.__aborted = True |
|
101 |
|
102 def __loadApiFileIfNewer(self, apiFile): |
|
103 """ |
|
104 Private method to load an API file, if it is newer than the one read |
|
105 into the database. |
|
106 |
|
107 @param apiFile filename of the raw API file (string) |
|
108 """ |
|
109 db = QSqlDatabase.database(self.__language) |
|
110 db.transaction() |
|
111 try: |
|
112 query = QSqlQuery(db) |
|
113 query.prepare(self.file_loaded_stmt) |
|
114 query.bindValue(":file", apiFile) |
|
115 query.exec_() |
|
116 if query.next() and query.isValid(): |
|
117 loadTime = QDateTime.fromString(query.value(0), Qt.ISODate) |
|
118 else: |
|
119 loadTime = QDateTime(1970, 1, 1, 0, 0) |
|
120 del query |
|
121 finally: |
|
122 db.commit() |
|
123 if self.__projectPath: |
|
124 modTime = QFileInfo(os.path.join(self.__projectPath, apiFile)).lastModified() |
|
125 else: |
|
126 modTime = QFileInfo(apiFile).lastModified() |
|
127 if loadTime < modTime: |
|
128 self.__loadApiFile(apiFile) |
|
129 |
|
130 def __loadApiFile(self, apiFile): |
|
131 """ |
|
132 Private method to read a raw API file into the database. |
|
133 |
|
134 @param apiFile filename of the raw API file (string) |
|
135 """ |
|
136 if self.__language == ApisNameProject: |
|
137 try: |
|
138 module = Utilities.ModuleParser.readModule( |
|
139 os.path.join(self.__projectPath, apiFile), |
|
140 basename = self.__projectPath + os.sep, |
|
141 caching = False) |
|
142 language = module.getType() |
|
143 if language: |
|
144 apiGenerator = APIGenerator(module) |
|
145 apis = apiGenerator.genAPI(True, "", True) |
|
146 else: |
|
147 apis = [] |
|
148 except (IOError, ImportError): |
|
149 apis = [] |
|
150 else: |
|
151 try: |
|
152 apis = Utilities.readEncodedFile(apiFile)[0].splitlines(True) |
|
153 except (IOError, UnicodeError): |
|
154 apis = [] |
|
155 language = None |
|
156 |
|
157 if len(apis) > 0: |
|
158 self.__storeApis(apis, apiFile, language) |
|
159 |
|
160 def __storeApis(self, apis, apiFile, language): |
|
161 """ |
|
162 Private method to put the API entries into the database. |
|
163 |
|
164 @param apis list of api entries (list of strings) |
|
165 @param apiFile filename of the file read to get the APIs (string) |
|
166 @param language programming language of the file of the APIs (string) |
|
167 """ |
|
168 if language: |
|
169 wseps = self.__autoCompletionWordSeparators(language) |
|
170 else: |
|
171 wseps = self.__proxy.autoCompletionWordSeparators() |
|
172 if wseps is None: |
|
173 return |
|
174 |
|
175 db = QSqlDatabase.database(self.__language) |
|
176 db.transaction() |
|
177 try: |
|
178 query = QSqlQuery(db) |
|
179 # step 1: create entry in file table and get the ID |
|
180 query.prepare(self.populate_file_stmt) |
|
181 query.bindValue(":file", apiFile) |
|
182 query.exec_() |
|
183 query.prepare(self.file_id_stmt) |
|
184 query.bindValue(":file", apiFile) |
|
185 query.exec_() |
|
186 query.next() |
|
187 id = int(query.value(0)) |
|
188 |
|
189 # step 2: delete all entries belonging to this file |
|
190 query.prepare(self.populate_del_api_stmt) |
|
191 query.bindValue(":fileId", id) |
|
192 query.exec_() |
|
193 |
|
194 # step 3: load the given api file |
|
195 query.prepare(self.populate_api_stmt) |
|
196 for api in apis: |
|
197 if self.__aborted: |
|
198 break |
|
199 |
|
200 api = api.strip() |
|
201 if len(api) == 0: |
|
202 continue |
|
203 |
|
204 b = api.find('(') |
|
205 if b == -1: |
|
206 path = api |
|
207 sig = "" |
|
208 else: |
|
209 path = api[:b] |
|
210 sig = api[b:] |
|
211 |
|
212 while len(path) > 0: |
|
213 acWord = "" |
|
214 context = "" |
|
215 fullContext = "" |
|
216 pictureId = "" |
|
217 |
|
218 # search for word separators |
|
219 index = len(path) |
|
220 while index > 0: |
|
221 index -= 1 |
|
222 found = False |
|
223 for wsep in wseps: |
|
224 if path[:index].endswith(wsep): |
|
225 found = True |
|
226 break |
|
227 if found: |
|
228 if acWord == "": |
|
229 # completion found |
|
230 acWord = path[index:] |
|
231 path = path[:(index - len(wsep))] |
|
232 index = len(path) |
|
233 fullContext = path |
|
234 context = path |
|
235 try: |
|
236 acWord, pictureId = acWord.split("?", 1) |
|
237 except ValueError: |
|
238 pass |
|
239 else: |
|
240 context = path[index:] |
|
241 break |
|
242 # none found? |
|
243 if acWord == "": |
|
244 acWord = path |
|
245 path = "" |
|
246 |
|
247 query.bindValue(":acWord", acWord) |
|
248 query.bindValue(":context", context) |
|
249 query.bindValue(":fullContext", fullContext) |
|
250 query.bindValue(":signature", sig) |
|
251 query.bindValue(":fileId", id) |
|
252 query.bindValue(":pictureId", pictureId) |
|
253 query.exec_() |
|
254 |
|
255 sig = "" |
|
256 |
|
257 if not self.__aborted: |
|
258 # step 4: update the file entry |
|
259 query.prepare(self.update_file_stmt) |
|
260 query.bindValue(":lastRead", QDateTime.currentDateTime()) |
|
261 query.bindValue(":file", apiFile) |
|
262 query.exec_() |
|
263 finally: |
|
264 del query |
|
265 if self.__aborted: |
|
266 db.rollback() |
|
267 else: |
|
268 db.commit() |
|
269 |
|
270 def __deleteApiFile(self, apiFile): |
|
271 """ |
|
272 Private method to delete all references to an api file. |
|
273 |
|
274 @param apiFile filename of the raw API file (string) |
|
275 """ |
|
276 db = QSqlDatabase.database(self.__language) |
|
277 db.transaction() |
|
278 try: |
|
279 query = QSqlQuery(db) |
|
280 |
|
281 # step 1: get the ID belonging to the api file |
|
282 query.prepare(self.file_id_stmt) |
|
283 query.bindValue(":file", apiFile) |
|
284 query.exec_() |
|
285 query.next() |
|
286 id = int(query.value(0)) |
|
287 |
|
288 # step 2: delete all api entries belonging to this file |
|
289 query.prepare(self.populate_del_api_stmt) |
|
290 query.bindValue(":fileId", id) |
|
291 query.exec_() |
|
292 |
|
293 # step 3: delete the file entry |
|
294 query.prepare(self.file_delete_id_stmt) |
|
295 query.bindValue(":id", id) |
|
296 query.exec_() |
|
297 finally: |
|
298 del query |
|
299 db.commit() |
|
300 |
|
301 def run(self): |
|
302 """ |
|
303 Public method to perform the threads work. |
|
304 """ |
|
305 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerStarted))) |
|
306 |
|
307 db = QSqlDatabase.database(self.__language) |
|
308 if db.isValid() and db.isOpen(): |
|
309 # step 1: remove API files not wanted any longer |
|
310 loadedApiFiles = self.__proxy.getApiFiles() |
|
311 for apiFile in loadedApiFiles: |
|
312 if not self.__aborted and apiFile not in self.__apiFiles: |
|
313 self.__deleteApiFile(apiFile) |
|
314 |
|
315 # step 2: (re-)load api files |
|
316 for apiFile in self.__apiFiles: |
|
317 if not self.__aborted: |
|
318 self.__loadApiFileIfNewer(apiFile) |
|
319 |
|
320 if self.__aborted: |
|
321 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerAborted))) |
|
322 else: |
|
323 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerFinished))) |
|
324 |
|
325 class DbAPIs(QObject): |
|
326 """ |
|
327 Class implementing an API storage entity. |
|
328 |
|
329 @signal apiPreparationFinished() emitted after the API preparation has finished |
|
330 @signal apiPreparationStarted() emitted after the API preparation has started |
|
331 @signal apiPreparationCancelled() emitted after the API preparation has been |
|
332 cancelled |
|
333 """ |
|
334 DB_VERSION = 3 |
|
335 |
|
336 create_mgmt_stmt = """ |
|
337 CREATE TABLE mgmt |
|
338 (format INTEGER) |
|
339 """ |
|
340 drop_mgmt_stmt = """DROP TABLE IF EXISTS mgmt""" |
|
341 |
|
342 create_api_stmt = """ |
|
343 CREATE TABLE api |
|
344 (acWord TEXT, |
|
345 context TEXT, |
|
346 fullContext TEXT, |
|
347 signature TEXT, |
|
348 fileId INTEGER, |
|
349 pictureId INTEGER, |
|
350 UNIQUE(acWord, fullContext, signature) ON CONFLICT IGNORE |
|
351 ) |
|
352 """ |
|
353 drop_api_stmt = """DROP TABLE IF EXISTS api""" |
|
354 |
|
355 create_file_stmt = """ |
|
356 CREATE TABLE file |
|
357 (id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
358 file TEXT UNIQUE ON CONFLICT IGNORE, |
|
359 lastRead TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|
360 ) |
|
361 """ |
|
362 drop_file_stmt = """DROP TABLE IF EXISTS file""" |
|
363 |
|
364 create_acWord_idx = """CREATE INDEX acWord_idx on api (acWord)""" |
|
365 drop_acWord_idx = """DROP INDEX IF EXISTS acWord_idx""" |
|
366 |
|
367 create_context_idx = """CREATE INDEX context_idx on api (context)""" |
|
368 drop_context_idx = """DROP INDEX IF EXISTS context_idx""" |
|
369 |
|
370 create_fullContext_idx = """CREATE INDEX fullContext_idx on api (fullContext)""" |
|
371 drop_fullContext_idx = """DROP INDEX IF EXISTS fullContext_idx""" |
|
372 |
|
373 create_file_idx = """CREATE INDEX file_idx on file (file)""" |
|
374 drop_file_idx = """DROP INDEX IF EXISTS file_idx""" |
|
375 |
|
376 api_files_stmt = """ |
|
377 SELECT file FROM file WHERE file LIKE '%.api' |
|
378 """ |
|
379 |
|
380 ac_stmt = """ |
|
381 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
|
382 WHERE acWord GLOB :acWord |
|
383 ORDER BY acWord |
|
384 """ |
|
385 ac_context_stmt = """ |
|
386 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
|
387 WHERE context = :context |
|
388 ORDER BY acWord |
|
389 """ |
|
390 ct_stmt = """ |
|
391 SELECT DISTINCT acWord, signature, fullContext FROM api |
|
392 WHERE acWord = :acWord |
|
393 """ |
|
394 ct_context_stmt = """ |
|
395 SELECT DISTINCT acWord, signature, fullContext FROM api |
|
396 WHERE acWord = :acWord |
|
397 AND context = :context |
|
398 """ |
|
399 ct_fullContext_stmt = """ |
|
400 SELECT DISTINCT acWord, signature, fullContext FROM api |
|
401 WHERE acWord = :acWord |
|
402 AND fullContext = :fullContext |
|
403 """ |
|
404 format_select_stmt = """ |
|
405 SELECT format FROM mgmt |
|
406 """ |
|
407 mgmt_insert_stmt = """ |
|
408 INSERT INTO mgmt (format) VALUES (%d) |
|
409 """ % DB_VERSION |
|
410 |
|
411 def __init__(self, language, parent = None): |
|
412 """ |
|
413 Constructor |
|
414 |
|
415 @param language language of the APIs object (string) |
|
416 @param parent reference to the parent object (QObject) |
|
417 """ |
|
418 QObject.__init__(self, parent) |
|
419 self.setObjectName("DbAPIs_%s" % language) |
|
420 |
|
421 self.__inPreparation = False |
|
422 self.__worker = None |
|
423 self.__workerQueue = [] |
|
424 |
|
425 self.__language = language |
|
426 if self.__language == ApisNameProject: |
|
427 self.__initAsProject() |
|
428 else: |
|
429 self.__initAsLanguage() |
|
430 |
|
431 def __initAsProject(self): |
|
432 """ |
|
433 Private method to initialize as a project API object. |
|
434 """ |
|
435 self.__lexer = None |
|
436 |
|
437 self.__project = e5App().getObject("Project") |
|
438 self.connect(self.__project, SIGNAL("projectOpened"), self.__projectOpened) |
|
439 self.connect(self.__project, SIGNAL("newProject"), self.__projectOpened) |
|
440 self.connect(self.__project, SIGNAL("projectClosed"), self.__projectClosed) |
|
441 if self.__project.isOpen(): |
|
442 self.__projectOpened() |
|
443 |
|
444 def __initAsLanguage(self): |
|
445 """ |
|
446 Private method to initialize as a language API object. |
|
447 """ |
|
448 if self.__language in ["Python", "Python3"]: |
|
449 self.__discardFirst = "self" |
|
450 else: |
|
451 self.__discardFirst = "" |
|
452 self.__lexer = QScintilla.Lexers.getLexer(self.__language) |
|
453 self.__apifiles = Preferences.getEditorAPI(self.__language) |
|
454 self.__apifiles.sort() |
|
455 if self.__lexer is not None: |
|
456 self.__openAPIs() |
|
457 |
|
458 def _apiDbName(self): |
|
459 """ |
|
460 Protected method to determine the name of the database file. |
|
461 |
|
462 @return name of the database file (string) |
|
463 """ |
|
464 if self.__language == ApisNameProject: |
|
465 return os.path.join(self.__project.getProjectManagementDir(), |
|
466 "project-apis.db") |
|
467 else: |
|
468 return os.path.join(Utilities.getConfigDir(), "%s-api.db" % self.__language) |
|
469 |
|
470 def close(self): |
|
471 """ |
|
472 Public method to close the database. |
|
473 """ |
|
474 self.__workerQueue = [] |
|
475 if self.__worker is not None: |
|
476 self.__worker.abort() |
|
477 if self.__worker is not None: |
|
478 self.__worker.wait(5000) |
|
479 if self.__worker is not None and \ |
|
480 not self.__worker.isFinished(): |
|
481 self.__worker.terminate() |
|
482 if self.__worker is not None: |
|
483 self.__worker.wait(5000) |
|
484 |
|
485 QSqlDatabase.database(self.__language).close() |
|
486 QSqlDatabase.removeDatabase(self.__language) |
|
487 |
|
488 def __openApiDb(self): |
|
489 """ |
|
490 Private method to open the API database. |
|
491 """ |
|
492 db = QSqlDatabase.database(self.__language, False) |
|
493 if not db.isValid(): |
|
494 # the database connection is a new one |
|
495 db = QSqlDatabase.addDatabase("QSQLITE", self.__language) |
|
496 db.setDatabaseName(self._apiDbName()) |
|
497 db.open() |
|
498 |
|
499 def __createApiDB(self): |
|
500 """ |
|
501 Private method to create an API database. |
|
502 """ |
|
503 db = QSqlDatabase.database(self.__language) |
|
504 db.transaction() |
|
505 try: |
|
506 query = QSqlQuery(db) |
|
507 # step 1: drop old tables |
|
508 query.exec_(self.drop_mgmt_stmt) |
|
509 query.exec_(self.drop_api_stmt) |
|
510 query.exec_(self.drop_file_stmt) |
|
511 # step 2: drop old indices |
|
512 query.exec_(self.drop_acWord_idx) |
|
513 query.exec_(self.drop_context_idx) |
|
514 query.exec_(self.drop_fullContext_idx) |
|
515 query.exec_(self.drop_file_idx) |
|
516 # step 3: create tables |
|
517 query.exec_(self.create_api_stmt) |
|
518 query.exec_(self.create_file_stmt) |
|
519 query.exec_(self.create_mgmt_stmt) |
|
520 query.exec_(self.mgmt_insert_stmt) |
|
521 # step 4: create indices |
|
522 query.exec_(self.create_acWord_idx) |
|
523 query.exec_(self.create_context_idx) |
|
524 query.exec_(self.create_fullContext_idx) |
|
525 query.exec_(self.create_file_idx) |
|
526 finally: |
|
527 del query |
|
528 db.commit() |
|
529 |
|
530 def getApiFiles(self): |
|
531 """ |
|
532 Public method to get a list of API files loaded into the database. |
|
533 |
|
534 @return list of API filenames (list of strings) |
|
535 """ |
|
536 apiFiles = [] |
|
537 |
|
538 db = QSqlDatabase.database(self.__language) |
|
539 db.transaction() |
|
540 try: |
|
541 query = QSqlQuery(db) |
|
542 query.exec_(self.api_files_stmt) |
|
543 while query.next(): |
|
544 apiFiles.append(query.value(0)) |
|
545 finally: |
|
546 del query |
|
547 db.commit() |
|
548 |
|
549 return apiFiles |
|
550 |
|
551 def __isPrepared(self): |
|
552 """ |
|
553 Private method to check, if the database has been prepared. |
|
554 |
|
555 @return flag indicating the prepared status (boolean) |
|
556 """ |
|
557 db = QSqlDatabase.database(self.__language) |
|
558 prepared = len(db.tables()) > 0 |
|
559 if prepared: |
|
560 db.transaction() |
|
561 prepared = False |
|
562 try: |
|
563 query = QSqlQuery(db) |
|
564 ok = query.exec_(self.format_select_stmt) |
|
565 if ok: |
|
566 query.next() |
|
567 format = int(query.value(0)) |
|
568 if format >= self.DB_VERSION: |
|
569 prepared = True |
|
570 finally: |
|
571 del query |
|
572 db.commit() |
|
573 return prepared |
|
574 |
|
575 def getCompletions(self, start = None, context = None): |
|
576 """ |
|
577 Public method to determine the possible completions. |
|
578 |
|
579 @keyparam start string giving the start of the word to be |
|
580 completed (string) |
|
581 @keyparam context string giving the context (e.g. classname) |
|
582 to be completed (string) |
|
583 @return list of dictionaries with possible completions (key 'completion' |
|
584 contains the completion (string), key 'context' |
|
585 contains the context (string) and key 'pictureId' |
|
586 contains the ID of the icon to be shown (string)) |
|
587 """ |
|
588 completions = [] |
|
589 |
|
590 db = QSqlDatabase.database(self.__language) |
|
591 if db.isOpen() and not self.__inPreparation: |
|
592 db.transaction() |
|
593 try: |
|
594 query = None |
|
595 |
|
596 if start is not None: |
|
597 query = QSqlQuery(db) |
|
598 query.prepare(self.ac_stmt) |
|
599 query.bindValue(":acWord", start + '*') |
|
600 elif context is not None: |
|
601 query = QSqlQuery(db) |
|
602 query.prepare(self.ac_context_stmt) |
|
603 query.bindValue(":context", context) |
|
604 |
|
605 if query is not None: |
|
606 query.exec_() |
|
607 while query.next(): |
|
608 completions.append({"completion" : query.value(0), |
|
609 "context" : query.value(1), |
|
610 "pictureId" : query.value(2)}) |
|
611 del query |
|
612 finally: |
|
613 db.commit() |
|
614 |
|
615 return completions |
|
616 |
|
617 def getCalltips(self, acWord, commas, context = None, fullContext = None, |
|
618 showContext = True): |
|
619 """ |
|
620 Public method to determine the calltips. |
|
621 |
|
622 @param acWord function to get calltips for (string) |
|
623 @param commas minimum number of commas contained in the calltip (integer) |
|
624 @param context string giving the context (e.g. classname) (string) |
|
625 @param fullContext string giving the full context (string) |
|
626 @param showContext flag indicating to show the calltip context (boolean) |
|
627 @return list of calltips (list of string) |
|
628 """ |
|
629 calltips = [] |
|
630 |
|
631 db = QSqlDatabase.database(self.__language) |
|
632 if db.isOpen() and not self.__inPreparation: |
|
633 if self.autoCompletionWordSeparators(): |
|
634 contextSeparator = self.autoCompletionWordSeparators()[0] |
|
635 else: |
|
636 contextSeparator = " " |
|
637 db.transaction() |
|
638 try: |
|
639 query = QSqlQuery(db) |
|
640 if fullContext: |
|
641 query.prepare(self.ct_fullContext_stmt) |
|
642 query.bindValue(":fullContext", fullContext) |
|
643 elif context: |
|
644 query.prepare(self.ct_context_stmt) |
|
645 query.bindValue(":context", context) |
|
646 else: |
|
647 query.prepare(self.ct_stmt) |
|
648 query.bindValue(":acWord", acWord) |
|
649 query.exec_() |
|
650 while query.next(): |
|
651 word = query.value(0) |
|
652 sig = query.value(1) |
|
653 fullCtx = query.value(2) |
|
654 if sig: |
|
655 if self.__discardFirst: |
|
656 sig = "(%s" % sig[1:]\ |
|
657 .replace(self.__discardFirst, "", 1)\ |
|
658 .strip(", \t\r\n") |
|
659 if self.__enoughCommas(sig, commas): |
|
660 if showContext: |
|
661 calltips.append("%s%s%s%s" % \ |
|
662 (fullCtx, contextSeparator, word, sig)) |
|
663 else: |
|
664 calltips.append("%s%s" % (word, sig)) |
|
665 del query |
|
666 finally: |
|
667 db.commit() |
|
668 |
|
669 if context and len(calltips) == 0: |
|
670 # nothing found, try without a context |
|
671 calltips = self.getCalltips(acWord, commas, showContext = showContext) |
|
672 |
|
673 return calltips |
|
674 |
|
675 def __enoughCommas(self, s, commas): |
|
676 """ |
|
677 Private method to determine, if the given string contains enough commas. |
|
678 |
|
679 @param s string to check (string) |
|
680 @param commas number of commas to check for (integer) |
|
681 @return flag indicating, that there are enough commas (boolean) |
|
682 """ |
|
683 end = s.find(')') |
|
684 |
|
685 if end < 0: |
|
686 return False |
|
687 |
|
688 w = s[:end] |
|
689 return w.count(',') >= commas |
|
690 |
|
691 def __openAPIs(self): |
|
692 """ |
|
693 Private method to open the API database. |
|
694 """ |
|
695 self.__openApiDb() |
|
696 if not self.__isPrepared(): |
|
697 self.__createApiDB() |
|
698 |
|
699 # prepare the database if neccessary |
|
700 self.prepareAPIs() |
|
701 |
|
702 def prepareAPIs(self, rawList = None): |
|
703 """ |
|
704 Public method to prepare the APIs if neccessary. |
|
705 |
|
706 @keyparam rawList list of raw API files (list of strings) |
|
707 """ |
|
708 if self.__inPreparation: |
|
709 return |
|
710 |
|
711 projectPath = "" |
|
712 if rawList: |
|
713 apiFiles = rawList[:] |
|
714 elif self.__language == ApisNameProject: |
|
715 apiFiles = self.__project.getSources() |
|
716 projectPath = self.__project.getProjectPath() |
|
717 else: |
|
718 apiFiles = Preferences.getEditorAPI(self.__language) |
|
719 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath) |
|
720 self.__worker.start() |
|
721 |
|
722 def __processQueue(self): |
|
723 """ |
|
724 Private slot to process the queue of files to load. |
|
725 """ |
|
726 if self.__worker is not None and self.__worker.isFinished(): |
|
727 self.__worker = None |
|
728 |
|
729 if self.__worker is None and len(self.__workerQueue) > 0: |
|
730 apiFiles = [self.__workerQueue.pop(0)] |
|
731 if self.__language == ApisNameProject: |
|
732 projectPath = self.__project.getProjectPath() |
|
733 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] |
|
734 else: |
|
735 projectPath = "" |
|
736 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath) |
|
737 self.__worker.start() |
|
738 |
|
739 def getLexer(self): |
|
740 """ |
|
741 Public method to return a reference to our lexer object. |
|
742 |
|
743 @return reference to the lexer object (QScintilla.Lexers.Lexer) |
|
744 """ |
|
745 return self.__lexer |
|
746 |
|
747 def autoCompletionWordSeparators(self): |
|
748 """ |
|
749 Private method to get the word separator characters. |
|
750 |
|
751 @return word separator characters (list of strings) |
|
752 """ |
|
753 if self.__lexer: |
|
754 return self.__lexer.autoCompletionWordSeparators() |
|
755 return None |
|
756 |
|
757 def event(self, evt): |
|
758 """ |
|
759 Protected method to handle events from the worker thread. |
|
760 |
|
761 @param evt reference to the event object (QEvent) |
|
762 @return flag indicating, if the event was handled (boolean) |
|
763 """ |
|
764 if evt.type() == WorkerStarted: |
|
765 self.__inPreparation = True |
|
766 self.emit(SIGNAL('apiPreparationStarted()')) |
|
767 return True |
|
768 |
|
769 elif evt.type() == WorkerFinished: |
|
770 self.__inPreparation = False |
|
771 self.emit(SIGNAL('apiPreparationFinished()')) |
|
772 QTimer.singleShot(0, self.__processQueue) |
|
773 return True |
|
774 |
|
775 elif evt.type() == WorkerAborted: |
|
776 self.__inPreparation = False |
|
777 self.emit(SIGNAL('apiPreparationCancelled()')) |
|
778 QTimer.singleShot(0, self.__processQueue) |
|
779 return True |
|
780 |
|
781 else: |
|
782 return QObject.event(self, evt) |
|
783 |
|
784 ######################################################## |
|
785 ## project related stuff below |
|
786 ######################################################## |
|
787 |
|
788 def __projectOpened(self): |
|
789 """ |
|
790 Private slot to perform actions after a project has been opened. |
|
791 """ |
|
792 if self.__project.getProjectLanguage() in ["Python", "Python3"]: |
|
793 self.__discardFirst = "self" |
|
794 else: |
|
795 self.__discardFirst = "" |
|
796 self.__lexer = QScintilla.Lexers.getLexer(self.__project.getProjectLanguage()) |
|
797 self.__openAPIs() |
|
798 |
|
799 def __projectClosed(self): |
|
800 """ |
|
801 Private slot to perform actions after a project has been closed. |
|
802 """ |
|
803 self.close() |
|
804 |
|
805 def editorSaved(self, filename): |
|
806 """ |
|
807 Public slot to handle the editorSaved signal. |
|
808 |
|
809 @param filename name of the file that was saved (string) |
|
810 """ |
|
811 if self.__project.isProjectSource(filename): |
|
812 self.__workerQueue.append(filename) |
|
813 self.__processQueue() |
|
814 |
|
815 class APIsManager(QObject): |
|
816 """ |
|
817 Class implementing the APIsManager class, which is the central store for |
|
818 API information used by autocompletion and calltips. |
|
819 """ |
|
820 def __init__(self, parent = None): |
|
821 """ |
|
822 Constructor |
|
823 |
|
824 @param parent reference to the parent object (QObject) |
|
825 """ |
|
826 QObject.__init__(self, parent) |
|
827 self.setObjectName("APIsManager") |
|
828 |
|
829 # initialize the apis dictionary |
|
830 self.__apis = {} |
|
831 |
|
832 def reloadAPIs(self): |
|
833 """ |
|
834 Public slot to reload the api information. |
|
835 """ |
|
836 for api in list(self.__apis.values()): |
|
837 api and api.prepareAPIs() |
|
838 |
|
839 def getAPIs(self, language): |
|
840 """ |
|
841 Public method to get an apis object for autocompletion/calltips. |
|
842 |
|
843 This method creates and loads an APIs object dynamically upon request. |
|
844 This saves memory for languages, that might not be needed at the moment. |
|
845 |
|
846 @param language the language of the requested api object (string) |
|
847 @return the apis object (APIs) |
|
848 """ |
|
849 try: |
|
850 return self.__apis[language] |
|
851 except KeyError: |
|
852 if language in QScintilla.Lexers.getSupportedLanguages() or \ |
|
853 language == ApisNameProject: |
|
854 # create the api object |
|
855 self.__apis[language] = DbAPIs(language) |
|
856 return self.__apis[language] |
|
857 else: |
|
858 return None |
|
859 |
|
860 def deactivate(self): |
|
861 """ |
|
862 Public method to perform actions upon deactivation. |
|
863 """ |
|
864 for apiLang in self.__apis: |
|
865 self.__apis[apiLang].close() |
|
866 self.__apis[apiLang] = None |