Thu, 15 Feb 2024 13:59:02 +0100
Implemented the profiling and code coverage interface of the dialogs to the eric-ide server.
--- a/eric7.epj Wed Feb 14 10:24:23 2024 +0100 +++ b/eric7.epj Thu Feb 15 13:59:02 2024 +0100 @@ -2126,10 +2126,12 @@ "src/eric7/QtHelpInterface/__init__.py", "src/eric7/RemoteServer/EricRequestCategory.py", "src/eric7/RemoteServer/EricServer.py", + "src/eric7/RemoteServer/EricServerCoverageRequestHandler.py", "src/eric7/RemoteServer/EricServerDebuggerRequestHandler.py", "src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py", "src/eric7/RemoteServer/__init__.py", "src/eric7/RemoteServerInterface/EricServerConnectionDialog.py", + "src/eric7/RemoteServerInterface/EricServerCoverageInterface.py", "src/eric7/RemoteServerInterface/EricServerDebuggerInterface.py", "src/eric7/RemoteServerInterface/EricServerFileDialog.py", "src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py",
--- a/src/eric7/DataViews/PyCoverageDialog.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/DataViews/PyCoverageDialog.py Thu Feb 15 13:59:02 2024 +0100 @@ -25,6 +25,9 @@ from eric7.EricWidgets import EricMessageBox from eric7.EricWidgets.EricApplication import ericApp +from eric7.RemoteServerInterface.EricServerCoverageInterface import ( + EricServerCoverageError, +) from eric7.SystemUtilities import FileSystemUtilities from .Ui_PyCoverageDialog import Ui_PyCoverageDialog @@ -57,7 +60,6 @@ self.resultList.headerItem().setText(self.resultList.columnCount(), "") self.cancelled = False - self.path = "." self.reload = False self.excludeList = ["# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]"] @@ -77,6 +79,11 @@ self.__menu.addAction(self.tr("Erase Coverage Info"), self.__erase) self.resultList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.resultList.customContextMenuRequested.connect(self.__showContextMenu) + + # eric-ide server interface + self.__serverCoverageInterface = ericApp().getObject( + "EricServer" + ).getServiceInterface("Coverage") def __format_lines(self, lines): """ @@ -143,7 +150,7 @@ @param coverage percent of coverage @type int @param excluded list of excluded lines - @type str + @type list of int @param missing list of lines without coverage @type str """ @@ -154,7 +161,7 @@ str(statements), str(executed), "{0:.0f}%".format(coverage), - excluded, + excluded and self.__format_lines(excluded) or "", missing, ], ) @@ -192,20 +199,16 @@ else "{0}.coverage".format(os.path.splitext(cfn)[0]) ) + # TODO: adapt this to remote server if isinstance(fn, list): files = fn - self.path = os.path.dirname(cfn) - elif os.path.isdir(fn): + elif os.path.isdir(fn) and not FileSystemUtilities.isRemoteFileName(self.cfn): + # This case is not yet supported files = FileSystemUtilities.direntries(fn, True, "*.py", False) - self.path = fn else: files = [fn] - self.path = os.path.dirname(cfn) files.sort() - cover = Coverage(data_file=self.cfn) - cover.load() - # set the exclude pattern self.excludeCombo.clear() self.excludeCombo.addItems(self.excludeList) @@ -217,7 +220,23 @@ total_executed = 0 total_exceptions = 0 - cover.exclude(self.excludeList[0]) + if FileSystemUtilities.isRemoteFileName(self.cfn): + ok, error = self.__serverCoverageInterface.loadCoverageData( + self.cfn, self.excludeList[0] + ) + if not ok: + EricMessageBox.critical( + self, + self.tr("Load Coverage Data"), + self.tr( + "<p>The coverage data could not be loaded from file" + " <b>{0}</b>.</p><p>Reason: {1}</p>" + ).format(self.cfn, error), + ) + else: + cover = Coverage(data_file=self.cfn) + cover.load() + cover.exclude(self.excludeList[0]) try: # disable updates of the list for speed @@ -231,18 +250,24 @@ return try: - statements, excluded, missing, readable = cover.analysis2(file)[1:] - readableEx = excluded and self.__format_lines(excluded) or "" + if FileSystemUtilities.isRemoteFileName(self.cfn): + file, statements, excluded, missing, readable = ( + self.__serverCoverageInterface.analyzeFile(file) + ) + else: + statements, excluded, missing, readable = ( + cover.analysis2(file)[1:] + ) n = len(statements) m = n - len(missing) pc = 100.0 * m / n if n > 0 else 100.0 self.__createResultItem( - file, str(n), str(m), pc, readableEx, readable + file, str(n), str(m), pc, excluded, readable ) total_statements += n total_executed += m - except CoverageException: + except (CoverageException, EricServerCoverageError): total_exceptions += 1 self.checkProgress.setValue(progress)
--- a/src/eric7/DataViews/PyProfileDialog.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/DataViews/PyProfileDialog.py Thu Feb 15 13:59:02 2024 +0100 @@ -22,7 +22,8 @@ ) from eric7.EricWidgets import EricMessageBox -from eric7.SystemUtilities import PythonUtilities +from eric7.EricWidgets.EricApplication import ericApp +from eric7.SystemUtilities import FileSystemUtilities, PythonUtilities from .Ui_PyProfileDialog import Ui_PyProfileDialog @@ -104,6 +105,11 @@ self.summaryList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.summaryList.customContextMenuRequested.connect(self.__showContextMenu) + # eric-ide server interface + self.__serverFsInterface = ericApp().getObject( + "EricServer" + ).getServiceInterface("FileSystem") + def __createResultItem( self, calls, @@ -265,7 +271,16 @@ self.basename = os.path.splitext(pfn)[0] fname = "{0}.profile".format(self.basename) - if not os.path.exists(fname): + if ( + ( + FileSystemUtilities.isRemoteFileName(fname) + and not self.__serverFsInterface.exists(fname) + ) + or ( + FileSystemUtilities.isPlainFileName(fname) + and not os.path.exists(fname) + ) + ): EricMessageBox.warning( self, self.tr("Profile Results"), @@ -277,8 +292,12 @@ self.close() return try: - with open(fname, "rb") as f: - self.stats = pickle.load(f) # secok + if FileSystemUtilities.isRemoteFileName(fname): + data = self.__serverFsInterface.readFile(fname) + self.stats = pickle.loads(data) + else: + with open(fname, "rb") as f: + self.stats = pickle.load(f) # secok except (EOFError, OSError, pickle.PickleError): EricMessageBox.critical( self, @@ -291,7 +310,10 @@ self.close() return - self.file = fn + if FileSystemUtilities.isRemoteFileName(fname): + self.file = FileSystemUtilities.plainFileName(fn) + else: + self.file = fn self.__populateLists() self.__finish()
--- a/src/eric7/Plugins/PluginCodeStyleChecker.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/Plugins/PluginCodeStyleChecker.py Thu Feb 15 13:59:02 2024 +0100 @@ -476,6 +476,7 @@ if menuName == "Checks": if self.__editorAct not in menu.actions(): menu.addAction(self.__editorAct) + # TODO: disable the action for eric-ide server files self.__editorAct.setEnabled(editor.isPyFile()) def __editorCodeStyleCheck(self):
--- a/src/eric7/Plugins/PluginSyntaxChecker.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/Plugins/PluginSyntaxChecker.py Thu Feb 15 13:59:02 2024 +0100 @@ -408,6 +408,7 @@ if menuName == "Checks": if self.__editorAct not in menu.actions(): menu.addAction(self.__editorAct) + # TODO: disable the action for eric-ide server files self.__editorAct.setEnabled( editor.getLanguage() in self.syntaxCheckService.getLanguages() )
--- a/src/eric7/QScintilla/Editor.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/QScintilla/Editor.py Thu Feb 15 13:59:02 2024 +0100 @@ -1190,7 +1190,9 @@ """ menu = QMenu(self.tr("Show")) - menu.addAction(self.tr("Code metrics..."), self.__showCodeMetrics) + self.codeMetricsAct = menu.addAction( + self.tr("Code metrics..."), self.__showCodeMetrics + ) self.coverageMenuAct = menu.addAction( self.tr("Code coverage..."), self.__showCodeCoverage ) @@ -6416,10 +6418,12 @@ self.menuActs["Show"].setEnabled(True) else: self.menuActs["Show"].setEnabled(False) + # TODO: disable that for server files if self.fileName and (self.isPyFile() or self.isRubyFile()): self.menuActs["Diagrams"].setEnabled(True) else: self.menuActs["Diagrams"].setEnabled(False) + # TODO: disable 'Check' and 'Code Formatting' for server files if not self.miniMenu: if self.lexer_ is not None: self.menuActs["Comment"].setEnabled(self.lexer_.canBlockComment()) @@ -6511,6 +6515,12 @@ ) self.coverageHideAnnotationMenuAct.setEnabled(len(self.notcoveredMarkers) > 0) + # disable actions not supporting eric-ide server + self.codeMetricsAct.setEnabled( + False if fn is None else FileSystemUtilities.isPlainFileName(fn) + ) + + # TODO: disable action in Radon plugin for server files self.showMenu.emit("Show", self.menuShow, self) @pyqtSlot()
--- a/src/eric7/RemoteServer/EricRequestCategory.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServer/EricRequestCategory.py Thu Feb 15 13:59:02 2024 +0100 @@ -18,6 +18,7 @@ FileSystem = 0 Project = 1 Debugger = 2 + Coverage = 3 Echo = 253 Server = 254
--- a/src/eric7/RemoteServer/EricServer.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServer/EricServer.py Thu Feb 15 13:59:02 2024 +0100 @@ -20,6 +20,7 @@ from eric7.UI.Info import Version from .EricRequestCategory import EricRequestCategory +from .EricServerCoverageRequestHandler import EricServerCoverageRequestHandler from .EricServerDebuggerRequestHandler import EricServerDebuggerRequestHandler from .EricServerFileSystemRequestHandler import EricServerFileSystemRequestHandler @@ -65,6 +66,13 @@ self.__fileSystemRequestHandler.handleRequest, ) + # create and register the 'Coverage' request handler + self.__coverageRequestHandler = EricServerCoverageRequestHandler(self) + self.registerRequestHandler( + EricRequestCategory.Coverage, + self.__coverageRequestHandler.handleRequest, + ) + # TODO: 'Project' handler not implemented yet # TODO: implement an 'EditorConfig' handler (?) @@ -331,6 +339,9 @@ def __closeIdeConnection(self, shutdown=False): """ Private method to close the connection to an eric-ide. + + @param shutdown flag indicating a shutdown process + @type bool """ if self.__connection is not None: self.__selector.unregister(self.__connection)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/RemoteServer/EricServerCoverageRequestHandler.py Thu Feb 15 13:59:02 2024 +0100 @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the code coverage request handler of the eric-ide server. +""" + +from coverage import Coverage +from coverage.misc import CoverageException + +from eric7.SystemUtilities import FileSystemUtilities + +from .EricRequestCategory import EricRequestCategory + + +class EricServerCoverageRequestHandler: + """ + Class implementing the code coverage request handler of the eric-ide server. + """ + + def __init__(self, server): + """ + Constructor + + @param server reference to the eric-ide server object + @type EricServer + """ + self.__server = server + + self.__requestMethodMapping = { + "LoadData": self.__loadCoverageData, + "AnalyzeFile": self.__analyzeFile, + "AnalyzeFiles": self.__analyzeFiles, + "AnalyzeDirectory": self.__analyzeDirectory, + } + + self.__cover = None + + def handleRequest(self, request, params, reqestUuid): + """ + Public method handling the received file system requests. + + @param request request name + @type str + @param params dictionary containing the request parameters + @type dict + @param reqestUuid UUID of the associated request as sent by the eric IDE + @type str + """ + try: + result = self.__requestMethodMapping[request](params) + self.__server.sendJson( + category=EricRequestCategory.Coverage, + reply=request, + params=result, + reqestUuid=reqestUuid, + ) + + except KeyError: + self.__server.sendJson( + category=EricRequestCategory.Coverage, + reply=request, + params={"Error": f"Request type '{request}' is not supported."}, + ) + + def __loadCoverageData(self, params): + """ + Private method to load the data collected by a code coverage run. + + @param params dictionary containing the request data + @type dict + @return dictionary containing the reply data + @rtype dict + """ + if self.__cover is not None: + del self.__cover + self.__cover = None + + try: + self.__cover = Coverage(data_file=params["data_file"]) + self.__cover.load() + self.__cover.exclude(params["exclude"]) + return {"ok": True} + except CoverageException as err: + return { + "ok": False, + "error": str(err), + } + + def __analyzeFile(self, params): + """ + Private method to analyze a single file. + + @param params dictionary containing the request data + @type dict + @return dictionary containing the reply data + @rtype dict + """ + if self.__cover is None: + return { + "ok": False, + "error": "Coverage data has to be loaded first.", + } + + try: + return { + "ok": True, + "result": self.__cover.analysis2(params["filename"]), + } + except CoverageException as err: + return { + "ok": False, + "error": str(err), + } + + def __analyzeFiles(self, params): + """ + Private method to analyze a list of files. + + @param params dictionary containing the request data + @type dict + @return dictionary containing the reply data + @rtype dict + """ + # TODO: not implemented yet + if self.__cover is None: + return { + "ok": False, + "error": "Coverage data has to be loaded first.", + } + + try: + return { + "ok": True, + "results": [self.__cover.analysis2(f) for f in params["filenames"]], + } + except CoverageException as err: + return { + "ok": False, + "error": str(err), + } + + def __analyzeDirectory(self, params): + """ + Private method to analyze files of a directory tree. + + @param params dictionary containing the request data + @type dict + @return dictionary containing the reply data + @rtype dict + """ + # TODO: not implemented yet + if self.__cover is None: + return { + "ok": False, + "error": "Coverage data has to be loaded first.", + } + + files = FileSystemUtilities.direntries(params["directory"], True, "*.py", False) + + try: + return { + "ok": True, + "results": [self.__cover.analysis2(f) for f in files], + } + except CoverageException as err: + return { + "ok": False, + "error": str(err), + }
--- a/src/eric7/RemoteServer/EricServerDebuggerRequestHandler.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServer/EricServerDebuggerRequestHandler.py Thu Feb 15 13:59:02 2024 +0100 @@ -115,7 +115,9 @@ address=address, handler=self.__serviceDbgClientConnection, ) - self.__server.getSelector().register(connection, selectors.EVENT_READ, data=data) + self.__server.getSelector().register( + connection, selectors.EVENT_READ, data=data + ) def __serviceDbgClientConnection(self, key): """
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/RemoteServerInterface/EricServerCoverageInterface.py Thu Feb 15 13:59:02 2024 +0100 @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the code coverage interface to the eric-ide server. +""" + +import contextlib + +from PyQt6.QtCore import QEventLoop, QObject + +from eric7.RemoteServer.EricRequestCategory import EricRequestCategory +from eric7.SystemUtilities import FileSystemUtilities + + +class EricServerCoverageError(Exception): + """ + Class defining a substitute exception for coverage errors of the server. + """ + + pass + + +class EricServerCoverageInterface(QObject): + """ + Class implementing the code coverage interface to the eric-ide server. + """ + + def __init__(self, serverInterface): + """ + Constructor + + @param serverInterface reference to the eric-ide server interface + @type EricServerInterface + """ + super().__init__(parent=serverInterface) + + self.__serverInterface = serverInterface + + def loadCoverageData(self, dataFile, excludePattern): + """ + Public method to tell the server to load the coverage data for a later analysis. + + @param dataFile name of the data file to be loaded + @type str + @param excludePattern regular expression determining files to be excluded + @type str + @return tuple containing a success flag and an error message + @rtype tuple of (bool, str) + """ + loop = QEventLoop() + ok = False + error = "" + + def callback(reply, params): + """ + Function to handle the server reply + + @param reply name of the server reply + @type str + @param params dictionary containing the reply data + @type dict + """ + nonlocal ok, error + + if reply == "LoadData": + ok = params["ok"] + with contextlib.suppress(KeyError): + error = params["error"] + loop.quit() + + self.__serverInterface.sendJson( + category=EricRequestCategory.Coverage, + request="LoadData", + params={ + "data_file": FileSystemUtilities.plainFileName(dataFile), + "exclude": excludePattern, + }, + callback=callback, + ) + + loop.exec() + return ok, error + + def analyzeFile(self, filename): + """ + Public method to analyze the code coverage of one file. + + @param filename name of the file to be analyzed + @type str + @return list containing coverage result as reported by Coverage.analysis2() + @rtype list of [str, list of int, list of int, list of int, str] + @exception EricServerCoverageException raised to indicate a coverage exception + """ + loop = QEventLoop() + ok = False + error = "" + result = None + + def callback(reply, params): + """ + Function to handle the server reply + + @param reply name of the server reply + @type str + @param params dictionary containing the reply data + @type dict + """ + nonlocal ok, error, result + + if reply == "AnalyzeFile": + ok = params["ok"] + if ok: + result = params["result"] + else: + error = params["error"] + loop.quit() + + self.__serverInterface.sendJson( + category=EricRequestCategory.Coverage, + request="AnalyzeFile", + params={"filename": FileSystemUtilities.plainFileName(filename)}, + callback=callback, + ) + + loop.exec() + if not ok: + raise EricServerCoverageError(error) + + return result + + def analyzeFiles(self, filenames): + """ + Public method to analyze the code coverage of a list of files. + + @param filenames list of file names to be analyzed + @type str + @return lists containing coverage results as reported by Coverage.analysis2() + @rtype list of [list of [str, list of int, list of int, list of int, str]] + @exception EricServerCoverageException raised to indicate a coverage exception + """ + loop = QEventLoop() + ok = False + error = "" + result = None + + def callback(reply, params): + """ + Function to handle the server reply + + @param reply name of the server reply + @type str + @param params dictionary containing the reply data + @type dict + """ + nonlocal ok, error, result + + if reply == "AnalyzeFiles": + ok = params["ok"] + if ok: + result = params["results"] + else: + error = params["error"] + loop.quit() + + self.__serverInterface.sendJson( + category=EricRequestCategory.Coverage, + request="AnalyzeFiles", + params={ + "filenames": [FileSystemUtilities.plainFileName(f) for f in filenames] + }, + callback=callback, + ) + + loop.exec() + if not ok: + raise EricServerCoverageError(error) + + return result + + def analyzeDirectory(self, directory): + """ + Public method to analyze the code coverage of a directory. + + @param directory directory name to be analyzed + @type str + @return lists containing coverage results as reported by Coverage.analysis2() + @rtype list of [list of [str, list of int, list of int, list of int, str]] + @exception EricServerCoverageException raised to indicate a coverage exception + """ + loop = QEventLoop() + ok = False + error = "" + result = None + + def callback(reply, params): + """ + Function to handle the server reply + + @param reply name of the server reply + @type str + @param params dictionary containing the reply data + @type dict + """ + nonlocal ok, error, result + + if reply == "AnalyzeDirectory": + ok = params["ok"] + if ok: + result = params["results"] + else: + error = params["error"] + loop.quit() + + self.__serverInterface.sendJson( + category=EricRequestCategory.Coverage, + request="AnalyzeDirectory", + params={"directory": FileSystemUtilities.plainFileName(directory)}, + callback=callback, + ) + + loop.exec() + if not ok: + raise EricServerCoverageError(error) + + return result
--- a/src/eric7/RemoteServerInterface/EricServerDebuggerInterface.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServerInterface/EricServerDebuggerInterface.py Thu Feb 15 13:59:02 2024 +0100 @@ -19,6 +19,13 @@ class EricServerDebuggerInterface(QObject): """ Class implementing the file system interface to the eric-ide server. + + @signal debugClientResponse(response:str) emitted to relay a response of + the remote debug client + @signal debugClientDisconnected(debuggerId:str) emitted when a remote debug + client did disconnect from the eric-ide server + @signal lastClientExited() emitted to indicate that the last debug client of + the eric-ide server exited """ debugClientResponse = pyqtSignal(str)
--- a/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py Thu Feb 15 13:59:02 2024 +0100 @@ -13,6 +13,7 @@ from PyQt6.QtCore import QEventLoop, QObject from eric7.RemoteServer.EricRequestCategory import EricRequestCategory +from eric7.SystemUtilities import FileSystemUtilities # TODO: sanitize all file names with FileSystemUtilities.plainFileName() @@ -100,7 +101,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Chdir", - params={"directory": directory}, + params={"directory": FileSystemUtilities.plainFileName(directory)}, callback=callback, ) @@ -155,7 +156,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Listdir", - params={"directory": directory}, + params={"directory": FileSystemUtilities.plainFileName(directory)}, callback=callback, ) @@ -204,7 +205,10 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Stat", - params={"filename": filename, "st_names": stNames}, + params={ + "filename": FileSystemUtilities.plainFileName(filename), + "st_names": stNames, + }, callback=callback, ) @@ -244,7 +248,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Exists", - params={"name": name}, + params={"name": FileSystemUtilities.plainFileName(name)}, callback=callback, ) @@ -295,7 +299,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Access", - params={"name": name, "modes": modes}, + params={"name": FileSystemUtilities.plainFileName(name), "modes": modes}, callback=callback, ) @@ -335,7 +339,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Mkdir", - params={"directory": directory}, + params={"directory": FileSystemUtilities.plainFileName(directory)}, callback=callback, ) @@ -375,7 +379,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Rmdir", - params={"directory": directory}, + params={"directory": FileSystemUtilities.plainFileName(directory)}, callback=callback, ) @@ -417,7 +421,10 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Replace", - params={"old_name": oldName, "new_name": newName}, + params={ + "old_name": FileSystemUtilities.plainFileName(oldName), + "new_name": FileSystemUtilities.plainFileName(newName), + }, callback=callback, ) @@ -457,7 +464,7 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="Remove", - params={"filename": filename}, + params={"filename": FileSystemUtilities.plainFileName(filename)}, callback=callback, ) @@ -510,7 +517,10 @@ self.__serverInterface.sendJson( category=EricRequestCategory.FileSystem, request="ReadFile", - params={"filename": filename, "create": create}, + params={ + "filename": FileSystemUtilities.plainFileName(filename), + "create": create, + }, callback=callback, ) @@ -558,7 +568,7 @@ category=EricRequestCategory.FileSystem, request="WriteFile", params={ - "filename": filename, + "filename": FileSystemUtilities.plainFileName(filename), "filedata": str(base64.b85encode(data), encoding="ascii"), "with_backup": withBackup, },
--- a/src/eric7/RemoteServerInterface/EricServerInterface.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/RemoteServerInterface/EricServerInterface.py Thu Feb 15 13:59:02 2024 +0100 @@ -38,6 +38,8 @@ @signal remoteReply(category:int, request:str, params:dict) emitted to deliver the reply of an unknown category + @signal remoteCoverageReply(request:str, params:dict) emitted to deliver the reply + of a remote server code coverage request @signal remoteDebuggerReply(request:str, params:dict) emitted to deliver the reply of a remote server debugger request @signal remoteEchoReply(request:str, params:dict) emitted to deliver the reply of @@ -57,6 +59,7 @@ remoteReply = pyqtSignal(int, str, dict) + remoteCoverageReply = pyqtSignal(str, dict) remoteDebuggerReply = pyqtSignal(str, dict) remoteEchoReply = pyqtSignal(str, dict) remoteFileSystemReply = pyqtSignal(str, dict) @@ -75,6 +78,7 @@ self.__ui = parent self.__categorySignalMapping = { + EricRequestCategory.Coverage: self.remoteCoverageReply, EricRequestCategory.Debugger: self.remoteDebuggerReply, EricRequestCategory.Echo: self.remoteEchoReply, EricRequestCategory.FileSystem: self.remoteFileSystemReply, @@ -107,21 +111,27 @@ try: return self.__serviceInterfaces[lname] except KeyError: - if lname not in ("debugger", "filesystem", "project"): + if lname not in ("coverage", "debugger", "filesystem", "project"): raise ValueError(f"no such service supported ({name})") else: # instantiate the service interface if lname == "filesystem": from .EricServerFileSystemInterface import ( # noqa: I101 - EricServerFileSystemInterface + EricServerFileSystemInterface, ) self.__serviceInterfaces[lname] = ( EricServerFileSystemInterface(self) ) elif lname == "debugger": - from .EricServerDebuggerInterface import EricServerDebuggerInterface - # noqa: I101 + from .EricServerDebuggerInterface import ( # noqa: I101 + EricServerDebuggerInterface, + ) self.__serviceInterfaces[lname] = EricServerDebuggerInterface(self) + elif lname == "coverage": + from .EricServerCoverageInterface import ( # noqa: I101 + EricServerCoverageInterface, + ) + self.__serviceInterfaces[lname] = EricServerCoverageInterface(self) elif lname == "project": # TODO: 'Project Interface' not implemented yet pass
--- a/src/eric7/Utilities/__init__.py Wed Feb 14 10:24:23 2024 +0100 +++ b/src/eric7/Utilities/__init__.py Thu Feb 15 13:59:02 2024 +0100 @@ -35,7 +35,7 @@ from eric7 import Preferences from eric7.EricWidgets.EricApplication import ericApp -from eric7.SystemUtilities import DesktopUtilities, OSUtilities +from eric7.SystemUtilities import DesktopUtilities, FileSystemUtilities, OSUtilities from eric7.UI.Info import Program, Version @@ -903,6 +903,16 @@ basename = os.path.splitext(fn)[0] filename = "{0}.coverage".format(basename) if mustExist: + if FileSystemUtilities.isRemoteFileName(fn): + ericServer = ericApp().getObject("EricServer") + if ericServer.isServerConnected() and ericServer.getServiceInterface( + "FileSystem" + ).exists(filename): + return filename + else: + return "" + + # It is a local file. if os.path.isfile(filename): return filename else: @@ -943,12 +953,22 @@ basename = os.path.splitext(fn)[0] filename = "{0}.profile".format(basename) if mustExist: + if FileSystemUtilities.isRemoteFileName(fn): + ericServer = ericApp().getObject("EricServer") + if ericServer.isServerConnected() and ericServer.getServiceInterface( + "FileSystem" + ).exists(filename): + return filename + else: + return "" + + # It is a local file. if os.path.isfile(filename): return filename else: return "" - else: - return filename + + return filename def parseOptionString(s):