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