|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2020 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the editor outline model. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import contextlib |
|
12 |
|
13 from PyQt6.QtCore import QCoreApplication, QModelIndex |
|
14 |
|
15 from UI.BrowserModel import ( |
|
16 BrowserModel, BrowserItem, BrowserClassItem, BrowserCodingItem, |
|
17 BrowserGlobalsItem, BrowserImportsItem, BrowserImportItem, |
|
18 BrowserClassAttributesItem, BrowserMethodItem |
|
19 ) |
|
20 |
|
21 import Preferences |
|
22 |
|
23 |
|
24 class EditorOutlineModel(BrowserModel): |
|
25 """ |
|
26 Class implementing the editor outline model. |
|
27 """ |
|
28 SupportedLanguages = ( |
|
29 "IDL", "JavaScript", "Protocol", "Python3", "MicroPython", "Cython", |
|
30 "Ruby", |
|
31 ) |
|
32 |
|
33 def __init__(self, editor, populate=True): |
|
34 """ |
|
35 Constructor |
|
36 |
|
37 @param editor reference to the editor containing the source text |
|
38 @type Editor |
|
39 @param populate flag indicating to populate the outline |
|
40 @type bool |
|
41 """ |
|
42 super().__init__(nopopulate=True) |
|
43 |
|
44 self.__editor = editor |
|
45 |
|
46 self.__populated = False |
|
47 |
|
48 rootData = QCoreApplication.translate("EditorOutlineModel", "Name") |
|
49 self.rootItem = BrowserItem(None, rootData) |
|
50 |
|
51 if populate: |
|
52 self.__populateModel() |
|
53 |
|
54 def __populateModel(self, repopulate=False): |
|
55 """ |
|
56 Private slot to populate the model. |
|
57 |
|
58 @param repopulate flag indicating a repopulation |
|
59 @type bool |
|
60 """ |
|
61 self.__filename = self.__editor.getFileName() |
|
62 self.__module = os.path.basename(self.__filename) |
|
63 |
|
64 language = self.__editor.getLanguage() |
|
65 if language in EditorOutlineModel.SupportedLanguages: |
|
66 if language == "IDL": |
|
67 from Utilities.ClassBrowsers import idlclbr |
|
68 dictionary = idlclbr.scan( |
|
69 self.__editor.text(), self.__filename, self.__module) |
|
70 idlclbr._modules.clear() |
|
71 elif language == "Protocol": |
|
72 from Utilities.ClassBrowsers import protoclbr |
|
73 dictionary = protoclbr.scan( |
|
74 self.__editor.text(), self.__filename, self.__module) |
|
75 protoclbr._modules.clear() |
|
76 elif language == "Ruby": |
|
77 from Utilities.ClassBrowsers import rbclbr |
|
78 dictionary = rbclbr.scan( |
|
79 self.__editor.text(), self.__filename, self.__module) |
|
80 rbclbr._modules.clear() |
|
81 elif language == "JavaScript": |
|
82 from Utilities.ClassBrowsers import jsclbr |
|
83 dictionary = jsclbr.scan( |
|
84 self.__editor.text(), self.__filename, self.__module) |
|
85 jsclbr._modules.clear() |
|
86 elif language in ("Python3", "MicroPython", "Cython"): |
|
87 from Utilities.ClassBrowsers import pyclbr |
|
88 dictionary = pyclbr.scan( |
|
89 self.__editor.text(), self.__filename, self.__module) |
|
90 pyclbr._modules.clear() |
|
91 |
|
92 keys = list(dictionary.keys()) |
|
93 if len(keys) > 0: |
|
94 parentItem = self.rootItem |
|
95 |
|
96 if repopulate: |
|
97 last = len(keys) - 1 |
|
98 if ( |
|
99 "@@Coding@@" in keys and |
|
100 not Preferences.getEditor("SourceOutlineShowCoding") |
|
101 ): |
|
102 last -= 1 |
|
103 self.beginInsertRows(QModelIndex(), 0, last) |
|
104 |
|
105 for key in keys: |
|
106 if key.startswith("@@"): |
|
107 # special treatment done later |
|
108 continue |
|
109 cl = dictionary[key] |
|
110 with contextlib.suppress(AttributeError): |
|
111 if cl.module == self.__module: |
|
112 node = BrowserClassItem( |
|
113 parentItem, cl, self.__filename) |
|
114 self._addItem(node, parentItem) |
|
115 if ( |
|
116 "@@Coding@@" in keys and |
|
117 Preferences.getEditor("SourceOutlineShowCoding") |
|
118 ): |
|
119 node = BrowserCodingItem( |
|
120 parentItem, |
|
121 QCoreApplication.translate( |
|
122 "EditorOutlineModel", "Coding: {0}") |
|
123 .format(dictionary["@@Coding@@"].coding), |
|
124 dictionary["@@Coding@@"].linenumber) |
|
125 self._addItem(node, parentItem) |
|
126 if "@@Globals@@" in keys: |
|
127 node = BrowserGlobalsItem( |
|
128 parentItem, |
|
129 dictionary["@@Globals@@"].globals, |
|
130 QCoreApplication.translate( |
|
131 "EditorOutlineModel", "Globals")) |
|
132 self._addItem(node, parentItem) |
|
133 if "@@Import@@" in keys or "@@ImportFrom@@" in keys: |
|
134 node = BrowserImportsItem( |
|
135 parentItem, |
|
136 QCoreApplication.translate( |
|
137 "EditorOutlineModel", "Imports")) |
|
138 self._addItem(node, parentItem) |
|
139 if "@@Import@@" in keys: |
|
140 for importedModule in ( |
|
141 dictionary["@@Import@@"].getImports().values() |
|
142 ): |
|
143 m_node = BrowserImportItem( |
|
144 node, |
|
145 importedModule.importedModuleName, |
|
146 importedModule.file, |
|
147 importedModule.linenos) |
|
148 self._addItem(m_node, node) |
|
149 for importedName, linenos in ( |
|
150 importedModule.importedNames.items() |
|
151 ): |
|
152 mn_node = BrowserImportItem( |
|
153 m_node, |
|
154 importedName, |
|
155 importedModule.file, |
|
156 linenos, |
|
157 isModule=False) |
|
158 self._addItem(mn_node, m_node) |
|
159 if repopulate: |
|
160 self.endInsertRows() |
|
161 |
|
162 self.__populated = True |
|
163 else: |
|
164 self.clear() |
|
165 self.__populated = False |
|
166 |
|
167 def isPopulated(self): |
|
168 """ |
|
169 Public method to check, if the model is populated. |
|
170 |
|
171 @return flag indicating a populated model |
|
172 @rtype bool |
|
173 """ |
|
174 return self.__populated |
|
175 |
|
176 def repopulate(self): |
|
177 """ |
|
178 Public slot to repopulate the model. |
|
179 """ |
|
180 self.clear() |
|
181 self.__populateModel(repopulate=True) |
|
182 |
|
183 def editor(self): |
|
184 """ |
|
185 Public method to retrieve a reference to the editor. |
|
186 |
|
187 @return reference to the editor |
|
188 @rtype Editor |
|
189 """ |
|
190 return self.__editor |
|
191 |
|
192 def fileName(self): |
|
193 """ |
|
194 Public method to retrieve the file name of the editor. |
|
195 |
|
196 @return file name of the editor |
|
197 @rtype str |
|
198 """ |
|
199 return self.__filename |
|
200 |
|
201 def itemIndexByLine(self, lineno): |
|
202 """ |
|
203 Public method to find an item's index given a line number. |
|
204 |
|
205 @param lineno one based line number of the item |
|
206 @type int |
|
207 @return index of the item found |
|
208 @rtype QModelIndex |
|
209 """ |
|
210 def findItem(lineno, parent): |
|
211 """ |
|
212 Function to iteratively search for an item containing the given |
|
213 line. |
|
214 |
|
215 @param lineno one based line number of the item |
|
216 @type int |
|
217 @param parent reference to the parent item |
|
218 @type BrowserItem |
|
219 @return found item or None |
|
220 @rtype BrowserItem |
|
221 """ |
|
222 if not parent.isPopulated(): |
|
223 if parent.isLazyPopulated(): |
|
224 self.populateItem(parent) |
|
225 else: |
|
226 return None |
|
227 for child in parent.children(): |
|
228 if isinstance(child, BrowserClassAttributesItem): |
|
229 itm = findItem(lineno, child) |
|
230 if itm is not None: |
|
231 return itm |
|
232 elif isinstance(child, (BrowserClassItem, BrowserMethodItem)): |
|
233 start, end = child.boundaries() |
|
234 if end == -1: |
|
235 end = 1000000 # assume end of file |
|
236 if start <= lineno <= end: |
|
237 itm = findItem(lineno, child) |
|
238 if itm is not None: |
|
239 return itm |
|
240 else: |
|
241 return child |
|
242 elif hasattr(child, "linenos"): |
|
243 if lineno in child.linenos(): |
|
244 return child |
|
245 elif ( |
|
246 hasattr(child, "lineno") and |
|
247 lineno == child.lineno() |
|
248 ): |
|
249 return child |
|
250 else: |
|
251 return None |
|
252 |
|
253 if self.__populated: |
|
254 for rootChild in self.rootItem.children(): |
|
255 itm = None |
|
256 if isinstance(rootChild, BrowserClassItem): |
|
257 start, end = rootChild.boundaries() |
|
258 if end == -1: |
|
259 end = 1000000 # assume end of file |
|
260 if start <= lineno <= end: |
|
261 itm = findItem(lineno, rootChild) |
|
262 if itm is None: |
|
263 itm = rootChild |
|
264 elif isinstance(rootChild, |
|
265 (BrowserImportsItem, BrowserGlobalsItem)): |
|
266 itm = findItem(lineno, rootChild) |
|
267 elif ( |
|
268 isinstance(rootChild, BrowserCodingItem) and |
|
269 lineno == rootChild.lineno() |
|
270 ): |
|
271 itm = rootChild |
|
272 if itm is not None: |
|
273 return self.createIndex(itm.row(), 0, itm) |
|
274 else: |
|
275 return QModelIndex() |
|
276 |
|
277 return QModelIndex() |