--- a/AssistantEric/APIsManager.py Fri Sep 30 18:19:21 2011 +0200 +++ b/AssistantEric/APIsManager.py Sat Oct 01 16:21:09 2011 +0200 @@ -40,6 +40,13 @@ populate_del_api_stmt = """ DELETE FROM api WHERE fileId = :fileId """ + populate_bases_stmt = """ + INSERT INTO bases (class, baseClasses, fileId) + VALUES (:class, :baseClasses, :fileId) + """ + populate_del_bases_stmt = """ + DELETE FROM bases WHERE fileId = :fileId + """ populate_file_stmt = """ INSERT INTO file (file) VALUES (:file) """ @@ -128,6 +135,11 @@ modTime = QFileInfo(os.path.join(self.__projectPath, apiFile)).lastModified() else: modTime = QFileInfo(apiFile).lastModified() + basesFile = os.path.splitext(apiFile)[0] + ".bas" + if os.path.exists(basesFile): + modTimeBases = QFileInfo(basesFile).lastModified() + if modTimeBases > modTime: + modTime = modTimeBases if loadTime < modTime: self.__loadApiFile(apiFile) @@ -137,6 +149,9 @@ @param apiFile filename of the raw API file (string) """ + apis = [] + bases = [] + if self.__language == ApisNameProject: try: module = Utilities.ModuleParser.readModule( @@ -147,25 +162,35 @@ if language: apiGenerator = APIGenerator(module) apis = apiGenerator.genAPI(True, "", True) - else: - apis = [] + basesDict = apiGenerator.genBases(True) + for baseEntry in basesDict: + if basesDict[baseEntry]: + bases.append("{0} {1}\n".format( + baseEntry, " ".join(sorted(basesDict[baseEntry])))) except (IOError, ImportError): - apis = [] + pass else: try: apis = Utilities.readEncodedFile(apiFile)[0].splitlines(True) except (IOError, UnicodeError): - apis = [] + pass + try: + basesFile = os.path.splitext(apiFile)[0] + ".bas" + if os.path.exists(basesFile): + bases = Utilities.readEncodedFile(basesFile)[0].splitlines(True) + except (IOError, UnicodeError): + pass language = None if len(apis) > 0: - self.__storeApis(apis, apiFile, language) + self.__storeApis(apis, bases, apiFile, language) - def __storeApis(self, apis, apiFile, language): + def __storeApis(self, apis, bases, apiFile, language): """ Private method to put the API entries into the database. @param apis list of api entries (list of strings) + @param bases list of base class entries (list of strings) @param apiFile filename of the file read to get the APIs (string) @param language programming language of the file of the APIs (string) """ @@ -195,7 +220,11 @@ query.bindValue(":fileId", id) query.exec_() - # step 3: load the given api file + query.prepare(self.populate_del_bases_stmt) + query.bindValue(":fileId", id) + query.exec_() + + # step 3: load the given API info query.prepare(self.populate_api_stmt) for api in apis: if self.__aborted: @@ -258,6 +287,22 @@ sig = "" + # step 4: load the given base classes info + query.prepare(self.populate_bases_stmt) + for base in bases: + if self.__aborted: + break + + base = base.strip() + if len(base) == 0: + continue + + class_, baseClasses = base.split(" ", 1) + query.bindValue(":class", class_) + query.bindValue(":baseClasses", baseClasses) + query.bindValue(":fileId", id) + query.exec_() + if not self.__aborted: # step 4: update the file entry query.prepare(self.update_file_stmt) @@ -289,12 +334,17 @@ query.next() id = int(query.value(0)) - # step 2: delete all api entries belonging to this file + # step 2: delete all API entries belonging to this file query.prepare(self.populate_del_api_stmt) query.bindValue(":fileId", id) query.exec_() - # step 3: delete the file entry + # step 3: delete all base classes entries belonging to this file + query.prepare(self.populate_del_bases_stmt) + query.bindValue(":fileId", id) + query.exec_() + + # step 4: delete the file entry query.prepare(self.file_delete_id_stmt) query.bindValue(":id", id) query.exec_() @@ -341,7 +391,7 @@ apiPreparationStarted = pyqtSignal() apiPreparationCancelled = pyqtSignal() - DB_VERSION = 3 + DB_VERSION = 4 create_mgmt_stmt = """ CREATE TABLE mgmt @@ -362,6 +412,15 @@ """ drop_api_stmt = """DROP TABLE IF EXISTS api""" + create_bases_stmt = """ + CREATE TABLE bases + (class TEXT UNIQUE ON CONFLICT IGNORE, + baseClasses TEXT, + fileId INTEGER + ) + """ + drop_bases_stmt = """DROP TABLE IF EXISTS bases""" + create_file_stmt = """ CREATE TABLE file (id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -380,6 +439,9 @@ create_fullContext_idx = """CREATE INDEX fullContext_idx on api (fullContext)""" drop_fullContext_idx = """DROP INDEX IF EXISTS fullContext_idx""" + create_bases_idx = """CREATE INDEX base_idx on bases (class)""" + drop_bases_idx = """DROP INDEX IF EXISTS base_idx""" + create_file_idx = """CREATE INDEX file_idx on file (file)""" drop_file_idx = """DROP INDEX IF EXISTS file_idx""" @@ -402,6 +464,10 @@ WHERE acWord GLOB :acWord AND context = :context ORDER BY acWord """ + bases_stmt = """ + SELECT baseClasses from bases + WHERE class = :class + """ ct_stmt = """ SELECT DISTINCT acWord, signature, fullContext FROM api WHERE acWord = :acWord @@ -462,6 +528,7 @@ """ if self.__language in ["Python", "Python2", "Python3"]: self.__discardFirst = "self" + # TODO: discard first can be 'cls' as well else: self.__discardFirst = "" self.__lexer = QScintilla.Lexers.getLexer(self.__language) @@ -525,14 +592,17 @@ # step 1: drop old tables query.exec_(self.drop_mgmt_stmt) query.exec_(self.drop_api_stmt) + query.exec_(self.drop_bases_stmt) query.exec_(self.drop_file_stmt) # step 2: drop old indices query.exec_(self.drop_acWord_idx) query.exec_(self.drop_context_idx) query.exec_(self.drop_fullContext_idx) + query.exec_(self.drop_bases_idx) query.exec_(self.drop_file_idx) # step 3: create tables query.exec_(self.create_api_stmt) + query.exec_(self.create_bases_stmt) query.exec_(self.create_file_stmt) query.exec_(self.create_mgmt_stmt) query.exec_(self.mgmt_insert_stmt) @@ -540,6 +610,7 @@ query.exec_(self.create_acWord_idx) query.exec_(self.create_context_idx) query.exec_(self.create_fullContext_idx) + query.exec_(self.create_bases_idx) query.exec_(self.create_file_idx) finally: del query @@ -590,7 +661,7 @@ db.commit() return prepared - def getCompletions(self, start=None, context=None): + def getCompletions(self, start=None, context=None, followHierarchy=False): """ Public method to determine the possible completions. @@ -598,6 +669,8 @@ completed (string) @keyparam context string giving the context (e.g. classname) to be completed (string) + @keyparam followHierarchy flag indicating to follow the hierarchy of + base classes (boolean) @return list of dictionaries with possible completions (key 'completion' contains the completion (string), key 'context' contains the context (string) and key 'pictureId' @@ -634,11 +707,24 @@ del query finally: db.commit() + + if followHierarchy: + query = QSqlQuery(db) + query.prepare(self.bases_stmt) + query.bindValue(":class", context) + query.exec_() + if query.next(): + bases = query.value(0).split() + else: + bases = [] + for base in bases: + completions.extend(self.getCompletions(start, base, + followHierarchy=True)) return completions def getCalltips(self, acWord, commas, context=None, fullContext=None, - showContext=True): + showContext=True, followHierarchy=False): """ Public method to determine the calltips. @@ -647,6 +733,8 @@ @param context string giving the context (e.g. classname) (string) @param fullContext string giving the full context (string) @param showContext flag indicating to show the calltip context (boolean) + @keyparam followHierarchy flag indicating to follow the hierarchy of + base classes (boolean) @return list of calltips (list of string) """ calltips = [] @@ -689,7 +777,21 @@ finally: db.commit() - if context and len(calltips) == 0: + if followHierarchy: + query = QSqlQuery(db) + query.prepare(self.bases_stmt) + query.bindValue(":class", context) + query.exec_() + if query.next(): + bases = query.value(0).split() + else: + bases = [] + for base in bases: + calltips.extend(self.getCalltips(acWord, commas, context=base, + showContext=showContext, + followHierarchy=True)) + + if context and len(calltips) == 0 and not followHierarchy: # nothing found, try without a context calltips = self.getCalltips(acWord, commas, showContext=showContext)