Sat, 01 Oct 2011 16:21:09 +0200
More improvements of auto-completions and calltips for Python sources.
--- 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)
--- a/AssistantEric/Assistant.py Fri Sep 30 18:19:21 2011 +0200 +++ b/AssistantEric/Assistant.py Sat Oct 01 16:21:09 2011 +0200 @@ -363,10 +363,12 @@ for super in cl.super: if prefix == word: completions.extend( - api.getCompletions(context=super)) + api.getCompletions(context=super, + followHierarchy=True)) else: completions.extend( - api.getCompletions(start=word, context=super)) + api.getCompletions(start=word, context=super, + followHierarchy=True)) for completion in completions: if not completion["context"]: entry = completion["completion"] @@ -427,7 +429,8 @@ completionsList.append(entry) return completionsList - def __getDocumentCompletions(self, editor, word, context, sep, prefix, module): + def __getDocumentCompletions(self, editor, word, context, sep, prefix, module, + doHierarchy=False): """ Private method to determine autocompletion proposals from the document. @@ -437,6 +440,7 @@ @param sep separator string (string) @param prefix prefix of the word to be completed (string) @param module reference to the scanned module info (Module) + @keyparam doHierarchy flag indicating a hierarchical search (boolean) @return list of possible completions (list of strings) """ completionsList = [] @@ -496,6 +500,17 @@ completionsList.extend( ["{0} ({1})?{2}".format(c[0], c[1], c[2]) for c in comps]) + + for sup in cl.super: + if sup in module.classes: + if word == prefix: + nword = sup + else: + nword = word + completionsList.extend(self.__getDocumentCompletions( + editor, nword, context, sep, sup, module, + doHierarchy=True)) + break else: # possibly completing a named class attribute or method @@ -506,12 +521,15 @@ for method in cl.methods.values(): if method.name == "__init__": continue - if not hasattr(method, "modifier"): + if not doHierarchy and not hasattr(method, "modifier"): # eric 5.1 cannot differentiate method types continue - if method.modifier in [method.Class, method.Static]: + if doHierarchy or \ + method.modifier in [method.Class, method.Static]: # determine icon type if method.isPrivate(): + if doHierarchy: + continue iconID = Editor.MethodPrivateID elif method.isProtected(): iconID = Editor.MethodProtectedID @@ -536,6 +554,16 @@ completionsList.extend( ["{0} ({1})?{2}".format(c[0], c[1], c[2]) for c in comps]) + + for sup in cl.super: + if sup in module.classes: + if word == prefix: + nword = sup + else: + nword = word + completionsList.extend(self.__getDocumentCompletions( + editor, nword, context, sep, sup, module, + doHierarchy=True)) if not prefixFound: currentPos = editor.currentPosition() @@ -678,7 +706,8 @@ (cl.endlineno == -1 or line <= cl.endlineno): for super in cl.super: calltips.extend(api.getCalltips(word, commas, super, None, - self.__plugin.getPreferences("CallTipsContextShown"))) + self.__plugin.getPreferences("CallTipsContextShown"), + followHierarchy=True)) break else: calltips = api.getCalltips(word, commas, self.__lastContext, @@ -687,7 +716,7 @@ return calltips - def __getDocumentCalltips(self, word, prefix, module, editor): + def __getDocumentCalltips(self, word, prefix, module, editor, doHierarchy=False): """ Private method to determine calltips from the document. @@ -695,6 +724,7 @@ @param prefix prefix of the word to be completed (string) @param module reference to the scanned module info (Module) @param editor reference to the editor object (QScintilla.Editor) + @keyparam doHierarchy flag indicating a hierarchical search (boolean) @return list of calltips (list of string) """ calltips = [] @@ -725,27 +755,37 @@ sep, word, ', '.join(method.parameters[1:]))) + + for sup in cl.super: + calltips.extend(self.__getDocumentCalltips( + word, sup, module, editor, doHierarchy=True)) + break else: if prefix in module.classes: cl = module.classes[prefix] if word in cl.methods: method = cl.methods[word] - if hasattr(method, "modifier") and \ - method.modifier == method.Class: + if doHierarchy or \ + (hasattr(method, "modifier") and \ + method.modifier == method.Class): # only eric 5.2 and newer can differentiate method types calltips.append("{0}{1}{2}({3})".format( cl.name, sep, word, ', '.join(method.parameters[1:]))) + + for sup in cl.super: + calltips.extend(self.__getDocumentCalltips( + word, sup, module, editor, doHierarchy=True)) else: # calltip for a module function or class if word in module.functions: fun = module.functions[word] calltips.append("{0}({1})".format( word, - ', '.join(fun.parameters[1:]))) + ', '.join(fun.parameters))) elif word in module.classes: cl = module.classes[word] if "__init__" in cl.methods:
--- a/AssistantEric/Documentation/source/Plugin_Assistant_Eric.AssistantEric.APIsManager.html Fri Sep 30 18:19:21 2011 +0200 +++ b/AssistantEric/Documentation/source/Plugin_Assistant_Eric.AssistantEric.APIsManager.html Sat Oct 01 16:21:09 2011 +0200 @@ -145,7 +145,7 @@ QObject <h3>Class Attributes</h3> <table> -<tr><td>DB_VERSION</td></tr><tr><td>ac_context_stmt</td></tr><tr><td>ac_context_word_stmt</td></tr><tr><td>ac_stmt</td></tr><tr><td>api_files_stmt</td></tr><tr><td>create_acWord_idx</td></tr><tr><td>create_api_stmt</td></tr><tr><td>create_context_idx</td></tr><tr><td>create_file_idx</td></tr><tr><td>create_file_stmt</td></tr><tr><td>create_fullContext_idx</td></tr><tr><td>create_mgmt_stmt</td></tr><tr><td>ct_context_stmt</td></tr><tr><td>ct_fullContext_stmt</td></tr><tr><td>ct_stmt</td></tr><tr><td>drop_acWord_idx</td></tr><tr><td>drop_api_stmt</td></tr><tr><td>drop_context_idx</td></tr><tr><td>drop_file_idx</td></tr><tr><td>drop_file_stmt</td></tr><tr><td>drop_fullContext_idx</td></tr><tr><td>drop_mgmt_stmt</td></tr><tr><td>format_select_stmt</td></tr><tr><td>mgmt_insert_stmt</td></tr> +<tr><td>DB_VERSION</td></tr><tr><td>ac_context_stmt</td></tr><tr><td>ac_context_word_stmt</td></tr><tr><td>ac_stmt</td></tr><tr><td>api_files_stmt</td></tr><tr><td>bases_stmt</td></tr><tr><td>create_acWord_idx</td></tr><tr><td>create_api_stmt</td></tr><tr><td>create_bases_idx</td></tr><tr><td>create_bases_stmt</td></tr><tr><td>create_context_idx</td></tr><tr><td>create_file_idx</td></tr><tr><td>create_file_stmt</td></tr><tr><td>create_fullContext_idx</td></tr><tr><td>create_mgmt_stmt</td></tr><tr><td>ct_context_stmt</td></tr><tr><td>ct_fullContext_stmt</td></tr><tr><td>ct_stmt</td></tr><tr><td>drop_acWord_idx</td></tr><tr><td>drop_api_stmt</td></tr><tr><td>drop_bases_idx</td></tr><tr><td>drop_bases_stmt</td></tr><tr><td>drop_context_idx</td></tr><tr><td>drop_file_idx</td></tr><tr><td>drop_file_stmt</td></tr><tr><td>drop_fullContext_idx</td></tr><tr><td>drop_mgmt_stmt</td></tr><tr><td>format_select_stmt</td></tr><tr><td>mgmt_insert_stmt</td></tr> </table> <h3>Class Methods</h3> <table> @@ -365,7 +365,7 @@ </dd> </dl><a NAME="DbAPIs.getCalltips" ID="DbAPIs.getCalltips"></a> <h4>DbAPIs.getCalltips</h4> -<b>getCalltips</b>(<i>acWord, commas, context=None, fullContext=None, showContext=True</i>) +<b>getCalltips</b>(<i>acWord, commas, context=None, fullContext=None, showContext=True, followHierarchy=False</i>) <p> Public method to determine the calltips. </p><dl> @@ -384,6 +384,10 @@ </dd><dt><i>showContext</i></dt> <dd> flag indicating to show the calltip context (boolean) +</dd><dt><i>followHierarchy=</i></dt> +<dd> +flag indicating to follow the hierarchy of + base classes (boolean) </dd> </dl><dl> <dt>Returns:</dt> @@ -392,7 +396,7 @@ </dd> </dl><a NAME="DbAPIs.getCompletions" ID="DbAPIs.getCompletions"></a> <h4>DbAPIs.getCompletions</h4> -<b>getCompletions</b>(<i>start=None, context=None</i>) +<b>getCompletions</b>(<i>start=None, context=None, followHierarchy=False</i>) <p> Public method to determine the possible completions. </p><dl> @@ -404,6 +408,10 @@ <dd> string giving the context (e.g. classname) to be completed (string) +</dd><dt><i>followHierarchy=</i></dt> +<dd> +flag indicating to follow the hierarchy of + base classes (boolean) </dd> </dl><dl> <dt>Returns:</dt> @@ -445,7 +453,7 @@ QThread <h3>Class Attributes</h3> <table> -<tr><td>file_delete_id_stmt</td></tr><tr><td>file_id_stmt</td></tr><tr><td>file_loaded_stmt</td></tr><tr><td>populate_api_stmt</td></tr><tr><td>populate_del_api_stmt</td></tr><tr><td>populate_file_stmt</td></tr><tr><td>update_file_stmt</td></tr> +<tr><td>file_delete_id_stmt</td></tr><tr><td>file_id_stmt</td></tr><tr><td>file_loaded_stmt</td></tr><tr><td>populate_api_stmt</td></tr><tr><td>populate_bases_stmt</td></tr><tr><td>populate_del_api_stmt</td></tr><tr><td>populate_del_bases_stmt</td></tr><tr><td>populate_file_stmt</td></tr><tr><td>update_file_stmt</td></tr> </table> <h3>Class Methods</h3> <table> @@ -554,13 +562,16 @@ </dd> </dl><a NAME="DbAPIsWorker.__storeApis" ID="DbAPIsWorker.__storeApis"></a> <h4>DbAPIsWorker.__storeApis</h4> -<b>__storeApis</b>(<i>apis, apiFile, language</i>) +<b>__storeApis</b>(<i>apis, bases, apiFile, language</i>) <p> Private method to put the API entries into the database. </p><dl> <dt><i>apis</i></dt> <dd> list of api entries (list of strings) +</dd><dt><i>bases</i></dt> +<dd> +list of base class entries (list of strings) </dd><dt><i>apiFile</i></dt> <dd> filename of the file read to get the APIs (string)
--- a/AssistantEric/Documentation/source/Plugin_Assistant_Eric.AssistantEric.Assistant.html Fri Sep 30 18:19:21 2011 +0200 +++ b/AssistantEric/Documentation/source/Plugin_Assistant_Eric.AssistantEric.Assistant.html Sat Oct 01 16:21:09 2011 +0200 @@ -252,7 +252,7 @@ </dd> </dl><a NAME="Assistant.__getDocumentCalltips" ID="Assistant.__getDocumentCalltips"></a> <h4>Assistant.__getDocumentCalltips</h4> -<b>__getDocumentCalltips</b>(<i>word, prefix, module, editor</i>) +<b>__getDocumentCalltips</b>(<i>word, prefix, module, editor, doHierarchy=False</i>) <p> Private method to determine calltips from the document. </p><dl> @@ -268,6 +268,9 @@ </dd><dt><i>editor</i></dt> <dd> reference to the editor object (QScintilla.Editor) +</dd><dt><i>doHierarchy=</i></dt> +<dd> +flag indicating a hierarchical search (boolean) </dd> </dl><dl> <dt>Returns:</dt> @@ -276,7 +279,7 @@ </dd> </dl><a NAME="Assistant.__getDocumentCompletions" ID="Assistant.__getDocumentCompletions"></a> <h4>Assistant.__getDocumentCompletions</h4> -<b>__getDocumentCompletions</b>(<i>editor, word, context, sep, prefix, module</i>) +<b>__getDocumentCompletions</b>(<i>editor, word, context, sep, prefix, module, doHierarchy=False</i>) <p> Private method to determine autocompletion proposals from the document. </p><dl> @@ -298,6 +301,9 @@ </dd><dt><i>module</i></dt> <dd> reference to the scanned module info (Module) +</dd><dt><i>doHierarchy=</i></dt> +<dd> +flag indicating a hierarchical search (boolean) </dd> </dl><dl> <dt>Returns:</dt>
--- a/PluginEricAssistant.e4p Fri Sep 30 18:19:21 2011 +0200 +++ b/PluginEricAssistant.e4p Sat Oct 01 16:21:09 2011 +0200 @@ -138,16 +138,16 @@ </VcsOtherData> </Vcs> <FiletypeAssociations> + <FiletypeAssociation pattern="*.idl" type="INTERFACES"/> + <FiletypeAssociation pattern="*.ptl" type="SOURCES"/> + <FiletypeAssociation pattern="*.py" type="SOURCES"/> + <FiletypeAssociation pattern="*.pyw" type="SOURCES"/> + <FiletypeAssociation pattern="*.qm" type="TRANSLATIONS"/> + <FiletypeAssociation pattern="*.qrc" type="RESOURCES"/> + <FiletypeAssociation pattern="*.ts" type="TRANSLATIONS"/> <FiletypeAssociation pattern="*.ui" type="FORMS"/> - <FiletypeAssociation pattern="*.idl" type="INTERFACES"/> - <FiletypeAssociation pattern="*.qm" type="TRANSLATIONS"/> - <FiletypeAssociation pattern="*.ptl" type="SOURCES"/> + <FiletypeAssociation pattern="*.ui.h" type="FORMS"/> <FiletypeAssociation pattern="Ui_*" type="__IGNORE__"/> - <FiletypeAssociation pattern="*.pyw" type="SOURCES"/> - <FiletypeAssociation pattern="*.ui.h" type="FORMS"/> - <FiletypeAssociation pattern="*.ts" type="TRANSLATIONS"/> - <FiletypeAssociation pattern="*.py" type="SOURCES"/> - <FiletypeAssociation pattern="*.qrc" type="RESOURCES"/> </FiletypeAssociations> <Documentation> <DocumentationParams> @@ -171,6 +171,8 @@ <string>.ropeproject</string> <string>.eric4project</string> <string>.eric5project</string> + <string>_eric5project</string> + <string>_ropeproject</string> </list> </value> <key>