AssistantEric/APIsManager.py

branch
eric7
changeset 190
3104a5a3ea13
parent 186
c228779ea15d
child 191
6798a98189da
equal deleted inserted replaced
189:c0d638327085 190:3104a5a3ea13
8 """ 8 """
9 9
10 import contextlib 10 import contextlib
11 import os 11 import os
12 12
13 from PyQt6.QtCore import ( 13 from PyQt6.QtCore import QTimer, QThread, QFileInfo, pyqtSignal, QDateTime, QObject, Qt
14 QTimer, QThread, QFileInfo, pyqtSignal, QDateTime, QObject, Qt 14
15 )
16 with contextlib.suppress(ImportError): 15 with contextlib.suppress(ImportError):
17 from PyQt6.QtSql import QSqlDatabase, QSqlQuery 16 from PyQt6.QtSql import QSqlDatabase, QSqlQuery
18 17
19 from EricWidgets.EricApplication import ericApp 18 from EricWidgets.EricApplication import ericApp
20 19
34 33
35 34
36 class DbAPIsWorker(QThread): 35 class DbAPIsWorker(QThread):
37 """ 36 """
38 Class implementing a worker thread to prepare the API database. 37 Class implementing a worker thread to prepare the API database.
39 38
40 @signal processing(status, file) emitted to indicate the processing status 39 @signal processing(status, file) emitted to indicate the processing status
41 (one of WorkerStatus..., string) 40 (one of WorkerStatus..., string)
42 """ 41 """
42
43 processing = pyqtSignal(int, str) 43 processing = pyqtSignal(int, str)
44 44
45 populate_api_stmt = """ 45 populate_api_stmt = """
46 INSERT INTO api ( 46 INSERT INTO api (
47 acWord, context, fullContext, signature, fileId, pictureId) 47 acWord, context, fullContext, signature, fileId, pictureId)
48 VALUES ( 48 VALUES (
49 :acWord, :context, :fullContext, :signature, :fileId, :pictureId) 49 :acWord, :context, :fullContext, :signature, :fileId, :pictureId)
76 """ 76 """
77 77
78 api_files_stmt = """ 78 api_files_stmt = """
79 SELECT file FROM file 79 SELECT file FROM file
80 """ 80 """
81 81
82 def __init__(self, proxy, language, apiFiles, dbName, projectPath="", 82 def __init__(
83 refresh=False, projectType=""): 83 self,
84 proxy,
85 language,
86 apiFiles,
87 dbName,
88 projectPath="",
89 refresh=False,
90 projectType="",
91 ):
84 """ 92 """
85 Constructor 93 Constructor
86 94
87 @param proxy reference to the object that is proxied 95 @param proxy reference to the object that is proxied
88 @type DbAPIs 96 @type DbAPIs
89 @param language language of the APIs object 97 @param language language of the APIs object
90 @type str 98 @type str
91 @param apiFiles list of API files to process 99 @param apiFiles list of API files to process
99 @type bool 107 @type bool
100 @param projectType type of the project 108 @param projectType type of the project
101 @type str 109 @type str
102 """ 110 """
103 QThread.__init__(self) 111 QThread.__init__(self)
104 112
105 self.setTerminationEnabled(True) 113 self.setTerminationEnabled(True)
106 114
107 # Get the AC word separators for all of the languages that the editor 115 # Get the AC word separators for all of the languages that the editor
108 # supports. This has to be before we create a new thread, because 116 # supports. This has to be before we create a new thread, because
109 # access to GUI elements is not allowed from non-GUI threads. 117 # access to GUI elements is not allowed from non-GUI threads.
110 self.__wseps = {} 118 self.__wseps = {}
111 for lang in QScintilla.Lexers.getSupportedLanguages(): 119 for lang in QScintilla.Lexers.getSupportedLanguages():
112 lexer = QScintilla.Lexers.getLexer(lang) 120 lexer = QScintilla.Lexers.getLexer(lang)
113 if lexer is not None: 121 if lexer is not None:
114 self.__wseps[lang] = lexer.autoCompletionWordSeparators() 122 self.__wseps[lang] = lexer.autoCompletionWordSeparators()
115 123
116 self.__proxy = proxy 124 self.__proxy = proxy
117 self.__language = language 125 self.__language = language
118 self.__projectType = projectType 126 self.__projectType = projectType
119 self.__apiFiles = apiFiles[:] 127 self.__apiFiles = apiFiles[:]
120 self.__aborted = False 128 self.__aborted = False
121 self.__projectPath = projectPath 129 self.__projectPath = projectPath
122 self.__refresh = refresh 130 self.__refresh = refresh
123 131
124 self.__databaseName = dbName 132 self.__databaseName = dbName
125 133
126 if self.__projectType: 134 if self.__projectType:
127 self.__connectionName = "{0}_{1}_{2}".format( 135 self.__connectionName = "{0}_{1}_{2}".format(
128 self.__language, self.__projectType, id(self)) 136 self.__language, self.__projectType, id(self)
129 else: 137 )
130 self.__connectionName = "{0}_{1}".format( 138 else:
131 self.__language, id(self)) 139 self.__connectionName = "{0}_{1}".format(self.__language, id(self))
132 140
133 def __autoCompletionWordSeparators(self, language): 141 def __autoCompletionWordSeparators(self, language):
134 """ 142 """
135 Private method to get the word separator characters for a language. 143 Private method to get the word separator characters for a language.
136 144
137 @param language language of the APIs object 145 @param language language of the APIs object
138 @type str 146 @type str
139 @return word separator characters 147 @return word separator characters
140 @rtype list of str 148 @rtype list of str
141 """ 149 """
142 if language: 150 if language:
143 return self.__wseps.get(language, None) 151 return self.__wseps.get(language, None)
144 else: 152 else:
145 return self.__proxy.autoCompletionWordSeparators() 153 return self.__proxy.autoCompletionWordSeparators()
146 154
147 def abort(self): 155 def abort(self):
148 """ 156 """
149 Public method to ask the thread to stop. 157 Public method to ask the thread to stop.
150 """ 158 """
151 self.__aborted = True 159 self.__aborted = True
152 160
153 def __loadApiFileIfNewer(self, apiFile): 161 def __loadApiFileIfNewer(self, apiFile):
154 """ 162 """
155 Private method to load an API file, if it is newer than the one read 163 Private method to load an API file, if it is newer than the one read
156 into the database. 164 into the database.
157 165
158 @param apiFile filename of the raw API file 166 @param apiFile filename of the raw API file
159 @type str 167 @type str
160 """ 168 """
161 db = QSqlDatabase.database(self.__connectionName) 169 db = QSqlDatabase.database(self.__connectionName)
162 db.transaction() 170 db.transaction()
165 query.prepare(self.file_loaded_stmt) 173 query.prepare(self.file_loaded_stmt)
166 query.bindValue(":file", apiFile) 174 query.bindValue(":file", apiFile)
167 query.exec() 175 query.exec()
168 loadTime = ( 176 loadTime = (
169 QDateTime.fromString(query.value(0), Qt.DateFormat.ISODate) 177 QDateTime.fromString(query.value(0), Qt.DateFormat.ISODate)
170 if query.next() and query.isValid() else 178 if query.next() and query.isValid()
179 else
171 # __IGNORE_WARNING_M513__ 180 # __IGNORE_WARNING_M513__
172 QDateTime(1970, 1, 1, 0, 0) 181 QDateTime(1970, 1, 1, 0, 0)
173 ) 182 )
174 query.finish() 183 query.finish()
175 del query 184 del query
176 finally: 185 finally:
177 db.commit() 186 db.commit()
178 if self.__projectPath: 187 if self.__projectPath:
179 modTime = ( 188 modTime = QFileInfo(
180 QFileInfo(os.path.join(self.__projectPath, apiFile)) 189 os.path.join(self.__projectPath, apiFile)
181 .lastModified() 190 ).lastModified()
182 )
183 else: 191 else:
184 modTime = QFileInfo(apiFile).lastModified() 192 modTime = QFileInfo(apiFile).lastModified()
185 basesFile = os.path.splitext(apiFile)[0] + ".bas" 193 basesFile = os.path.splitext(apiFile)[0] + ".bas"
186 if os.path.exists(basesFile): 194 if os.path.exists(basesFile):
187 modTimeBases = QFileInfo(basesFile).lastModified() 195 modTimeBases = QFileInfo(basesFile).lastModified()
188 if modTimeBases > modTime: 196 if modTimeBases > modTime:
189 modTime = modTimeBases 197 modTime = modTimeBases
190 if loadTime < modTime: 198 if loadTime < modTime:
191 self.processing.emit(WorkerStatusFile, apiFile) 199 self.processing.emit(WorkerStatusFile, apiFile)
192 self.__loadApiFile(apiFile) 200 self.__loadApiFile(apiFile)
193 201
194 def __classesAttributesApi(self, module): 202 def __classesAttributesApi(self, module):
195 """ 203 """
196 Private method to generate class api section for class attributes. 204 Private method to generate class api section for class attributes.
197 205
198 @param module module object to get the info from 206 @param module module object to get the info from
199 @type Module 207 @type Module
200 @return API information 208 @return API information
201 @rtype list of str 209 @rtype list of str
202 """ 210 """
203 api = [] 211 api = []
204 modulePath = module.name.split('.') 212 modulePath = module.name.split(".")
205 moduleName = "{0}.".format('.'.join(modulePath)) 213 moduleName = "{0}.".format(".".join(modulePath))
206 214
207 for className in sorted(module.classes.keys()): 215 for className in sorted(module.classes.keys()):
208 _class = module.classes[className] 216 _class = module.classes[className]
209 classNameStr = "{0}{1}.".format(moduleName, className) 217 classNameStr = "{0}{1}.".format(moduleName, className)
210 for variable in sorted(_class.attributes.keys()): 218 for variable in sorted(_class.attributes.keys()):
211 if not _class.attributes[variable].isPrivate(): 219 if not _class.attributes[variable].isPrivate():
212 from QScintilla.Editor import Editor 220 from QScintilla.Editor import Editor
221
213 if _class.attributes[variable].isPublic(): 222 if _class.attributes[variable].isPublic():
214 iconId = Editor.AttributeID 223 iconId = Editor.AttributeID
215 elif _class.attributes[variable].isProtected(): 224 elif _class.attributes[variable].isProtected():
216 iconId = Editor.AttributeProtectedID 225 iconId = Editor.AttributeProtectedID
217 else: 226 else:
218 iconId = Editor.AttributePrivateID 227 iconId = Editor.AttributePrivateID
219 api.append('{0}{1}?{2:d}'.format(classNameStr, variable, 228 api.append("{0}{1}?{2:d}".format(classNameStr, variable, iconId))
220 iconId))
221 return api 229 return api
222 230
223 def __loadApiFile(self, apiFile): 231 def __loadApiFile(self, apiFile):
224 """ 232 """
225 Private method to read a raw API file into the database. 233 Private method to read a raw API file into the database.
226 234
227 @param apiFile filename of the raw API file 235 @param apiFile filename of the raw API file
228 @type str 236 @type str
229 """ 237 """
230 apis = [] 238 apis = []
231 bases = [] 239 bases = []
232 240
233 if self.__language == ApisNameProject: 241 if self.__language == ApisNameProject:
234 with contextlib.suppress(OSError, ImportError): 242 with contextlib.suppress(OSError, ImportError):
235 module = Utilities.ModuleParser.readModule( 243 module = Utilities.ModuleParser.readModule(
236 os.path.join(self.__projectPath, apiFile), 244 os.path.join(self.__projectPath, apiFile),
237 basename=self.__projectPath + os.sep, 245 basename=self.__projectPath + os.sep,
238 caching=False) 246 caching=False,
247 )
239 language = module.getType() 248 language = module.getType()
240 if language: 249 if language:
241 from DocumentationTools.APIGenerator import APIGenerator 250 from DocumentationTools.APIGenerator import APIGenerator
251
242 apiGenerator = APIGenerator(module) 252 apiGenerator = APIGenerator(module)
243 apis = apiGenerator.genAPI(True, "", True) 253 apis = apiGenerator.genAPI(True, "", True)
244 if os.path.basename(apiFile).startswith("Ui_"): 254 if os.path.basename(apiFile).startswith("Ui_"):
245 # it is a forms source file, extract public attributes 255 # it is a forms source file, extract public attributes
246 # as well 256 # as well
247 apis.extend(self.__classesAttributesApi(module)) 257 apis.extend(self.__classesAttributesApi(module))
248 258
249 basesDict = apiGenerator.genBases(True) 259 basesDict = apiGenerator.genBases(True)
250 for baseEntry in basesDict: 260 for baseEntry in basesDict:
251 if basesDict[baseEntry]: 261 if basesDict[baseEntry]:
252 bases.append("{0} {1}\n".format( 262 bases.append(
253 baseEntry, " ".join( 263 "{0} {1}\n".format(
254 sorted(basesDict[baseEntry])))) 264 baseEntry, " ".join(sorted(basesDict[baseEntry]))
265 )
266 )
255 else: 267 else:
256 with contextlib.suppress(OSError, UnicodeError): 268 with contextlib.suppress(OSError, UnicodeError):
257 apis = Utilities.readEncodedFile(apiFile)[0].splitlines(True) 269 apis = Utilities.readEncodedFile(apiFile)[0].splitlines(True)
258 with contextlib.suppress(OSError, UnicodeError): 270 with contextlib.suppress(OSError, UnicodeError):
259 basesFile = os.path.splitext(apiFile)[0] + ".bas" 271 basesFile = os.path.splitext(apiFile)[0] + ".bas"
260 if os.path.exists(basesFile): 272 if os.path.exists(basesFile):
261 bases = ( 273 bases = Utilities.readEncodedFile(basesFile)[0].splitlines(True)
262 Utilities.readEncodedFile(basesFile)[0]
263 .splitlines(True)
264 )
265 language = None 274 language = None
266 275
267 if len(apis) > 0: 276 if len(apis) > 0:
268 self.__storeApis(apis, bases, apiFile, language) 277 self.__storeApis(apis, bases, apiFile, language)
269 else: 278 else:
270 # just store file info to avoid rereading it every time 279 # just store file info to avoid rereading it every time
271 self.__storeFileInfoOnly(apiFile) 280 self.__storeFileInfoOnly(apiFile)
272 281
273 def __storeFileInfoOnly(self, apiFile): 282 def __storeFileInfoOnly(self, apiFile):
274 """ 283 """
275 Private method to store file info only. 284 Private method to store file info only.
276 285
277 Doing this avoids rereading the file whenever the API is initialized 286 Doing this avoids rereading the file whenever the API is initialized
278 in case the given file doesn't contain API data. 287 in case the given file doesn't contain API data.
279 288
280 @param apiFile file name of the API file 289 @param apiFile file name of the API file
281 @type str 290 @type str
282 """ 291 """
283 db = QSqlDatabase.database(self.__connectionName) 292 db = QSqlDatabase.database(self.__connectionName)
284 db.transaction() 293 db.transaction()
286 query = QSqlQuery(db) 295 query = QSqlQuery(db)
287 # step 1: create entry in file table 296 # step 1: create entry in file table
288 query.prepare(self.populate_file_stmt) 297 query.prepare(self.populate_file_stmt)
289 query.bindValue(":file", apiFile) 298 query.bindValue(":file", apiFile)
290 query.exec() 299 query.exec()
291 300
292 # step 2: update the file entry 301 # step 2: update the file entry
293 query.prepare(self.update_file_stmt) 302 query.prepare(self.update_file_stmt)
294 query.bindValue(":lastRead", QDateTime.currentDateTime()) 303 query.bindValue(":lastRead", QDateTime.currentDateTime())
295 query.bindValue(":file", apiFile) 304 query.bindValue(":file", apiFile)
296 query.exec() 305 query.exec()
299 del query 308 del query
300 if self.__aborted: 309 if self.__aborted:
301 db.rollback() 310 db.rollback()
302 else: 311 else:
303 db.commit() 312 db.commit()
304 313
305 def __storeApis(self, apis, bases, apiFile, language): 314 def __storeApis(self, apis, bases, apiFile, language):
306 """ 315 """
307 Private method to put the API entries into the database. 316 Private method to put the API entries into the database.
308 317
309 @param apis list of api entries 318 @param apis list of api entries
310 @type list of str 319 @type list of str
311 @param bases list of base class entries 320 @param bases list of base class entries
312 @type list of str 321 @type list of str
313 @param apiFile filename of the file read to get the APIs 322 @param apiFile filename of the file read to get the APIs
330 return 339 return
331 query.prepare(self.file_id_stmt) 340 query.prepare(self.file_id_stmt)
332 query.bindValue(":file", apiFile) 341 query.bindValue(":file", apiFile)
333 if not query.exec(): 342 if not query.exec():
334 return 343 return
335 if not query.next(): # __IGNORE_WARNING_M513__ 344 if not query.next(): # __IGNORE_WARNING_M513__
336 return 345 return
337 fileId = int(query.value(0)) 346 fileId = int(query.value(0))
338 347
339 # step 2: delete all entries belonging to this file 348 # step 2: delete all entries belonging to this file
340 query.prepare(self.populate_del_api_stmt) 349 query.prepare(self.populate_del_api_stmt)
341 query.bindValue(":fileId", fileId) 350 query.bindValue(":fileId", fileId)
342 query.exec() 351 query.exec()
343 352
344 query.prepare(self.populate_del_bases_stmt) 353 query.prepare(self.populate_del_bases_stmt)
345 query.bindValue(":fileId", fileId) 354 query.bindValue(":fileId", fileId)
346 query.exec() 355 query.exec()
347 356
348 # step 3: load the given API info 357 # step 3: load the given API info
349 query.prepare(self.populate_api_stmt) 358 query.prepare(self.populate_api_stmt)
350 for api in apis: 359 for api in apis:
351 if self.__aborted: 360 if self.__aborted:
352 break 361 break
353 362
354 api = api.strip() 363 api = api.strip()
355 if len(api) == 0: 364 if len(api) == 0:
356 continue 365 continue
357 366
358 b = api.find('(') 367 b = api.find("(")
359 if b == -1: 368 if b == -1:
360 path = api 369 path = api
361 sig = "" 370 sig = ""
362 else: 371 else:
363 path = api[:b] 372 path = api[:b]
364 sig = api[b:] 373 sig = api[b:]
365 374
366 while len(path) > 0: 375 while len(path) > 0:
367 acWord = "" 376 acWord = ""
368 context = "" 377 context = ""
369 fullContext = "" 378 fullContext = ""
370 pictureId = "" 379 pictureId = ""
371 380
372 # search for word separators 381 # search for word separators
373 index = len(path) 382 index = len(path)
374 while index > 0: 383 while index > 0:
375 index -= 1 384 index -= 1
376 found = False 385 found = False
380 break 389 break
381 if found: 390 if found:
382 if acWord == "": 391 if acWord == "":
383 # completion found 392 # completion found
384 acWord = path[index:] 393 acWord = path[index:]
385 path = path[:(index - len(wsep))] 394 path = path[: (index - len(wsep))]
386 index = len(path) 395 index = len(path)
387 fullContext = path 396 fullContext = path
388 context = path 397 context = path
389 with contextlib.suppress(ValueError): 398 with contextlib.suppress(ValueError):
390 acWord, pictureId = acWord.split("?", 1) 399 acWord, pictureId = acWord.split("?", 1)
393 break 402 break
394 # none found? 403 # none found?
395 if acWord == "": 404 if acWord == "":
396 acWord = path 405 acWord = path
397 path = "" 406 path = ""
398 407
399 query.bindValue(":acWord", acWord) 408 query.bindValue(":acWord", acWord)
400 query.bindValue(":context", context) 409 query.bindValue(":context", context)
401 query.bindValue(":fullContext", fullContext) 410 query.bindValue(":fullContext", fullContext)
402 query.bindValue(":signature", sig) 411 query.bindValue(":signature", sig)
403 query.bindValue(":fileId", fileId) 412 query.bindValue(":fileId", fileId)
404 query.bindValue(":pictureId", pictureId) 413 query.bindValue(":pictureId", pictureId)
405 query.exec() 414 query.exec()
406 415
407 sig = "" 416 sig = ""
408 417
409 # step 4: load the given base classes info 418 # step 4: load the given base classes info
410 query.prepare(self.populate_bases_stmt) 419 query.prepare(self.populate_bases_stmt)
411 for base in bases: 420 for base in bases:
412 if self.__aborted: 421 if self.__aborted:
413 break 422 break
414 423
415 base = base.strip() 424 base = base.strip()
416 if len(base) == 0: 425 if len(base) == 0:
417 continue 426 continue
418 427
419 class_, baseClasses = base.split(" ", 1) 428 class_, baseClasses = base.split(" ", 1)
420 query.bindValue(":class", class_) 429 query.bindValue(":class", class_)
421 query.bindValue(":baseClasses", baseClasses) 430 query.bindValue(":baseClasses", baseClasses)
422 query.bindValue(":fileId", fileId) 431 query.bindValue(":fileId", fileId)
423 query.exec() 432 query.exec()
424 433
425 if not self.__aborted: 434 if not self.__aborted:
426 # step 5: update the file entry 435 # step 5: update the file entry
427 query.prepare(self.update_file_stmt) 436 query.prepare(self.update_file_stmt)
428 query.bindValue(":lastRead", QDateTime.currentDateTime()) 437 query.bindValue(":lastRead", QDateTime.currentDateTime())
429 query.bindValue(":file", apiFile) 438 query.bindValue(":file", apiFile)
433 del query 442 del query
434 if self.__aborted: 443 if self.__aborted:
435 db.rollback() 444 db.rollback()
436 else: 445 else:
437 db.commit() 446 db.commit()
438 447
439 def __deleteApiFile(self, apiFile): 448 def __deleteApiFile(self, apiFile):
440 """ 449 """
441 Private method to delete all references to an api file. 450 Private method to delete all references to an api file.
442 451
443 @param apiFile filename of the raw API file 452 @param apiFile filename of the raw API file
444 @type str 453 @type str
445 """ 454 """
446 db = QSqlDatabase.database(self.__connectionName) 455 db = QSqlDatabase.database(self.__connectionName)
447 db.transaction() 456 db.transaction()
448 try: 457 try:
449 query = QSqlQuery(db) 458 query = QSqlQuery(db)
450 459
451 # step 1: get the ID belonging to the api file 460 # step 1: get the ID belonging to the api file
452 query.prepare(self.file_id_stmt) 461 query.prepare(self.file_id_stmt)
453 query.bindValue(":file", apiFile) 462 query.bindValue(":file", apiFile)
454 query.exec() 463 query.exec()
455 query.next() # __IGNORE_WARNING_M513__ 464 query.next() # __IGNORE_WARNING_M513__
456 fileId = int(query.value(0)) 465 fileId = int(query.value(0))
457 466
458 # step 2: delete all API entries belonging to this file 467 # step 2: delete all API entries belonging to this file
459 query.prepare(self.populate_del_api_stmt) 468 query.prepare(self.populate_del_api_stmt)
460 query.bindValue(":fileId", fileId) 469 query.bindValue(":fileId", fileId)
461 query.exec() 470 query.exec()
462 471
463 # step 3: delete all base classes entries belonging to this file 472 # step 3: delete all base classes entries belonging to this file
464 query.prepare(self.populate_del_bases_stmt) 473 query.prepare(self.populate_del_bases_stmt)
465 query.bindValue(":fileId", fileId) 474 query.bindValue(":fileId", fileId)
466 query.exec() 475 query.exec()
467 476
468 # step 4: delete the file entry 477 # step 4: delete the file entry
469 query.prepare(self.file_delete_id_stmt) 478 query.prepare(self.file_delete_id_stmt)
470 query.bindValue(":id", fileId) 479 query.bindValue(":id", fileId)
471 query.exec() 480 query.exec()
472 finally: 481 finally:
475 db.commit() 484 db.commit()
476 485
477 def __getApiFiles(self): 486 def __getApiFiles(self):
478 """ 487 """
479 Private method to get a list of API files loaded into the database. 488 Private method to get a list of API files loaded into the database.
480 489
481 @return list of API filenames 490 @return list of API filenames
482 @rtype list of str 491 @rtype list of str
483 """ 492 """
484 apiFiles = [] 493 apiFiles = []
485 494
486 db = QSqlDatabase.database(self.__connectionName) 495 db = QSqlDatabase.database(self.__connectionName)
487 db.transaction() 496 db.transaction()
488 try: 497 try:
489 query = QSqlQuery(db) 498 query = QSqlQuery(db)
490 query.exec(self.api_files_stmt) 499 query.exec(self.api_files_stmt)
491 while query.next(): # __IGNORE_WARNING_M513__ 500 while query.next(): # __IGNORE_WARNING_M513__
492 apiFiles.append(query.value(0)) 501 apiFiles.append(query.value(0))
493 finally: 502 finally:
494 query.finish() 503 query.finish()
495 del query 504 del query
496 db.commit() 505 db.commit()
497 506
498 return apiFiles 507 return apiFiles
499 508
500 def run(self): 509 def run(self):
501 """ 510 """
502 Public method to perform the threads work. 511 Public method to perform the threads work.
503 """ 512 """
504 self.processing.emit(WorkerStatusStarted, "") 513 self.processing.emit(WorkerStatusStarted, "")
505 514
506 if QSqlDatabase.contains(self.__connectionName): 515 if QSqlDatabase.contains(self.__connectionName):
507 QSqlDatabase.removeDatabase(self.__connectionName) 516 QSqlDatabase.removeDatabase(self.__connectionName)
508 517
509 db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName) 518 db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName)
510 db.setDatabaseName(self.__databaseName) 519 db.setDatabaseName(self.__databaseName)
511 db.open() 520 db.open()
512 521
513 if db.isValid() and db.isOpen(): 522 if db.isValid() and db.isOpen():
514 # step 1: remove API files not wanted any longer 523 # step 1: remove API files not wanted any longer
515 if not self.__refresh: 524 if not self.__refresh:
516 loadedApiFiles = self.__getApiFiles() 525 loadedApiFiles = self.__getApiFiles()
517 for apiFile in loadedApiFiles: 526 for apiFile in loadedApiFiles:
518 if not self.__aborted and apiFile not in self.__apiFiles: 527 if not self.__aborted and apiFile not in self.__apiFiles:
519 self.__deleteApiFile(apiFile) 528 self.__deleteApiFile(apiFile)
520 529
521 # step 2: (re-)load api files 530 # step 2: (re-)load api files
522 for apiFile in self.__apiFiles: 531 for apiFile in self.__apiFiles:
523 if not self.__aborted: 532 if not self.__aborted:
524 self.__loadApiFileIfNewer(apiFile) 533 self.__loadApiFileIfNewer(apiFile)
525 534
526 db.close() 535 db.close()
527 536
528 if self.__aborted: 537 if self.__aborted:
529 self.processing.emit(WorkerStatusAborted, "") 538 self.processing.emit(WorkerStatusAborted, "")
530 else: 539 else:
531 self.processing.emit(WorkerStatusFinished, "") 540 self.processing.emit(WorkerStatusFinished, "")
532 541
533 542
534 class DbAPIs(QObject): 543 class DbAPIs(QObject):
535 """ 544 """
536 Class implementing an API storage entity. 545 Class implementing an API storage entity.
537 546
538 @signal apiPreparationStatus(language, status, file) emitted to indicate 547 @signal apiPreparationStatus(language, status, file) emitted to indicate
539 the API preparation status for a language 548 the API preparation status for a language
540 """ 549 """
550
541 apiPreparationStatus = pyqtSignal(str, int, str) 551 apiPreparationStatus = pyqtSignal(str, int, str)
542 552
543 DB_VERSION = 4 553 DB_VERSION = 4
544 554
545 create_mgmt_stmt = """ 555 create_mgmt_stmt = """
546 CREATE TABLE mgmt 556 CREATE TABLE mgmt
547 (format INTEGER) 557 (format INTEGER)
548 """ 558 """
549 drop_mgmt_stmt = """DROP TABLE IF EXISTS mgmt""" 559 drop_mgmt_stmt = """DROP TABLE IF EXISTS mgmt"""
550 560
551 create_api_stmt = """ 561 create_api_stmt = """
552 CREATE TABLE api 562 CREATE TABLE api
553 (acWord TEXT, 563 (acWord TEXT,
554 context TEXT, 564 context TEXT,
555 fullContext TEXT, 565 fullContext TEXT,
558 pictureId INTEGER, 568 pictureId INTEGER,
559 UNIQUE(acWord, fullContext, signature) ON CONFLICT IGNORE 569 UNIQUE(acWord, fullContext, signature) ON CONFLICT IGNORE
560 ) 570 )
561 """ 571 """
562 drop_api_stmt = """DROP TABLE IF EXISTS api""" 572 drop_api_stmt = """DROP TABLE IF EXISTS api"""
563 573
564 create_bases_stmt = """ 574 create_bases_stmt = """
565 CREATE TABLE bases 575 CREATE TABLE bases
566 (class TEXT UNIQUE ON CONFLICT IGNORE, 576 (class TEXT UNIQUE ON CONFLICT IGNORE,
567 baseClasses TEXT, 577 baseClasses TEXT,
568 fileId INTEGER 578 fileId INTEGER
569 ) 579 )
570 """ 580 """
571 drop_bases_stmt = """DROP TABLE IF EXISTS bases""" 581 drop_bases_stmt = """DROP TABLE IF EXISTS bases"""
572 582
573 create_file_stmt = """ 583 create_file_stmt = """
574 CREATE TABLE file 584 CREATE TABLE file
575 (id INTEGER PRIMARY KEY AUTOINCREMENT, 585 (id INTEGER PRIMARY KEY AUTOINCREMENT,
576 file TEXT UNIQUE ON CONFLICT IGNORE, 586 file TEXT UNIQUE ON CONFLICT IGNORE,
577 lastRead TIMESTAMP DEFAULT CURRENT_TIMESTAMP 587 lastRead TIMESTAMP DEFAULT CURRENT_TIMESTAMP
578 ) 588 )
579 """ 589 """
580 drop_file_stmt = """DROP TABLE IF EXISTS file""" 590 drop_file_stmt = """DROP TABLE IF EXISTS file"""
581 591
582 create_acWord_idx = """CREATE INDEX acWord_idx on api (acWord)""" 592 create_acWord_idx = """CREATE INDEX acWord_idx on api (acWord)"""
583 drop_acWord_idx = """DROP INDEX IF EXISTS acWord_idx""" 593 drop_acWord_idx = """DROP INDEX IF EXISTS acWord_idx"""
584 594
585 create_context_idx = """CREATE INDEX context_idx on api (context)""" 595 create_context_idx = """CREATE INDEX context_idx on api (context)"""
586 drop_context_idx = """DROP INDEX IF EXISTS context_idx""" 596 drop_context_idx = """DROP INDEX IF EXISTS context_idx"""
587 597
588 create_fullContext_idx = ( 598 create_fullContext_idx = """CREATE INDEX fullContext_idx on api (fullContext)"""
589 """CREATE INDEX fullContext_idx on api (fullContext)"""
590 )
591 drop_fullContext_idx = """DROP INDEX IF EXISTS fullContext_idx""" 599 drop_fullContext_idx = """DROP INDEX IF EXISTS fullContext_idx"""
592 600
593 create_bases_idx = """CREATE INDEX base_idx on bases (class)""" 601 create_bases_idx = """CREATE INDEX base_idx on bases (class)"""
594 drop_bases_idx = """DROP INDEX IF EXISTS base_idx""" 602 drop_bases_idx = """DROP INDEX IF EXISTS base_idx"""
595 603
596 create_file_idx = """CREATE INDEX file_idx on file (file)""" 604 create_file_idx = """CREATE INDEX file_idx on file (file)"""
597 drop_file_idx = """DROP INDEX IF EXISTS file_idx""" 605 drop_file_idx = """DROP INDEX IF EXISTS file_idx"""
598 606
599 api_files_stmt = """ 607 api_files_stmt = """
600 SELECT file FROM file 608 SELECT file FROM file
636 format_select_stmt = """ 644 format_select_stmt = """
637 SELECT format FROM mgmt 645 SELECT format FROM mgmt
638 """ 646 """
639 mgmt_insert_stmt = """ 647 mgmt_insert_stmt = """
640 INSERT INTO mgmt (format) VALUES ({0:d}) 648 INSERT INTO mgmt (format) VALUES ({0:d})
641 """.format(DB_VERSION) 649 """.format(
642 650 DB_VERSION
651 )
652
643 def __init__(self, language, projectType="", parent=None): 653 def __init__(self, language, projectType="", parent=None):
644 """ 654 """
645 Constructor 655 Constructor
646 656
647 @param language language of the APIs object 657 @param language language of the APIs object
648 @type str 658 @type str
649 @param projectType type of the project 659 @param projectType type of the project
650 @type str 660 @type str
651 @param parent reference to the parent object 661 @param parent reference to the parent object
654 QObject.__init__(self, parent) 664 QObject.__init__(self, parent)
655 if projectType: 665 if projectType:
656 self.setObjectName("DbAPIs_{0}_{1}".format(language, projectType)) 666 self.setObjectName("DbAPIs_{0}_{1}".format(language, projectType))
657 else: 667 else:
658 self.setObjectName("DbAPIs_{0}".format(language)) 668 self.setObjectName("DbAPIs_{0}".format(language))
659 669
660 self.__inPreparation = False 670 self.__inPreparation = False
661 self.__worker = None 671 self.__worker = None
662 self.__workerQueue = [] 672 self.__workerQueue = []
663 self.__opened = False 673 self.__opened = False
664 674
665 self.__projectType = projectType 675 self.__projectType = projectType
666 self.__language = language 676 self.__language = language
667 677
668 if self.__projectType: 678 if self.__projectType:
669 self.__connectionName = "{0}_{1}".format( 679 self.__connectionName = "{0}_{1}".format(
670 self.__language, self.__projectType) 680 self.__language, self.__projectType
681 )
671 else: 682 else:
672 self.__connectionName = self.__language 683 self.__connectionName = self.__language
673 684
674 if self.__language == ApisNameProject: 685 if self.__language == ApisNameProject:
675 self.__initAsProject() 686 self.__initAsProject()
676 else: 687 else:
677 self.__initAsLanguage() 688 self.__initAsLanguage()
678 689
679 def __initAsProject(self): 690 def __initAsProject(self):
680 """ 691 """
681 Private method to initialize as a project API object. 692 Private method to initialize as a project API object.
682 """ 693 """
683 self.__lexer = None 694 self.__lexer = None
684 695
685 self.__project = ericApp().getObject("Project") 696 self.__project = ericApp().getObject("Project")
686 self.__project.projectOpened.connect(self.__projectOpened) 697 self.__project.projectOpened.connect(self.__projectOpened)
687 self.__project.newProject.connect(self.__projectOpened) 698 self.__project.newProject.connect(self.__projectOpened)
688 self.__project.projectClosed.connect(self.__projectClosed) 699 self.__project.projectClosed.connect(self.__projectClosed)
689 self.__project.projectFormCompiled.connect(self.__projectFormCompiled) 700 self.__project.projectFormCompiled.connect(self.__projectFormCompiled)
690 self.__project.projectChanged.connect(self.__projectChanged) 701 self.__project.projectChanged.connect(self.__projectChanged)
691 702
692 if self.__project.isOpen(): 703 if self.__project.isOpen():
693 self.__projectOpened() 704 self.__projectOpened()
694 705
695 def __initAsLanguage(self): 706 def __initAsLanguage(self):
696 """ 707 """
697 Private method to initialize as a language API object. 708 Private method to initialize as a language API object.
698 """ 709 """
699 if self.__language == "Python3": 710 if self.__language == "Python3":
701 else: 712 else:
702 self.__discardFirst = [] 713 self.__discardFirst = []
703 self.__lexer = QScintilla.Lexers.getLexer(self.__language) 714 self.__lexer = QScintilla.Lexers.getLexer(self.__language)
704 try: 715 try:
705 self.__apifiles = Preferences.getEditorAPI( 716 self.__apifiles = Preferences.getEditorAPI(
706 self.__language, projectType=self.__projectType) 717 self.__language, projectType=self.__projectType
718 )
707 except TypeError: 719 except TypeError:
708 # older interface 720 # older interface
709 self.__apifiles = Preferences.getEditorAPI(self.__language) 721 self.__apifiles = Preferences.getEditorAPI(self.__language)
710 self.__apifiles.sort() 722 self.__apifiles.sort()
711 if self.__lexer is not None: 723 if self.__lexer is not None:
712 self.__openAPIs() 724 self.__openAPIs()
713 725
714 def _apiDbName(self): 726 def _apiDbName(self):
715 """ 727 """
716 Protected method to determine the name of the database file. 728 Protected method to determine the name of the database file.
717 729
718 @return name of the database file 730 @return name of the database file
719 @rtype str 731 @rtype str
720 """ 732 """
721 if self.__language == ApisNameProject: 733 if self.__language == ApisNameProject:
722 return os.path.join(self.__project.getProjectManagementDir(), 734 return os.path.join(
723 "project-apis.db") 735 self.__project.getProjectManagementDir(), "project-apis.db"
736 )
724 else: 737 else:
725 apisDir = os.path.join(Globals.getConfigDir(), "APIs") 738 apisDir = os.path.join(Globals.getConfigDir(), "APIs")
726 if not os.path.exists(apisDir): 739 if not os.path.exists(apisDir):
727 os.makedirs(apisDir) 740 os.makedirs(apisDir)
728 if self.__projectType: 741 if self.__projectType:
729 filename = "{0}_{1}-api.db".format(self.__language, 742 filename = "{0}_{1}-api.db".format(self.__language, self.__projectType)
730 self.__projectType)
731 else: 743 else:
732 filename = "{0}-api.db".format(self.__language) 744 filename = "{0}-api.db".format(self.__language)
733 return os.path.join(apisDir, filename) 745 return os.path.join(apisDir, filename)
734 746
735 def close(self): 747 def close(self):
736 """ 748 """
737 Public method to close the database. 749 Public method to close the database.
738 """ 750 """
739 self.__workerQueue = [] 751 self.__workerQueue = []
740 if self.__worker is not None: 752 if self.__worker is not None:
741 self.__worker.abort() 753 self.__worker.abort()
742 if self.__worker is not None: 754 if self.__worker is not None:
743 self.__worker.wait(5000) 755 self.__worker.wait(5000)
744 if ( 756 if self.__worker is not None and not self.__worker.isFinished():
745 self.__worker is not None and
746 not self.__worker.isFinished()
747 ):
748 self.__worker.terminate() 757 self.__worker.terminate()
749 if self.__worker is not None: 758 if self.__worker is not None:
750 self.__worker.wait(5000) 759 self.__worker.wait(5000)
751 760
752 if QSqlDatabase and QSqlDatabase.database( 761 if QSqlDatabase and QSqlDatabase.database(self.__connectionName).isOpen():
753 self.__connectionName).isOpen():
754 QSqlDatabase.database(self.__connectionName).close() 762 QSqlDatabase.database(self.__connectionName).close()
755 QSqlDatabase.removeDatabase(self.__connectionName) 763 QSqlDatabase.removeDatabase(self.__connectionName)
756 764
757 self.__opened = False 765 self.__opened = False
758 766
759 def __openApiDb(self): 767 def __openApiDb(self):
760 """ 768 """
761 Private method to open the API database. 769 Private method to open the API database.
762 770
763 @return flag indicating the database status 771 @return flag indicating the database status
764 @rtype bool 772 @rtype bool
765 """ 773 """
766 db = QSqlDatabase.database(self.__connectionName, False) 774 db = QSqlDatabase.database(self.__connectionName, False)
767 if not db.isValid(): 775 if not db.isValid():
768 # the database connection is a new one 776 # the database connection is a new one
769 db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName) 777 db = QSqlDatabase.addDatabase("QSQLITE", self.__connectionName)
770 dbName = self._apiDbName() 778 dbName = self._apiDbName()
771 if ( 779 if self.__language == ApisNameProject and not os.path.exists(
772 self.__language == ApisNameProject and 780 self.__project.getProjectManagementDir()
773 not os.path.exists(self.__project.getProjectManagementDir())
774 ): 781 ):
775 opened = False 782 opened = False
776 else: 783 else:
777 db.setDatabaseName(dbName) 784 db.setDatabaseName(dbName)
778 opened = db.open() 785 opened = db.open()
779 if not opened: 786 if not opened:
780 QSqlDatabase.removeDatabase(self.__connectionName) 787 QSqlDatabase.removeDatabase(self.__connectionName)
781 else: 788 else:
782 opened = True 789 opened = True
783 return opened 790 return opened
784 791
785 def __createApiDB(self): 792 def __createApiDB(self):
786 """ 793 """
787 Private method to create an API database. 794 Private method to create an API database.
788 """ 795 """
789 db = QSqlDatabase.database(self.__connectionName) 796 db = QSqlDatabase.database(self.__connectionName)
819 db.commit() 826 db.commit()
820 827
821 def getApiFiles(self): 828 def getApiFiles(self):
822 """ 829 """
823 Public method to get a list of API files loaded into the database. 830 Public method to get a list of API files loaded into the database.
824 831
825 @return list of API filenames 832 @return list of API filenames
826 @rtype list of str 833 @rtype list of str
827 """ 834 """
828 apiFiles = [] 835 apiFiles = []
829 836
830 db = QSqlDatabase.database(self.__connectionName) 837 db = QSqlDatabase.database(self.__connectionName)
831 db.transaction() 838 db.transaction()
832 try: 839 try:
833 query = QSqlQuery(db) 840 query = QSqlQuery(db)
834 query.exec(self.api_files_stmt) 841 query.exec(self.api_files_stmt)
835 while query.next(): # __IGNORE_WARNING_M513__ 842 while query.next(): # __IGNORE_WARNING_M513__
836 apiFiles.append(query.value(0)) 843 apiFiles.append(query.value(0))
837 finally: 844 finally:
838 query.finish() 845 query.finish()
839 del query 846 del query
840 db.commit() 847 db.commit()
841 848
842 return apiFiles 849 return apiFiles
843 850
844 def __isPrepared(self): 851 def __isPrepared(self):
845 """ 852 """
846 Private method to check, if the database has been prepared. 853 Private method to check, if the database has been prepared.
847 854
848 @return flag indicating the prepared status 855 @return flag indicating the prepared status
849 @rtype bool 856 @rtype bool
850 """ 857 """
851 db = QSqlDatabase.database(self.__connectionName) 858 db = QSqlDatabase.database(self.__connectionName)
852 prepared = len(db.tables()) > 0 859 prepared = len(db.tables()) > 0
855 prepared = False 862 prepared = False
856 try: 863 try:
857 query = QSqlQuery(db) 864 query = QSqlQuery(db)
858 ok = query.exec(self.format_select_stmt) 865 ok = query.exec(self.format_select_stmt)
859 if ok: 866 if ok:
860 query.next() # __IGNORE_WARNING_M513__ 867 query.next() # __IGNORE_WARNING_M513__
861 formatVersion = int(query.value(0)) 868 formatVersion = int(query.value(0))
862 if formatVersion >= self.DB_VERSION: 869 if formatVersion >= self.DB_VERSION:
863 prepared = True 870 prepared = True
864 finally: 871 finally:
865 query.finish() 872 query.finish()
866 del query 873 del query
867 db.commit() 874 db.commit()
868 return prepared 875 return prepared
869 876
870 def getCompletions(self, start=None, context=None, followHierarchy=False): 877 def getCompletions(self, start=None, context=None, followHierarchy=False):
871 """ 878 """
872 Public method to determine the possible completions. 879 Public method to determine the possible completions.
873 880
874 @param start string giving the start of the word to be 881 @param start string giving the start of the word to be
875 completed 882 completed
876 @type str 883 @type str
877 @param context string giving the context (e.g. classname) 884 @param context string giving the context (e.g. classname)
878 to be completed 885 to be completed
885 key 'context' contains the context and 892 key 'context' contains the context and
886 key 'pictureId' contains the ID of the icon to be shown) 893 key 'pictureId' contains the ID of the icon to be shown)
887 @rtype list of dict 894 @rtype list of dict
888 """ 895 """
889 completions = [] 896 completions = []
890 897
891 db = QSqlDatabase.database(self.__connectionName) 898 db = QSqlDatabase.database(self.__connectionName)
892 if db.isOpen() and not self.__inPreparation: 899 if db.isOpen() and not self.__inPreparation:
893 db.transaction() 900 db.transaction()
894 try: 901 try:
895 query = None 902 query = None
896 903
897 if start is not None and context is not None: 904 if start is not None and context is not None:
898 query = QSqlQuery(db) 905 query = QSqlQuery(db)
899 query.prepare(self.ac_context_word_stmt) 906 query.prepare(self.ac_context_word_stmt)
900 query.bindValue(":acWord", start + '*') 907 query.bindValue(":acWord", start + "*")
901 query.bindValue(":context", context) 908 query.bindValue(":context", context)
902 elif start is not None: 909 elif start is not None:
903 query = QSqlQuery(db) 910 query = QSqlQuery(db)
904 query.prepare(self.ac_stmt) 911 query.prepare(self.ac_stmt)
905 query.bindValue(":acWord", start + '*') 912 query.bindValue(":acWord", start + "*")
906 elif context is not None: 913 elif context is not None:
907 query = QSqlQuery(db) 914 query = QSqlQuery(db)
908 query.prepare(self.ac_context_stmt) 915 query.prepare(self.ac_context_stmt)
909 query.bindValue(":context", context) 916 query.bindValue(":context", context)
910 917
911 if query is not None: 918 if query is not None:
912 query.exec() 919 query.exec()
913 while query.next(): # __IGNORE_WARNING_M513__ 920 while query.next(): # __IGNORE_WARNING_M513__
914 completions.append({"completion": query.value(0), 921 completions.append(
915 "context": query.value(1), 922 {
916 "pictureId": query.value(2)}) 923 "completion": query.value(0),
924 "context": query.value(1),
925 "pictureId": query.value(2),
926 }
927 )
917 query.finish() 928 query.finish()
918 del query 929 del query
919 finally: 930 finally:
920 db.commit() 931 db.commit()
921 932
922 if followHierarchy: 933 if followHierarchy:
923 query = QSqlQuery(db) 934 query = QSqlQuery(db)
924 query.prepare(self.bases_stmt) 935 query.prepare(self.bases_stmt)
925 query.bindValue(":class", context) 936 query.bindValue(":class", context)
926 query.exec() 937 query.exec()
927 if query.next(): # __IGNORE_WARNING_M513__ 938 if query.next(): # __IGNORE_WARNING_M513__
928 bases = query.value(0).split() 939 bases = query.value(0).split()
929 else: 940 else:
930 bases = [] 941 bases = []
931 for base in bases: 942 for base in bases:
932 completions.extend(self.getCompletions(start, base, 943 completions.extend(
933 followHierarchy=True)) 944 self.getCompletions(start, base, followHierarchy=True)
945 )
934 query.finish() 946 query.finish()
935 del query 947 del query
936 948
937 return completions 949 return completions
938 950
939 def getCalltips(self, acWord, commas, context=None, fullContext=None, 951 def getCalltips(
940 showContext=True, followHierarchy=False): 952 self,
953 acWord,
954 commas,
955 context=None,
956 fullContext=None,
957 showContext=True,
958 followHierarchy=False,
959 ):
941 """ 960 """
942 Public method to determine the calltips. 961 Public method to determine the calltips.
943 962
944 @param acWord function to get calltips for 963 @param acWord function to get calltips for
945 @type str 964 @type str
946 @param commas minimum number of commas contained in the calltip 965 @param commas minimum number of commas contained in the calltip
947 @type int 966 @type int
948 @param context string giving the context (e.g. classname) 967 @param context string giving the context (e.g. classname)
956 @type bool 975 @type bool
957 @return list of calltips 976 @return list of calltips
958 @rtype list of str 977 @rtype list of str
959 """ 978 """
960 calltips = [] 979 calltips = []
961 980
962 db = QSqlDatabase.database(self.__connectionName) 981 db = QSqlDatabase.database(self.__connectionName)
963 if db.isOpen() and not self.__inPreparation: 982 if db.isOpen() and not self.__inPreparation:
964 if self.autoCompletionWordSeparators(): 983 if self.autoCompletionWordSeparators():
965 contextSeparator = self.autoCompletionWordSeparators()[0] 984 contextSeparator = self.autoCompletionWordSeparators()[0]
966 else: 985 else:
976 query.bindValue(":context", context) 995 query.bindValue(":context", context)
977 else: 996 else:
978 query.prepare(self.ct_stmt) 997 query.prepare(self.ct_stmt)
979 query.bindValue(":acWord", acWord) 998 query.bindValue(":acWord", acWord)
980 query.exec() 999 query.exec()
981 while query.next(): # __IGNORE_WARNING_M513__ 1000 while query.next(): # __IGNORE_WARNING_M513__
982 word = query.value(0) 1001 word = query.value(0)
983 sig = query.value(1) 1002 sig = query.value(1)
984 fullCtx = query.value(2) 1003 fullCtx = query.value(2)
985 if sig: 1004 if sig:
986 if self.__discardFirst: 1005 if self.__discardFirst:
988 for discard in self.__discardFirst: 1007 for discard in self.__discardFirst:
989 sig = sig.replace(discard, "", 1) 1008 sig = sig.replace(discard, "", 1)
990 sig = sig.strip(", \t\r\n") 1009 sig = sig.strip(", \t\r\n")
991 if self.__enoughCommas(sig, commas): 1010 if self.__enoughCommas(sig, commas):
992 if showContext: 1011 if showContext:
993 calltips.append("{0}{1}{2}{3}".format( 1012 calltips.append(
994 fullCtx, 1013 "{0}{1}{2}{3}".format(
995 contextSeparator if fullCtx else "", 1014 fullCtx,
996 word, sig)) 1015 contextSeparator if fullCtx else "",
1016 word,
1017 sig,
1018 )
1019 )
997 else: 1020 else:
998 calltips.append("{0}{1}".format(word, sig)) 1021 calltips.append("{0}{1}".format(word, sig))
999 query.finish() 1022 query.finish()
1000 del query 1023 del query
1001 finally: 1024 finally:
1002 db.commit() 1025 db.commit()
1003 1026
1004 if followHierarchy: 1027 if followHierarchy:
1005 query = QSqlQuery(db) 1028 query = QSqlQuery(db)
1006 query.prepare(self.bases_stmt) 1029 query.prepare(self.bases_stmt)
1007 query.bindValue(":class", context) 1030 query.bindValue(":class", context)
1008 query.exec() 1031 query.exec()
1009 if query.next(): # __IGNORE_WARNING_M513__ 1032 if query.next(): # __IGNORE_WARNING_M513__
1010 bases = query.value(0).split() 1033 bases = query.value(0).split()
1011 else: 1034 else:
1012 bases = [] 1035 bases = []
1013 for base in bases: 1036 for base in bases:
1014 calltips.extend(self.getCalltips( 1037 calltips.extend(
1015 acWord, commas, context=base, showContext=showContext, 1038 self.getCalltips(
1016 followHierarchy=True)) 1039 acWord,
1040 commas,
1041 context=base,
1042 showContext=showContext,
1043 followHierarchy=True,
1044 )
1045 )
1017 query.finish() 1046 query.finish()
1018 del query 1047 del query
1019 1048
1020 if context and len(calltips) == 0 and not followHierarchy: 1049 if context and len(calltips) == 0 and not followHierarchy:
1021 # nothing found, try without a context 1050 # nothing found, try without a context
1022 calltips = self.getCalltips( 1051 calltips = self.getCalltips(acWord, commas, showContext=showContext)
1023 acWord, commas, showContext=showContext) 1052
1024
1025 return calltips 1053 return calltips
1026 1054
1027 def __enoughCommas(self, s, commas): 1055 def __enoughCommas(self, s, commas):
1028 """ 1056 """
1029 Private method to determine, if the given string contains enough 1057 Private method to determine, if the given string contains enough
1030 commas. 1058 commas.
1031 1059
1032 @param s string to check 1060 @param s string to check
1033 @type str 1061 @type str
1034 @param commas number of commas to check for 1062 @param commas number of commas to check for
1035 @type int 1063 @type int
1036 @return flag indicating, that there are enough commas 1064 @return flag indicating, that there are enough commas
1037 @rtype bool 1065 @rtype bool
1038 """ 1066 """
1039 end = s.find(')') 1067 end = s.find(")")
1040 1068
1041 if end < 0: 1069 if end < 0:
1042 return False 1070 return False
1043 1071
1044 w = s[:end] 1072 w = s[:end]
1045 return w.count(',') >= commas 1073 return w.count(",") >= commas
1046 1074
1047 def __openAPIs(self): 1075 def __openAPIs(self):
1048 """ 1076 """
1049 Private method to open the API database. 1077 Private method to open the API database.
1050 """ 1078 """
1051 self.__opened = self.__openApiDb() 1079 self.__opened = self.__openApiDb()
1052 if self.__opened: 1080 if self.__opened:
1053 if not self.__isPrepared(): 1081 if not self.__isPrepared():
1054 self.__createApiDB() 1082 self.__createApiDB()
1055 1083
1056 # prepare the database if neccessary 1084 # prepare the database if neccessary
1057 self.prepareAPIs() 1085 self.prepareAPIs()
1058 1086
1059 def __getProjectFormSources(self, normalized=False): 1087 def __getProjectFormSources(self, normalized=False):
1060 """ 1088 """
1061 Private method to get the source files for the project forms. 1089 Private method to get the source files for the project forms.
1062 1090
1063 @param normalized flag indicating a normalized filename is wanted 1091 @param normalized flag indicating a normalized filename is wanted
1064 @type bool 1092 @type bool
1065 @return list of project form sources 1093 @return list of project form sources
1066 @rtype list of str 1094 @rtype list of str
1067 """ 1095 """
1068 if self.__project.getProjectLanguage() in ( 1096 if self.__project.getProjectLanguage() in ("Python3", "MicroPython", "Cython"):
1069 "Python3", "MicroPython", "Cython"
1070 ):
1071 sourceExt = ".py" 1097 sourceExt = ".py"
1072 elif self.__project.getProjectLanguage() == "Ruby": 1098 elif self.__project.getProjectLanguage() == "Ruby":
1073 sourceExt = ".rb" 1099 sourceExt = ".rb"
1074 else: 1100 else:
1075 return [] 1101 return []
1076 1102
1077 formsSources = [] 1103 formsSources = []
1078 forms = self.__project.getProjectFiles("FORMS") 1104 forms = self.__project.getProjectFiles("FORMS")
1079 for fn in forms: 1105 for fn in forms:
1080 ofn = os.path.splitext(fn)[0] 1106 ofn = os.path.splitext(fn)[0]
1081 dirname, filename = os.path.split(ofn) 1107 dirname, filename = os.path.split(ofn)
1082 formSource = os.path.join(dirname, "Ui_" + filename + sourceExt) 1108 formSource = os.path.join(dirname, "Ui_" + filename + sourceExt)
1083 if normalized: 1109 if normalized:
1084 formSource = os.path.join( 1110 formSource = os.path.join(self.__project.getProjectPath(), formSource)
1085 self.__project.getProjectPath(), formSource)
1086 formsSources.append(formSource) 1111 formsSources.append(formSource)
1087 return formsSources 1112 return formsSources
1088 1113
1089 def prepareAPIs(self, rawList=None): 1114 def prepareAPIs(self, rawList=None):
1090 """ 1115 """
1091 Public method to prepare the APIs if neccessary. 1116 Public method to prepare the APIs if neccessary.
1092 1117
1093 @param rawList list of raw API files 1118 @param rawList list of raw API files
1094 @type list of str 1119 @type list of str
1095 """ 1120 """
1096 if self.__inPreparation: 1121 if self.__inPreparation:
1097 return 1122 return
1098 1123
1099 projectPath = "" 1124 projectPath = ""
1100 if rawList: 1125 if rawList:
1101 apiFiles = rawList[:] 1126 apiFiles = rawList[:]
1102 elif self.__language == ApisNameProject: 1127 elif self.__language == ApisNameProject:
1103 apiFiles = self.__project.getSources()[:] 1128 apiFiles = self.__project.getSources()[:]
1104 apiFiles.extend( 1129 apiFiles.extend(
1105 [f for f in self.__getProjectFormSources() if 1130 [f for f in self.__getProjectFormSources() if f not in apiFiles]
1106 f not in apiFiles]) 1131 )
1107 projectPath = self.__project.getProjectPath() 1132 projectPath = self.__project.getProjectPath()
1108 projectType = "" 1133 projectType = ""
1109 else: 1134 else:
1110 apiFiles = Preferences.getEditorAPI( 1135 apiFiles = Preferences.getEditorAPI(
1111 self.__language, projectType=self.__projectType) 1136 self.__language, projectType=self.__projectType
1137 )
1112 projectType = self.__projectType 1138 projectType = self.__projectType
1113 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, 1139 self.__worker = DbAPIsWorker(
1114 self._apiDbName(), projectPath, 1140 self,
1115 projectType=projectType) 1141 self.__language,
1142 apiFiles,
1143 self._apiDbName(),
1144 projectPath,
1145 projectType=projectType,
1146 )
1116 self.__worker.processing.connect( 1147 self.__worker.processing.connect(
1117 self.__processingStatus, Qt.ConnectionType.QueuedConnection) 1148 self.__processingStatus, Qt.ConnectionType.QueuedConnection
1149 )
1118 self.__worker.start() 1150 self.__worker.start()
1119 1151
1120 def __processQueue(self): 1152 def __processQueue(self):
1121 """ 1153 """
1122 Private slot to process the queue of files to load. 1154 Private slot to process the queue of files to load.
1123 """ 1155 """
1124 if self.__worker is not None and self.__worker.isFinished(): 1156 if self.__worker is not None and self.__worker.isFinished():
1125 self.__worker.deleteLater() 1157 self.__worker.deleteLater()
1126 self.__worker = None 1158 self.__worker = None
1127 1159
1128 if self.__worker is None and len(self.__workerQueue) > 0: 1160 if self.__worker is None and len(self.__workerQueue) > 0:
1129 apiFiles = [self.__workerQueue.pop(0)] 1161 apiFiles = [self.__workerQueue.pop(0)]
1130 if self.__language == ApisNameProject: 1162 if self.__language == ApisNameProject:
1131 projectPath = self.__project.getProjectPath() 1163 projectPath = self.__project.getProjectPath()
1132 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")] 1164 apiFiles = [apiFiles[0].replace(projectPath + os.sep, "")]
1133 projectType = "" 1165 projectType = ""
1134 else: 1166 else:
1135 projectPath = "" 1167 projectPath = ""
1136 projectType = self.__projectType 1168 projectType = self.__projectType
1137 self.__worker = DbAPIsWorker(self, self.__language, apiFiles, 1169 self.__worker = DbAPIsWorker(
1138 self._apiDbName(), projectPath, 1170 self,
1139 projectType=projectType, refresh=True) 1171 self.__language,
1172 apiFiles,
1173 self._apiDbName(),
1174 projectPath,
1175 projectType=projectType,
1176 refresh=True,
1177 )
1140 self.__worker.processing.connect( 1178 self.__worker.processing.connect(
1141 self.__processingStatus, Qt.ConnectionType.QueuedConnection) 1179 self.__processingStatus, Qt.ConnectionType.QueuedConnection
1180 )
1142 self.__worker.start() 1181 self.__worker.start()
1143 1182
1144 def getLexer(self): 1183 def getLexer(self):
1145 """ 1184 """
1146 Public method to return a reference to our lexer object. 1185 Public method to return a reference to our lexer object.
1147 1186
1148 @return reference to the lexer object 1187 @return reference to the lexer object
1149 @rtype Lexer 1188 @rtype Lexer
1150 """ 1189 """
1151 return self.__lexer 1190 return self.__lexer
1152 1191
1153 def autoCompletionWordSeparators(self): 1192 def autoCompletionWordSeparators(self):
1154 """ 1193 """
1155 Public method to get the word separator characters. 1194 Public method to get the word separator characters.
1156 1195
1157 @return word separator characters 1196 @return word separator characters
1158 @rtype list of str 1197 @rtype list of str
1159 """ 1198 """
1160 if self.__lexer: 1199 if self.__lexer:
1161 return self.__lexer.autoCompletionWordSeparators() 1200 return self.__lexer.autoCompletionWordSeparators()
1162 return None 1201 return None
1163 1202
1164 def __processingStatus(self, status, filename): 1203 def __processingStatus(self, status, filename):
1165 """ 1204 """
1166 Private slot handling the processing signal of the API preparation 1205 Private slot handling the processing signal of the API preparation
1167 thread. 1206 thread.
1168 1207
1169 @param status preparation status (one of WorkerStatus...) 1208 @param status preparation status (one of WorkerStatus...)
1170 @type int 1209 @type int
1171 @param filename name of the file being processed 1210 @param filename name of the file being processed
1172 @type str 1211 @type str
1173 """ 1212 """
1174 if status == WorkerStatusStarted: 1213 if status == WorkerStatusStarted:
1175 self.__inPreparation = True 1214 self.__inPreparation = True
1176 self.apiPreparationStatus.emit( 1215 self.apiPreparationStatus.emit(self.__language, WorkerStatusStarted, "")
1177 self.__language, WorkerStatusStarted, "")
1178 elif status == WorkerStatusFinished: 1216 elif status == WorkerStatusFinished:
1179 self.__inPreparation = False 1217 self.__inPreparation = False
1180 self.apiPreparationStatus.emit( 1218 self.apiPreparationStatus.emit(self.__language, WorkerStatusFinished, "")
1181 self.__language, WorkerStatusFinished, "")
1182 QTimer.singleShot(0, self.__processQueue) 1219 QTimer.singleShot(0, self.__processQueue)
1183 elif status == WorkerStatusAborted: 1220 elif status == WorkerStatusAborted:
1184 self.__inPreparation = False 1221 self.__inPreparation = False
1185 self.apiPreparationStatus.emit( 1222 self.apiPreparationStatus.emit(self.__language, WorkerStatusAborted, "")
1186 self.__language, WorkerStatusAborted, "")
1187 QTimer.singleShot(0, self.__processQueue) 1223 QTimer.singleShot(0, self.__processQueue)
1188 elif status == WorkerStatusFile: 1224 elif status == WorkerStatusFile:
1189 self.apiPreparationStatus.emit( 1225 self.apiPreparationStatus.emit(self.__language, WorkerStatusFile, filename)
1190 self.__language, WorkerStatusFile, filename) 1226
1191
1192 ######################################################## 1227 ########################################################
1193 ## project related stuff below 1228 ## project related stuff below
1194 ######################################################## 1229 ########################################################
1195 1230
1196 def __projectOpened(self): 1231 def __projectOpened(self):
1197 """ 1232 """
1198 Private slot to perform actions after a project has been opened. 1233 Private slot to perform actions after a project has been opened.
1199 """ 1234 """
1200 if self.__project.getProjectLanguage() in ( 1235 if self.__project.getProjectLanguage() in ("Python3", "MicroPython", "Cython"):
1201 "Python3", "MicroPython", "Cython"
1202 ):
1203 self.__discardFirst = ["self", "cls"] 1236 self.__discardFirst = ["self", "cls"]
1204 else: 1237 else:
1205 self.__discardFirst = [] 1238 self.__discardFirst = []
1206 self.__lexer = QScintilla.Lexers.getLexer( 1239 self.__lexer = QScintilla.Lexers.getLexer(self.__project.getProjectLanguage())
1207 self.__project.getProjectLanguage())
1208 self.__openAPIs() 1240 self.__openAPIs()
1209 1241
1210 def __projectClosed(self): 1242 def __projectClosed(self):
1211 """ 1243 """
1212 Private slot to perform actions after a project has been closed. 1244 Private slot to perform actions after a project has been closed.
1213 """ 1245 """
1214 self.close() 1246 self.close()
1215 1247
1216 def __projectFormCompiled(self, filename): 1248 def __projectFormCompiled(self, filename):
1217 """ 1249 """
1218 Private slot to handle the projectFormCompiled signal. 1250 Private slot to handle the projectFormCompiled signal.
1219 1251
1220 @param filename name of the form file that was compiled 1252 @param filename name of the form file that was compiled
1221 @type str 1253 @type str
1222 """ 1254 """
1223 self.__workerQueue.append(filename) 1255 self.__workerQueue.append(filename)
1224 self.__processQueue() 1256 self.__processQueue()
1225 1257
1226 def __projectChanged(self): 1258 def __projectChanged(self):
1227 """ 1259 """
1228 Private slot to handle the projectChanged signal. 1260 Private slot to handle the projectChanged signal.
1229 """ 1261 """
1230 if self.__opened: 1262 if self.__opened:
1231 self.__projectClosed() 1263 self.__projectClosed()
1232 self.__projectOpened() 1264 self.__projectOpened()
1233 1265
1234 def editorSaved(self, filename): 1266 def editorSaved(self, filename):
1235 """ 1267 """
1236 Public slot to handle the editorSaved signal. 1268 Public slot to handle the editorSaved signal.
1237 1269
1238 @param filename name of the file that was saved 1270 @param filename name of the file that was saved
1239 @type str 1271 @type str
1240 """ 1272 """
1241 if self.__project.isProjectSource(filename): 1273 if self.__project.isProjectSource(filename):
1242 self.__workerQueue.append(filename) 1274 self.__workerQueue.append(filename)
1246 class APIsManager(QObject): 1278 class APIsManager(QObject):
1247 """ 1279 """
1248 Class implementing the APIsManager class, which is the central store for 1280 Class implementing the APIsManager class, which is the central store for
1249 API information used by autocompletion and calltips. 1281 API information used by autocompletion and calltips.
1250 """ 1282 """
1283
1251 def __init__(self, mainWindow, parent=None): 1284 def __init__(self, mainWindow, parent=None):
1252 """ 1285 """
1253 Constructor 1286 Constructor
1254 1287
1255 @param mainWindow reference to the main eric7 window 1288 @param mainWindow reference to the main eric7 window
1256 @type QMainWindow 1289 @type QMainWindow
1257 @param parent reference to the parent object 1290 @param parent reference to the parent object
1258 @type QObject 1291 @type QObject
1259 """ 1292 """
1260 QObject.__init__(self, parent) 1293 QObject.__init__(self, parent)
1261 self.setObjectName("Assistant_APIsManager") 1294 self.setObjectName("Assistant_APIsManager")
1262 1295
1263 self.__mw = mainWindow 1296 self.__mw = mainWindow
1264 1297
1265 # initialize the apis dictionary 1298 # initialize the apis dictionary
1266 self.__apis = {} 1299 self.__apis = {}
1267 1300
1268 def reloadAPIs(self): 1301 def reloadAPIs(self):
1269 """ 1302 """
1270 Public slot to reload the api information. 1303 Public slot to reload the api information.
1271 """ 1304 """
1272 for api in list(self.__apis.values()): 1305 for api in list(self.__apis.values()):
1273 api and api.prepareAPIs() 1306 api and api.prepareAPIs()
1274 1307
1275 def getAPIs(self, language, projectType=""): 1308 def getAPIs(self, language, projectType=""):
1276 """ 1309 """
1277 Public method to get an apis object for autocompletion/calltips. 1310 Public method to get an apis object for autocompletion/calltips.
1278 1311
1279 This method creates and loads an APIs object dynamically upon request. 1312 This method creates and loads an APIs object dynamically upon request.
1280 This saves memory for languages, that might not be needed at the 1313 This saves memory for languages, that might not be needed at the
1281 moment. 1314 moment.
1282 1315
1283 @param language language of the requested APIs object 1316 @param language language of the requested APIs object
1284 @type str 1317 @type str
1285 @param projectType type of the project 1318 @param projectType type of the project
1286 @type str 1319 @type str
1287 @return reference to the APIs object 1320 @return reference to the APIs object
1289 """ 1322 """
1290 try: 1323 try:
1291 return self.__apis[(language, projectType)] 1324 return self.__apis[(language, projectType)]
1292 except KeyError: 1325 except KeyError:
1293 if ( 1326 if (
1294 language in QScintilla.Lexers.getSupportedApiLanguages() or 1327 language in QScintilla.Lexers.getSupportedApiLanguages()
1295 language == ApisNameProject 1328 or language == ApisNameProject
1296 ): 1329 ):
1297 # create the api object 1330 # create the api object
1298 api = DbAPIs(language, projectType=projectType) 1331 api = DbAPIs(language, projectType=projectType)
1299 api.apiPreparationStatus.connect(self.__apiPreparationStatus) 1332 api.apiPreparationStatus.connect(self.__apiPreparationStatus)
1300 self.__apis[(language, projectType)] = api 1333 self.__apis[(language, projectType)] = api
1301 return self.__apis[(language, projectType)] 1334 return self.__apis[(language, projectType)]
1302 else: 1335 else:
1303 return None 1336 return None
1304 1337
1305 def deactivate(self): 1338 def deactivate(self):
1306 """ 1339 """
1307 Public method to perform actions upon deactivation. 1340 Public method to perform actions upon deactivation.
1308 """ 1341 """
1309 for apiLang in self.__apis: 1342 for apiLang in self.__apis:
1310 self.__apis[apiLang].close() 1343 self.__apis[apiLang].close()
1311 self.__apis[apiLang].deleteLater() 1344 self.__apis[apiLang].deleteLater()
1312 self.__apis[apiLang] = None 1345 self.__apis[apiLang] = None
1313 1346
1314 def __showMessage(self, msg): 1347 def __showMessage(self, msg):
1315 """ 1348 """
1316 Private message to show a message in the main windows status bar. 1349 Private message to show a message in the main windows status bar.
1317 1350
1318 @param msg message to be shown 1351 @param msg message to be shown
1319 @type str 1352 @type str
1320 """ 1353 """
1321 if msg: 1354 if msg:
1322 self.__mw.statusBar().showMessage(msg, 2000) 1355 self.__mw.statusBar().showMessage(msg, 2000)
1323 1356
1324 def __apiPreparationStatus(self, language, status, filename): 1357 def __apiPreparationStatus(self, language, status, filename):
1325 """ 1358 """
1326 Private slot handling the preparation status signal of an API object. 1359 Private slot handling the preparation status signal of an API object.
1327 1360
1328 @param language language of the API 1361 @param language language of the API
1329 @type str 1362 @type str
1330 @param status preparation status (one of WorkerStatus...) 1363 @param status preparation status (one of WorkerStatus...)
1331 @type int 1364 @type int
1332 @param filename name of the file being processed 1365 @param filename name of the file being processed
1333 @type str 1366 @type str
1334 """ 1367 """
1335 if language == ApisNameProject: 1368 if language == ApisNameProject:
1336 language = self.tr("Project") 1369 language = self.tr("Project")
1337 1370
1338 if status == WorkerStatusStarted: 1371 if status == WorkerStatusStarted:
1339 self.__showMessage(self.tr( 1372 self.__showMessage(
1340 "Preparation of '{0}' APIs started.").format(language)) 1373 self.tr("Preparation of '{0}' APIs started.").format(language)
1374 )
1341 elif status == WorkerStatusFile: 1375 elif status == WorkerStatusFile:
1342 self.__showMessage(self.tr( 1376 self.__showMessage(
1343 "'{0}' APIs: Processing '{1}'").format( 1377 self.tr("'{0}' APIs: Processing '{1}'").format(
1344 language, os.path.basename(filename))) 1378 language, os.path.basename(filename)
1379 )
1380 )
1345 elif status == WorkerStatusFinished: 1381 elif status == WorkerStatusFinished:
1346 self.__showMessage(self.tr( 1382 self.__showMessage(
1347 "Preparation of '{0}' APIs finished.").format(language)) 1383 self.tr("Preparation of '{0}' APIs finished.").format(language)
1384 )
1348 elif status == WorkerStatusAborted: 1385 elif status == WorkerStatusAborted:
1349 self.__showMessage(self.tr( 1386 self.__showMessage(
1350 "Preparation of '{0}' APIs cancelled.").format(language)) 1387 self.tr("Preparation of '{0}' APIs cancelled.").format(language)
1388 )
1389
1351 1390
1352 # 1391 #
1353 # eflag: noqa = M523, M834, S608 1392 # eflag: noqa = M523, M834, S608

eric ide

mercurial