Wed, 27 Jan 2021 15:09:20 +0100
Implemented the JSON based session files.
--- a/eric6.e4p Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6.e4p Wed Jan 27 15:09:20 2021 +0100 @@ -912,6 +912,8 @@ <Source>eric6/QScintilla/TypingCompleters/__init__.py</Source> <Source>eric6/QScintilla/ZoomDialog.py</Source> <Source>eric6/QScintilla/__init__.py</Source> + <Source>eric6/Sessions/SessionFile.py</Source> + <Source>eric6/Sessions/__init__.py</Source> <Source>eric6/Snapshot/SnapWidget.py</Source> <Source>eric6/Snapshot/SnapshotDefaultGrabber.py</Source> <Source>eric6/Snapshot/SnapshotFreehandGrabber.py</Source>
--- a/eric6/Debugger/BreakPointModel.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/Debugger/BreakPointModel.py Wed Jan 27 15:09:20 2021 +0100 @@ -7,6 +7,8 @@ Module implementing the Breakpoint model. """ +import copy + from PyQt5.QtCore import pyqtSignal, Qt, QAbstractItemModel, QModelIndex @@ -227,6 +229,19 @@ self.breakpoints.append(bp) self.endInsertRows() + def addBreakPoints(self, breakpoints): + """ + Public method to add multiple breakpoints to the list. + + @param breakpoints list of breakpoints with file name, line number, + condition, temporary flag, enabled flag and ignore count each + @type list of (str, int, str, bool, bool, int) + """ + cnt = len(self.breakpoints) + self.beginInsertRows(QModelIndex(), cnt, cnt + len(breakpoints) - 1) + self.breakpoints += breakpoints + self.endInsertRows() + def setBreakPointByIndex(self, index, fn, line, properties): """ Public method to set the values of a breakpoint given by index. @@ -321,7 +336,16 @@ return self.breakpoints[index.row()][:] # return a copy else: return [] - + + def getAllBreakpoints(self): + """ + Public method to get a copy of the breakpoints. + + @return list of breakpoints + @rtype list of list of [str, int, str, bool, bool, int] + """ + return copy.deepcopy(self.breakpoints) + def getBreakPointIndex(self, fn, lineno): """ Public method to get the index of a breakpoint given by filename and
--- a/eric6/Debugger/DebugUI.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/Debugger/DebugUI.py Wed Jan 27 15:09:20 2021 +0100 @@ -1714,7 +1714,8 @@ # save the info for later use self.project.setDbgInfo( lastUsedVenvName, argv, wd, env, exceptions, self.excList, - self.excIgnoreList, clearShell) + self.excIgnoreList, clearShell + ) self.lastStartAction = 6 self.clientType = self.project.getProjectLanguage() @@ -1850,7 +1851,8 @@ # save the info for later use self.project.setDbgInfo( lastUsedVenvName, argv, wd, env, exceptions, self.excList, - self.excIgnoreList, clearShell) + self.excIgnoreList, clearShell + ) self.lastStartAction = 8 self.clientType = self.project.getProjectLanguage() @@ -1985,7 +1987,8 @@ # save the info for later use self.project.setDbgInfo( lastUsedVenvName, argv, wd, env, exceptions, self.excList, - self.excIgnoreList, clearShell) + self.excIgnoreList, clearShell + ) self.lastStartAction = 4 self.clientType = self.project.getProjectLanguage() @@ -2123,7 +2126,10 @@ self.project.setDbgInfo( lastUsedVenvName, argv, wd, env, exceptions, self.excList, self.excIgnoreList, clearShell, tracePython=tracePython, - autoContinue=self.autoContinue) + autoContinue=autoContinue, + enableMultiprocess=enableMultiprocess, + multiprocessNoDebug=multiprocessNoDebug + ) self.lastStartAction = 2 self.clientType = self.project.getProjectLanguage() @@ -2548,3 +2554,13 @@ if noDebugList in self.multiprocessNoDebugHistory: self.multiprocessNoDebugHistory.remove(noDebugList) self.multiprocessNoDebugHistory.insert(0, noDebugList) + + def setEnableMultiprocess(self, enableMultiprocess): + """ + Public slot to initialize the enableMultiprocess flag. + + @param enableMultiprocess flag indicating, that the debugger should be + run in multi process mode + @type bool + """ + self.enableMultiprocess = enableMultiprocess
--- a/eric6/Debugger/WatchPointModel.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/Debugger/WatchPointModel.py Wed Jan 27 15:09:20 2021 +0100 @@ -7,6 +7,8 @@ Module implementing the Watch expression model. """ +import copy + from PyQt5.QtCore import pyqtSignal, Qt, QAbstractItemModel, QModelIndex @@ -169,11 +171,13 @@ """ Public method to add a new watch expression to the list. - @param cond expression of the watch expression (string) - @param special special condition of the watch expression (string) + @param cond expression of the watch expression + @type str + @param special special condition of the watch expression + @type str @param properties properties of the watch expression - (tuple of temporary flag (bool), enabled flag (bool), - ignore count (integer)) + (tuple of temporary flag, enabled flag, ignore count) + @type tuple of (bool, bool, int) """ wp = [cond, special] + list(properties) cnt = len(self.watchpoints) @@ -181,6 +185,19 @@ self.watchpoints.append(wp) self.endInsertRows() + def addWatchPoints(self, watchpoints): + """ + Public method to add multiple watch expressions to the list. + + @param watchpoints list of watch expressions with expression, special + condition, temporary flag, enabled flag and ignore count each + @type list of (str, str, bool, bool, int) + """ + cnt = len(self.watchpoints) + self.beginInsertRows(QModelIndex(), cnt, cnt + len(watchpoints) - 1) + self.watchpoints += watchpoints + self.endInsertRows() + def setWatchPointByIndex(self, index, cond, special, properties): """ Public method to set the values of a watch expression given by index. @@ -198,10 +215,7 @@ index2 = self.createIndex( row, len(self.watchpoints[row]), self.watchpoints[row]) self.dataAboutToBeChanged.emit(index1, index2) - i = 0 - for value in [cond, special] + list(properties): - self.watchpoints[row][i] = value - i += 1 + self.watchpoints[row] = [cond, special] + list(properties) self.dataChanged.emit(index1, index2) def setWatchPointEnabledByIndex(self, index, enabled): @@ -265,14 +279,23 @@ @param index index of the watch expression (QModelIndex) @return watch expression (list of six values (expression, - special condition, temporary flag, enabled flag, ignore count, - index)) + special condition, temporary flag, enabled flag, ignore count)) + @rtype tuple of (str, str, bool, bool, int) """ if index.isValid(): return self.watchpoints[index.row()][:] # return a copy else: return [] - + + def getAllWatchpoints(self): + """ + Public method to get the list of watchpoints. + + @return list of watchpoints + @rtype list of list of [str, str, bool, bool, int] + """ + return copy.deepcopy(self.watchpoints) + def getWatchPointIndex(self, cond, special=""): """ Public method to get the index of a watch expression given by
--- a/eric6/E5XML/SessionWriter.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/E5XML/SessionWriter.py Wed Jan 27 15:09:20 2021 +0100 @@ -110,7 +110,7 @@ self.writeEndElement() aw = self.vm.getActiveName() - if aw and self.project.isProjectFile(aw): + if aw and (isGlobal or self.project.isProjectFile(aw)): ed = self.vm.getOpenEditor(aw) line, index = ed.getCursorPosition() self.writeStartElement("ActiveWindow")
--- a/eric6/Project/Project.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/Project/Project.py Wed Jan 27 15:09:20 2021 +0100 @@ -45,6 +45,8 @@ from .UserProjectFile import UserProjectFile from .DebuggerPropertiesFile import DebuggerPropertiesFile +from Sessions.SessionFile import SessionFile + class Project(QObject): """ @@ -191,6 +193,7 @@ self.__projectFile = ProjectFile(self) self.__userProjectFile = UserProjectFile(self) self.__debuggerPropertiesFile = DebuggerPropertiesFile(self) + self.__sessionFile = SessionFile(False) self.recent = [] self.__loadRecent() @@ -447,6 +450,8 @@ self.dbgAutoClearShell = True self.dbgTracePython = False self.dbgAutoContinue = True + self.dbgEnableMultiprocess = True + self.dbgMultiprocessNoDebug = "" self.pdata = { "DESCRIPTION": "", @@ -950,15 +955,15 @@ if self.pfile is None: return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.eqj'.format(fn)) + fn1, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), '{0}.eqj'.format(fn1)) if os.path.exists(fn): # try the new JSON based format first self.__userProjectFile.readFile(fn) else: # try the old XML based format second fn = os.path.join(self.getProjectManagementDir(), - '{0}.e4q'.format(fn)) + '{0}.e4q'.format(fn1)) if os.path.exists(fn): f = QFile(fn) if f.open(QIODevice.ReadOnly): @@ -997,9 +1002,9 @@ else: fn, ext = os.path.splitext(os.path.basename(self.pfile)) fn_new = os.path.join(self.getProjectManagementDir(), - '{0}.e5s'.format(fn)) + '{0}.esj'.format(fn)) fn_old = os.path.join(self.getProjectManagementDir(), - '{0}.e4s'.format(fn)) + '{0}.e5s'.format(fn)) enable = os.path.exists(fn_new) or os.path.exists(fn_old) self.sessActGrp.findChild( QAction, "project_load_session").setEnabled(enable) @@ -1009,50 +1014,53 @@ @pyqtSlot() def __readSession(self, quiet=False, indicator=""): """ - Private method to read in the project session file (.e5s or .e4s). + Private method to read in the project session file (.esj or .e5s). @param quiet flag indicating quiet operations. If this flag is true, no errors are reported. @param indicator indicator string (string) """ - # TODO: read project session if self.pfile is None: if not quiet: E5MessageBox.critical( self.ui, - self.tr("Read project session"), + self.tr("Read Project Session"), self.tr("Please save the project first.")) return fn1, ext = os.path.splitext(os.path.basename(self.pfile)) fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e5s'.format(fn1, indicator)) - if not os.path.exists(fn): + '{0}{1}.esj'.format(fn1, indicator)) + if os.path.exists(fn): + # try the new JSON based format first + self.__sessionFile.readFile(fn) + else: + # try the old XML based format second fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e4s'.format(fn1, indicator)) - - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.SessionReader import SessionReader - reader = SessionReader(f, False) - reader.readXML(quiet=quiet) - f.close() - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Read project session"), - self.tr( - "<p>The project session file <b>{0}</b> could not be" - " read.</p>").format(fn)) + '{0}{1}.e5s'.format(fn1, indicator)) + if os.path.exists(fn): + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.SessionReader import SessionReader + reader = SessionReader(f, False) + reader.readXML(quiet=quiet) + f.close() + else: + if not quiet: + E5MessageBox.critical( + self.ui, + self.tr("Read project session"), + self.tr( + "<p>The project session file <b>{0}</b> could" + " not be read.</p>").format(fn)) @pyqtSlot() def __writeSession(self, quiet=False, indicator=""): """ - Private method to write the session data to an XML file (.e5s). + Private method to write the session data to an XML file (.esj). @param quiet flag indicating quiet operations. - If this flag is true, no errors are reported. + If this flag is true, no errors are reported. @param indicator indicator string (string) """ # TODO: write project session @@ -1060,29 +1068,16 @@ if not quiet: E5MessageBox.critical( self.ui, - self.tr("Save project session"), + self.tr("Save Project Session"), self.tr("Please save the project first.")) return fn, ext = os.path.splitext(os.path.basename(self.pfile)) fn = os.path.join(self.getProjectManagementDir(), - '{0}{1}.e5s'.format(fn, indicator)) - - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.SessionWriter import SessionWriter - SessionWriter( - f, os.path.splitext(os.path.basename(fn))[0]).writeXML() - f.close() - else: - if not quiet: - E5MessageBox.critical( - self.ui, - self.tr("Save project session"), - self.tr( - "<p>The project session file <b>{0}</b> could not be" - " written.</p>").format(fn)) - + '{0}{1}.esj'.format(fn, indicator)) + + self.__sessionFile.writeFile(fn) + def __deleteSession(self): """ Private method to delete the session file. @@ -1206,16 +1201,16 @@ self.tr("Please save the project first.")) return - fn, ext = os.path.splitext(os.path.basename(self.pfile)) - fn = os.path.join(self.getProjectManagementDir(), '{0}.edj'.format(fn)) + fn1, ext = os.path.splitext(os.path.basename(self.pfile)) + fn = os.path.join(self.getProjectManagementDir(), + '{0}.edj'.format(fn1)) if os.path.exists(fn): # try the new JSON based format first self.__debuggerPropertiesFile.readFile(fn) else: # try the old XML based format second - fn, ext = os.path.splitext(os.path.basename(self.pfile)) fn = os.path.join(self.getProjectManagementDir(), - '{0}.e4d'.format(fn)) + '{0}.e4d'.format(fn1)) f = QFile(fn) if f.open(QIODevice.ReadOnly): @@ -1344,7 +1339,8 @@ def setDbgInfo(self, venvName, argv, wd, env, excReporting, excList, excIgnoreList, autoClearShell, tracePython=None, - autoContinue=None): + autoContinue=None, enableMultiprocess=None, + multiprocessNoDebug=None): """ Public method to set the debugging information. @@ -1371,6 +1367,12 @@ @param autoContinue flag indicating, that the debugger should not stop at the first executable line @type bool + @param enableMultiprocess flag indicating, that the debugger should + run in multi process mode + @type bool + @param multiprocessNoDebug list of programs not to be debugged in + multi process mode + @type str """ self.dbgVirtualEnv = venvName self.dbgCmdline = argv @@ -1384,6 +1386,10 @@ self.dbgTracePython = tracePython if autoContinue is not None: self.dbgAutoContinue = autoContinue + if enableMultiprocess is not None: + self.dbgEnableMultiprocess = enableMultiprocess + if multiprocessNoDebug is not None: + self.dbgMultiprocessNoDebug = multiprocessNoDebug def getTranslationPattern(self): """
--- a/eric6/QScintilla/Lexers/__init__.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/QScintilla/Lexers/__init__.py Wed Jan 27 15:09:20 2021 +0100 @@ -863,6 +863,13 @@ '*.e6q': "XML", '*.e6s': "XML", '*.e6t': "XML", + '*.edj': "JSON", + '*.ekj': "JSON", + '*.emj': "JSON", + '*.epj': "JSON", + '*.eqj': "JSON", + '*.esj': "JSON", + '*.etj': "JSON", '*.proto': "Protocol", '*.po': "Gettext", '*.coffee': "CoffeeScript",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Sessions/SessionFile.py Wed Jan 27 15:09:20 2021 +0100 @@ -0,0 +1,369 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a class representing the project debugger properties +JSON file. +""" + +import json +import os +import time + +from PyQt5.QtCore import QObject + +from E5Gui import E5MessageBox +from E5Gui.E5OverrideCursor import E5OverridenCursor +from E5Gui.E5Application import e5App + +import Preferences + + +class SessionFile(QObject): + """ + Class representing the project debugger properties JSON file. + """ + def __init__(self, isGlobal: bool, parent: QObject = None): + """ + Constructor + + @param project reference to the project object + @type Project + @param parent reference to the parent object + @type QObject (optional) + """ + super(SessionFile, self).__init__(parent) + + self.__isGlobal = isGlobal + + def writeFile(self, filename: str) -> bool: + """ + Public method to write the project debugger properties data to a + project debugger properties JSON file. + + @param filename name of the user project file + @type str + @return flag indicating a successful write + @rtype bool + """ + # get references to objects we need + project = e5App().getObject("Project") + projectBrowser = e5App().getObject("ProjectBrowser") + multiProject = e5App().getObject("MultiProject") + vm = e5App().getObject("ViewManager") + dbg = e5App().getObject("DebugUI") + dbs = e5App().getObject("DebugServer") + + name = os.path.splitext(os.path.basename(filename))[0] + + # prepare the session data dictionary + # step 0: header + sessionDict = {} + sessionDict["header"] = {} + if not self.__isGlobal: + sessionDict["header"]["comment"] = ( + f"eric session file for project {name}" + ) + sessionDict["header"]["warning"] = ( + "This file was generated automatically, do not edit." + ) + + # TODO: replace 'XMLTimestamp' by 'Timestamp' + if Preferences.getProject("XMLTimestamp") or self.__isGlobal: + sessionDict["header"]["saved"] = ( + time.strftime('%Y-%m-%d, %H:%M:%S') + ) + + # step 1: open multi project and project for global session + sessionDict["MultiProject"] = "" + sessionDict["Project"] = "" + if self.__isGlobal: + if multiProject.isOpen(): + sessionDict["MultiProject"] = ( + multiProject.getMultiProjectFile() + ) + if project.isOpen(): + sessionDict["Project"] = project.getProjectFile() + + # step 2: all open (project) filenames and the active editor + if vm.canSplit(): + sessionDict["ViewManagerSplits"] = { + "Count": vm.splitCount(), + "Orientation": vm.getSplitOrientation(), + } + else: + sessionDict["ViewManagerSplits"] = { + "Count": 0, + "Orientation": 1, + } + + editorsDict = {} # remember editors by file name to detect clones + sessionDict["Editors"] = [] + allOpenEditorLists = vm.getOpenEditorsForSession() + for splitIndex, openEditorList in enumerate(allOpenEditorLists): + for editorIndex, editor in enumerate(openEditorList): + fileName = editor.getFileName() + if self.__isGlobal or project.isProjectFile(fileName): + if fileName in editorsDict: + isClone = editorsDict[fileName].isClone(editor) + else: + isClone = False + editorsDict[fileName] = editor + editorDict = { + "Filename": fileName, + "Cursor": editor.getCursorPosition(), + "Folds": editor.contractedFolds(), + "Zoom": editor.getZoom(), + "Clone": isClone, + "Splitindex": splitIndex, + "Editorindex": editorIndex, + } + sessionDict["Editors"].append(editorDict) + + aw = vm.getActiveName() + sessionDict["ActiveWindow"] = {} + if aw and (self.__isGlobal or project.isProjectFile(aw)): + ed = vm.getOpenEditor(aw) + sessionDict["ActiveWindow"] = { + "Filename": aw, + "Cursor": ed.getCursorPosition(), + } + + # step 3: breakpoints + allBreaks = Preferences.getProject("SessionAllBreakpoints") + projectFiles = project.getSources(True) + bpModel = dbs.getBreakPointModel() + if self.__isGlobal or allBreaks: + sessionDict["Breakpoints"] = bpModel.getAllBreakpoints() + else: + sessionDict["Breakpoints"] = [ + bp + for bp in bpModel.getAllBreakpoints() + if bp[0] in projectFiles + ] + + # step 4: watch expressions + wpModel = dbs.getWatchPointModel() + sessionDict["Watchpoints"] = wpModel.getAllWatchpoints() + + # step 5: debug info + if self.__isGlobal: + if len(dbg.argvHistory): + dbgCmdline = dbg.argvHistory[0] + else: + dbgCmdline = "" + if len(dbg.wdHistory): + dbgWd = dbg.wdHistory[0] + else: + dbgWd = "" + if len(dbg.envHistory): + dbgEnv = dbg.envHistory[0] + else: + dbgEnv = "" + if len(dbg.multiprocessNoDebugHistory): + dbgMultiprocessNoDebug = ( + dbg.multiprocessNoDebugHistory[0] + ) + else: + dbgMultiprocessNoDebug = "" + sessionDict["DebugInfo"] = { + "VirtualEnv": dbg.lastUsedVenvName, + "CommandLine": dbgCmdline, + "WorkingDirectory": dbgWd, + "Environment": dbgEnv, + "ReportExceptions": dbg.exceptions, + "Exceptions": dbg.excList, + "IgnoredExceptions": dbg.excIgnoreList, + "AutoClearShell": dbg.autoClearShell, + "TracePython": dbg.tracePython, + "AutoContinue": dbg.autoContinue, + "EnableMultiprocess": dbg.enableMultiprocess, + "MultiprocessNoDebug": dbgMultiprocessNoDebug + } + else: + sessionDict["DebugInfo"] = { + "VirtualEnv": project.dbgVirtualEnv, + "CommandLine": project.dbgCmdline, + "WorkingDirectory": project.dbgWd, + "Environment": project.dbgEnv, + "ReportExceptions": project.dbgReportExceptions, + "Exceptions": project.dbgExcList, + "IgnoredExceptions": project.dbgExcIgnoreList, + "AutoClearShell": project.dbgAutoClearShell, + "TracePython": project.dbgTracePython, + "AutoContinue": project.dbgAutoContinue, + "EnableMultiprocess": project.dbgEnableMultiprocess, + "MultiprocessNoDebug": project.dbgMultiprocessNoDebug, + } + + # step 6: bookmarks + bookmarksList = [] + for fileName in editorsDict: + if self.__isGlobal or project.isProjectFile(fileName): + editor = editorsDict[fileName] + bookmarks = editor.getBookmarks() + if bookmarks: + bookmarksList.append({ + "Filename": fileName, + "Lines": bookmarks, + }) + sessionDict["Bookmarks"] = bookmarksList + + # step 7: state of the various project browsers + browsersList = [] + for browserName in projectBrowser.getProjectBrowserNames(): + expandedItems = ( + projectBrowser.getProjectBrowser(browserName) + .getExpandedItemNames() + ) + if expandedItems: + browsersList.append({ + "Name": browserName, + "ExpandedItems": expandedItems, + }) + sessionDict["ProjectBrowserStates"] = browsersList + + try: + jsonString = json.dumps(sessionDict, indent=2) + with open(filename, "w") as f: + f.write(jsonString) + except (TypeError, EnvironmentError) as err: + with E5OverridenCursor(): + E5MessageBox.critical( + None, + self.tr("Save Session"), + self.tr( + "<p>The session file <b>{0}</b> could not be" + " written.</p><p>Reason: {1}</p>" + ).format(filename, str(err)) + ) + return False + + return True + + def readFile(self, filename: str) -> bool: + """ + Public method to read the session data from a session JSON file. + + @param filename name of the project file + @type str + @return flag indicating a successful read + @rtype bool + """ + try: + with open(filename, "r") as f: + jsonString = f.read() + sessionDict = json.loads(jsonString) + except (EnvironmentError, json.JSONDecodeError) as err: + E5MessageBox.critical( + None, + self.tr("Read Debugger Properties"), + self.tr( + "<p>The project debugger properties file <b>{0}</b>" + " could not be read.</p><p>Reason: {1}</p>" + ).format(filename, str(err)) + ) + return False + + # get references to objects we need + project = e5App().getObject("Project") + projectBrowser = e5App().getObject("ProjectBrowser") + multiProject = e5App().getObject("MultiProject") + vm = e5App().getObject("ViewManager") + dbg = e5App().getObject("DebugUI") + dbs = e5App().getObject("DebugServer") + + # step 1: multi project and project + if sessionDict["MultiProject"]: + multiProject.openMultiProject(sessionDict["MultiProject"], False) + if sessionDict["Project"]: + project.openProject(sessionDict["Project"], False) + + # step 2: (project) filenames and the active editor + vm.setSplitOrientation(sessionDict["ViewManagerSplits"]["Orientation"]) + vm.setSplitCount(sessionDict["ViewManagerSplits"]["Count"]) + + editorsDict = {} + for editorDict in sessionDict["Editors"]: + if editorDict["Clone"] and editorDict["Filename"] in editorsDict: + editor = editorsDict[editorDict["Filename"]] + ed = vm.newEditorView( + editorDict["Filename"], editor, editor.getFileType(), + indexes=(editorDict["Splitindex"], + editorDict["Editorindex"]) + ) + else: + ed = vm.openSourceFile( + editorDict["Filename"], + indexes=(editorDict["Splitindex"], + editorDict["Editorindex"]) + ) + editorsDict[editorDict["Filename"]] = ed + if ed is not None: + ed.zoomTo(editorDict["Zoom"]) + if editorDict["Folds"]: + ed.recolor() + ed.setContractedFolds(editorDict["Folds"]) + ed.setCursorPosition(*editorDict["Cursor"]) + + # step 3: breakpoints + bpModel = dbs.getBreakPointModel() + bpModel.addBreakPoints(sessionDict["Breakpoints"]) + + # step 4: watch expressions + wpModel = dbs.getWatchPointModel() + wpModel.addWatchPoints(sessionDict["Watchpoints"]) + + # step 5: debug info + debugInfoDict = sessionDict["DebugInfo"] + dbg.lastUsedVenvName = debugInfoDict["VirtualEnv"] + dbg.setArgvHistory(debugInfoDict["CommandLine"]) + dbg.setWdHistory(debugInfoDict["WorkingDirectory"]) + dbg.setEnvHistory(debugInfoDict["Environment"]) + dbg.setExceptionReporting(debugInfoDict["ReportExceptions"]) + dbg.setExcList(debugInfoDict["Exceptions"]) + dbg.setExcIgnoreList(debugInfoDict["IgnoredExceptions"]) + dbg.setAutoClearShell(debugInfoDict["AutoClearShell"]) + dbg.setTracePython(debugInfoDict["TracePython"]) + dbg.setAutoContinue(debugInfoDict["AutoContinue"]) + dbg.setEnableMultiprocess(debugInfoDict["EnableMultiprocess"]) + dbg.setMultiprocessNoDebugHistory(debugInfoDict["MultiprocessNoDebug"]) + if not self.__isGlobal: + project.setDbgInfo( + debugInfoDict["VirtualEnv"], + debugInfoDict["CommandLine"], + debugInfoDict["WorkingDirectory"], + debugInfoDict["Environment"], + debugInfoDict["ReportExceptions"], + debugInfoDict["Exceptions"], + debugInfoDict["IgnoredExceptions"], + debugInfoDict["AutoClearShell"], + debugInfoDict["TracePython"], + debugInfoDict["AutoContinue"], + debugInfoDict["EnableMultiprocess"], + debugInfoDict["MultiprocessNoDebug"] + ) + + # step 6: bookmarks + for bookmark in sessionDict["Bookmarks"]: + editor = vm.getOpenEditor(bookmark["Filename"]) + if editor is not None: + for lineno in bookmark["Lines"]: + editor.toggleBookmark(lineno) + + # step 7: state of the various project browsers + for browserState in sessionDict["ProjectBrowserStates"]: + browser = projectBrowser.getProjectBrowser(browserState["Name"]) + if browser is not None: + browser.expandItemsByName(browserState["ExpandedItems"]) + + # step 8: active window + vm.openFiles(sessionDict["ActiveWindow"]["Filename"]) + ed = vm.getOpenEditor(sessionDict["ActiveWindow"]["Filename"]) + if ed is not None: + ed.setCursorPosition(*sessionDict["ActiveWindow"]["Cursor"]) + ed.ensureCursorVisible() + + return True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Sessions/__init__.py Wed Jan 27 15:09:20 2021 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package containing session related modules. +"""
--- a/eric6/UI/UserInterface.py Mon Jan 25 20:07:51 2021 +0100 +++ b/eric6/UI/UserInterface.py Wed Jan 27 15:09:20 2021 +0100 @@ -51,6 +51,8 @@ import UI.PixmapCache +from Sessions.SessionFile import SessionFile + from E5Network.E5NetworkProxyFactory import ( E5NetworkProxyFactory, proxyAuthenticationRequired ) @@ -610,6 +612,9 @@ self.__inVersionCheck = False self.__versionCheckProgress = None + # create the various JSON file interfaces + self.__sessionFile = SessionFile(True) + # Initialize the actions, menus, toolbars and statusbar splash.showMessage(self.tr("Initializing Actions...")) self.__initActions() @@ -6516,7 +6521,7 @@ def __writeSession(self, filename="", crashSession=False): """ - Private slot to write the session data to an XML file (.e5s). + Private slot to write the session data to a JSON file (.esj). @param filename name of a session file to write @type str @@ -6525,33 +6530,38 @@ @return flag indicating success @rtype bool """ - res = False if filename: fn = filename elif crashSession: fn = os.path.join(Utilities.getConfigDir(), - "eric6_crash_session.e5s") + "eric6_crash_session.esj") else: fn = os.path.join(Utilities.getConfigDir(), - "eric6session.e5s") - f = QFile(fn) - if f.open(QIODevice.WriteOnly): - from E5XML.SessionWriter import SessionWriter - SessionWriter(f, None).writeXML() - f.close() - res = True + "eric6session.esj") + + if fn.endswith(".esj"): + res = self.__sessionFile.writeFile(fn) else: - E5MessageBox.critical( - self, - self.tr("Save session"), - self.tr("<p>The session file <b>{0}</b> could not be" - " written.</p>") - .format(fn)) + f = QFile(fn) + if f.open(QIODevice.WriteOnly): + from E5XML.SessionWriter import SessionWriter + SessionWriter(f, None).writeXML() + f.close() + res = True + else: + E5MessageBox.critical( + self, + self.tr("Save Session"), + self.tr("<p>The session file <b>{0}</b> could not be" + " written.</p>") + .format(fn)) + res = False + return res - + def __readSession(self, filename=""): """ - Private slot to read in the session file (.e5s or .e4s). + Private slot to read in the session file (.esj or .e5s). @param filename name of a session file to read @type str @@ -6562,14 +6572,14 @@ fn = filename else: fn = os.path.join(Utilities.getConfigDir(), - "eric6session.e5s") + "eric6session.esj") if not os.path.exists(fn): fn = os.path.join(Utilities.getConfigDir(), - "eric6session.e4s") + "eric6session.e5s") if not os.path.exists(fn): E5MessageBox.critical( self, - self.tr("Read session"), + self.tr("Read Session"), self.tr("<p>The session file <b>{0}</b> could not" " be read.</p>") .format(fn)) @@ -6577,22 +6587,29 @@ res = False if fn: - f = QFile(fn) - if f.open(QIODevice.ReadOnly): - from E5XML.SessionReader import SessionReader + if fn.endswith(".esj"): + # new JSON based format self.__readingSession = True - reader = SessionReader(f, True) - reader.readXML() + res = self.__sessionFile.readFile(fn) self.__readingSession = False - f.close() - res = True else: - E5MessageBox.critical( - self, - self.tr("Read session"), - self.tr("<p>The session file <b>{0}</b> could not be" - " read.</p>") - .format(fn)) + # old XML based format + f = QFile(fn) + if f.open(QIODevice.ReadOnly): + from E5XML.SessionReader import SessionReader + self.__readingSession = True + reader = SessionReader(f, True) + reader.readXML() + self.__readingSession = False + f.close() + res = True + else: + E5MessageBox.critical( + self, + self.tr("Read session"), + self.tr("<p>The session file <b>{0}</b> could not be" + " read.</p>") + .format(fn)) # Write a crash session after a session was read. self.__writeCrashSession() @@ -6605,9 +6622,10 @@ """ sessionFile, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, - self.tr("Save session"), + self.tr("Save Session"), Utilities.getHomeDir(), - self.tr("eric Session Files (*.e5s)"), + self.tr("eric Session Files (*.esj);;" + "eric XML Session Files (*.e5s)"), "") if not sessionFile: @@ -6629,7 +6647,8 @@ self, self.tr("Load session"), Utilities.getHomeDir(), - self.tr("eric Session Files (*.e5s)")) + self.tr("eric Session Files (*.esj);;" + "eric XML Session Files (*.e5s)")) if not sessionFile: return @@ -6640,14 +6659,15 @@ """ Private slot to delete the crash session file. """ - fn = os.path.join(Utilities.getConfigDir(), - "eric6_crash_session.e5s") - if os.path.exists(fn): - try: - os.remove(fn) - except OSError: - # ignore it silently - pass + for ext in (".esj", ".e5s"): + fn = os.path.join(Utilities.getConfigDir(), + f"eric6_crash_session{ext}") + if os.path.exists(fn): + try: + os.remove(fn) + except OSError: + # ignore it silently + pass def __writeCrashSession(self): """ @@ -6674,7 +6694,7 @@ Preferences.getUI("OpenCrashSessionOnStartup") ): fn = os.path.join(Utilities.getConfigDir(), - "eric6_crash_session.e5s") + "eric6_crash_session.esj") if os.path.exists(fn): yes = E5MessageBox.yesNo( self,