Wed, 21 Sep 2022 16:59:53 +0200
Reformatted source code with 'Black'.
--- a/AssistantEric/APIsManager.py Thu Dec 30 11:32:05 2021 +0100 +++ b/AssistantEric/APIsManager.py Wed Sep 21 16:59:53 2022 +0200 @@ -10,9 +10,8 @@ import contextlib import os -from PyQt6.QtCore import ( - QTimer, QThread, QFileInfo, pyqtSignal, QDateTime, QObject, Qt -) +from PyQt6.QtCore import QTimer, QThread, QFileInfo, pyqtSignal, QDateTime, QObject, Qt + with contextlib.suppress(ImportError): from PyQt6.QtSql import QSqlDatabase, QSqlQuery @@ -36,12 +35,13 @@ class DbAPIsWorker(QThread): """ Class implementing a worker thread to prepare the API database. - + @signal processing(status, file) emitted to indicate the processing status (one of WorkerStatus..., string) """ + processing = pyqtSignal(int, str) - + populate_api_stmt = """ INSERT INTO api ( acWord, context, fullContext, signature, fileId, pictureId) @@ -78,12 +78,20 @@ api_files_stmt = """ SELECT file FROM file """ - - def __init__(self, proxy, language, apiFiles, dbName, projectPath="", - refresh=False, projectType=""): + + def __init__( + self, + proxy, + language, + apiFiles, + dbName, + projectPath="", + refresh=False, + projectType="", + ): """ Constructor - + @param proxy reference to the object that is proxied @type DbAPIs @param language language of the APIs object @@ -101,9 +109,9 @@ @type str """ QThread.__init__(self) - + self.setTerminationEnabled(True) - + # Get the AC word separators for all of the languages that the editor # supports. This has to be before we create a new thread, because # access to GUI elements is not allowed from non-GUI threads. @@ -112,7 +120,7 @@ lexer = QScintilla.Lexers.getLexer(lang) if lexer is not None: self.__wseps[lang] = lexer.autoCompletionWordSeparators() - + self.__proxy = proxy self.__language = language self.__projectType = projectType @@ -120,20 +128,20 @@ self.__aborted = False self.__projectPath = projectPath self.__refresh = refresh - + self.__databaseName = dbName - + if self.__projectType: self.__connectionName = "{0}_{1}_{2}".format( - self.__language, self.__projectType, id(self)) + self.__language, self.__projectType, id(self) + ) else: - self.__connectionName = "{0}_{1}".format( - self.__language, id(self)) - + self.__connectionName = "{0}_{1}".format(self.__language, id(self)) + def __autoCompletionWordSeparators(self, language): """ Private method to get the word separator characters for a language. - + @param language language of the APIs object @type str @return word separator characters @@ -143,18 +151,18 @@ return self.__wseps.get(language, None) else: return self.__proxy.autoCompletionWordSeparators() - + def abort(self): """ Public method to ask the thread to stop. """ self.__aborted = True - + def __loadApiFileIfNewer(self, apiFile): """ Private method to load an API file, if it is newer than the one read into the database. - + @param apiFile filename of the raw API file @type str """ @@ -167,7 +175,8 @@ query.exec() loadTime = ( QDateTime.fromString(query.value(0), Qt.DateFormat.ISODate) - if query.next() and query.isValid() else + if query.next() and query.isValid() + else # __IGNORE_WARNING_M513__ QDateTime(1970, 1, 1, 0, 0) ) @@ -176,10 +185,9 @@ finally: db.commit() if self.__projectPath: - modTime = ( - QFileInfo(os.path.join(self.__projectPath, apiFile)) - .lastModified() - ) + modTime = QFileInfo( + os.path.join(self.__projectPath, apiFile) + ).lastModified() else: modTime = QFileInfo(apiFile).lastModified() basesFile = os.path.splitext(apiFile)[0] + ".bas" @@ -190,93 +198,94 @@ if loadTime < modTime: self.processing.emit(WorkerStatusFile, apiFile) self.__loadApiFile(apiFile) - + def __classesAttributesApi(self, module): """ Private method to generate class api section for class attributes. - + @param module module object to get the info from @type Module @return API information @rtype list of str """ api = [] - modulePath = module.name.split('.') - moduleName = "{0}.".format('.'.join(modulePath)) - + modulePath = module.name.split(".") + moduleName = "{0}.".format(".".join(modulePath)) + for className in sorted(module.classes.keys()): _class = module.classes[className] classNameStr = "{0}{1}.".format(moduleName, className) for variable in sorted(_class.attributes.keys()): if not _class.attributes[variable].isPrivate(): from QScintilla.Editor import Editor + if _class.attributes[variable].isPublic(): iconId = Editor.AttributeID elif _class.attributes[variable].isProtected(): iconId = Editor.AttributeProtectedID else: iconId = Editor.AttributePrivateID - api.append('{0}{1}?{2:d}'.format(classNameStr, variable, - iconId)) + api.append("{0}{1}?{2:d}".format(classNameStr, variable, iconId)) return api - + def __loadApiFile(self, apiFile): """ Private method to read a raw API file into the database. - + @param apiFile filename of the raw API file @type str """ apis = [] bases = [] - + if self.__language == ApisNameProject: with contextlib.suppress(OSError, ImportError): module = Utilities.ModuleParser.readModule( os.path.join(self.__projectPath, apiFile), basename=self.__projectPath + os.sep, - caching=False) + caching=False, + ) language = module.getType() if language: from DocumentationTools.APIGenerator import APIGenerator + apiGenerator = APIGenerator(module) apis = apiGenerator.genAPI(True, "", True) if os.path.basename(apiFile).startswith("Ui_"): # it is a forms source file, extract public attributes # as well apis.extend(self.__classesAttributesApi(module)) - + basesDict = apiGenerator.genBases(True) for baseEntry in basesDict: if basesDict[baseEntry]: - bases.append("{0} {1}\n".format( - baseEntry, " ".join( - sorted(basesDict[baseEntry])))) + bases.append( + "{0} {1}\n".format( + baseEntry, " ".join(sorted(basesDict[baseEntry])) + ) + ) else: with contextlib.suppress(OSError, UnicodeError): apis = Utilities.readEncodedFile(apiFile)[0].splitlines(True) with contextlib.suppress(OSError, UnicodeError): basesFile = os.path.splitext(apiFile)[0] + ".bas" if os.path.exists(basesFile): - bases = ( - Utilities.readEncodedFile(basesFile)[0] - .splitlines(True) - ) + bases = Utilities.readEncodedFile(basesFile)[0].splitlines(True) language = None - + if len(apis) > 0: self.__storeApis(apis, bases, apiFile, language) else: # just store file info to avoid rereading it every time self.__storeFileInfoOnly(apiFile) - + def __storeFileInfoOnly(self, apiFile): """ Private method to store file info only. - + Doing this avoids rereading the file whenever the API is initialized in case the given file doesn't contain API data. - + @param apiFile file name of the API file @type str """ @@ -288,7 +297,7 @@ query.prepare(self.populate_file_stmt) query.bindValue(":file", apiFile) query.exec() - + # step 2: update the file entry query.prepare(self.update_file_stmt) query.bindValue(":lastRead", QDateTime.currentDateTime()) @@ -301,11 +310,11 @@ db.rollback() else: db.commit() - + def __storeApis(self, apis, bases, apiFile, language): """ Private method to put the API entries into the database. - + @param apis list of api entries @type list of str @param bases list of base class entries @@ -332,43 +341,43 @@ query.bindValue(":file", apiFile) if not query.exec(): return - if not query.next(): # __IGNORE_WARNING_M513__ + if not query.next(): # __IGNORE_WARNING_M513__ return fileId = int(query.value(0)) - + # step 2: delete all entries belonging to this file query.prepare(self.populate_del_api_stmt) query.bindValue(":fileId", fileId) query.exec() - + query.prepare(self.populate_del_bases_stmt) query.bindValue(":fileId", fileId) query.exec() - + # step 3: load the given API info query.prepare(self.populate_api_stmt) for api in apis: if self.__aborted: break - + api = api.strip() if len(api) == 0: continue - - b = api.find('(') + + b = api.find("(") if b == -1: path = api sig = "" else: path = api[:b] sig = api[b:] - + while len(path) > 0: acWord = "" context = "" fullContext = "" pictureId = "" - + # search for word separators index = len(path) while index > 0: @@ -382,7 +391,7 @@ if acWord == "": # completion found acWord = path[index:] - path = path[:(index - len(wsep))] + path = path[: (index - len(wsep))] index = len(path) fullContext = path context = path @@ -395,7 +404,7 @@ if acWord == "": acWord = path path = "" - + query.bindValue(":acWord", acWord) query.bindValue(":context", context) query.bindValue(":fullContext", fullContext) @@ -403,25 +412,25 @@ query.bindValue(":fileId", fileId) query.bindValue(":pictureId", pictureId) query.exec() - + 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", fileId) query.exec() - + if not self.__aborted: # step 5: update the file entry query.prepare(self.update_file_stmt) @@ -435,11 +444,11 @@ db.rollback() else: db.commit() - + def __deleteApiFile(self, apiFile): """ Private method to delete all references to an api file. - + @param apiFile filename of the raw API file @type str """ @@ -447,24 +456,24 @@ db.transaction() try: query = QSqlQuery(db) - + # step 1: get the ID belonging to the api file query.prepare(self.file_id_stmt) query.bindValue(":file", apiFile) query.exec() - query.next() # __IGNORE_WARNING_M513__ + query.next() # __IGNORE_WARNING_M513__ fileId = int(query.value(0)) - + # step 2: delete all API entries belonging to this file query.prepare(self.populate_del_api_stmt) query.bindValue(":fileId", fileId) query.exec() - + # step 3: delete all base classes entries belonging to this file query.prepare(self.populate_del_bases_stmt) query.bindValue(":fileId", fileId) query.exec() - + # step 4: delete the file entry query.prepare(self.file_delete_id_stmt) query.bindValue(":id", fileId) @@ -477,39 +486,39 @@ def __getApiFiles(self): """ Private method to get a list of API files loaded into the database. - + @return list of API filenames @rtype list of str """ apiFiles = [] - + db = QSqlDatabase.database(self.__connectionName) db.transaction() try: query = QSqlQuery(db) query.exec(self.api_files_stmt) - while query.next(): # __IGNORE_WARNING_M513__ + while query.next(): # __IGNORE_WARNING_M513__ apiFiles.append(query.value(0)) finally: query.finish() del query db.commit() - + return apiFiles - + def run(self): """ Public method to perform the threads work. """ self.processing.emit(WorkerStatusStarted, "") - + if QSqlDatabase.contains(self.__connectionName): QSqlDatabase.removeDatabase(self.__connectionName) - + db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName) db.setDatabaseName(self.__databaseName) db.open() - + if db.isValid() and db.isOpen(): # step 1: remove API files not wanted any longer if not self.__refresh: @@ -517,14 +526,14 @@ for apiFile in loadedApiFiles: if not self.__aborted and apiFile not in self.__apiFiles: self.__deleteApiFile(apiFile) - + # step 2: (re-)load api files for apiFile in self.__apiFiles: if not self.__aborted: self.__loadApiFileIfNewer(apiFile) - + db.close() - + if self.__aborted: self.processing.emit(WorkerStatusAborted, "") else: @@ -534,20 +543,21 @@ class DbAPIs(QObject): """ Class implementing an API storage entity. - + @signal apiPreparationStatus(language, status, file) emitted to indicate the API preparation status for a language """ + apiPreparationStatus = pyqtSignal(str, int, str) - + DB_VERSION = 4 - + create_mgmt_stmt = """ CREATE TABLE mgmt (format INTEGER) """ drop_mgmt_stmt = """DROP TABLE IF EXISTS mgmt""" - + create_api_stmt = """ CREATE TABLE api (acWord TEXT, @@ -560,7 +570,7 @@ ) """ drop_api_stmt = """DROP TABLE IF EXISTS api""" - + create_bases_stmt = """ CREATE TABLE bases (class TEXT UNIQUE ON CONFLICT IGNORE, @@ -569,7 +579,7 @@ ) """ drop_bases_stmt = """DROP TABLE IF EXISTS bases""" - + create_file_stmt = """ CREATE TABLE file (id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -578,21 +588,19 @@ ) """ drop_file_stmt = """DROP TABLE IF EXISTS file""" - + create_acWord_idx = """CREATE INDEX acWord_idx on api (acWord)""" drop_acWord_idx = """DROP INDEX IF EXISTS acWord_idx""" - + create_context_idx = """CREATE INDEX context_idx on api (context)""" drop_context_idx = """DROP INDEX IF EXISTS context_idx""" - - create_fullContext_idx = ( - """CREATE INDEX fullContext_idx on api (fullContext)""" - ) + + 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""" @@ -638,12 +646,14 @@ """ mgmt_insert_stmt = """ INSERT INTO mgmt (format) VALUES ({0:d}) - """.format(DB_VERSION) - + """.format( + DB_VERSION + ) + def __init__(self, language, projectType="", parent=None): """ Constructor - + @param language language of the APIs object @type str @param projectType type of the project @@ -656,42 +666,43 @@ self.setObjectName("DbAPIs_{0}_{1}".format(language, projectType)) else: self.setObjectName("DbAPIs_{0}".format(language)) - + self.__inPreparation = False self.__worker = None self.__workerQueue = [] self.__opened = False - + self.__projectType = projectType self.__language = language - + if self.__projectType: self.__connectionName = "{0}_{1}".format( - self.__language, self.__projectType) + self.__language, self.__projectType + ) else: self.__connectionName = self.__language - + if self.__language == ApisNameProject: self.__initAsProject() else: self.__initAsLanguage() - + def __initAsProject(self): """ Private method to initialize as a project API object. """ self.__lexer = None - + self.__project = ericApp().getObject("Project") self.__project.projectOpened.connect(self.__projectOpened) self.__project.newProject.connect(self.__projectOpened) self.__project.projectClosed.connect(self.__projectClosed) self.__project.projectFormCompiled.connect(self.__projectFormCompiled) self.__project.projectChanged.connect(self.__projectChanged) - + if self.__project.isOpen(): self.__projectOpened() - + def __initAsLanguage(self): """ Private method to initialize as a language API object. @@ -703,35 +714,36 @@ self.__lexer = QScintilla.Lexers.getLexer(self.__language) try: self.__apifiles = Preferences.getEditorAPI( - self.__language, projectType=self.__projectType) + self.__language, projectType=self.__projectType + ) except TypeError: # older interface self.__apifiles = Preferences.getEditorAPI(self.__language) self.__apifiles.sort() if self.__lexer is not None: self.__openAPIs() - + def _apiDbName(self): """ Protected method to determine the name of the database file. - + @return name of the database file @rtype str """ if self.__language == ApisNameProject: - return os.path.join(self.__project.getProjectManagementDir(), - "project-apis.db") + return os.path.join( + self.__project.getProjectManagementDir(), "project-apis.db" + ) else: apisDir = os.path.join(Globals.getConfigDir(), "APIs") if not os.path.exists(apisDir): os.makedirs(apisDir) if self.__projectType: - filename = "{0}_{1}-api.db".format(self.__language, - self.__projectType) + filename = "{0}_{1}-api.db".format(self.__language, self.__projectType) else: filename = "{0}-api.db".format(self.__language) return os.path.join(apisDir, filename) - + def close(self): """ Public method to close the database. @@ -741,25 +753,21 @@ self.__worker.abort() if self.__worker is not None: self.__worker.wait(5000) - if ( - self.__worker is not None and - not self.__worker.isFinished() - ): + if self.__worker is not None and not self.__worker.isFinished(): self.__worker.terminate() if self.__worker is not None: self.__worker.wait(5000) - - if QSqlDatabase and QSqlDatabase.database( - self.__connectionName).isOpen(): + + if QSqlDatabase and QSqlDatabase.database(self.__connectionName).isOpen(): QSqlDatabase.database(self.__connectionName).close() QSqlDatabase.removeDatabase(self.__connectionName) - + self.__opened = False - + def __openApiDb(self): """ Private method to open the API database. - + @return flag indicating the database status @rtype bool """ @@ -768,9 +776,8 @@ # the database connection is a new one db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName) dbName = self._apiDbName() - if ( - self.__language == ApisNameProject and - not os.path.exists(self.__project.getProjectManagementDir()) + if self.__language == ApisNameProject and not os.path.exists( + self.__project.getProjectManagementDir() ): opened = False else: @@ -781,7 +788,7 @@ else: opened = True return opened - + def __createApiDB(self): """ Private method to create an API database. @@ -821,30 +828,30 @@ def getApiFiles(self): """ Public method to get a list of API files loaded into the database. - + @return list of API filenames @rtype list of str """ apiFiles = [] - + db = QSqlDatabase.database(self.__connectionName) db.transaction() try: query = QSqlQuery(db) query.exec(self.api_files_stmt) - while query.next(): # __IGNORE_WARNING_M513__ + while query.next(): # __IGNORE_WARNING_M513__ apiFiles.append(query.value(0)) finally: query.finish() del query db.commit() - + return apiFiles - + def __isPrepared(self): """ Private method to check, if the database has been prepared. - + @return flag indicating the prepared status @rtype bool """ @@ -857,7 +864,7 @@ query = QSqlQuery(db) ok = query.exec(self.format_select_stmt) if ok: - query.next() # __IGNORE_WARNING_M513__ + query.next() # __IGNORE_WARNING_M513__ formatVersion = int(query.value(0)) if formatVersion >= self.DB_VERSION: prepared = True @@ -866,11 +873,11 @@ del query db.commit() return prepared - + def getCompletions(self, start=None, context=None, followHierarchy=False): """ Public method to determine the possible completions. - + @param start string giving the start of the word to be completed @type str @@ -887,60 +894,72 @@ @rtype list of dict """ completions = [] - + db = QSqlDatabase.database(self.__connectionName) if db.isOpen() and not self.__inPreparation: db.transaction() try: query = None - + if start is not None and context is not None: query = QSqlQuery(db) query.prepare(self.ac_context_word_stmt) - query.bindValue(":acWord", start + '*') + query.bindValue(":acWord", start + "*") query.bindValue(":context", context) elif start is not None: query = QSqlQuery(db) query.prepare(self.ac_stmt) - query.bindValue(":acWord", start + '*') + query.bindValue(":acWord", start + "*") elif context is not None: query = QSqlQuery(db) query.prepare(self.ac_context_stmt) query.bindValue(":context", context) - + if query is not None: query.exec() - while query.next(): # __IGNORE_WARNING_M513__ - completions.append({"completion": query.value(0), - "context": query.value(1), - "pictureId": query.value(2)}) + while query.next(): # __IGNORE_WARNING_M513__ + completions.append( + { + "completion": query.value(0), + "context": query.value(1), + "pictureId": query.value(2), + } + ) query.finish() del query finally: db.commit() - + if followHierarchy: query = QSqlQuery(db) query.prepare(self.bases_stmt) query.bindValue(":class", context) query.exec() - if query.next(): # __IGNORE_WARNING_M513__ + if query.next(): # __IGNORE_WARNING_M513__ bases = query.value(0).split() else: bases = [] for base in bases: - completions.extend(self.getCompletions(start, base, - followHierarchy=True)) + completions.extend( + self.getCompletions(start, base, followHierarchy=True) + ) query.finish() del query - + return completions - - def getCalltips(self, acWord, commas, context=None, fullContext=None, - showContext=True, followHierarchy=False): + + def getCalltips( + self, + acWord, + commas, + context=None, + fullContext=None, + showContext=True, + followHierarchy=False, + ): """ Public method to determine the calltips. - + @param acWord function to get calltips for @type str @param commas minimum number of commas contained in the calltip @@ -958,7 +977,7 @@ @rtype list of str """ calltips = [] - + db = QSqlDatabase.database(self.__connectionName) if db.isOpen() and not self.__inPreparation: if self.autoCompletionWordSeparators(): @@ -978,7 +997,7 @@ query.prepare(self.ct_stmt) query.bindValue(":acWord", acWord) query.exec() - while query.next(): # __IGNORE_WARNING_M513__ + while query.next(): # __IGNORE_WARNING_M513__ word = query.value(0) sig = query.value(1) fullCtx = query.value(2) @@ -990,45 +1009,54 @@ sig = sig.strip(", \t\r\n") if self.__enoughCommas(sig, commas): if showContext: - calltips.append("{0}{1}{2}{3}".format( - fullCtx, - contextSeparator if fullCtx else "", - word, sig)) + calltips.append( + "{0}{1}{2}{3}".format( + fullCtx, + contextSeparator if fullCtx else "", + word, + sig, + ) + ) else: calltips.append("{0}{1}".format(word, sig)) query.finish() del query finally: db.commit() - + if followHierarchy: query = QSqlQuery(db) query.prepare(self.bases_stmt) query.bindValue(":class", context) query.exec() - if query.next(): # __IGNORE_WARNING_M513__ + if query.next(): # __IGNORE_WARNING_M513__ bases = query.value(0).split() else: bases = [] for base in bases: - calltips.extend(self.getCalltips( - acWord, commas, context=base, showContext=showContext, - followHierarchy=True)) + calltips.extend( + self.getCalltips( + acWord, + commas, + context=base, + showContext=showContext, + followHierarchy=True, + ) + ) query.finish() del query - + if context and len(calltips) == 0 and not followHierarchy: # nothing found, try without a context - calltips = self.getCalltips( - acWord, commas, showContext=showContext) - + calltips = self.getCalltips(acWord, commas, showContext=showContext) + return calltips - + def __enoughCommas(self, s, commas): """ Private method to determine, if the given string contains enough commas. - + @param s string to check @type str @param commas number of commas to check for @@ -1036,14 +1064,14 @@ @return flag indicating, that there are enough commas @rtype bool """ - end = s.find(')') - + end = s.find(")") + if end < 0: return False - + w = s[:end] - return w.count(',') >= commas - + return w.count(",") >= commas + def __openAPIs(self): """ Private method to open the API database. @@ -1052,28 +1080,26 @@ if self.__opened: if not self.__isPrepared(): self.__createApiDB() - + # prepare the database if neccessary self.prepareAPIs() - + def __getProjectFormSources(self, normalized=False): """ Private method to get the source files for the project forms. - + @param normalized flag indicating a normalized filename is wanted @type bool @return list of project form sources @rtype list of str """ - if self.__project.getProjectLanguage() in ( - "Python3", "MicroPython", "Cython" - ): + if self.__project.getProjectLanguage() in ("Python3", "MicroPython", "Cython"): sourceExt = ".py" elif self.__project.getProjectLanguage() == "Ruby": sourceExt = ".rb" else: return [] - + formsSources = [] forms = self.__project.getProjectFiles("FORMS") for fn in forms: @@ -1081,42 +1107,48 @@ dirname, filename = os.path.split(ofn) formSource = os.path.join(dirname, "Ui_" + filename + sourceExt) if normalized: - formSource = os.path.join( - self.__project.getProjectPath(), formSource) + formSource = os.path.join(self.__project.getProjectPath(), formSource) formsSources.append(formSource) return formsSources - + def prepareAPIs(self, rawList=None): """ Public method to prepare the APIs if neccessary. - + @param rawList list of raw API files @type list of str """ if self.__inPreparation: return - + projectPath = "" if rawList: apiFiles = rawList[:] elif self.__language == ApisNameProject: apiFiles = self.__project.getSources()[:] apiFiles.extend( - [f for f in self.__getProjectFormSources() if - f not in apiFiles]) + [f for f in self.__getProjectFormSources() if f not in apiFiles] + ) projectPath = self.__project.getProjectPath() projectType = "" else: apiFiles = Preferences.getEditorAPI( - self.__language, projectType=self.__projectType) + self.__language, projectType=self.__projectType + ) projectType = self.__projectType - self.__worker = DbAPIsWorker(self, self.__language, apiFiles, - self._apiDbName(), projectPath, - projectType=projectType) + self.__worker = DbAPIsWorker( + self, + self.__language, + apiFiles, + self._apiDbName(), + projectPath, + projectType=projectType, + ) self.__worker.processing.connect( - self.__processingStatus, Qt.ConnectionType.QueuedConnection) + self.__processingStatus, Qt.ConnectionType.QueuedConnection + ) self.__worker.start() - + def __processQueue(self): """ Private slot to process the queue of files to load. @@ -1124,7 +1156,7 @@ if self.__worker is not None and self.__worker.isFinished(): self.__worker.deleteLater() self.__worker = None - + if self.__worker is None and len(self.__workerQueue) > 0: apiFiles = [self.__workerQueue.pop(0)] if self.__language == ApisNameProject: @@ -1134,38 +1166,45 @@ else: projectPath = "" projectType = self.__projectType - self.__worker = DbAPIsWorker(self, self.__language, apiFiles, - self._apiDbName(), projectPath, - projectType=projectType, refresh=True) + self.__worker = DbAPIsWorker( + self, + self.__language, + apiFiles, + self._apiDbName(), + projectPath, + projectType=projectType, + refresh=True, + ) self.__worker.processing.connect( - self.__processingStatus, Qt.ConnectionType.QueuedConnection) + self.__processingStatus, Qt.ConnectionType.QueuedConnection + ) self.__worker.start() - + def getLexer(self): """ Public method to return a reference to our lexer object. - + @return reference to the lexer object @rtype Lexer """ return self.__lexer - + def autoCompletionWordSeparators(self): """ Public method to get the word separator characters. - + @return word separator characters @rtype list of str """ if self.__lexer: return self.__lexer.autoCompletionWordSeparators() return None - + def __processingStatus(self, status, filename): """ Private slot handling the processing signal of the API preparation thread. - + @param status preparation status (one of WorkerStatus...) @type int @param filename name of the file being processed @@ -1173,56 +1212,49 @@ """ if status == WorkerStatusStarted: self.__inPreparation = True - self.apiPreparationStatus.emit( - self.__language, WorkerStatusStarted, "") + self.apiPreparationStatus.emit(self.__language, WorkerStatusStarted, "") elif status == WorkerStatusFinished: self.__inPreparation = False - self.apiPreparationStatus.emit( - self.__language, WorkerStatusFinished, "") + self.apiPreparationStatus.emit(self.__language, WorkerStatusFinished, "") QTimer.singleShot(0, self.__processQueue) elif status == WorkerStatusAborted: self.__inPreparation = False - self.apiPreparationStatus.emit( - self.__language, WorkerStatusAborted, "") + self.apiPreparationStatus.emit(self.__language, WorkerStatusAborted, "") QTimer.singleShot(0, self.__processQueue) elif status == WorkerStatusFile: - self.apiPreparationStatus.emit( - self.__language, WorkerStatusFile, filename) - + self.apiPreparationStatus.emit(self.__language, WorkerStatusFile, filename) + ######################################################## ## project related stuff below ######################################################## - + def __projectOpened(self): """ Private slot to perform actions after a project has been opened. """ - if self.__project.getProjectLanguage() in ( - "Python3", "MicroPython", "Cython" - ): + if self.__project.getProjectLanguage() in ("Python3", "MicroPython", "Cython"): self.__discardFirst = ["self", "cls"] else: self.__discardFirst = [] - self.__lexer = QScintilla.Lexers.getLexer( - self.__project.getProjectLanguage()) + self.__lexer = QScintilla.Lexers.getLexer(self.__project.getProjectLanguage()) self.__openAPIs() - + def __projectClosed(self): """ Private slot to perform actions after a project has been closed. """ self.close() - + def __projectFormCompiled(self, filename): """ Private slot to handle the projectFormCompiled signal. - + @param filename name of the form file that was compiled @type str """ self.__workerQueue.append(filename) self.__processQueue() - + def __projectChanged(self): """ Private slot to handle the projectChanged signal. @@ -1230,11 +1262,11 @@ if self.__opened: self.__projectClosed() self.__projectOpened() - + def editorSaved(self, filename): """ Public slot to handle the editorSaved signal. - + @param filename name of the file that was saved @type str """ @@ -1248,10 +1280,11 @@ Class implementing the APIsManager class, which is the central store for API information used by autocompletion and calltips. """ + def __init__(self, mainWindow, parent=None): """ Constructor - + @param mainWindow reference to the main eric7 window @type QMainWindow @param parent reference to the parent object @@ -1259,27 +1292,27 @@ """ QObject.__init__(self, parent) self.setObjectName("Assistant_APIsManager") - + self.__mw = mainWindow - + # initialize the apis dictionary self.__apis = {} - + def reloadAPIs(self): """ Public slot to reload the api information. """ for api in list(self.__apis.values()): api and api.prepareAPIs() - + def getAPIs(self, language, projectType=""): """ Public method to get an apis object for autocompletion/calltips. - + This method creates and loads an APIs object dynamically upon request. This saves memory for languages, that might not be needed at the moment. - + @param language language of the requested APIs object @type str @param projectType type of the project @@ -1291,8 +1324,8 @@ return self.__apis[(language, projectType)] except KeyError: if ( - language in QScintilla.Lexers.getSupportedApiLanguages() or - language == ApisNameProject + language in QScintilla.Lexers.getSupportedApiLanguages() + or language == ApisNameProject ): # create the api object api = DbAPIs(language, projectType=projectType) @@ -1301,7 +1334,7 @@ return self.__apis[(language, projectType)] else: return None - + def deactivate(self): """ Public method to perform actions upon deactivation. @@ -1310,21 +1343,21 @@ self.__apis[apiLang].close() self.__apis[apiLang].deleteLater() self.__apis[apiLang] = None - + def __showMessage(self, msg): """ Private message to show a message in the main windows status bar. - + @param msg message to be shown @type str """ if msg: self.__mw.statusBar().showMessage(msg, 2000) - + def __apiPreparationStatus(self, language, status, filename): """ Private slot handling the preparation status signal of an API object. - + @param language language of the API @type str @param status preparation status (one of WorkerStatus...) @@ -1334,20 +1367,26 @@ """ if language == ApisNameProject: language = self.tr("Project") - + if status == WorkerStatusStarted: - self.__showMessage(self.tr( - "Preparation of '{0}' APIs started.").format(language)) + self.__showMessage( + self.tr("Preparation of '{0}' APIs started.").format(language) + ) elif status == WorkerStatusFile: - self.__showMessage(self.tr( - "'{0}' APIs: Processing '{1}'").format( - language, os.path.basename(filename))) + self.__showMessage( + self.tr("'{0}' APIs: Processing '{1}'").format( + language, os.path.basename(filename) + ) + ) elif status == WorkerStatusFinished: - self.__showMessage(self.tr( - "Preparation of '{0}' APIs finished.").format(language)) + self.__showMessage( + self.tr("Preparation of '{0}' APIs finished.").format(language) + ) elif status == WorkerStatusAborted: - self.__showMessage(self.tr( - "Preparation of '{0}' APIs cancelled.").format(language)) + self.__showMessage( + self.tr("Preparation of '{0}' APIs cancelled.").format(language) + ) + # # eflag: noqa = M523, M834, S608
--- a/AssistantEric/Assistant.py Thu Dec 30 11:32:05 2021 +0100 +++ b/AssistantEric/Assistant.py Wed Sep 21 16:59:53 2022 +0200 @@ -26,75 +26,77 @@ """ Class implementing the autocompletion and calltips system. """ + def __init__(self, plugin, parent=None): """ Constructor - + @param plugin reference to the plugin object @type AssistantEricPlugin @param parent parent @type QObject """ QObject.__init__(self, parent) - + self.__plugin = plugin self.__ui = parent self.__project = ericApp().getObject("Project") self.__viewmanager = ericApp().getObject("ViewManager") self.__pluginManager = ericApp().getObject("PluginManager") - + self.__apisManager = APIsManager(self.__ui, self) - + self.__editors = [] self.__lastContext = None self.__lastFullContext = None - + from QScintilla.Editor import Editor + self.__fromDocumentID = Editor.FromDocumentID - + def activate(self): """ Public method to perform actions upon activation. """ self.__pluginManager.shutdown.connect(self.__shutdown) - + self.__ui.preferencesChanged.connect(self.__preferencesChanged) - + self.__viewmanager.editorOpenedEd.connect(self.__editorOpened) self.__viewmanager.editorClosedEd.connect(self.__editorClosed) - + # preload the project APIs object self.__apisManager.getAPIs(ApisNameProject) - + for editor in self.__viewmanager.getOpenEditors(): self.__editorOpened(editor) - + def deactivate(self): """ Public method to perform actions upon deactivation. """ self.__pluginManager.shutdown.disconnect(self.__shutdown) - + self.__ui.preferencesChanged.disconnect(self.__preferencesChanged) - + self.__viewmanager.editorOpenedEd.disconnect(self.__editorOpened) self.__viewmanager.editorClosedEd.disconnect(self.__editorClosed) - + self.__shutdown() - + def __shutdown(self): """ Private slot to handle the shutdown signal. """ for editor in self.__editors[:]: self.__editorClosed(editor) - + self.__apisManager.deactivate() - + def setEnabled(self, key, enabled): """ Public method to enable or disable a feature. - + @param key feature to set @type str @param enabled flag indicating the status @@ -104,11 +106,11 @@ self.__editorClosed(editor) for editor in self.__viewmanager.getOpenEditors(): self.__editorOpened(editor) - + def __editorOpened(self, editor): """ Private slot called, when a new editor was opened. - + @param editor reference to the new editor @type Editor """ @@ -117,41 +119,43 @@ if self.__plugin.getPreferences("CalltipsEnabled"): self.__setCalltipsHook(editor) editor.editorSaved.connect( - self.__apisManager.getAPIs(ApisNameProject).editorSaved) + self.__apisManager.getAPIs(ApisNameProject).editorSaved + ) self.__editors.append(editor) - + # preload the api to give the manager a chance to prepare the database language = editor.getApiLanguage() if language: projectType = self.__getProjectType(editor) self.__apisManager.getAPIs(language, projectType=projectType) - + def __editorClosed(self, editor): """ Private slot called, when an editor was closed. - + @param editor reference to the editor @type Editor """ if editor in self.__editors: editor.editorSaved.disconnect( - self.__apisManager.getAPIs(ApisNameProject).editorSaved) + self.__apisManager.getAPIs(ApisNameProject).editorSaved + ) self.__editors.remove(editor) if editor.getCompletionListHook("Assistant"): self.__unsetAutoCompletionHook(editor) if editor.getCallTipHook("Assistant"): self.__unsetCalltipsHook(editor) - + def __preferencesChanged(self): """ Private method to handle a change of the global configuration. """ self.__apisManager.reloadAPIs() - + def __getProjectType(self, editor): """ Private method to determine the project type to be used. - + @param editor reference to the editor to check @type Editor @return project type @@ -160,61 +164,63 @@ filename = editor.getFileName() projectType = ( self.__project.getProjectType() - if (self.__project.isOpen() and - filename and - self.__project.isProjectFile(filename)) else - "" + if ( + self.__project.isOpen() + and filename + and self.__project.isProjectFile(filename) + ) + else "" ) - + return projectType - + ################################# ## auto-completion methods below ################################# - + def __recordSelectedContext(self, userListId, txt): """ Private slot to handle the selection from the completion list to record the selected completion context. - + @param userListId the ID of the user list (should be 1) @type int @param txt the selected text @type str """ from QScintilla.Editor import EditorAutoCompletionListID - + if userListId == EditorAutoCompletionListID: lst = txt.split() if len(lst) > 1: self.__lastFullContext = lst[1][1:].split(")")[0] else: self.__lastFullContext = None - + def __setAutoCompletionHook(self, editor): """ Private method to set the autocompletion hook. - + @param editor reference to the editor @type Editor """ editor.userListActivated.connect(self.__recordSelectedContext) editor.addCompletionListHook("Assistant", self.getCompletionsList) - + def __unsetAutoCompletionHook(self, editor): """ Private method to unset the autocompletion hook. - + @param editor reference to the editor @type Editor """ editor.userListActivated.disconnect(self.__recordSelectedContext) editor.removeCompletionListHook("Assistant") - + def getCompletionsList(self, editor, context): """ Public method to get a list of possible completions. - + @param editor reference to the editor object, that called this method @type Editor @param context flag indicating to autocomplete a context @@ -225,54 +231,48 @@ language = editor.getApiLanguage() completeFromDocumentOnly = False if language in ["", "Guessed"] or language.startswith("Pygments|"): - if ( - self.__plugin.getPreferences("AutoCompletionSource") & - AcsDocument - ): + if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: completeFromDocumentOnly = True else: return [] - + projectType = self.__getProjectType(editor) - + line, col = editor.getCursorPosition() sep = "" if language and context: wc = re.sub("\w", "", editor.wordCharacters()) pat = re.compile("\w{0}".format(re.escape(wc))) text = editor.text(line) - + beg = text[:col] for wsep in editor.getLexer().autoCompletionWordSeparators(): if beg.endswith(wsep): sep = wsep break - + depth = 0 - while ( - col > 0 and - not pat.match(text[col - 1]) - ): + while col > 0 and not pat.match(text[col - 1]): ch = text[col - 1] - if ch == ')': + if ch == ")": depth = 1 - + # ignore everything back to the start of the # corresponding parenthesis col -= 1 while col > 0: ch = text[col - 1] - if ch == ')': + if ch == ")": depth += 1 - elif ch == '(': + elif ch == "(": depth -= 1 if depth == 0: break col -= 1 - elif ch == '(': + elif ch == "(": break col -= 1 - + word = editor.getWordLeft(line, col) if context and not sep: # no separator was found -> no context completion @@ -281,16 +281,12 @@ self.__lastContext = word else: self.__lastContext = None - + prefix = "" mod = None - beg = beg[:col + 1] if context else editor.text(line)[:col] + beg = beg[: col + 1] if context else editor.text(line)[:col] col = len(beg) - wseps = ( - editor.getLexer().autoCompletionWordSeparators() - if language else - [] - ) + wseps = editor.getLexer().autoCompletionWordSeparators() if language else [] if wseps: wseps.append(" ") if col > 0 and beg[col - 1] in wseps: @@ -303,13 +299,14 @@ prefix = editor.getWordLeft(line, col) if editor.isPyFile(): from Utilities.ModuleParser import Module + src = editor.text() fn = editor.getFileName() if fn is None: fn = "" mod = Module("", fn, imp.PY_SOURCE) mod.scan(src) - + importCompletion = False if editor.isPyFile(): # check, if we are completing a from import statement @@ -332,28 +329,57 @@ while col >= 0 and prefix[col] not in wseps: col -= 1 if col >= 0: - prefix = prefix[col + 1:] + prefix = prefix[col + 1 :] if word == tokens[2]: word = "" - + if word or importCompletion: completionsList = self.__getCompletions( - word, context, prefix, language, projectType, mod, editor, - importCompletion, completeFromDocumentOnly, sep) + word, + context, + prefix, + language, + projectType, + mod, + editor, + importCompletion, + completeFromDocumentOnly, + sep, + ) if len(completionsList) == 0 and prefix: # searching with prefix didn't return anything, try without completionsList = self.__getCompletions( - word, context, "", language, projectType, mod, editor, - importCompletion, completeFromDocumentOnly, sep) + word, + context, + "", + language, + projectType, + mod, + editor, + importCompletion, + completeFromDocumentOnly, + sep, + ) return completionsList - + return [] - - def __getCompletions(self, word, context, prefix, language, projectType, - module, editor, importCompletion, documentOnly, sep): + + def __getCompletions( + self, + word, + context, + prefix, + language, + projectType, + module, + editor, + importCompletion, + documentOnly, + sep, + ): """ Private method to get the list of possible completions. - + @param word word (or wordpart) to complete @type str @param context flag indicating to autocomplete a context @@ -380,41 +406,39 @@ apiCompletionsList = [] docCompletionsList = [] projectCompletionList = [] - + if not documentOnly: if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: - api = self.__apisManager.getAPIs( - language, projectType=projectType) + api = self.__apisManager.getAPIs(language, projectType=projectType) apiCompletionsList = self.__getApiCompletions( - api, word, context, prefix, module, editor) - - if ( - self.__plugin.getPreferences("AutoCompletionSource") & - AcsProject - ): + api, word, context, prefix, module, editor + ) + + if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: api = self.__apisManager.getAPIs(ApisNameProject) projectCompletionList = self.__getApiCompletions( - api, word, context, prefix, module, editor) - + api, word, context, prefix, module, editor + ) + if ( - self.__plugin.getPreferences("AutoCompletionSource") & - AcsDocument and - not importCompletion + self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument + and not importCompletion ): docCompletionsList = self.__getDocumentCompletions( - editor, word, context, sep, prefix, module) - + editor, word, context, sep, prefix, module + ) + completionsList = list( set(apiCompletionsList) .union(set(docCompletionsList)) .union(set(projectCompletionList)) ) return completionsList - + def __getApiCompletions(self, api, word, context, prefix, module, editor): """ Private method to determine a list of completions from an API object. - + @param api reference to the API object to be used @type APIsManager.DbAPIs @param word word (or wordpart) to complete @@ -435,9 +459,8 @@ if prefix and module and prefix == "self": line, col = editor.getCursorPosition() for cl in module.classes.values(): - if ( - line >= cl.lineno and - (cl.endlineno == -1 or line <= cl.endlineno) + if line >= cl.lineno and ( + cl.endlineno == -1 or line <= cl.endlineno ): completions = [] for superClass in cl.super: @@ -445,20 +468,19 @@ completions.extend( api.getCompletions( context=superClass, - followHierarchy=self.__plugin - .getPreferences( + followHierarchy=self.__plugin.getPreferences( "AutoCompletionFollowHierarchy" - ) + ), ) ) else: completions.extend( api.getCompletions( - start=word, context=superClass, - followHierarchy=self.__plugin - .getPreferences( + start=word, + context=superClass, + followHierarchy=self.__plugin.getPreferences( "AutoCompletionFollowHierarchy" - ) + ), ) ) for completion in completions: @@ -466,8 +488,7 @@ entry = completion["completion"] else: entry = "{0} ({1})".format( - completion["completion"], - completion["context"] + completion["completion"], completion["context"] ) if entry in completionsList: completionsList.remove(entry) @@ -475,8 +496,7 @@ entry += "?{0}".format(completion["pictureId"]) else: cont = False - regexp = re.compile( - re.escape(entry) + r"\?\d{,2}") + regexp = re.compile(re.escape(entry) + r"\?\d{,2}") for comp in completionsList: if regexp.fullmatch(comp): cont = True @@ -485,7 +505,7 @@ continue if entry not in completionsList: completionsList.append(entry) - + break elif context: completions = api.getCompletions(context=word) @@ -497,8 +517,7 @@ completionsList.append(entry) else: if prefix: - completions = api.getCompletions( - start=word, context=prefix) + completions = api.getCompletions(start=word, context=prefix) if not prefix or not completions: # if no completions were returned try without prefix completions = api.getCompletions(start=word) @@ -507,8 +526,7 @@ entry = completion["completion"] else: entry = "{0} ({1})".format( - completion["completion"], - completion["context"] + completion["completion"], completion["context"] ) if entry in completionsList: completionsList.remove(entry) @@ -526,12 +544,13 @@ if entry not in completionsList: completionsList.append(entry) return completionsList - - def __getDocumentCompletions(self, editor, word, context, sep, prefix, - module, doHierarchy=False): + + def __getDocumentCompletions( + self, editor, word, context, sep, prefix, module, doHierarchy=False + ): """ Private method to determine autocompletion proposals from the document. - + @param editor reference to the editor object @type Editor @param word string to be completed @@ -550,18 +569,17 @@ @rtype list of str """ completionsList = [] - + prefixFound = False if prefix and module: from QScintilla.Editor import Editor - + line, col = editor.getCursorPosition() if prefix in ["cls", "self"]: prefixFound = True for cl in module.classes.values(): - if ( - line >= cl.lineno and - (cl.endlineno == -1 or line <= cl.endlineno) + if line >= cl.lineno and ( + cl.endlineno == -1 or line <= cl.endlineno ): comps = [] for method in cl.methods.values(): @@ -575,10 +593,8 @@ else: iconID = Editor.MethodID if ( - (prefix == "cls" and - method.modifier == method.Class) or - prefix == "self" - ): + prefix == "cls" and method.modifier == method.Class + ) or prefix == "self": comps.append((method.name, cl.name, iconID)) if prefix != "cls": for attribute in cl.attributes.values(): @@ -599,16 +615,23 @@ else: iconID = Editor.AttributeID comps.append((attribute.name, cl.name, iconID)) - + if word != prefix: completionsList.extend( - ["{0} ({1})?{2}".format(c[0], c[1], c[2]) - for c in comps if c[0].startswith(word)]) + [ + "{0} ({1})?{2}".format(c[0], c[1], c[2]) + for c in comps + if c[0].startswith(word) + ] + ) else: completionsList.extend( - ["{0} ({1})?{2}".format(c[0], c[1], c[2]) - for c in comps]) - + [ + "{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: @@ -617,9 +640,16 @@ nword = word completionsList.extend( self.__getDocumentCompletions( - editor, nword, context, sep, sup, - module, doHierarchy=True)) - + editor, + nword, + context, + sep, + sup, + module, + doHierarchy=True, + ) + ) + break else: # possibly completing a named class attribute or method @@ -630,10 +660,10 @@ for method in cl.methods.values(): if method.name == "__init__": continue - if ( - doHierarchy or - 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: @@ -653,16 +683,20 @@ else: iconID = Editor.AttributeID comps.append((attribute.name, cl.name, iconID)) - + if word != prefix: completionsList.extend( - ["{0} ({1})?{2}".format(c[0], c[1], c[2]) - for c in comps if c[0].startswith(word)]) + [ + "{0} ({1})?{2}".format(c[0], c[1], c[2]) + for c in comps + if c[0].startswith(word) + ] + ) else: completionsList.extend( - ["{0} ({1})?{2}".format(c[0], c[1], c[2]) - for c in comps]) - + ["{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: @@ -671,21 +705,34 @@ nword = word completionsList.extend( self.__getDocumentCompletions( - editor, nword, context, sep, sup, module, - doHierarchy=True)) - + editor, + nword, + context, + sep, + sup, + module, + doHierarchy=True, + ) + ) + if not prefixFound: currentPos = editor.currentPosition() if context: word += sep - + if editor.isUtf8(): sword = word.encode("utf-8") else: sword = word res = editor.findFirstTarget( - sword, False, editor.autoCompletionCaseSensitivity(), - False, begline=0, begindex=0, ws_=True) + sword, + False, + editor.autoCompletionCaseSensitivity(), + False, + begline=0, + begindex=0, + ws_=True, + ) while res: start, length = editor.getFoundTarget() pos = start + length @@ -696,47 +743,47 @@ completion = word line, index = editor.lineIndexFromPosition(pos) curWord = editor.getWord(line, index, useWordChars=False) - completion += curWord[len(completion):] + completion += curWord[len(completion) :] if ( - completion and - completion not in completionsList and - completion != word + completion + and completion not in completionsList + and completion != word ): completionsList.append( - "{0}?{1}".format( - completion, self.__fromDocumentID)) - + "{0}?{1}".format(completion, self.__fromDocumentID) + ) + res = editor.findNextTarget() - + completionsList.sort() return completionsList - + ########################### ## calltips methods below ########################### - + def __setCalltipsHook(self, editor): """ Private method to set the calltip hook. - + @param editor reference to the editor @type Editor """ editor.addCallTipHook("Assistant", self.calltips) - + def __unsetCalltipsHook(self, editor): """ Private method to unset the calltip hook. - + @param editor reference to the editor @type Editor """ editor.removeCallTipHook("Assistant") - + def calltips(self, editor, pos, commas): """ Public method to return a list of calltips. - + @param editor reference to the editor @type Editor @param pos position in the text for the calltip @@ -749,16 +796,13 @@ language = editor.getApiLanguage() completeFromDocumentOnly = False if language in ["", "Guessed"] or language.startswith("Pygments|"): - if ( - self.__plugin.getPreferences("AutoCompletionSource") & - AcsDocument - ): + if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: completeFromDocumentOnly = True else: return [] - + projectType = self.__getProjectType(editor) - + line, col = editor.lineIndexFromPosition(pos) wc = re.sub("\w", "", editor.wordCharacters()) pat = re.compile("\w{0}".format(re.escape(wc))) @@ -766,16 +810,12 @@ while col > 0 and not pat.match(text[col - 1]): col -= 1 word = editor.getWordLeft(line, col) - + prefix = "" mod = None beg = editor.text(line)[:col] col = len(beg) - wseps = ( - editor.getLexer().autoCompletionWordSeparators() - if language else - [] - ) + wseps = editor.getLexer().autoCompletionWordSeparators() if language else [] if wseps: if col > 0 and beg[col - 1] in wseps: col -= 1 @@ -787,47 +827,43 @@ prefix = editor.getWordLeft(line, col) if editor.isPyFile(): from Utilities.ModuleParser import Module + src = editor.text() fn = editor.getFileName() if fn is None: fn = "" mod = Module("", fn, imp.PY_SOURCE) mod.scan(src) - + apiCalltips = [] projectCalltips = [] documentCalltips = [] - + if not completeFromDocumentOnly: if self.__plugin.getPreferences("AutoCompletionSource") & AcsAPIs: - api = self.__apisManager.getAPIs( - language, projectType=projectType) + api = self.__apisManager.getAPIs(language, projectType=projectType) if api is not None: apiCalltips = self.__getApiCalltips( - api, word, commas, prefix, mod, editor) - - if ( - self.__plugin.getPreferences("AutoCompletionSource") & - AcsProject - ): + api, word, commas, prefix, mod, editor + ) + + if self.__plugin.getPreferences("AutoCompletionSource") & AcsProject: api = self.__apisManager.getAPIs(ApisNameProject) projectCalltips = self.__getApiCalltips( - api, word, commas, prefix, mod, editor) - + api, word, commas, prefix, mod, editor + ) + if self.__plugin.getPreferences("AutoCompletionSource") & AcsDocument: - documentCalltips = self.__getDocumentCalltips( - word, prefix, mod, editor) - + documentCalltips = self.__getDocumentCalltips(word, prefix, mod, editor) + return sorted( - set(apiCalltips) - .union(set(projectCalltips)) - .union(set(documentCalltips)) + set(apiCalltips).union(set(projectCalltips)).union(set(documentCalltips)) ) - + def __getApiCalltips(self, api, word, commas, prefix, module, editor): """ Private method to determine calltips from APIs. - + @param api reference to the API object to be used @type APIsManager.DbAPIs @param word function to get calltips for @@ -847,30 +883,36 @@ if prefix and module and prefix == "self": line, col = editor.getCursorPosition() for cl in module.classes.values(): - if ( - line >= cl.lineno and - (cl.endlineno == -1 or line <= cl.endlineno) - ): + if line >= cl.lineno and (cl.endlineno == -1 or line <= cl.endlineno): for superClass in cl.super: - calltips.extend(api.getCalltips( - word, commas, superClass, None, - self.__plugin.getPreferences( - "CallTipsContextShown"), - followHierarchy=self.__plugin.getPreferences( - "CallTipsFollowHierarchy"))) + calltips.extend( + api.getCalltips( + word, + commas, + superClass, + None, + self.__plugin.getPreferences("CallTipsContextShown"), + followHierarchy=self.__plugin.getPreferences( + "CallTipsFollowHierarchy" + ), + ) + ) break else: calltips = api.getCalltips( - word, commas, self.__lastContext, self.__lastFullContext, - self.__plugin.getPreferences("CallTipsContextShown")) - + word, + commas, + self.__lastContext, + self.__lastFullContext, + self.__plugin.getPreferences("CallTipsContextShown"), + ) + return calltips - - def __getDocumentCalltips(self, word, prefix, module, editor, - doHierarchy=False): + + def __getDocumentCalltips(self, word, prefix, module, editor, doHierarchy=False): """ Private method to determine calltips from the document. - + @param word function to get calltips for @type str @param prefix prefix of the word to be completed @@ -892,30 +934,30 @@ if prefix in ["self", "cls"]: line, col = editor.getCursorPosition() for cl in module.classes.values(): - if ( - line >= cl.lineno and - (cl.endlineno == -1 or line <= cl.endlineno) + if line >= cl.lineno and ( + cl.endlineno == -1 or line <= cl.endlineno ): if word in cl.methods: method = cl.methods[word] - if ( - prefix == "self" or - (prefix == "cls" and - method.modifier == method.Class) + if prefix == "self" or ( + prefix == "cls" and method.modifier == method.Class ): calltips.append( "{0}{1}{2}({3})".format( cl.name, sep, word, - ', '.join(method.parameters[1:] - ))) - + ", ".join(method.parameters[1:]), + ) + ) + for sup in cl.super: - calltips.extend(self.__getDocumentCalltips( - word, sup, module, editor, - doHierarchy=True)) - + calltips.extend( + self.__getDocumentCalltips( + word, sup, module, editor, doHierarchy=True + ) + ) + break else: if prefix in module.classes: @@ -923,31 +965,36 @@ if word in cl.methods: method = cl.methods[word] if doHierarchy or method.modifier == method.Class: - calltips.append("{0}{1}{2}({3})".format( - cl.name, - sep, - word, - ', '.join(method.parameters[1:]))) - + 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)) + 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))) + calltips.append("{0}({1})".format(word, ", ".join(fun.parameters))) elif word in module.classes: cl = module.classes[word] if "__init__" in cl.methods: method = cl.methods["__init__"] - calltips.append("{0}({1})".format( - word, - ', '.join(method.parameters[1:]))) - + calltips.append( + "{0}({1})".format(word, ", ".join(method.parameters[1:])) + ) + return calltips + # # eflag: noqa = M834, W605
--- a/AssistantEric/ConfigurationPages/AutoCompletionEricPage.py Thu Dec 30 11:32:05 2021 +0100 +++ b/AssistantEric/ConfigurationPages/AutoCompletionEricPage.py Wed Sep 21 16:59:53 2022 +0200 @@ -9,9 +9,7 @@ from AssistantEric.Assistant import AcsAPIs, AcsDocument, AcsProject -from Preferences.ConfigurationPages.ConfigurationPageBase import ( - ConfigurationPageBase -) +from Preferences.ConfigurationPages.ConfigurationPageBase import ConfigurationPageBase from .Ui_AutoCompletionEricPage import Ui_AutoCompletionEricPage @@ -19,38 +17,41 @@ """ Class implementing the Eric Autocompletion configuration page. """ + def __init__(self, plugin): """ Constructor - + @param plugin reference to the plugin object """ ConfigurationPageBase.__init__(self) self.setupUi(self) self.setObjectName("AutoCompletionEricPage") - + self.__plugin = plugin - + # set initial values self.autocompletionCheckBox.setChecked( - self.__plugin.getPreferences("AutoCompletionEnabled")) - + self.__plugin.getPreferences("AutoCompletionEnabled") + ) + acSource = self.__plugin.getPreferences("AutoCompletionSource") self.apisCheckBox.setChecked(acSource & AcsAPIs) self.documentCheckBox.setChecked(acSource & AcsDocument) self.projectCheckBox.setChecked(acSource & AcsProject) - + self.hierarchyCheckBox.setChecked( - self.__plugin.getPreferences("AutoCompletionFollowHierarchy")) - + self.__plugin.getPreferences("AutoCompletionFollowHierarchy") + ) + def save(self): """ Public slot to save the Eric Autocompletion configuration. """ self.__plugin.setPreferences( - "AutoCompletionEnabled", - self.autocompletionCheckBox.isChecked()) - + "AutoCompletionEnabled", self.autocompletionCheckBox.isChecked() + ) + acSource = 0 if self.apisCheckBox.isChecked(): acSource |= AcsAPIs @@ -59,7 +60,7 @@ if self.projectCheckBox.isChecked(): acSource |= AcsProject self.__plugin.setPreferences("AutoCompletionSource", acSource) - + self.__plugin.setPreferences( - "AutoCompletionFollowHierarchy", - self.hierarchyCheckBox.isChecked()) + "AutoCompletionFollowHierarchy", self.hierarchyCheckBox.isChecked() + )
--- a/AssistantEric/ConfigurationPages/CallTipsEricPage.py Thu Dec 30 11:32:05 2021 +0100 +++ b/AssistantEric/ConfigurationPages/CallTipsEricPage.py Wed Sep 21 16:59:53 2022 +0200 @@ -7,9 +7,7 @@ Module implementing the Eric Calltips configuration page. """ -from Preferences.ConfigurationPages.ConfigurationPageBase import ( - ConfigurationPageBase -) +from Preferences.ConfigurationPages.ConfigurationPageBase import ConfigurationPageBase from .Ui_CallTipsEricPage import Ui_CallTipsEricPage @@ -17,36 +15,40 @@ """ Class implementing the Eric Calltips configuration page. """ + def __init__(self, plugin): """ Constructor - + @param plugin reference to the plugin object """ ConfigurationPageBase.__init__(self) self.setupUi(self) self.setObjectName("CallTipsEricPage") - + self.__plugin = plugin - + # set initial values self.calltipsCheckBox.setChecked( - self.__plugin.getPreferences("CalltipsEnabled")) + self.__plugin.getPreferences("CalltipsEnabled") + ) self.ctContextCheckBox.setChecked( - self.__plugin.getPreferences("CallTipsContextShown")) + self.__plugin.getPreferences("CallTipsContextShown") + ) self.hierarchyCheckBox.setChecked( - self.__plugin.getPreferences("CallTipsFollowHierarchy")) - + self.__plugin.getPreferences("CallTipsFollowHierarchy") + ) + def save(self): """ Public slot to save the Eric Calltips configuration. """ self.__plugin.setPreferences( - "CalltipsEnabled", - self.calltipsCheckBox.isChecked()) + "CalltipsEnabled", self.calltipsCheckBox.isChecked() + ) self.__plugin.setPreferences( - "CallTipsContextShown", - self.ctContextCheckBox.isChecked()) + "CallTipsContextShown", self.ctContextCheckBox.isChecked() + ) self.__plugin.setPreferences( - "CallTipsFollowHierarchy", - self.hierarchyCheckBox.isChecked()) + "CallTipsFollowHierarchy", self.hierarchyCheckBox.isChecked() + )
--- a/PluginAssistantEric.py Thu Dec 30 11:32:05 2021 +0100 +++ b/PluginAssistantEric.py Wed Sep 21 16:59:53 2022 +0200 @@ -42,7 +42,7 @@ def createAutoCompletionPage(configDlg): """ Module function to create the autocompletion configuration page. - + @param configDlg reference to the configuration dialog @type ConfigurationWidget @return reference to the configuration page @@ -50,31 +50,31 @@ """ global assistantEricPluginObject from AssistantEric.ConfigurationPages.AutoCompletionEricPage import ( - AutoCompletionEricPage + AutoCompletionEricPage, ) + return AutoCompletionEricPage(assistantEricPluginObject) def createCallTipsPage(configDlg): """ Module function to create the calltips configuration page. - + @param configDlg reference to the configuration dialog @type ConfigurationWidget @return reference to the configuration page @rtype QWidget """ global assistantEricPluginObject - from AssistantEric.ConfigurationPages.CallTipsEricPage import ( - CallTipsEricPage - ) + from AssistantEric.ConfigurationPages.CallTipsEricPage import CallTipsEricPage + return CallTipsEricPage(assistantEricPluginObject) def getConfigData(): """ Module function returning data as required by the configuration dialog. - + @return dictionary containing the relevant data @rtype dict """ @@ -82,22 +82,31 @@ usesDarkPalette = ericApp().usesDarkPalette() except AttributeError: from PyQt6.QtGui import QPalette + palette = ericApp().palette() lightness = palette.color(QPalette.ColorRole.Window).lightness() usesDarkPalette = lightness <= 128 iconSuffix = "dark" if usesDarkPalette else "light" - + return { "ericAutoCompletionPage": [ QCoreApplication.translate("AssistantEricPlugin", "Eric"), - os.path.join("AssistantEric", "ConfigurationPages", - "eric-{0}".format(iconSuffix)), - createAutoCompletionPage, "1editorAutocompletionPage", None], + os.path.join( + "AssistantEric", "ConfigurationPages", "eric-{0}".format(iconSuffix) + ), + createAutoCompletionPage, + "1editorAutocompletionPage", + None, + ], "ericCallTipsPage": [ QCoreApplication.translate("AssistantEricPlugin", "Eric"), - os.path.join("AssistantEric", "ConfigurationPages", - "eric-{0}".format(iconSuffix)), - createCallTipsPage, "1editorCalltipsPage", None], + os.path.join( + "AssistantEric", "ConfigurationPages", "eric-{0}".format(iconSuffix) + ), + createCallTipsPage, + "1editorCalltipsPage", + None, + ], } @@ -106,25 +115,26 @@ Module function to prepare for an uninstallation. """ Preferences.Prefs.settings.remove(AssistantEricPlugin.PreferencesKey) - + class AssistantEricPlugin(QObject): """ Class implementing the Eric assistant plugin. """ + PreferencesKey = "AssistantEric" - + def __init__(self, ui): """ Constructor - + @param ui reference to the user interface object @type UserInterface """ QObject.__init__(self, ui) self.__ui = ui self.__initialize() - + self.__defaults = { "AutoCompletionEnabled": False, "AutoCompletionSource": AcsAPIs | AcsProject, @@ -133,73 +143,73 @@ "CallTipsContextShown": True, "CallTipsFollowHierarchy": False, } - + self.__translator = None self.__loadTranslator() - + def __initialize(self): """ Private slot to (re)initialize the plugin. """ self.__object = None - + def __checkQSql(self): """ Private method to perform some checks on QSql. - + @return flag indicating QSql is ok @rtype bool """ global error - + try: from PyQt6.QtSql import QSqlDatabase except ImportError: error = self.tr("PyQt6.QtSql is not available.") return False - + drivers = QSqlDatabase.drivers() if "QSQLITE" not in drivers: error = self.tr("The SQLite database driver is not available.") return False - + return True - + def activate(self): """ Public method to activate this plugin. - + @return tuple of None and activation status @rtype bool """ global error - error = "" # clear previous error - + error = "" # clear previous error + if not self.__checkQSql(): return None, False - + global assistantEricPluginObject assistantEricPluginObject = self - + from AssistantEric.Assistant import Assistant - + self.__object = Assistant(self, self.__ui) ericApp().registerPluginObject("AssistantEric", self.__object) - + self.__object.activate() - + return None, True - + def deactivate(self): """ Public method to deactivate this plugin. """ ericApp().unregisterPluginObject("AssistantEric") - + self.__object.deactivate() - + self.__initialize() - + def __loadTranslator(self): """ Private method to load the translation file. @@ -208,7 +218,8 @@ loc = self.__ui.getLocale() if loc and loc != "C": locale_dir = os.path.join( - os.path.dirname(__file__), "AssistantEric", "i18n") + os.path.dirname(__file__), "AssistantEric", "i18n" + ) translation = "assistant_{0}".format(loc) translator = QTranslator(None) loaded = translator.load(translation, locale_dir) @@ -216,42 +227,54 @@ self.__translator = translator ericApp().installTranslator(self.__translator) else: - print("Warning: translation file '{0}' could not be" - " loaded.".format(translation)) + print( + "Warning: translation file '{0}' could not be" + " loaded.".format(translation) + ) print("Using default.") - + def getPreferences(self, key): """ Public method to retrieve the various settings. - + @param key the key of the value to get @type str @return value of the requested setting @rtype Any """ - if key in ["AutoCompletionEnabled", "AutoCompletionFollowHierarchy", - "CalltipsEnabled", "CallTipsContextShown", - "CallTipsFollowHierarchy"]: - return Preferences.toBool(Preferences.Prefs.settings.value( - self.PreferencesKey + "/" + key, self.__defaults[key])) + if key in [ + "AutoCompletionEnabled", + "AutoCompletionFollowHierarchy", + "CalltipsEnabled", + "CallTipsContextShown", + "CallTipsFollowHierarchy", + ]: + return Preferences.toBool( + Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key] + ) + ) else: - return int(Preferences.Prefs.settings.value( - self.PreferencesKey + "/" + key, self.__defaults[key])) - + return int( + Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key] + ) + ) + def setPreferences(self, key, value): """ Public method to store the various settings. - + @param key the key of the setting to be set @type str @param value value to be set @type Any """ - Preferences.Prefs.settings.setValue( - self.PreferencesKey + "/" + key, value) - + Preferences.Prefs.settings.setValue(self.PreferencesKey + "/" + key, value) + if key in ["AutoCompletionEnabled", "CalltipsEnabled"]: self.__object.setEnabled(key, value) + # # eflag: noqa = M801
--- a/PluginEricAssistant.epj Thu Dec 30 11:32:05 2021 +0100 +++ b/PluginEricAssistant.epj Wed Sep 21 16:59:53 2022 +0200 @@ -1,19 +1,21 @@ { "header": { "comment": "eric project file for project PluginEricAssistant", - "copyright": "Copyright (C) 2021 Detlev Offenbach, detlev@die-offenbachs.de" + "copyright": "Copyright (C) 2022 Detlev Offenbach, detlev@die-offenbachs.de" }, "project": { "AUTHOR": "Detlev Offenbach", "CHECKERSPARMS": { "Pep8Checker": { "AnnotationsChecker": { + "AllowStarArgAny": false, "AllowUntypedDefs": false, "AllowUntypedNested": false, "DispatchDecorators": [ "singledispatch", "singledispatchmethod" ], + "ForceFutureAnnotations": false, "MaximumComplexity": 3, "MaximumLength": 7, "MinimumCoverage": 75, @@ -59,20 +61,25 @@ }, "CopyrightAuthor": "", "CopyrightMinFileSize": 0, - "DocstringType": "eric", + "DocstringType": "eric_black", "EnabledCheckerCategories": "C, D, E, M, N, Y, W", "ExcludeFiles": "*/Ui_*.py, */*_rc.py", - "ExcludeMessages": "C101,E265,E266,E305,E402,M201,M301,M302,M303,M304,M305,M306,M307,M308,M311,M312,M313,M314,M315,M321,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W504,Y401,Y402", + "ExcludeMessages": "C101,E203,E265,E266,E305,E402,M201,M301,M302,M303,M304,M305,M306,M307,M308,M311,M312,M313,M314,M315,M321,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W503,Y401,Y402", "FixCodes": "", "FixIssues": false, "FutureChecker": "", "HangClosing": false, + "ImportsChecker": { + "ApplicationPackageNames": [], + "BanRelativeImports": "", + "BannedModules": [] + }, "IncludeMessages": "", "LineComplexity": 25, "LineComplexityScore": 10, "MaxCodeComplexity": 10, - "MaxDocLineLength": 79, - "MaxLineLength": 79, + "MaxDocLineLength": 88, + "MaxLineLength": 88, "NoFixCodes": "E501", "RepeatMessages": true, "SecurityChecker": { @@ -128,6 +135,7 @@ } }, "EMAIL": "detlev@die-offenbachs.de", + "EMBEDDED_VENV": false, "EOL": 1, "FILETYPES": { "*.e4p": "OTHERS", @@ -162,6 +170,7 @@ }, "INTERFACES": [], "LEXERASSOCS": {}, + "LICENSE": "GNU General Public License v3 or later (GPLv3+)", "MAINSCRIPT": "PluginAssistantEric.py", "MAKEPARAMS": { "MakeEnabled": false, @@ -183,7 +192,24 @@ "PluginAssistantEric.zip", "PluginEricAssistant.epj" ], - "OTHERTOOLSPARMS": {}, + "OTHERTOOLSPARMS": { + "Black": { + "exclude": "/(\\.direnv|\\.eggs|\\.git|\\.hg|\\.mypy_cache|\\.nox|\\.tox|\\.venv|venv|\\.svn|_build|buck-out|build|dist|__pypackages__)/", + "extend-exclude": "", + "force-exclude": "", + "line-length": 88, + "skip-magic-trailing-comma": false, + "skip-string-normalization": false, + "source": "project", + "target-version": [ + "py311", + "py310", + "py39", + "py38", + "py37" + ] + } + }, "PACKAGERSPARMS": {}, "PROGLANGUAGE": "Python3", "PROJECTTYPE": "E7Plugin", @@ -204,13 +230,12 @@ "AssistantEric/ConfigurationPages/__init__.py", "AssistantEric/__init__.py", "PluginAssistantEric.py", - "__init__.py", - "AssistantEric/ConfigurationPages/Ui_AutoCompletionEricPage.py", - "AssistantEric/ConfigurationPages/Ui_CallTipsEricPage.py" + "__init__.py" ], "SPELLEXCLUDES": "", "SPELLLANGUAGE": "en", "SPELLWORDS": "", + "TESTING_FRAMEWORK": "", "TRANSLATIONEXCEPTIONS": [], "TRANSLATIONPATTERN": "AssistantEric/i18n/assistant_%language%.ts", "TRANSLATIONS": [