1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing the APIsManager. 8""" 9 10import os 11 12from PyQt5.QtCore import QDir, QFileInfo, pyqtSignal, QObject 13from PyQt5.Qsci import QsciAPIs 14 15from . import Lexers 16import Preferences 17import Globals 18 19 20class APIs(QObject): 21 """ 22 Class implementing an API storage entity. 23 24 @signal apiPreparationFinished() emitted after the API preparation has 25 finished 26 @signal apiPreparationCancelled() emitted after the API preparation has 27 been cancelled 28 @signal apiPreparationStarted() emitted after the API preparation has 29 started 30 """ 31 apiPreparationFinished = pyqtSignal() 32 apiPreparationCancelled = pyqtSignal() 33 apiPreparationStarted = pyqtSignal() 34 35 def __init__(self, language, projectType="", forPreparation=False, 36 parent=None): 37 """ 38 Constructor 39 40 @param language language of the APIs object 41 @type str 42 @param projectType type of the project 43 @type str 44 @param forPreparation flag indicating this object is just needed 45 for a preparation process 46 @type bool 47 @param parent reference to the parent object 48 @type QObject 49 """ 50 super().__init__(parent) 51 if projectType: 52 self.setObjectName("APIs_{0}_{1}".format(language, projectType)) 53 else: 54 self.setObjectName("APIs_{0}".format(language)) 55 56 self.__inPreparation = False 57 self.__language = language 58 self.__projectType = projectType 59 self.__forPreparation = forPreparation 60 self.__lexer = Lexers.getLexer(self.__language) 61 self.__apifiles = Preferences.getEditorAPI(self.__language, 62 self.__projectType) 63 self.__apifiles.sort() 64 if self.__lexer is None: 65 self.__apis = None 66 else: 67 self.__apis = QsciAPIs(self.__lexer) 68 self.__apis.apiPreparationFinished.connect( 69 self.__apiPreparationFinished) 70 self.__apis.apiPreparationCancelled.connect( 71 self.__apiPreparationCancelled) 72 self.__apis.apiPreparationStarted.connect( 73 self.__apiPreparationStarted) 74 self.__loadAPIs() 75 76 def __loadAPIs(self): 77 """ 78 Private method to load the APIs. 79 """ 80 if self.__apis.isPrepared(): 81 # load a prepared API file 82 if ( 83 not self.__forPreparation and 84 Preferences.getEditor("AutoPrepareAPIs") 85 ): 86 self.prepareAPIs() 87 self.__apis.loadPrepared(self.__preparedName()) 88 else: 89 # load the raw files and prepare the API file 90 if ( 91 not self.__forPreparation and 92 Preferences.getEditor("AutoPrepareAPIs") 93 ): 94 self.prepareAPIs(ondemand=True) 95 96 def reloadAPIs(self): 97 """ 98 Public method to reload the API information. 99 """ 100 if ( 101 not self.__forPreparation and 102 Preferences.getEditor("AutoPrepareAPIs") 103 ): 104 self.prepareAPIs() 105 self.__loadAPIs() 106 107 def getQsciAPIs(self): 108 """ 109 Public method to get a reference to QsciAPIs object. 110 111 @return reference to the QsciAPIs object (QsciAPIs) 112 """ 113 if ( 114 not self.__forPreparation and 115 Preferences.getEditor("AutoPrepareAPIs") 116 ): 117 self.prepareAPIs() 118 return self.__apis 119 120 def isEmpty(self): 121 """ 122 Public method to check, if the object has API files configured. 123 124 @return flag indicating no API files have been configured (boolean) 125 """ 126 return len(self.__apifiles) == 0 127 128 def __apiPreparationFinished(self): 129 """ 130 Private method called to save an API, after it has been prepared. 131 """ 132 self.__apis.savePrepared(self.__preparedName()) 133 self.__inPreparation = False 134 self.apiPreparationFinished.emit() 135 136 def __apiPreparationCancelled(self): 137 """ 138 Private method called, after the API preparation process has been 139 cancelled. 140 """ 141 self.__inPreparation = False 142 self.apiPreparationCancelled.emit() 143 144 def __apiPreparationStarted(self): 145 """ 146 Private method called, when the API preparation process started. 147 """ 148 self.__inPreparation = True 149 self.apiPreparationStarted.emit() 150 151 def prepareAPIs(self, ondemand=False, rawList=None): 152 """ 153 Public method to prepare the APIs if necessary. 154 155 @param ondemand flag indicating a requested preparation (boolean) 156 @param rawList list of raw API files (list of strings) 157 """ 158 if self.__apis is None or self.__inPreparation: 159 return 160 161 needsPreparation = False 162 if ondemand: 163 needsPreparation = True 164 else: 165 # check, if a new preparation is necessary 166 preparedAPIs = self.__preparedName() 167 if preparedAPIs: 168 preparedAPIsInfo = QFileInfo(preparedAPIs) 169 if not preparedAPIsInfo.exists(): 170 needsPreparation = True 171 else: 172 preparedAPIsTime = preparedAPIsInfo.lastModified() 173 apifiles = sorted(Preferences.getEditorAPI( 174 self.__language, self.__projectType)) 175 if self.__apifiles != apifiles: 176 needsPreparation = True 177 for apifile in apifiles: 178 if ( 179 QFileInfo(apifile).lastModified() > 180 preparedAPIsTime 181 ): 182 needsPreparation = True 183 break 184 185 if needsPreparation: 186 # do the preparation 187 self.__apis.clear() 188 if rawList: 189 apifiles = rawList 190 else: 191 apifiles = Preferences.getEditorAPI( 192 self.__language, self.__projectType) 193 for apifile in apifiles: 194 self.__apis.load(apifile) 195 self.__apis.prepare() 196 self.__apifiles = apifiles 197 198 def cancelPreparation(self): 199 """ 200 Public slot to cancel the APIs preparation. 201 """ 202 self.__apis and self.__apis.cancelPreparation() 203 204 def installedAPIFiles(self): 205 """ 206 Public method to get a list of installed API files. 207 208 @return list of installed API files (list of strings) 209 """ 210 if self.__apis is not None: 211 if Globals.isWindowsPlatform(): 212 qsciPath = os.path.join( 213 Globals.getPyQt5ModulesDirectory(), "qsci") 214 if os.path.exists(qsciPath): 215 # it's the installer 216 if self.__lexer.lexerName() is not None: 217 apidir = os.path.join(qsciPath, "api", 218 self.__lexer.lexerName()) 219 fnames = [] 220 filist = QDir(apidir).entryInfoList( 221 ["*.api"], QDir.Filter.Files, 222 QDir.SortFlag.IgnoreCase) 223 for fi in filist: 224 fnames.append(fi.absoluteFilePath()) 225 return fnames 226 else: 227 return [] 228 229 return self.__apis.installedAPIFiles() 230 else: 231 return [] 232 233 def __preparedName(self): 234 """ 235 Private method returning the default name of a prepared API file. 236 237 @return complete filename for the Prepared APIs file (string) 238 """ 239 apisDir = os.path.join(Globals.getConfigDir(), "APIs") 240 if self.__apis is not None: 241 if self.__projectType: 242 filename = "{0}_{1}.pap".format(self.__language, 243 self.__projectType) 244 else: 245 filename = "{0}.pap".format(self.__language) 246 return os.path.join(apisDir, filename) 247 else: 248 return "" 249 250 251class APIsManager(QObject): 252 """ 253 Class implementing the APIsManager class, which is the central store for 254 API information used by autocompletion and calltips. 255 """ 256 def __init__(self, parent=None): 257 """ 258 Constructor 259 260 @param parent reference to the parent object (QObject) 261 """ 262 super().__init__(parent) 263 self.setObjectName("APIsManager") 264 265 self.__apis = {} 266 267 def reloadAPIs(self): 268 """ 269 Public slot to reload the api information. 270 """ 271 for api in list(self.__apis.values()): 272 api and api.reloadAPIs() 273 274 def getAPIs(self, language, projectType="", forPreparation=False): 275 """ 276 Public method to get an APIs object for autocompletion/calltips. 277 278 This method creates and loads an APIs object dynamically upon request. 279 This saves memory for languages, that might not be needed at the 280 moment. 281 282 @param language language of the requested APIs object 283 @type str 284 @param projectType type of the project 285 @type str 286 @param forPreparation flag indicating the requested APIs object is just 287 needed for a preparation process 288 @type bool 289 @return reference to the APIs object 290 @rtype APIs 291 """ 292 if forPreparation: 293 return APIs(language, projectType=projectType, 294 forPreparation=forPreparation) 295 else: 296 try: 297 return self.__apis[(language, projectType)] 298 except KeyError: 299 if language in Lexers.getSupportedApiLanguages(): 300 # create the api object 301 self.__apis[(language, projectType)] = APIs( 302 language, projectType=projectType) 303 return self.__apis[(language, projectType)] 304 else: 305 return None 306