7 Module implementing the APIsManager. |
7 Module implementing the APIsManager. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
11 |
11 |
12 from PyQt4.QtCore import * |
12 from PyQt4.QtCore import QTimer, QThread, QFileInfo, pyqtSignal, QCoreApplication, \ |
|
13 QEvent, QDateTime, QObject, Qt |
13 from PyQt4.QtSql import QSqlDatabase, QSqlQuery |
14 from PyQt4.QtSql import QSqlDatabase, QSqlQuery |
14 |
15 |
15 from E5Gui.E5Application import e5App |
16 from E5Gui.E5Application import e5App |
16 |
17 |
17 import QScintilla.Lexers |
18 import QScintilla.Lexers |
25 WorkerFinished = QEvent.User + 2002 |
26 WorkerFinished = QEvent.User + 2002 |
26 WorkerAborted = QEvent.User + 2003 |
27 WorkerAborted = QEvent.User + 2003 |
27 |
28 |
28 ApisNameProject = "__Project__" |
29 ApisNameProject = "__Project__" |
29 |
30 |
|
31 |
30 class DbAPIsWorker(QThread): |
32 class DbAPIsWorker(QThread): |
31 """ |
33 """ |
32 Class implementing a worker thread to prepare the API database. |
34 Class implementing a worker thread to prepare the API database. |
33 """ |
35 """ |
34 populate_api_stmt = """ |
36 populate_api_stmt = """ |
35 INSERT INTO api (acWord, context, fullContext, signature, fileId, pictureId) |
37 INSERT INTO api (acWord, context, fullContext, signature, fileId, pictureId) |
36 VALUES (:acWord, :context, :fullContext, :signature, :fileId, :pictureId) |
38 VALUES (:acWord, :context, :fullContext, :signature, :fileId, :pictureId) |
37 """ |
39 """ |
38 populate_del_api_stmt = """ |
40 populate_del_api_stmt = """ |
39 DELETE FROM api WHERE fileId = :fileId |
41 DELETE FROM api WHERE fileId = :fileId |
40 """ |
42 """ |
53 """ |
55 """ |
54 file_delete_id_stmt = """ |
56 file_delete_id_stmt = """ |
55 DELETE FROM file WHERE id = :id |
57 DELETE FROM file WHERE id = :id |
56 """ |
58 """ |
57 |
59 |
58 def __init__(self, proxy, language, apiFiles, projectPath = "", refresh = False): |
60 def __init__(self, proxy, language, apiFiles, projectPath="", refresh=False): |
59 """ |
61 """ |
60 Constructor |
62 Constructor |
61 |
63 |
62 @param proxy reference to the object that is proxied (DbAPIs) |
64 @param proxy reference to the object that is proxied (DbAPIs) |
63 @param language language of the APIs object (string) |
65 @param language language of the APIs object (string) |
69 QThread.__init__(self) |
71 QThread.__init__(self) |
70 |
72 |
71 self.setTerminationEnabled(True) |
73 self.setTerminationEnabled(True) |
72 |
74 |
73 # Get the AC word separators for all of the languages that the editor supports. |
75 # Get the AC word separators for all of the languages that the editor supports. |
74 # This has to be before we create a new thread, because access to GUI elements |
76 # This has to be before we create a new thread, because access to GUI elements |
75 # is not allowed from non-gui threads. |
77 # is not allowed from non-gui threads. |
76 self.__wseps = {} |
78 self.__wseps = {} |
77 for lang in QScintilla.Lexers.getSupportedLanguages(): |
79 for lang in QScintilla.Lexers.getSupportedLanguages(): |
78 lexer = QScintilla.Lexers.getLexer(lang) |
80 lexer = QScintilla.Lexers.getLexer(lang) |
79 if lexer is not None: |
81 if lexer is not None: |
136 @param apiFile filename of the raw API file (string) |
138 @param apiFile filename of the raw API file (string) |
137 """ |
139 """ |
138 if self.__language == ApisNameProject: |
140 if self.__language == ApisNameProject: |
139 try: |
141 try: |
140 module = Utilities.ModuleParser.readModule( |
142 module = Utilities.ModuleParser.readModule( |
141 os.path.join(self.__projectPath, apiFile), |
143 os.path.join(self.__projectPath, apiFile), |
142 basename = self.__projectPath + os.sep, |
144 basename=self.__projectPath + os.sep, |
143 caching = False) |
145 caching=False) |
144 language = module.getType() |
146 language = module.getType() |
145 if language: |
147 if language: |
146 apiGenerator = APIGenerator(module) |
148 apiGenerator = APIGenerator(module) |
147 apis = apiGenerator.genAPI(True, "", True) |
149 apis = apiGenerator.genAPI(True, "", True) |
148 else: |
150 else: |
323 if self.__aborted: |
325 if self.__aborted: |
324 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerAborted))) |
326 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerAborted))) |
325 else: |
327 else: |
326 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerFinished))) |
328 QCoreApplication.postEvent(self.__proxy, QEvent(QEvent.Type(WorkerFinished))) |
327 |
329 |
|
330 |
328 class DbAPIs(QObject): |
331 class DbAPIs(QObject): |
329 """ |
332 """ |
330 Class implementing an API storage entity. |
333 Class implementing an API storage entity. |
331 |
334 |
332 @signal apiPreparationFinished() emitted after the API preparation has finished |
335 @signal apiPreparationFinished() emitted after the API preparation has finished |
333 @signal apiPreparationStarted() emitted after the API preparation has started |
336 @signal apiPreparationStarted() emitted after the API preparation has started |
334 @signal apiPreparationCancelled() emitted after the API preparation has been |
337 @signal apiPreparationCancelled() emitted after the API preparation has been |
335 cancelled |
338 cancelled |
336 """ |
339 """ |
337 apiPreparationFinished = pyqtSignal() |
340 apiPreparationFinished = pyqtSignal() |
338 apiPreparationStarted = pyqtSignal() |
341 apiPreparationStarted = pyqtSignal() |
339 apiPreparationCancelled = pyqtSignal() |
342 apiPreparationCancelled = pyqtSignal() |
383 api_files_stmt = """ |
386 api_files_stmt = """ |
384 SELECT file FROM file |
387 SELECT file FROM file |
385 """ |
388 """ |
386 |
389 |
387 ac_stmt = """ |
390 ac_stmt = """ |
388 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
391 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
389 WHERE acWord GLOB :acWord |
392 WHERE acWord GLOB :acWord |
390 ORDER BY acWord |
393 ORDER BY acWord |
391 """ |
394 """ |
392 ac_context_stmt = """ |
395 ac_context_stmt = """ |
393 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
396 SELECT DISTINCT acWord, fullContext, pictureId FROM api |
394 WHERE context = :context |
397 WHERE context = :context |
395 ORDER BY acWord |
398 ORDER BY acWord |
396 """ |
399 """ |
397 ct_stmt = """ |
400 ct_stmt = """ |
398 SELECT DISTINCT acWord, signature, fullContext FROM api |
401 SELECT DISTINCT acWord, signature, fullContext FROM api |
413 """ |
416 """ |
414 mgmt_insert_stmt = """ |
417 mgmt_insert_stmt = """ |
415 INSERT INTO mgmt (format) VALUES ({0:d}) |
418 INSERT INTO mgmt (format) VALUES ({0:d}) |
416 """.format(DB_VERSION) |
419 """.format(DB_VERSION) |
417 |
420 |
418 def __init__(self, language, newStyle, parent = None): |
421 def __init__(self, language, parent=None): |
419 """ |
422 """ |
420 Constructor |
423 Constructor |
421 |
424 |
422 @param language language of the APIs object (string) |
425 @param language language of the APIs object (string) |
423 @param newStyle flag indicating usage of new style signals (bool) |
|
424 @param parent reference to the parent object (QObject) |
426 @param parent reference to the parent object (QObject) |
425 """ |
427 """ |
426 QObject.__init__(self, parent) |
428 QObject.__init__(self, parent) |
427 self.setObjectName("DbAPIs_{0}".format(language)) |
429 self.setObjectName("DbAPIs_{0}".format(language)) |
428 |
430 |
429 self.__newStyle = newStyle |
|
430 self.__inPreparation = False |
431 self.__inPreparation = False |
431 self.__worker = None |
432 self.__worker = None |
432 self.__workerQueue = [] |
433 self.__workerQueue = [] |
433 |
434 |
434 self.__language = language |
435 self.__language = language |
442 Private method to initialize as a project API object. |
443 Private method to initialize as a project API object. |
443 """ |
444 """ |
444 self.__lexer = None |
445 self.__lexer = None |
445 |
446 |
446 self.__project = e5App().getObject("Project") |
447 self.__project = e5App().getObject("Project") |
447 if self.__newStyle: |
448 self.__project.projectOpened.connect(self.__projectOpened) |
448 self.__project.projectOpened.connect(self.__projectOpened) |
449 self.__project.newProject.connect(self.__projectOpened) |
449 self.__project.newProject.connect(self.__projectOpened) |
450 self.__project.projectClosed.connect(self.__projectClosed) |
450 self.__project.projectClosed.connect(self.__projectClosed) |
|
451 else: |
|
452 self.connect(self.__project, SIGNAL("projectOpened"), self.__projectOpened) |
|
453 self.connect(self.__project, SIGNAL("newProject"), self.__projectOpened) |
|
454 self.connect(self.__project, SIGNAL("projectClosed"), self.__projectClosed) |
|
455 if self.__project.isOpen(): |
451 if self.__project.isOpen(): |
456 self.__projectOpened() |
452 self.__projectOpened() |
457 |
453 |
458 def __initAsLanguage(self): |
454 def __initAsLanguage(self): |
459 """ |
455 """ |
474 Protected method to determine the name of the database file. |
470 Protected method to determine the name of the database file. |
475 |
471 |
476 @return name of the database file (string) |
472 @return name of the database file (string) |
477 """ |
473 """ |
478 if self.__language == ApisNameProject: |
474 if self.__language == ApisNameProject: |
479 return os.path.join(self.__project.getProjectManagementDir(), |
475 return os.path.join(self.__project.getProjectManagementDir(), |
480 "project-apis.db") |
476 "project-apis.db") |
481 else: |
477 else: |
482 apiDir = os.path.join(Utilities.getConfigDir(), "APIs") |
478 apiDir = os.path.join(Utilities.getConfigDir(), "APIs") |
483 if not os.path.exists(apiDir): |
479 if not os.path.exists(apiDir): |
484 os.makedirs(apiDir) |
480 os.makedirs(apiDir) |
587 finally: |
583 finally: |
588 del query |
584 del query |
589 db.commit() |
585 db.commit() |
590 return prepared |
586 return prepared |
591 |
587 |
592 def getCompletions(self, start = None, context = None): |
588 def getCompletions(self, start=None, context=None): |
593 """ |
589 """ |
594 Public method to determine the possible completions. |
590 Public method to determine the possible completions. |
595 |
591 |
596 @keyparam start string giving the start of the word to be |
592 @keyparam start string giving the start of the word to be |
597 completed (string) |
593 completed (string) |
598 @keyparam context string giving the context (e.g. classname) |
594 @keyparam context string giving the context (e.g. classname) |
599 to be completed (string) |
595 to be completed (string) |
600 @return list of dictionaries with possible completions (key 'completion' |
596 @return list of dictionaries with possible completions (key 'completion' |
601 contains the completion (string), key 'context' |
597 contains the completion (string), key 'context' |
620 query.bindValue(":context", context) |
616 query.bindValue(":context", context) |
621 |
617 |
622 if query is not None: |
618 if query is not None: |
623 query.exec_() |
619 query.exec_() |
624 while query.next(): |
620 while query.next(): |
625 completions.append({"completion" : query.value(0), |
621 completions.append({"completion": query.value(0), |
626 "context" : query.value(1), |
622 "context": query.value(1), |
627 "pictureId" : query.value(2)}) |
623 "pictureId": query.value(2)}) |
628 del query |
624 del query |
629 finally: |
625 finally: |
630 db.commit() |
626 db.commit() |
631 |
627 |
632 return completions |
628 return completions |
633 |
629 |
634 def getCalltips(self, acWord, commas, context = None, fullContext = None, |
630 def getCalltips(self, acWord, commas, context=None, fullContext=None, |
635 showContext = True): |
631 showContext=True): |
636 """ |
632 """ |
637 Public method to determine the calltips. |
633 Public method to determine the calltips. |
638 |
634 |
639 @param acWord function to get calltips for (string) |
635 @param acWord function to get calltips for (string) |
640 @param commas minimum number of commas contained in the calltip (integer) |
636 @param commas minimum number of commas contained in the calltip (integer) |
683 finally: |
679 finally: |
684 db.commit() |
680 db.commit() |
685 |
681 |
686 if context and len(calltips) == 0: |
682 if context and len(calltips) == 0: |
687 # nothing found, try without a context |
683 # nothing found, try without a context |
688 calltips = self.getCalltips(acWord, commas, showContext = showContext) |
684 calltips = self.getCalltips(acWord, commas, showContext=showContext) |
689 |
685 |
690 return calltips |
686 return calltips |
691 |
687 |
692 def __enoughCommas(self, s, commas): |
688 def __enoughCommas(self, s, commas): |
693 """ |
689 """ |
748 if self.__language == ApisNameProject: |
744 if self.__language == ApisNameProject: |
749 projectPath = self.__project.getProjectPath() |
745 projectPath = self.__project.getProjectPath() |
750 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] |
746 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] |
751 else: |
747 else: |
752 projectPath = "" |
748 projectPath = "" |
753 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath, |
749 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, projectPath, |
754 refresh = True) |
750 refresh=True) |
755 self.__worker.start() |
751 self.__worker.start() |
756 |
752 |
757 def getLexer(self): |
753 def getLexer(self): |
758 """ |
754 """ |
759 Public method to return a reference to our lexer object. |
755 Public method to return a reference to our lexer object. |
779 @param evt reference to the event object (QEvent) |
775 @param evt reference to the event object (QEvent) |
780 @return flag indicating, if the event was handled (boolean) |
776 @return flag indicating, if the event was handled (boolean) |
781 """ |
777 """ |
782 if evt.type() == WorkerStarted: |
778 if evt.type() == WorkerStarted: |
783 self.__inPreparation = True |
779 self.__inPreparation = True |
784 if self.__newStyle: |
780 self.apiPreparationStarted.emit() |
785 self.apiPreparationStarted.emit() |
|
786 else: |
|
787 self.emit(SIGNAL('apiPreparationStarted()')) |
|
788 return True |
781 return True |
789 |
782 |
790 elif evt.type() == WorkerFinished: |
783 elif evt.type() == WorkerFinished: |
791 self.__inPreparation = False |
784 self.__inPreparation = False |
792 if self.__newStyle: |
785 self.apiPreparationFinished.emit() |
793 self.apiPreparationFinished.emit() |
|
794 else: |
|
795 self.emit(SIGNAL('apiPreparationFinished()')) |
|
796 QTimer.singleShot(0, self.__processQueue) |
786 QTimer.singleShot(0, self.__processQueue) |
797 return True |
787 return True |
798 |
788 |
799 elif evt.type() == WorkerAborted: |
789 elif evt.type() == WorkerAborted: |
800 self.__inPreparation = False |
790 self.__inPreparation = False |
801 if self.__newStyle: |
791 self.apiPreparationCancelled.emit() |
802 self.apiPreparationCancelled.emit() |
|
803 else: |
|
804 self.emit(SIGNAL('apiPreparationCancelled()')) |
|
805 QTimer.singleShot(0, self.__processQueue) |
792 QTimer.singleShot(0, self.__processQueue) |
806 return True |
793 return True |
807 |
794 |
808 else: |
795 else: |
809 return QObject.event(self, evt) |
796 return QObject.event(self, evt) |
837 """ |
824 """ |
838 if self.__project.isProjectSource(filename): |
825 if self.__project.isProjectSource(filename): |
839 self.__workerQueue.append(filename) |
826 self.__workerQueue.append(filename) |
840 self.__processQueue() |
827 self.__processQueue() |
841 |
828 |
|
829 |
842 class APIsManager(QObject): |
830 class APIsManager(QObject): |
843 """ |
831 """ |
844 Class implementing the APIsManager class, which is the central store for |
832 Class implementing the APIsManager class, which is the central store for |
845 API information used by autocompletion and calltips. |
833 API information used by autocompletion and calltips. |
846 """ |
834 """ |
847 def __init__(self, newStyle, parent = None): |
835 def __init__(self, parent=None): |
848 """ |
836 """ |
849 Constructor |
837 Constructor |
850 |
838 |
851 @param newStyle flag indicating usage of new style signals (bool) |
|
852 @param parent reference to the parent object (QObject) |
839 @param parent reference to the parent object (QObject) |
853 """ |
840 """ |
854 QObject.__init__(self, parent) |
841 QObject.__init__(self, parent) |
855 self.setObjectName("APIsManager") |
842 self.setObjectName("APIsManager") |
856 |
843 |
857 self.__newStyle = newStyle |
|
858 |
|
859 # initialize the apis dictionary |
844 # initialize the apis dictionary |
860 self.__apis = {} |
845 self.__apis = {} |
861 |
846 |
862 def reloadAPIs(self): |
847 def reloadAPIs(self): |
863 """ |
848 """ |
868 |
853 |
869 def getAPIs(self, language): |
854 def getAPIs(self, language): |
870 """ |
855 """ |
871 Public method to get an apis object for autocompletion/calltips. |
856 Public method to get an apis object for autocompletion/calltips. |
872 |
857 |
873 This method creates and loads an APIs object dynamically upon request. |
858 This method creates and loads an APIs object dynamically upon request. |
874 This saves memory for languages, that might not be needed at the moment. |
859 This saves memory for languages, that might not be needed at the moment. |
875 |
860 |
876 @param language the language of the requested api object (string) |
861 @param language the language of the requested api object (string) |
877 @return the apis object (APIs) |
862 @return the apis object (APIs) |
878 """ |
863 """ |
880 return self.__apis[language] |
865 return self.__apis[language] |
881 except KeyError: |
866 except KeyError: |
882 if language in QScintilla.Lexers.getSupportedLanguages() or \ |
867 if language in QScintilla.Lexers.getSupportedLanguages() or \ |
883 language == ApisNameProject: |
868 language == ApisNameProject: |
884 # create the api object |
869 # create the api object |
885 self.__apis[language] = DbAPIs(language, self.__newStyle) |
870 self.__apis[language] = DbAPIs(language) |
886 return self.__apis[language] |
871 return self.__apis[language] |
887 else: |
872 else: |
888 return None |
873 return None |
889 |
874 |
890 def deactivate(self): |
875 def deactivate(self): |