|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a class representing the session JSON file. |
|
8 """ |
|
9 |
|
10 import contextlib |
|
11 import json |
|
12 import time |
|
13 |
|
14 from PyQt6.QtCore import Qt, QObject |
|
15 |
|
16 from EricWidgets import EricMessageBox |
|
17 from EricGui.EricOverrideCursor import EricOverridenCursor |
|
18 from EricWidgets.EricApplication import ericApp |
|
19 |
|
20 import Preferences |
|
21 |
|
22 |
|
23 class SessionFile(QObject): |
|
24 """ |
|
25 Class representing the session JSON file. |
|
26 """ |
|
27 def __init__(self, isGlobal: bool, parent: QObject = None): |
|
28 """ |
|
29 Constructor |
|
30 |
|
31 @param isGlobal flag indicating a file for a global session |
|
32 @type bool |
|
33 @param parent reference to the parent object (defaults to None) |
|
34 @type QObject (optional) |
|
35 """ |
|
36 super().__init__(parent) |
|
37 |
|
38 self.__isGlobal = isGlobal |
|
39 |
|
40 def writeFile(self, filename: str) -> bool: |
|
41 """ |
|
42 Public method to write the session data to a session JSON file. |
|
43 |
|
44 @param filename name of the session file |
|
45 @type str |
|
46 @return flag indicating a successful write |
|
47 @rtype bool |
|
48 """ |
|
49 # get references to objects we need |
|
50 project = ericApp().getObject("Project") |
|
51 projectBrowser = ericApp().getObject("ProjectBrowser") |
|
52 multiProject = ericApp().getObject("MultiProject") |
|
53 vm = ericApp().getObject("ViewManager") |
|
54 dbg = ericApp().getObject("DebugUI") |
|
55 dbs = ericApp().getObject("DebugServer") |
|
56 |
|
57 # prepare the session data dictionary |
|
58 # step 0: header |
|
59 sessionDict = {} |
|
60 sessionDict["header"] = {} |
|
61 if not self.__isGlobal: |
|
62 sessionDict["header"]["comment"] = ( |
|
63 "eric session file for project {0}" |
|
64 .format(project.getProjectName()) |
|
65 ) |
|
66 sessionDict["header"]["warning"] = ( |
|
67 "This file was generated automatically, do not edit." |
|
68 ) |
|
69 |
|
70 if Preferences.getProject("TimestampFile") or self.__isGlobal: |
|
71 sessionDict["header"]["saved"] = ( |
|
72 time.strftime('%Y-%m-%d, %H:%M:%S') |
|
73 ) |
|
74 |
|
75 # step 1: open multi project and project for global session |
|
76 sessionDict["MultiProject"] = "" |
|
77 sessionDict["Project"] = "" |
|
78 if self.__isGlobal: |
|
79 if multiProject.isOpen(): |
|
80 sessionDict["MultiProject"] = ( |
|
81 multiProject.getMultiProjectFile() |
|
82 ) |
|
83 if project.isOpen(): |
|
84 sessionDict["Project"] = project.getProjectFile() |
|
85 |
|
86 # step 2: all open (project) filenames and the active editor |
|
87 if vm.canSplit(): |
|
88 sessionDict["ViewManagerSplits"] = { |
|
89 "Count": vm.splitCount(), |
|
90 "Orientation": vm.getSplitOrientation().value, |
|
91 } |
|
92 else: |
|
93 sessionDict["ViewManagerSplits"] = { |
|
94 "Count": 0, |
|
95 "Orientation": 1, |
|
96 } |
|
97 |
|
98 editorsDict = {} # remember editors by file name to detect clones |
|
99 sessionDict["Editors"] = [] |
|
100 allOpenEditorLists = vm.getOpenEditorsForSession() |
|
101 for splitIndex, openEditorList in enumerate(allOpenEditorLists): |
|
102 for editorIndex, editor in enumerate(openEditorList): |
|
103 fileName = editor.getFileName() |
|
104 if self.__isGlobal or project.isProjectFile(fileName): |
|
105 if fileName in editorsDict: |
|
106 isClone = editorsDict[fileName].isClone(editor) |
|
107 else: |
|
108 isClone = False |
|
109 editorsDict[fileName] = editor |
|
110 editorDict = { |
|
111 "Filename": fileName, |
|
112 "Cursor": editor.getCursorPosition(), |
|
113 "Folds": editor.contractedFolds(), |
|
114 "Zoom": editor.getZoom(), |
|
115 "Clone": isClone, |
|
116 "Splitindex": splitIndex, |
|
117 "Editorindex": editorIndex, |
|
118 } |
|
119 sessionDict["Editors"].append(editorDict) |
|
120 |
|
121 aw = vm.getActiveName() |
|
122 sessionDict["ActiveWindow"] = {} |
|
123 if aw and (self.__isGlobal or project.isProjectFile(aw)): |
|
124 ed = vm.getOpenEditor(aw) |
|
125 sessionDict["ActiveWindow"] = { |
|
126 "Filename": aw, |
|
127 "Cursor": ed.getCursorPosition(), |
|
128 } |
|
129 |
|
130 # step 3: breakpoints |
|
131 allBreaks = Preferences.getProject("SessionAllBreakpoints") |
|
132 projectFiles = project.getSources(True) |
|
133 bpModel = dbs.getBreakPointModel() |
|
134 if self.__isGlobal or allBreaks: |
|
135 sessionDict["Breakpoints"] = bpModel.getAllBreakpoints() |
|
136 else: |
|
137 sessionDict["Breakpoints"] = [ |
|
138 bp |
|
139 for bp in bpModel.getAllBreakpoints() |
|
140 if bp[0] in projectFiles |
|
141 ] |
|
142 |
|
143 # step 4: watch expressions |
|
144 wpModel = dbs.getWatchPointModel() |
|
145 sessionDict["Watchpoints"] = wpModel.getAllWatchpoints() |
|
146 |
|
147 # step 5: debug info |
|
148 if self.__isGlobal: |
|
149 if len(dbg.scriptsHistory): |
|
150 dbgScriptName = dbg.scriptsHistory[0] |
|
151 else: |
|
152 dbgScriptName = "" |
|
153 if len(dbg.argvHistory): |
|
154 dbgCmdline = dbg.argvHistory[0] |
|
155 else: |
|
156 dbgCmdline = "" |
|
157 if len(dbg.wdHistory): |
|
158 dbgWd = dbg.wdHistory[0] |
|
159 else: |
|
160 dbgWd = "" |
|
161 if len(dbg.envHistory): |
|
162 dbgEnv = dbg.envHistory[0] |
|
163 else: |
|
164 dbgEnv = "" |
|
165 if len(dbg.multiprocessNoDebugHistory): |
|
166 dbgMultiprocessNoDebug = ( |
|
167 dbg.multiprocessNoDebugHistory[0] |
|
168 ) |
|
169 else: |
|
170 dbgMultiprocessNoDebug = "" |
|
171 sessionDict["DebugInfo"] = { |
|
172 "VirtualEnv": dbg.lastUsedVenvName, |
|
173 "ScriptName": dbgScriptName, |
|
174 "CommandLine": dbgCmdline, |
|
175 "WorkingDirectory": dbgWd, |
|
176 "Environment": dbgEnv, |
|
177 "ReportExceptions": dbg.exceptions, |
|
178 "Exceptions": dbg.excList, |
|
179 "IgnoredExceptions": dbg.excIgnoreList, |
|
180 "AutoClearShell": dbg.autoClearShell, |
|
181 "TracePython": dbg.tracePython, |
|
182 "AutoContinue": dbg.autoContinue, |
|
183 "EnableMultiprocess": dbg.enableMultiprocess, |
|
184 "MultiprocessNoDebug": dbgMultiprocessNoDebug, |
|
185 "GlobalConfigOverride": dbg.overrideGlobalConfig, |
|
186 } |
|
187 else: |
|
188 sessionDict["DebugInfo"] = { |
|
189 "VirtualEnv": project.dbgVirtualEnv, |
|
190 "ScriptName": "", |
|
191 "CommandLine": project.dbgCmdline, |
|
192 "WorkingDirectory": project.dbgWd, |
|
193 "Environment": project.dbgEnv, |
|
194 "ReportExceptions": project.dbgReportExceptions, |
|
195 "Exceptions": project.dbgExcList, |
|
196 "IgnoredExceptions": project.dbgExcIgnoreList, |
|
197 "AutoClearShell": project.dbgAutoClearShell, |
|
198 "TracePython": project.dbgTracePython, |
|
199 "AutoContinue": project.dbgAutoContinue, |
|
200 "EnableMultiprocess": project.dbgEnableMultiprocess, |
|
201 "MultiprocessNoDebug": project.dbgMultiprocessNoDebug, |
|
202 "GlobalConfigOverride": project.dbgGlobalConfigOverride, |
|
203 } |
|
204 |
|
205 # step 6: bookmarks |
|
206 bookmarksList = [] |
|
207 for fileName in editorsDict: |
|
208 if self.__isGlobal or project.isProjectFile(fileName): |
|
209 editor = editorsDict[fileName] |
|
210 bookmarks = editor.getBookmarks() |
|
211 if bookmarks: |
|
212 bookmarksList.append({ |
|
213 "Filename": fileName, |
|
214 "Lines": bookmarks, |
|
215 }) |
|
216 sessionDict["Bookmarks"] = bookmarksList |
|
217 |
|
218 # step 7: state of the various project browsers |
|
219 browsersList = [] |
|
220 for browserName in projectBrowser.getProjectBrowserNames(): |
|
221 expandedItems = ( |
|
222 projectBrowser.getProjectBrowser(browserName) |
|
223 .getExpandedItemNames() |
|
224 ) |
|
225 if expandedItems: |
|
226 browsersList.append({ |
|
227 "Name": browserName, |
|
228 "ExpandedItems": expandedItems, |
|
229 }) |
|
230 sessionDict["ProjectBrowserStates"] = browsersList |
|
231 |
|
232 try: |
|
233 jsonString = json.dumps(sessionDict, indent=2) |
|
234 with open(filename, "w") as f: |
|
235 f.write(jsonString) |
|
236 except (TypeError, OSError) as err: |
|
237 with EricOverridenCursor(): |
|
238 EricMessageBox.critical( |
|
239 None, |
|
240 self.tr("Save Session"), |
|
241 self.tr( |
|
242 "<p>The session file <b>{0}</b> could not be" |
|
243 " written.</p><p>Reason: {1}</p>" |
|
244 ).format(filename, str(err)) |
|
245 ) |
|
246 return False |
|
247 |
|
248 return True |
|
249 |
|
250 def readFile(self, filename: str) -> bool: |
|
251 """ |
|
252 Public method to read the session data from a session JSON file. |
|
253 |
|
254 @param filename name of the project file |
|
255 @type str |
|
256 @return flag indicating a successful read |
|
257 @rtype bool |
|
258 """ |
|
259 try: |
|
260 with open(filename, "r") as f: |
|
261 jsonString = f.read() |
|
262 sessionDict = json.loads(jsonString) |
|
263 except (OSError, json.JSONDecodeError) as err: |
|
264 EricMessageBox.critical( |
|
265 None, |
|
266 self.tr("Read Session"), |
|
267 self.tr( |
|
268 "<p>The session file <b>{0}</b> could not be read.</p>" |
|
269 "<p>Reason: {1}</p>" |
|
270 ).format(filename, str(err)) |
|
271 ) |
|
272 return False |
|
273 |
|
274 # get references to objects we need |
|
275 project = ericApp().getObject("Project") |
|
276 projectBrowser = ericApp().getObject("ProjectBrowser") |
|
277 multiProject = ericApp().getObject("MultiProject") |
|
278 vm = ericApp().getObject("ViewManager") |
|
279 dbg = ericApp().getObject("DebugUI") |
|
280 dbs = ericApp().getObject("DebugServer") |
|
281 |
|
282 # step 1: multi project and project |
|
283 # ================================= |
|
284 if sessionDict["MultiProject"]: |
|
285 multiProject.openMultiProject(sessionDict["MultiProject"], False) |
|
286 if sessionDict["Project"]: |
|
287 project.openProject(sessionDict["Project"], False) |
|
288 |
|
289 # step 2: (project) filenames and the active editor |
|
290 # ================================================= |
|
291 vm.setSplitOrientation( |
|
292 Qt.Orientation(sessionDict["ViewManagerSplits"]["Orientation"]) |
|
293 ) |
|
294 vm.setSplitCount(sessionDict["ViewManagerSplits"]["Count"]) |
|
295 |
|
296 editorsDict = {} |
|
297 for editorDict in sessionDict["Editors"]: |
|
298 if editorDict["Clone"] and editorDict["Filename"] in editorsDict: |
|
299 editor = editorsDict[editorDict["Filename"]] |
|
300 ed = vm.newEditorView( |
|
301 editorDict["Filename"], editor, editor.getFileType(), |
|
302 indexes=(editorDict["Splitindex"], |
|
303 editorDict["Editorindex"]) |
|
304 ) |
|
305 else: |
|
306 ed = vm.openSourceFile( |
|
307 editorDict["Filename"], |
|
308 indexes=(editorDict["Splitindex"], |
|
309 editorDict["Editorindex"]) |
|
310 ) |
|
311 editorsDict[editorDict["Filename"]] = ed |
|
312 if ed is not None: |
|
313 ed.zoomTo(editorDict["Zoom"]) |
|
314 if editorDict["Folds"]: |
|
315 ed.recolor() |
|
316 ed.setContractedFolds(editorDict["Folds"]) |
|
317 ed.setCursorPosition(*editorDict["Cursor"]) |
|
318 |
|
319 # step 3: breakpoints |
|
320 # =================== |
|
321 bpModel = dbs.getBreakPointModel() |
|
322 bpModel.addBreakPoints(sessionDict["Breakpoints"]) |
|
323 |
|
324 # step 4: watch expressions |
|
325 # ========================= |
|
326 wpModel = dbs.getWatchPointModel() |
|
327 wpModel.addWatchPoints(sessionDict["Watchpoints"]) |
|
328 |
|
329 # step 5: debug info |
|
330 # ================== |
|
331 debugInfoDict = sessionDict["DebugInfo"] |
|
332 |
|
333 # adjust for newer session types |
|
334 if "GlobalConfigOverride" not in debugInfoDict: |
|
335 debugInfoDict["GlobalConfigOverride"] = { |
|
336 "enable": False, |
|
337 "redirect": True, |
|
338 } |
|
339 |
|
340 dbg.lastUsedVenvName = debugInfoDict["VirtualEnv"] |
|
341 with contextlib.suppress(KeyError): |
|
342 dbg.setScriptsHistory(debugInfoDict["ScriptName"]) |
|
343 dbg.setArgvHistory(debugInfoDict["CommandLine"]) |
|
344 dbg.setWdHistory(debugInfoDict["WorkingDirectory"]) |
|
345 dbg.setEnvHistory(debugInfoDict["Environment"]) |
|
346 dbg.setExceptionReporting(debugInfoDict["ReportExceptions"]) |
|
347 dbg.setExcList(debugInfoDict["Exceptions"]) |
|
348 dbg.setExcIgnoreList(debugInfoDict["IgnoredExceptions"]) |
|
349 dbg.setAutoClearShell(debugInfoDict["AutoClearShell"]) |
|
350 dbg.setTracePython(debugInfoDict["TracePython"]) |
|
351 dbg.setAutoContinue(debugInfoDict["AutoContinue"]) |
|
352 dbg.setEnableMultiprocess(debugInfoDict["EnableMultiprocess"]) |
|
353 dbg.setMultiprocessNoDebugHistory(debugInfoDict["MultiprocessNoDebug"]) |
|
354 dbg.setEnableGlobalConfigOverride( |
|
355 debugInfoDict["GlobalConfigOverride"]) |
|
356 if not self.__isGlobal: |
|
357 project.setDbgInfo( |
|
358 debugInfoDict["VirtualEnv"], |
|
359 debugInfoDict["CommandLine"], |
|
360 debugInfoDict["WorkingDirectory"], |
|
361 debugInfoDict["Environment"], |
|
362 debugInfoDict["ReportExceptions"], |
|
363 debugInfoDict["Exceptions"], |
|
364 debugInfoDict["IgnoredExceptions"], |
|
365 debugInfoDict["AutoClearShell"], |
|
366 debugInfoDict["TracePython"], |
|
367 debugInfoDict["AutoContinue"], |
|
368 debugInfoDict["EnableMultiprocess"], |
|
369 debugInfoDict["MultiprocessNoDebug"], |
|
370 debugInfoDict["GlobalConfigOverride"], |
|
371 ) |
|
372 |
|
373 # step 6: bookmarks |
|
374 # ================= |
|
375 for bookmark in sessionDict["Bookmarks"]: |
|
376 editor = vm.getOpenEditor(bookmark["Filename"]) |
|
377 if editor is not None: |
|
378 for lineno in bookmark["Lines"]: |
|
379 editor.toggleBookmark(lineno) |
|
380 |
|
381 # step 7: state of the various project browsers |
|
382 # ============================================= |
|
383 for browserState in sessionDict["ProjectBrowserStates"]: |
|
384 browser = projectBrowser.getProjectBrowser(browserState["Name"]) |
|
385 if browser is not None: |
|
386 browser.expandItemsByName(browserState["ExpandedItems"]) |
|
387 |
|
388 # step 8: active window |
|
389 # ===================== |
|
390 if sessionDict["ActiveWindow"]: |
|
391 vm.openFiles(sessionDict["ActiveWindow"]["Filename"]) |
|
392 ed = vm.getOpenEditor(sessionDict["ActiveWindow"]["Filename"]) |
|
393 if ed is not None: |
|
394 ed.setCursorPosition(*sessionDict["ActiveWindow"]["Cursor"]) |
|
395 ed.ensureCursorVisible() |
|
396 |
|
397 return True |