Implemented first stage of remote project support. server

Thu, 22 Feb 2024 16:26:46 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 22 Feb 2024 16:26:46 +0100
branch
server
changeset 10596
ea35c92a3c7c
parent 10594
6156d9675f62
child 10597
fbe93720ee9f

Implemented first stage of remote project support.

src/eric7/Graphics/ApplicationDiagramBuilder.py file | annotate | diff | comparison | revisions
src/eric7/Graphics/PackageDiagramBuilder.py file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py file | annotate | diff | comparison | revisions
src/eric7/Project/DebuggerPropertiesFile.py file | annotate | diff | comparison | revisions
src/eric7/Project/Project.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectBrowserModel.py file | annotate | diff | comparison | revisions
src/eric7/Project/ProjectFile.py file | annotate | diff | comparison | revisions
src/eric7/Project/UserProjectFile.py file | annotate | diff | comparison | revisions
src/eric7/QScintilla/Editor.py file | annotate | diff | comparison | revisions
src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py file | annotate | diff | comparison | revisions
src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py file | annotate | diff | comparison | revisions
src/eric7/Sessions/SessionFile.py file | annotate | diff | comparison | revisions
src/eric7/Tasks/TasksFile.py file | annotate | diff | comparison | revisions
src/eric7/UI/BrowserModel.py file | annotate | diff | comparison | revisions
src/eric7/UI/UserInterface.py file | annotate | diff | comparison | revisions
src/eric7/Utilities/ClassBrowsers/pyclbr.py file | annotate | diff | comparison | revisions
src/eric7/Utilities/ClassBrowsers/rbclbr.py file | annotate | diff | comparison | revisions
src/eric7/Utilities/ModuleParser.py file | annotate | diff | comparison | revisions
src/eric7/ViewManager/ViewManager.py file | annotate | diff | comparison | revisions
src/eric7/ViewManager/__init__.py file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-dark/projectOpen-remote.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-light/projectOpen-remote.svg file | annotate | diff | comparison | revisions
src/eric7/icons/oxygen/fileSaveAsRemote.png file | annotate | diff | comparison | revisions
src/eric7/icons/oxygen/projectOpen-remote.png file | annotate | diff | comparison | revisions
--- a/src/eric7/Graphics/ApplicationDiagramBuilder.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Graphics/ApplicationDiagramBuilder.py	Thu Feb 22 16:26:46 2024 +0100
@@ -218,12 +218,13 @@
             # no root path detected
             return
 
-        if FileSystemUtilities.isRemoteFileName(rpath):
-            root = self.__remotefsInterface.splitdrive(rpath)[1][1:].replace(
+        root = (
+            self.__remotefsInterface.splitdrive(rpath)[1][1:].replace(
                 self.__remotefsInterface.separator(), "."
             )
-        else:
-            root = os.path.splitdrive(rpath)[1][1:].replace(os.sep, ".")
+            if FileSystemUtilities.isRemoteFileName(rpath)
+            else os.path.splitdrive(rpath)[1][1:].replace(os.sep, ".")
+        )
 
         packages = {}
         self.__shapes = {}
--- a/src/eric7/Graphics/PackageDiagramBuilder.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Graphics/PackageDiagramBuilder.py	Thu Feb 22 16:26:46 2024 +0100
@@ -183,7 +183,8 @@
                                 subpackage["path"], "__init__.*"
                             )
                         )
-                    ) != 0
+                    )
+                    != 0
                 ):
                     subpackagesList.append(
                         FileSystemUtilities.remoteFileName(subpackage["path"])
@@ -194,7 +195,8 @@
                     if (
                         subpackage.is_dir()
                         and subpackage.name != "__pycache__"
-                        and len(glob.glob(os.path.join(subpackage.path, "__init__.*"))) != 0
+                        and len(glob.glob(os.path.join(subpackage.path, "__init__.*")))
+                        != 0
                     ):
                         subpackagesList.append(subpackage.path)
 
@@ -280,10 +282,11 @@
         self.allClasses = {}
 
         globPattern = os.path.join(self.package, "__init__.*")
-        if FileSystemUtilities.isRemoteFileName(self.package):
-            initlist = self.__remotefsInterface.glob(globPattern)
-        else:
-            initlist = glob.glob(globPattern)
+        initlist = (
+            self.__remotefsInterface.glob(globPattern)
+            if FileSystemUtilities.isRemoteFileName(self.package)
+            else glob.glob(globPattern)
+        )
         if len(initlist) == 0:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Thu Feb 22 16:26:46 2024 +0100
@@ -1148,9 +1148,9 @@
                 self.__timenow = time.monotonic()
 
             try:
-                if FileSystemUtilities.isRemoteFileName(self.filename):
+                if FileSystemUtilities.isRemoteFileName(filename):
                     source, encoding = self.__remotefsInterface.readEncodedFile(
-                        self.filename
+                        filename
                     )
                 else:
                     source, encoding = Utilities.readEncodedFile(filename)
--- a/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py	Thu Feb 22 16:26:46 2024 +0100
@@ -467,12 +467,11 @@
                 self.__timenow = time.monotonic()
 
             try:
-                if FileSystemUtilities.isRemoteFileName(self.filename):
-                    source = self.__remotefsInterface.readEncodedFile(
-                        self.filename
-                    )[0]
-                else:
-                    source = Utilities.readEncodedFile(filename)[0]
+                source = (
+                    self.__remotefsInterface.readEncodedFile(self.filename)[0]
+                    if FileSystemUtilities.isRemoteFileName(self.filename)
+                    else Utilities.readEncodedFile(filename)[0]
+                )
                 source = Utilities.normalizeCode(source)
             except (OSError, UnicodeError) as msg:
                 self.noResults = False
--- a/src/eric7/Project/DebuggerPropertiesFile.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Project/DebuggerPropertiesFile.py	Thu Feb 22 16:26:46 2024 +0100
@@ -17,6 +17,8 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
 
@@ -48,6 +50,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         debuggerPropertiesDict = {
             "header": {
                 "comment": "eric debugger properties file for project {0}".format(
@@ -66,13 +72,18 @@
 
         try:
             jsonString = json.dumps(debuggerPropertiesDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Debugger Properties")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Debugger Properties")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Debugger Properties"),
+                    title,
                     self.tr(
                         "<p>The project debugger properties file"
                         " <b>{0}</b> could not be written.</p>"
@@ -93,14 +104,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Debugger Properties")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Debugger Properties")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             debuggerPropertiesDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Debugger Properties"),
+                title,
                 self.tr(
                     "<p>The project debugger properties file <b>{0}</b>"
                     " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/Project/Project.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Project/Project.py	Thu Feb 22 16:26:46 2024 +0100
@@ -23,8 +23,6 @@
 from PyQt6.QtCore import (
     QByteArray,
     QCryptographicHash,
-    QFile,
-    QIODevice,
     QObject,
     QProcess,
     pyqtSignal,
@@ -45,12 +43,8 @@
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
-from eric7.EricXML.DebuggerPropertiesReader import DebuggerPropertiesReader
-from eric7.EricXML.ProjectReader import ProjectReader
-from eric7.EricXML.SessionReader import SessionReader
-from eric7.EricXML.TasksReader import TasksReader
-from eric7.EricXML.UserProjectReader import UserProjectReader
 from eric7.Globals import recentNameProject
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.Sessions.SessionFile import SessionFile
 from eric7.SystemUtilities import (
     FileSystemUtilities,
@@ -165,18 +159,21 @@
     DefaultMake = "make"
     DefaultMakefile = "makefile"
 
-    def __init__(self, parent=None, filename=None):
+    def __init__(self, parent=None, filename=None, remoteServer=None):
         """
         Constructor
 
         @param parent parent widget (usually the ui object)
         @type QWidget
-        @param filename optional filename of a project file to open
-        @type str
+        @param filename optional filename of a project file to open (defaults to None)
+        @type str (optional)
+        @param remoteServer reference to the 'eric-ide' server interface object
+        @type EricServerInterface
         """
         super().__init__(parent)
 
         self.ui = parent
+        self.__remotefsInterface = remoteServer.getServiceInterface("FileSystem")
 
         self.__progLanguages = [
             "Python3",
@@ -217,7 +214,7 @@
         else:
             self.vcs = self.initVCS()
 
-        self.__model = ProjectBrowserModel(self)
+        self.__model = ProjectBrowserModel(self, fsInterface=self.__remotefsInterface)
 
         self.codemetrics = None
         self.codecoverage = None
@@ -1106,10 +1103,18 @@
         """
         removed = False
         removelist = []
-        for file in self.__pdata[index]:
-            if not os.path.exists(os.path.join(self.ppath, file)):
-                removelist.append(file)
-                removed = True
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            for file in self.__pdata[index]:
+                if not self.__remotefsInterface.exists(
+                    self.__remotefsInterface.join(self.ppath, file)
+                ):
+                    removelist.append(file)
+                    removed = True
+        else:
+            for file in self.__pdata[index]:
+                if not os.path.exists(os.path.join(self.ppath, file)):
+                    removelist.append(file)
+                    removed = True
 
         if removed:
             for file in removelist:
@@ -1118,50 +1123,27 @@
 
     def __readProject(self, fn):
         """
-        Private method to read in a project (.epj or .e4p) file.
+        Private method to read in a project file (.epj).
 
         @param fn filename of the project file to be read
         @type str
         @return flag indicating success
         @rtype bool
         """
-        if os.path.splitext(fn)[1] == ".epj":
-            # new JSON based format
-            with EricOverrideCursor():
-                res = self.__projectFile.readFile(fn)
-        else:
-            # old XML based format
-            f = QFile(fn)
-            if f.open(QIODevice.OpenModeFlag.ReadOnly):
-                reader = ProjectReader(f, self)
-                reader.readXML()
-                res = not reader.hasError()
-                f.close()
-
-                # create hash value, if it doesn't have one
-                if reader.version.startswith("5.") and not self.__pdata["HASH"]:
-                    hashStr = str(
-                        QCryptographicHash.hash(
-                            QByteArray(self.ppath.encode("utf-8")),
-                            QCryptographicHash.Algorithm.Sha1,
-                        ).toHex(),
-                        encoding="utf-8",
-                    )
-                    self.__pdata["HASH"] = hashStr
-                    self.setDirty(True)
-            else:
-                EricMessageBox.critical(
-                    self.ui,
-                    self.tr("Read Project File"),
-                    self.tr(
-                        "<p>The project file <b>{0}</b> could not be read.</p>"
-                    ).format(fn),
-                )
-                res = False
+        with EricOverrideCursor():
+            res = self.__projectFile.readFile(fn)
 
         if res:
-            self.pfile = os.path.abspath(fn)
-            self.ppath = os.path.abspath(os.path.dirname(fn))
+            if FileSystemUtilities.isRemoteFileName(fn):
+                self.pfile = fn
+                self.ppath = self.__remotefsInterface.dirname(fn)
+                self.name = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(fn)
+                )[0]
+            else:
+                self.pfile = os.path.abspath(fn)
+                self.ppath = os.path.abspath(os.path.dirname(fn))
+                self.name = os.path.splitext(os.path.basename(fn))[0]
 
             # insert filename into list of recently opened projects
             self.__syncRecent()
@@ -1172,15 +1154,22 @@
                 )[0]
             elif self.__pdata["MAINSCRIPT"]:
                 self.translationsRoot = os.path.splitext(self.__pdata["MAINSCRIPT"])[0]
-            if os.path.isdir(os.path.join(self.ppath, self.translationsRoot)):
-                dn = self.translationsRoot
+
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                if self.__remotefsInterface.isdir(
+                    self.__remotefsInterface.join(self.ppath, self.translationsRoot)
+                ):
+                    dn = self.translationsRoot
+                else:
+                    dn = self.__remotefsInterface.dirname(self.translationsRoot)
             else:
-                dn = os.path.dirname(self.translationsRoot)
-            if dn not in self.subdirs:
+                if os.path.isdir(os.path.join(self.ppath, self.translationsRoot)):
+                    dn = self.translationsRoot
+                else:
+                    dn = os.path.dirname(self.translationsRoot)
+            if dn and dn not in self.subdirs:
                 self.subdirs.append(dn)
 
-            self.name = os.path.splitext(os.path.basename(fn))[0]
-
             # check, if the files of the project still exist in the
             # project directory
             for fileCategory in self.getFileCategories():
@@ -1189,14 +1178,22 @@
             # get the names of subdirectories the files are stored in
             for fileCategory in [c for c in self.getFileCategories() if c != "OTHERS"]:
                 for fn in self.__pdata[fileCategory]:
-                    dn = os.path.dirname(fn)
-                    if dn not in self.subdirs:
+                    dn = (
+                        self.__remotefsInterface.dirname(fn)
+                        if FileSystemUtilities.isRemoteFileName(fn)
+                        else os.path.dirname(fn)
+                    )
+                    if dn and dn not in self.subdirs:
                         self.subdirs.append(dn)
 
             # get the names of other subdirectories
             for fn in self.__pdata["OTHERS"]:
-                dn = os.path.dirname(fn)
-                if dn not in self.otherssubdirs:
+                dn = (
+                    self.__remotefsInterface.dirname(fn)
+                    if FileSystemUtilities.isRemoteFileName(fn)
+                    else os.path.dirname(fn)
+                )
+                if dn and dn not in self.otherssubdirs:
                     self.otherssubdirs.append(dn)
 
         return res
@@ -1235,9 +1232,16 @@
             res = self.__projectFile.writeFile(fn)
 
         if res:
-            self.pfile = os.path.abspath(fn)
-            self.ppath = os.path.abspath(os.path.dirname(fn))
-            self.name = os.path.splitext(os.path.basename(fn))[0]
+            if FileSystemUtilities.isRemoteFileName(fn):
+                self.pfile = fn
+                self.ppath = self.__remotefsInterface.dirname(fn)
+                self.name = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(fn)
+                )[0]
+            else:
+                self.pfile = os.path.abspath(fn)
+                self.ppath = os.path.abspath(os.path.dirname(fn))
+                self.name = os.path.splitext(os.path.basename(fn))[0]
             self.setDirty(False)
 
             # insert filename into list of recently opened projects
@@ -1247,35 +1251,27 @@
 
     def __readUserProperties(self):
         """
-        Private method to read in the user specific project file (.eqj or
-        .e4q).
+        Private method to read in the user specific project file (.eqj).
         """
         if self.pfile is None:
             return
 
-        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)
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}.eqj"
+            )
+            if not self.__remotefsInterface.exists(fn):
+                return
         else:
-            # try the old XML based format second
-            fn = os.path.join(self.getProjectManagementDir(), "{0}.e4q".format(fn1))
-            if os.path.exists(fn):
-                f = QFile(fn)
-                if f.open(QIODevice.OpenModeFlag.ReadOnly):
-                    reader = UserProjectReader(f, self)
-                    reader.readXML()
-                    f.close()
-                else:
-                    EricMessageBox.critical(
-                        self.ui,
-                        self.tr("Read User Project Properties"),
-                        self.tr(
-                            "<p>The user specific project properties file"
-                            " <b>{0}</b> could not be read.</p>"
-                        ).format(fn),
-                    )
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.eqj")
+            if not os.path.exists(fn):
+                return
+
+        self.__userProjectFile.readFile(fn)
 
     def __writeUserProperties(self):
         """
@@ -1284,8 +1280,16 @@
         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))
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}.eqj"
+            )
+        else:
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.eqj")
 
         with EricOverrideCursor():
             self.__userProjectFile.writeFile(fn)
@@ -1298,17 +1302,25 @@
         if self.pfile is None:
             enable = False
         else:
-            fn, ext = os.path.splitext(os.path.basename(self.pfile))
-            fn_new = os.path.join(self.getProjectManagementDir(), "{0}.esj".format(fn))
-            fn_old = os.path.join(self.getProjectManagementDir(), "{0}.e5s".format(fn))
-            enable = os.path.exists(fn_new) or os.path.exists(fn_old)
+            if FileSystemUtilities.isRemoteFileName(self.pfile):
+                fn, _ext = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(self.pfile)
+                )
+                fn_sess = self.__remotefsInterface.join(
+                    self.getProjectManagementDir(), f"{fn}.esj"
+                )
+                enable = self.__remotefsInterface.exists(fn)
+            else:
+                fn, _ext = os.path.splitext(os.path.basename(self.pfile))
+                fn_sess = os.path.join(self.getProjectManagementDir(), f"{fn}.esj")
+                enable = os.path.exists(fn_sess)
         self.sessActGrp.findChild(QAction, "project_load_session").setEnabled(enable)
         self.sessActGrp.findChild(QAction, "project_delete_session").setEnabled(enable)
 
     @pyqtSlot()
     def __readSession(self, quiet=False, indicator=""):
         """
-        Private method to read in the project session file (.esj or .e5s).
+        Private method to read in the project session file (.esj).
 
         @param quiet flag indicating quiet operations.
                 If this flag is true, no errors are reported.
@@ -1325,34 +1337,24 @@
                 )
             return
 
-        fn1, ext = os.path.splitext(os.path.basename(self.pfile))
-        fn = os.path.join(
-            self.getProjectManagementDir(), "{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}.e5s".format(fn1, indicator)
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}{indicator}.esj"
             )
-            if os.path.exists(fn):
-                f = QFile(fn)
-                if f.open(QIODevice.OpenModeFlag.ReadOnly):
-                    reader = SessionReader(f, False)
-                    reader.readXML(quiet=quiet)
-                    f.close()
-                else:
-                    if not quiet:
-                        EricMessageBox.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),
-                        )
+            if not self.__remotefsInterface.exists(fn):
+                return
+        else:
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(
+                self.getProjectManagementDir(), f"{fn1}{indicator}.esj"
+            )
+            if not os.path.exists(fn):
+                return
+
+        self.__sessionFile.readFile(fn)
 
     @pyqtSlot()
     def __writeSession(self, quiet=False, indicator=""):
@@ -1374,10 +1376,18 @@
                 )
             return
 
-        fn, ext = os.path.splitext(os.path.basename(self.pfile))
-        fn = os.path.join(
-            self.getProjectManagementDir(), "{0}{1}.esj".format(fn, indicator)
-        )
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}{indicator}.esj"
+            )
+        else:
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(
+                self.getProjectManagementDir(), f"{fn1}{indicator}.esj"
+            )
 
         self.__sessionFile.writeFile(fn)
 
@@ -1393,28 +1403,36 @@
             )
             return
 
-        fname, ext = os.path.splitext(os.path.basename(self.pfile))
-
-        for ext in (".esj", ".e5s", ".e4s"):
-            fn = os.path.join(
-                self.getProjectManagementDir(), "{0}{1}".format(fname, ext)
-            )
-            if os.path.exists(fn):
-                try:
+        try:
+            if FileSystemUtilities.isRemoteFileName(self.pfile):
+                title = self.tr("Delete Remote Project Session")
+                fname, _ext = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(self.pfile)
+                )
+                fn = self.__remotefsInterface.join(
+                    self.getProjectManagementDir(), f"{fname}.esj"
+                )
+                if self.__remotefsInterface.exists(fn):
+                    self.__remotefsInterface.remove(fn)
+            else:
+                title = self.tr("Delete Project Session")
+                fname, _ext = os.path.splitext(os.path.basename(self.pfile))
+                fn = os.path.join(self.getProjectManagementDir(), f"{fname}.esj")
+                if os.path.exists(fn):
                     os.remove(fn)
-                except OSError:
-                    EricMessageBox.critical(
-                        self.ui,
-                        self.tr("Delete Project Session"),
-                        self.tr(
-                            "<p>The project session file <b>{0}</b> could"
-                            " not be deleted.</p>"
-                        ).format(fn),
-                    )
+        except OSError:
+            EricMessageBox.critical(
+                self.ui,
+                title,
+                self.tr(
+                    "<p>The project session file <b>{0}</b> could"
+                    " not be deleted.</p>"
+                ).format(fn),
+            )
 
     def __readTasks(self):
         """
-        Private method to read in the project tasks file (.etj or .e6t).
+        Private method to read in the project tasks file (.etj).
         """
         if self.pfile is None:
             EricMessageBox.critical(
@@ -1424,28 +1442,22 @@
             )
             return
 
-        base, ext = os.path.splitext(os.path.basename(self.pfile))
-        fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(base))
-        if os.path.exists(fn):
-            # try new style JSON file first
-            self.__tasksFile.readFile(fn)
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            base, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{base}.etj"
+            )
+            if not self.__remotefsInterface.exists(fn):
+                return
         else:
-            # try old style XML file second
-            fn = os.path.join(self.getProjectManagementDir(), "{0}.e6t".format(base))
-            if os.path.exists(fn):
-                f = QFile(fn)
-                if f.open(QIODevice.OpenModeFlag.ReadOnly):
-                    reader = TasksReader(f, True)
-                    reader.readXML()
-                    f.close()
-                else:
-                    EricMessageBox.critical(
-                        self.ui,
-                        self.tr("Read Tasks"),
-                        self.tr(
-                            "<p>The tasks file <b>{0}</b> could not be read.</p>"
-                        ).format(fn),
-                    )
+            base, ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{base}.etj")
+            if not os.path.exists(fn):
+                return
+
+        self.__tasksFile.readFile(fn)
 
     def writeTasks(self):
         """
@@ -1454,9 +1466,17 @@
         if self.pfile is None:
             return
 
-        fn, ext = os.path.splitext(os.path.basename(self.pfile))
-
-        fn = os.path.join(self.getProjectManagementDir(), "{0}.etj".format(fn))
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            base, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{base}.etj"
+            )
+        else:
+            base, ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{base}.etj")
+
         self.__tasksFile.writeFile(fn)
 
     def __showContextMenuDebugger(self):
@@ -1467,13 +1487,18 @@
         if self.pfile is None:
             enable = False
         else:
-            fn, ext = os.path.splitext(os.path.basename(self.pfile))
-            # try new style file first
-            fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn))
-            if not os.path.exists(fn):
-                # try old style file second
-                fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn))
-            enable = os.path.exists(fn)
+            if FileSystemUtilities.isRemoteFileName(self.pfile):
+                fn1, _ext = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(self.pfile)
+                )
+                fn = self.__remotefsInterface.join(
+                    self.getProjectManagementDir(), f"{fn1}.edj"
+                )
+                enable = self.__remotefsInterface.exists(fn)
+            else:
+                fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+                fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj")
+                enable = os.path.exists(fn)
         self.dbgActGrp.findChild(
             QAction, "project_debugger_properties_load"
         ).setEnabled(enable)
@@ -1500,34 +1525,24 @@
                 )
             return
 
-        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
-            if self.__debuggerPropertiesFile.readFile(fn):
-                self.debugPropertiesLoaded = True
-                self.debugPropertiesChanged = False
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}.edj"
+            )
+            if not self.__remotefsInterface.exists(fn):
+                return
         else:
-            # try the old XML based format second
-            fn = os.path.join(self.getProjectManagementDir(), "{0}.e4d".format(fn1))
-
-            f = QFile(fn)
-            if f.open(QIODevice.OpenModeFlag.ReadOnly):
-                reader = DebuggerPropertiesReader(f, self)
-                reader.readXML(quiet=quiet)
-                f.close()
-                self.debugPropertiesLoaded = True
-                self.debugPropertiesChanged = False
-            else:
-                if not quiet:
-                    EricMessageBox.critical(
-                        self.ui,
-                        self.tr("Read Debugger Properties"),
-                        self.tr(
-                            "<p>The project debugger properties file"
-                            " <b>{0}</b> could not be read.</p>"
-                        ).format(fn),
-                    )
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj")
+        if not os.path.exists(fn):
+            return
+
+        if self.__debuggerPropertiesFile.readFile(fn):
+            self.debugPropertiesLoaded = True
+            self.debugPropertiesChanged = False
 
     @pyqtSlot()
     def __writeDebugProperties(self, quiet=False):
@@ -1547,8 +1562,16 @@
                 )
             return
 
-        fn, ext = os.path.splitext(os.path.basename(self.pfile))
-        fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".format(fn))
+        if FileSystemUtilities.isRemoteFileName(self.pfile):
+            fn1, _ext = self.__remotefsInterface.splitext(
+                self.__remotefsInterface.basename(self.pfile)
+            )
+            fn = self.__remotefsInterface.join(
+                self.getProjectManagementDir(), f"{fn1}.edj"
+            )
+        else:
+            fn1, _ext = os.path.splitext(os.path.basename(self.pfile))
+            fn = os.path.join(self.getProjectManagementDir(), f"{fn1}.edj")
 
         with EricOverrideCursor():
             self.__debuggerPropertiesFile.writeFile(fn)
@@ -1566,24 +1589,32 @@
             )
             return
 
-        fname, ext = os.path.splitext(os.path.basename(self.pfile))
-
-        for ext in (".edj", ".e4d"):
-            fn = os.path.join(
-                self.getProjectManagementDir(), "{0}{1}".format(fname, ext)
-            )
-            if os.path.exists(fn):
-                try:
+        try:
+            if FileSystemUtilities.isRemoteFileName(self.pfile):
+                title = self.tr("Delete Remote Debugger Properties")
+                fname, _ext = self.__remotefsInterface.splitext(
+                    self.__remotefsInterface.basename(self.pfile)
+                )
+                fn = self.__remotefsInterface.join(
+                    self.getProjectManagementDir(), f"{fname}.edj"
+                )
+                if self.__remotefsInterface.exists(fn):
+                    self.__remotefsInterface.remove(fn)
+            else:
+                title = self.tr("Delete Debugger Properties")
+                fname, _ext = os.path.splitext(os.path.basename(self.pfile))
+                fn = os.path.join(self.getProjectManagementDir(), f"{fname}.edj")
+                if os.path.exists(fn):
                     os.remove(fn)
-                except OSError:
-                    EricMessageBox.critical(
-                        self.ui,
-                        self.tr("Delete Debugger Properties"),
-                        self.tr(
-                            "<p>The project debugger properties file"
-                            " <b>{0}</b> could not be deleted.</p>"
-                        ).format(fn),
-                    )
+        except OSError:
+            EricMessageBox.critical(
+                self.ui,
+                title,
+                self.tr(
+                    "<p>The project debugger properties file"
+                    " <b>{0}</b> could not be deleted.</p>"
+                ).format(fn),
+            )
 
     def __initDebugProperties(self):
         """
@@ -1804,16 +1835,37 @@
         for langFile in self.__pdata["TRANSLATIONS"][:]:
             qmFile = self.__binaryTranslationFile(langFile)
             if qmFile:
-                if qmFile not in self.__pdata["TRANSLATIONS"] and os.path.exists(
-                    os.path.join(self.ppath, qmFile)
-                ):
-                    self.appendFile(qmFile)
-                if tbPath:
-                    qmFile = os.path.join(tbPath, os.path.basename(qmFile))
+                if FileSystemUtilities.isRemoteFileName(self.ppath):
+                    if (
+                        qmFile not in self.__pdata["TRANSLATIONS"]
+                        and self.__remotefsInterface.exists(
+                            self.__remotefsInterface.join(self.ppath, qmFile)
+                        )
+                    ):
+                        self.appendFile(qmFile)
+                    if tbPath:
+                        qmFile = self.__remotefsInterface.join(
+                            tbPath, self.__remotefsInterface.basename(qmFile)
+                        )
+                        if (
+                            qmFile not in self.__pdata["TRANSLATIONS"]
+                            and self.__remotefsInterface.exists(
+                                self.__remotefsInterface.join(self.ppath, qmFile)
+                            )
+                        ):
+                            self.appendFile(qmFile)
+                else:
                     if qmFile not in self.__pdata["TRANSLATIONS"] and os.path.exists(
                         os.path.join(self.ppath, qmFile)
                     ):
                         self.appendFile(qmFile)
+                    if tbPath:
+                        qmFile = os.path.join(tbPath, os.path.basename(qmFile))
+                        if (
+                            qmFile not in self.__pdata["TRANSLATIONS"]
+                            and os.path.exists(os.path.join(self.ppath, qmFile))
+                        ):
+                            self.appendFile(qmFile)
 
     def removeLanguageFile(self, langFile):
         """
@@ -1832,12 +1884,18 @@
         if qmFile:
             with contextlib.suppress(ValueError):
                 if self.__pdata["TRANSLATIONSBINPATH"]:
-                    qmFile = self.getRelativePath(
-                        os.path.join(
+                    if FileSystemUtilities.isRemoteFileName(self.ppath):
+                        qmFile = self.__remotefsInterface.join(
                             self.__pdata["TRANSLATIONSBINPATH"],
-                            os.path.basename(qmFile),
+                            self.__remotefsInterface.basename(qmFile),
                         )
-                    )
+                    else:
+                        qmFile = self.getRelativePath(
+                            os.path.join(
+                                self.__pdata["TRANSLATIONSBINPATH"],
+                                os.path.basename(qmFile),
+                            )
+                        )
                 self.__model.removeItem(qmFile)
                 self.__pdata["TRANSLATIONS"].remove(qmFile)
         self.setDirty(True)
@@ -1853,13 +1911,18 @@
         qmFile = self.__binaryTranslationFile(langFile)
 
         try:
-            fn = os.path.join(self.ppath, langFile)
-            if os.path.exists(fn):
-                os.remove(fn)
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                fn = self.__remotefsInterface.join(self.ppath, langFile)
+                if self.__remotefsInterface.exists(fn):
+                    self.__remotefsInterface.remove(fn)
+            else:
+                fn = os.path.join(self.ppath, langFile)
+                if os.path.exists(fn):
+                    os.remove(fn)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
-                self.tr("Delete translation"),
+                self.tr("Delete Translation"),
                 self.tr(
                     "<p>The selected translation file <b>{0}</b> could not be"
                     " deleted.</p><p>Reason: {1}</p>"
@@ -1910,14 +1973,22 @@
 
         # make it relative to the project root, if it starts with that path
         # assume relative paths are relative to the project root
-        newfn = self.getRelativePath(fn) if os.path.isabs(fn) else fn
-        newdir = os.path.dirname(newfn)
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            newfn = self.getRelativePath(fn) if fn.startswith(self.ppath) else fn
+            newdir = self.__remotefsInterface.dirname(newfn)
+        else:
+            newfn = self.getRelativePath(fn) if os.path.isabs(fn) else fn
+            newdir = os.path.dirname(newfn)
 
         if isSourceFile:
             filetype = "SOURCES"
         else:
             filetype = "OTHERS"
-            bfn = os.path.basename(newfn)
+            bfn = (
+                self.__remotefsInterface.basename(newfn)
+                if FileSystemUtilities.isRemoteFileName(self.ppath)
+                else os.path.basename(newfn)
+            )
             if fnmatch.fnmatch(bfn, "*.ts") or fnmatch.fnmatch(bfn, "*.qm"):
                 filetype = "TRANSLATIONS"
             else:
@@ -1974,6 +2045,7 @@
         @param startdir start directory for the selection dialog
         @type str
         """
+        # TODO: adapt to remote server
         from .AddFileDialog import AddFileDialog
 
         if not startdir:
@@ -2038,6 +2110,7 @@
         @param quiet flag indicating quiet operations
         @type bool
         """
+        # TODO: adapt to remote server
         # get all relevant filename patterns
         patterns = []
         ignorePatterns = []
@@ -2121,6 +2194,7 @@
         @param target target directory
         @type str
         """
+        # TODO: adapt to remote server
         # first perform the addition of source
         self.__addSingleDirectory(filetype, source, target, True)
 
@@ -2159,6 +2233,7 @@
         @param startdir start directory for the selection dialog
         @type str
         """
+        # TODO: adapt to remote server
         from .AddDirectoryDialog import AddDirectoryDialog
 
         if not startdir:
@@ -2199,6 +2274,7 @@
         @param fn file name or directory name to add
         @type str
         """
+        # TODO: adapt to remote server
         if fn:
             # if it is below the project directory, make it relative to that
             fn = self.getRelativePath(fn)
@@ -2244,6 +2320,7 @@
         @return flag indicating success
         @rtype bool
         """
+        # TODO: adapt to remote server
         fn = self.getRelativePath(oldfn)
         isSourceFile = fn in self.__pdata["SOURCES"]
 
@@ -2302,6 +2379,7 @@
                 even if it doesn't have the source extension
         @type bool
         """
+        # TODO: adapt to remote server
         fn = self.getRelativePath(oldname)
         if os.path.dirname(oldname) == os.path.dirname(newname):
             if self.__isInPdata(oldname):
@@ -2324,6 +2402,7 @@
         @return list of files starting with a common prefix
         @rtype list of str
         """
+        # TODO: adapt to remote server
         filelist = []
         start = self.getRelativePath(start)
         for fileCategory in [
@@ -3423,9 +3502,6 @@
         """
         if self.isDirty():
             if len(self.pfile) > 0:
-                if self.pfile.endswith(".e4p"):
-                    self.pfile = self.pfile.replace(".e4p", ".epj")
-                    self.__syncRecent()
                 ok = self.__writeProject()
             else:
                 ok = self.saveProjectAs()
@@ -3927,8 +4003,7 @@
         @rtype str
         """
         if self.pfile:
-            name = os.path.splitext(self.pfile)[0]
-            return os.path.basename(name)
+            return self.name
         else:
             return ""
 
@@ -3939,7 +4014,10 @@
         @return path of the management directory
         @rtype str
         """
-        return os.path.join(self.ppath, ".eric7project")
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            return self.__remotefsInterface.join(self.ppath, ".eric7project")
+        else:
+            return os.path.join(self.ppath, ".eric7project")
 
     def createProjectManagementDir(self):
         """
@@ -3949,8 +4027,10 @@
         """
         # create management directory if not present
         mgmtDir = self.getProjectManagementDir()
-        if not os.path.exists(mgmtDir):
-            os.makedirs(mgmtDir)
+        if FileSystemUtilities.isRemoteFileName(mgmtDir):
+            self.__remotefsInterface.makedirs(mgmtDir, exist_ok=True)
+        else:
+            os.makedirs(mgmtDir, exist_ok=True)
 
     def getHash(self):
         """
@@ -3976,7 +4056,13 @@
             return ""
 
         try:
-            return str(pathlib.Path(path).relative_to(self.ppath))
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                if self.__remotefsInterface.separator() == "\\":
+                    return str(pathlib.PureWindowsPath(path).relative_to(self.ppath))
+                else:
+                    return str(pathlib.PurePosixPath(path).relative_to(self.ppath))
+            else:
+                return str(pathlib.PurePath(path).relative_to(self.ppath))
         except ValueError:
             return path
 
@@ -3991,7 +4077,12 @@
             belong to the project
         @rtype str
         """
-        return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path))
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            return self.__remotefsInterface.fromNativeSeparators(
+                self.getRelativePath(path)
+            )
+        else:
+            return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path))
 
     def getAbsolutePath(self, fn):
         """
@@ -4003,8 +4094,11 @@
         @return absolute path
         @rtype str
         """
-        if not os.path.isabs(fn):
-            fn = os.path.join(self.ppath, fn)
+        if not fn.startswith(self.ppath):
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                fn = self.__remotefsInterface.join(self.ppath, fn)
+            else:
+                fn = os.path.join(self.ppath, fn)
         return fn
 
     def getAbsoluteUniversalPath(self, fn):
@@ -4017,8 +4111,15 @@
         @return absolute path
         @rtype str
         """
-        if not os.path.isabs(fn):
-            fn = os.path.join(self.ppath, FileSystemUtilities.toNativeSeparators(fn))
+        if not fn.startswith(self.ppath):
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                fn = self.__remotefsInterface.join(
+                    self.ppath, self.__remotefsInterface.fromNativeSeparators(fn)
+                )
+            else:
+                fn = os.path.join(
+                    self.ppath, FileSystemUtilities.fromNativeSeparators(fn)
+                )
         return fn
 
     def getEolString(self):
@@ -4241,7 +4342,11 @@
         @return flag indicating membership
         @rtype bool
         """
-        newfn = os.path.abspath(fn)
+        newfn = (
+            fn
+            if FileSystemUtilities.isRemoteFileName(fn)
+            else os.path.abspath(fn)
+        )
         newfn = self.getRelativePath(newfn)
         if newfn in self.__pdata[group] or (
             group == "OTHERS"
@@ -4327,6 +4432,25 @@
         act.triggered.connect(self.openProject)
         self.actions.append(act)
 
+        self.openRemoteAct = EricAction(
+            self.tr("Open remote project"),
+            EricPixmapCache.getIcon("projectOpen-remote"),
+            self.tr("Open (Remote)..."),
+            0,
+            0,
+            self.actGrp1,
+            "project_open_remote",
+        )
+        self.openRemoteAct.setStatusTip(self.tr("Open an existing remote project"))
+        self.openRemoteAct.setWhatsThis(
+            self.tr(
+                "<b>Open (Remote)...</b><p>This opens an existing remote project.</p>"
+            )
+        )
+        self.openRemoteAct.triggered.connect(self.__openRemoteProject)
+        self.actions.append(self.openRemoteAct)
+        self.openRemoteAct.setEnabled(False)  # server is not connected initially
+
         self.reloadAct = EricAction(
             self.tr("Reload project"),
             EricPixmapCache.getIcon("projectReload"),
@@ -7294,6 +7418,34 @@
             # the configuration file does not exist or is invalid JSON
             self.__initVenvConfiguration()
 
+    #############################################################################
+    ## Below are methods implementing the support for 'eric-ide server projects
+    #############################################################################
+
+    @pyqtSlot(bool)
+    def remoteConnectionChanged(self, connected):
+        """
+        Public slot to handle a change of the 'eric-ide' server connection state.
+
+        @param connected flag indicating the connection state
+        @type bool
+        """
+        self.openRemoteAct.setEnabled(connected)
+
+    @pyqtSlot()
+    def __openRemoteProject(self):
+        """
+        Private slot to open a project of an 'eric-ide' server.
+        """
+        # TODO: not implemented yet
+        fn = EricServerFileDialog.getOpenFileName(
+            self.parent(),
+            self.tr("Open project"),
+            "",
+            self.tr("Project Files (*.epj)"),
+        )
+        if fn:
+            self.openProject(fn=fn)
 
 #
 # eflag: noqa = M601
--- a/src/eric7/Project/ProjectBrowserModel.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Project/ProjectBrowserModel.py	Thu Feb 22 16:26:46 2024 +0100
@@ -138,7 +138,7 @@
     Class implementing the data structure for project browser directory items.
     """
 
-    def __init__(self, parent, dinfo, projectType, full=True, bold=False):
+    def __init__(self, parent, dinfo, projectType, full=True, bold=False, fsInterface=None):
         """
         Constructor
 
@@ -152,8 +152,11 @@
         @type bool
         @param bold flag indicating a highlighted font
         @type bool
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserDirectoryItem.__init__(self, parent, dinfo, full)
+        BrowserDirectoryItem.__init__(self, parent, dinfo, full, fsInterface)
         ProjectBrowserItemMixin.__init__(self, projectType, bold)
 
         self.type_ = BrowserItemType.PbDirectory
@@ -165,7 +168,7 @@
     """
 
     def __init__(
-        self, parent, finfo, projectType, full=True, bold=False, sourceLanguage=""
+        self, parent, finfo, projectType, full=True, bold=False, sourceLanguage="", fsInterface=None
     ):
         """
         Constructor
@@ -182,8 +185,11 @@
         @type bool
         @param sourceLanguage source code language of the project
         @type str
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage)
+        BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage, fsInterface)
         ProjectBrowserItemMixin.__init__(self, projectType, bold)
 
         self.type_ = BrowserItemType.PbFile
@@ -198,12 +204,15 @@
 
     vcsStateChanged = pyqtSignal(str)
 
-    def __init__(self, parent):
+    def __init__(self, parent, fsInterface=None):
         """
         Constructor
 
         @param parent reference to parent object
         @type Project.Project
+        @param fsInterface reference to the 'eric-ide' server interface object
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
         super().__init__(parent, nopopulate=True)
 
@@ -215,6 +224,8 @@
         self.project = parent
         self.__projectBrowser = None
 
+        self.__remotefsInterface = fsInterface
+
         self.watchedItems = {}
         self.__watcherActive = True
         self.watcher = QFileSystemWatcher(self)
@@ -418,7 +429,16 @@
             )
 
             for fn in self.project.getProjectData(dataKey=fileCategory):
-                fname = os.path.join(self.project.ppath, fn)
+                fname = (
+                    self.__remotefsInterface.join(self.project.ppath, fn)
+                    if FileSystemUtilities.isRemoteFileName(self.project.ppath)
+                    else os.path.join(self.project.ppath, fn)
+                )
+                isdir = (
+                    self.__remotefsInterface.isdir(fname)
+                    if FileSystemUtilities.isRemoteFileName(fname)
+                    else os.path.isdir(fname)
+                )
                 parentItem, dt = self.findParentItemByName(
                     self.__projectBrowser.getProjectBrowserFilter(fileCategory), fn
                 )
@@ -429,8 +449,9 @@
                         self.__projectBrowser.getProjectBrowserFilter(fileCategory),
                         False,
                         bold,
+                        fsInterface=self.__remotefsInterface,
                     )
-                    if os.path.isdir(fname)
+                    if isdir
                     else ProjectBrowserFileItem(
                         parentItem,
                         fname,
@@ -438,6 +459,7 @@
                         False,
                         bold,
                         sourceLanguage=sourceLanguage,
+                        fsInterface=self.__remotefsInterface,
                     )
                 )
                 self._addItem(itm, parentItem)
--- a/src/eric7/Project/ProjectFile.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Project/ProjectFile.py	Thu Feb 22 16:26:46 2024 +0100
@@ -17,6 +17,7 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
@@ -48,6 +49,11 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+        isRemote = FileSystemUtilities.isRemoteFileName(filename)
+
         projectDict = {
             "header": {
                 "comment": "eric project file for project {0}".format(
@@ -85,19 +91,28 @@
             "SOURCESDIR",
         ):
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = FileSystemUtilities.fromNativeSeparators(
-                    projectDict["project"][key]
+                projectDict["project"][key] = (
+                    fsInterface.fromNativeSeparators(projectDict["project"][key])
+                    if isRemote
+                    else FileSystemUtilities.fromNativeSeparators(
+                        projectDict["project"][key]
+                    )
                 )
 
         try:
             jsonString = json.dumps(projectDict, indent=2, sort_keys=True) + "\n"
-            with open(filename, "w", newline="") as f:
-                f.write(jsonString)
+            if isRemote:
+                title = self.tr("Save Remote Project File")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Project File")
+                with open(filename, "w", newline="") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Project File"),
+                    title,
                     self.tr(
                         "<p>The project file <b>{0}</b> could not be "
                         "written.</p><p>Reason: {1}</p>"
@@ -116,14 +131,24 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
+        isRemote = FileSystemUtilities.isRemoteFileName(filename)
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if isRemote:
+                title = self.tr("Read Remote Project File")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Project File")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             projectDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Project File"),
+                title,
                 self.tr(
                     "<p>The project file <b>{0}</b> could not be "
                     "read.</p><p>Reason: {1}</p>"
@@ -134,10 +159,17 @@
         # modify paths to contain native separators
         for key in self.__project.getFileCategories() + ["TRANSLATIONEXCEPTIONS"]:
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = [
-                    FileSystemUtilities.toNativeSeparators(f)
-                    for f in projectDict["project"][key]
-                ]
+                projectDict["project"][key] = (
+                    [
+                        fsInterface.toNativeSeparators(f)
+                        for f in projectDict["project"][key]
+                    ]
+                    if isRemote
+                    else [
+                        FileSystemUtilities.toNativeSeparators(f)
+                        for f in projectDict["project"][key]
+                    ]
+                )
         for key in (
             "SPELLWORDS",
             "SPELLEXCLUDES",
@@ -148,8 +180,12 @@
             "SOURCESDIR",
         ):
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = FileSystemUtilities.toNativeSeparators(
-                    projectDict["project"][key]
+                projectDict["project"][key] = (
+                    fsInterface.toNativeSeparators(projectDict["project"][key])
+                    if isRemote
+                    else FileSystemUtilities.toNativeSeparators(
+                        projectDict["project"][key]
+                    )
                 )
 
         self.__project.setProjectData(projectDict["project"])
--- a/src/eric7/Project/UserProjectFile.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Project/UserProjectFile.py	Thu Feb 22 16:26:46 2024 +0100
@@ -16,6 +16,8 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
 
@@ -47,6 +49,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         userProjectDict = {
             "header": {
                 "comment": "eric user project file for project {0}".format(
@@ -62,13 +68,18 @@
 
         try:
             jsonString = json.dumps(userProjectDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote User Project Properties")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save User Project Properties")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save User Project Properties"),
+                    title,
                     self.tr(
                         "<p>The user specific project properties file"
                         " <b>{0}</b> could not be written.</p>"
@@ -89,14 +100,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote User Project Properties")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read User Project Properties")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             userProjectDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read User Project Properties"),
+                title,
                 self.tr(
                     "<p>The user specific project properties file <b>{0}</b>"
                     " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/QScintilla/Editor.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/QScintilla/Editor.py	Thu Feb 22 16:26:46 2024 +0100
@@ -8739,7 +8739,7 @@
         if not self.checkDirty():
             return
 
-        if FileSystemUtilities.isRemoteFileName(self.fileName):
+        if FileSystemUtilities.isRemoteFileName(self.fileName):  # noqa: Y108
             package = (
                 self.fileName
                 if self.__remotefsInterface.isdir(self.fileName)
--- a/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py	Thu Feb 22 16:26:46 2024 +0100
@@ -38,6 +38,7 @@
             "Getcwd": self.__getcwd,
             "Listdir": self.__listdir,
             "Mkdir": self.__mkdir,
+            "MakeDirs": self.__makedirs,
             "Rmdir": self.__rmdir,
             "Replace": self.__replace,
             "Remove": self.__remove,
@@ -73,7 +74,12 @@
             self.__server.sendJson(
                 category=EricRequestCategory.FileSystem,
                 reply=request,
-                params={"Error": f"Request type '{request}' is not supported."},
+                params={
+                    "ok": False,
+                    "error": f"Request type '{request}' is not supported.",
+                    "info": list(self.__requestMethodMapping.keys()),
+                },
+                reqestUuid=reqestUuid,
             )
 
     def __getPathSeparator(self, params):
@@ -96,9 +102,8 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        directory = params["directory"]
         try:
-            os.chdir(directory)
+            os.chdir(params["directory"])
             return {"ok": True}
         except OSError as err:
             return {
@@ -170,12 +175,9 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        filename = params["filename"]
-        statItems = params["st_names"]
-
         try:
-            result = os.stat(filename)
-            resultDict = {st: getattr(result, st) for st in statItems}
+            result = os.stat(params["filename"])
+            resultDict = {st: getattr(result, st) for st in params["st_names"]}
             return {"ok": True, "result": resultDict}
         except OSError as err:
             return {
@@ -224,10 +226,26 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        directory = params["directory"]
+        try:
+            os.mkdir(params["directory"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
 
+    def __makedirs(self, params):
+        """
+        Private method to create a new directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
         try:
-            os.makedirs(directory)
+            os.makedirs(params["directory"], exist_ok=params["exist_ok"])
             return {"ok": True}
         except OSError as err:
             return {
@@ -244,10 +262,8 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        directory = params["directory"]
-
         try:
-            os.rmdir(directory)
+            os.rmdir(params["directory"])
             return {"ok": True}
         except OSError as err:
             return {
@@ -265,11 +281,8 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        oldName = params["old_name"]
-        newName = params["new_name"]
-
         try:
-            os.replace(oldName, newName)
+            os.replace(params["old_name"], params["new_name"])
             return {"ok": True}
         except OSError as err:
             return {
@@ -286,10 +299,8 @@
         @return dictionary containing the reply data
         @rtype dict
         """
-        filename = params["filename"]
-
         try:
-            os.remove(filename)
+            os.remove(params["filename"])
             return {"ok": True}
         except OSError as err:
             return {
--- a/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py	Thu Feb 22 16:26:46 2024 +0100
@@ -74,6 +74,9 @@
     def __getPathSep(self):
         """
         Private method to get the path separator of the connected server.
+
+        @return path separator character of the server
+        @rtype str
         """
         loop = QEventLoop()
         sep = ""
@@ -349,10 +352,11 @@
                 entries = self.direntries(
                     dirname, pattern=basename, recursive=recursive, filesonly=True
                 )
-                if includeHidden:
-                    result = entries
-                else:
-                    result = [e for e in entries if not e.startswith(".")]
+                result = (
+                    entries
+                    if includeHidden
+                    else [e for e in entries if not e.startswith(".")]
+                )
 
         return result
 
@@ -578,6 +582,57 @@
         else:
             return False, "Not connected to an 'eric-ide' server."
 
+    def makedirs(self, directory, exist_ok=False):
+        """
+        Public method to create a new directory on the eric-ide serverincluding all
+        intermediate-level directories.
+
+        @param directory absolute path of the new directory
+        @type str
+        @param exist_ok flag indicating that the existence of the directory is
+            acceptable (defaults to False)
+        @type bool (optional)
+        @return tuple containing an OK flag and an error string in case of an issue
+        @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 == "MakeDirs":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="MakeDirs",
+                params={
+                    "directory": FileSystemUtilities.plainFileName(directory),
+                    "exist_ok": exist_ok,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            return ok, error
+
+        else:
+            return False, "Not connected to an 'eric-ide' server."
+
     def rmdir(self, directory):
         """
         Public method to delete a directory on the eric-ide server.
@@ -844,6 +899,32 @@
         """
         return self.split(p)[1]
 
+    def toNativeSeparators(self, p):
+        """
+        Public method to convert a path to use server native separator characters.
+
+        @param p path name to be converted
+        @type str
+        @return path name with converted separator characters
+        @rtype str
+        """
+        if self.__serverPathSep == "/":
+            return p.replace("\\", "/")
+        else:
+            return p.replace("/", "\\")
+
+    def fromNativeSeparators(self, p):
+        """
+        Public method to convert a path using server native separator characters to
+        use "/" separator characters.
+
+        @param p path name to be converted
+        @type str
+        @return path name with converted separator characters
+        @rtype str
+        """
+        return p.replace(self.__serverPathSep, "/")
+
     #######################################################################
     ## Methods for reading and writing files
     #######################################################################
@@ -962,7 +1043,7 @@
 
     def readEncodedFile(self, filename, create=False):
         """
-        Function to read a file and decode its contents into proper text.
+        Public method to read a file and decode its contents into proper text.
 
         @param filename name of the file to read
         @type str
@@ -977,7 +1058,7 @@
 
     def readEncodedFileWithEncoding(self, filename, encoding, create=False):
         """
-        Function to read a file and decode its contents into proper text.
+        Public method to read a file and decode its contents into proper text.
 
         @param filename name of the file to read
         @type str
@@ -992,9 +1073,11 @@
         data = self.readFile(filename, create=create)
         return Utilities.decodeWithEncoding(data, encoding)
 
-    def writeEncodedFile(self, filename, text, origEncoding, forcedEncoding="", withBackup=False):
+    def writeEncodedFile(
+        self, filename, text, origEncoding, forcedEncoding="", withBackup=False
+    ):
         """
-        Function to write a file with properly encoded text.
+        Public method to write a file with properly encoded text.
 
         @param filename name of the file to read
         @type str
--- a/src/eric7/Sessions/SessionFile.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Sessions/SessionFile.py	Thu Feb 22 16:26:46 2024 +0100
@@ -16,6 +16,7 @@
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 
 class SessionFile(QObject):
@@ -46,6 +47,10 @@
         @rtype bool
         """
         # get references to objects we need
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         project = ericApp().getObject("Project")
         projectBrowser = ericApp().getObject("ProjectBrowser")
         multiProject = ericApp().getObject("MultiProject")
@@ -223,13 +228,18 @@
 
         try:
             jsonString = json.dumps(sessionDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Session")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Session")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Session"),
+                    title,
                     self.tr(
                         "<p>The session file <b>{0}</b> could not be"
                         " written.</p><p>Reason: {1}</p>"
@@ -248,14 +258,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Session")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Session")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             sessionDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Session"),
+                title,
                 self.tr(
                     "<p>The session file <b>{0}</b> could not be read.</p>"
                     "<p>Reason: {1}</p>"
--- a/src/eric7/Tasks/TasksFile.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Tasks/TasksFile.py	Thu Feb 22 16:26:46 2024 +0100
@@ -16,6 +16,7 @@
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Task import TaskPriority, TaskType
 
@@ -46,6 +47,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         # prepare the tasks data dictionary
         # step 0: header
         tasksDict = {}
@@ -85,13 +90,18 @@
 
         try:
             jsonString = json.dumps(tasksDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Tasks")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Tasks")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Tasks"),
+                    title,
                     self.tr(
                         "<p>The tasks file <b>{0}</b> could not be"
                         " written.</p><p>Reason: {1}</p>"
@@ -110,14 +120,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Tasks")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Tasks")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             tasksDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Tasks"),
+                title,
                 self.tr(
                     "<p>The tasks file <b>{0}</b> could not be read.</p>"
                     "<p>Reason: {1}</p>"
--- a/src/eric7/UI/BrowserModel.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/UI/BrowserModel.py	Thu Feb 22 16:26:46 2024 +0100
@@ -618,7 +618,6 @@
         """
         self._addWatchedItem(parentItem)
 
-        # TODO: add support for 'remote:' directories
         dirName = parentItem.dirName()
         if FileSystemUtilities.isPlainFileName(dirName):
             qdir = QDir(dirName)
@@ -638,18 +637,26 @@
                     if f.isDir():
                         node = BrowserDirectoryItem(
                             parentItem,
-                            FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
                             False,
                         )
                     else:
-                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(";")
+                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(
+                            ";"
+                        )
                         if fileFilters:
                             fn = f.fileName()
-                            if any(fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters):
+                            if any(
+                                fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters
+                            ):
                                 continue
                         node = BrowserFileItem(
                             parentItem,
-                            FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
                         )
                     self._addItem(node, parentItem)
                 if repopulate:
@@ -673,10 +680,14 @@
                             fsInterface=self.__remotefsInterface,
                         )
                     else:
-                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(";")
+                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(
+                            ";"
+                        )
                         if fileFilters:
                             fn = entry["name"]
-                            if any(fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters):
+                            if any(
+                                fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters
+                            ):
                                 continue
                         node = BrowserFileItem(
                             parentItem,
@@ -1300,11 +1311,7 @@
 
         if FileSystemUtilities.isRemoteFileName(dinfo):
             self._dirName = dinfo
-            dn = (
-                self._dirName
-                if full
-                else self.__fsInterface.basename(self._dirName)
-            )
+            dn = self._dirName if full else self.__fsInterface.basename(self._dirName)
         else:
             self._dirName = os.path.abspath(dinfo)
             dn = self._dirName if full else os.path.basename(self._dirName)
@@ -1337,11 +1344,7 @@
         """
         if FileSystemUtilities.isRemoteFileName(dinfo):
             self._dirName = dinfo
-            dn = (
-                self._dirName
-                if full
-                else self.__fsInterface.basename(self._dirName)
-            )
+            dn = self._dirName if full else self.__fsInterface.basename(self._dirName)
         else:
             self._dirName = os.path.abspath(dinfo)
             dn = self._dirName if full else os.path.basename(self._dirName)
--- a/src/eric7/UI/UserInterface.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/UI/UserInterface.py	Thu Feb 22 16:26:46 2024 +0100
@@ -311,6 +311,10 @@
 
         splash.showMessage(self.tr("Initializing Basic Services..."))
 
+        # create the remote server interface
+        logging.debug("Creating 'eric-ide' Server Interface...")
+        self.__ericServerInterface = EricServerInterface(self)
+
         # Generate the conda interface
         logging.debug("Creating Conda Interface...")
         self.condaInterface = Conda(self)
@@ -329,7 +333,7 @@
 
         # Generate an empty project object and multi project object
         logging.debug("Creating Project Manager...")
-        self.project = Project(self)
+        self.project = Project(self, remoteServer=self.__ericServerInterface)
         ericApp().registerObject("Project", self.project)
 
         logging.debug("Creating Multi-Project Manager...")
@@ -597,6 +601,9 @@
         self.__ericServerInterface.connectionStateChanged.connect(
             self.shell.remoteConnectionChanged
         )
+        self.__ericServerInterface.connectionStateChanged.connect(
+            self.project.remoteConnectionChanged
+        )
         self.__ericServerInterface.aboutToDisconnect.connect(
             self.viewmanager.closeRemoteEditors
         )
@@ -804,15 +811,10 @@
         from .PythonAstViewer import PythonAstViewer
         from .PythonDisViewer import PythonDisViewer
 
-
-        # create the remote server interface
-        logging.debug("Creating 'eric-ide' Server Interface...")
-        self.__ericServerInterface = EricServerInterface(self)
-
         # Create the view manager depending on the configuration setting
         logging.debug("Creating Viewmanager...")
         self.viewmanager = ViewManager.factory(
-            self, self.__debugServer, self.pluginManager
+            self, self.__debugServer, self.__ericServerInterface, self.pluginManager
         )
 
         # Create previewer
--- a/src/eric7/Utilities/ClassBrowsers/pyclbr.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Utilities/ClassBrowsers/pyclbr.py	Thu Feb 22 16:26:46 2024 +0100
@@ -417,27 +417,28 @@
     fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem")
 
     if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]):
-        type = ClassBrowsers.determineSourceType(module, isTypeFile)
+        sourceType = ClassBrowsers.determineSourceType(module, isTypeFile)
         file = fsInterface.join(searchPath[0], module)
     else:
         # search the path for the module
         searchPath = [] if searchPath is None else searchPath[:]
         fullpath = searchPath[:] + sys.path[:]
-        f, file, (suff, mode, type) = ClassBrowsers.find_module(
+        f, file, (suff, mode, sourceType) = ClassBrowsers.find_module(
             module, fullpath, isTypeFile
         )
         if f:
             f.close()
 
-    if type not in SUPPORTED_TYPES:
+    if sourceType not in SUPPORTED_TYPES:
         # not Python source, can't do anything with this module
         return {}
 
     try:
-        if FileSystemUtilities.isRemoteFileName(file):
-            src = fsInterface.readEncodedFile(file)[0]
-        else:
-            src = Utilities.readEncodedFile(file)[0]
+        src = (
+            fsInterface.readEncodedFile(file)[0]
+            if FileSystemUtilities.isRemoteFileName(file)
+            else Utilities.readEncodedFile(file)[0]
+        )
     except (OSError, UnicodeError):
         # can't do anything with this module
         return {}
--- a/src/eric7/Utilities/ClassBrowsers/rbclbr.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Utilities/ClassBrowsers/rbclbr.py	Thu Feb 22 16:26:46 2024 +0100
@@ -295,24 +295,25 @@
     fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem")
 
     if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]):
-        type = ClassBrowsers.determineSourceType(module)
+        sourceType = ClassBrowsers.determineSourceType(module)
         file = fsInterface.join(searchPath[0], module)
     else:
         # search the path for the module
         fullpath = [] if searchPath is None else searchPath[:]
-        f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
+        f, file, (suff, mode, sourceType) = ClassBrowsers.find_module(module, fullpath)
         if f:
             f.close()
 
-    if type not in SUPPORTED_TYPES:
+    if sourceType not in SUPPORTED_TYPES:
         # not Ruby source, can't do anything with this module
         return {}
 
     try:
-        if FileSystemUtilities.isRemoteFileName(file):
-            src = fsInterface.readEncodedFile(file)[0]
-        else:
-            src = Utilities.readEncodedFile(file)[0]
+        src = (
+            fsInterface.readEncodedFile(file)[0]
+            if FileSystemUtilities.isRemoteFileName(file)
+            else Utilities.readEncodedFile(file)[0]
+        )
     except (OSError, UnicodeError):
         # can't do anything with this module
         return {}
--- a/src/eric7/Utilities/ModuleParser.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/Utilities/ModuleParser.py	Thu Feb 22 16:26:46 2024 +0100
@@ -1679,6 +1679,7 @@
     @return reference to a Module object containing the parsed
         module information
     @rtype Module
+    @exception ImportError raised to indicate an unsupported source code type
     """
     global _modules
 
@@ -1689,7 +1690,9 @@
     isRemoteFileName = FileSystemUtilities.isRemoteFileName(module)
 
     if isRemoteFileName:
-        fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
         module, extension = fsInterface.splitext(fsInterface.basename(module))
     else:
         with contextlib.suppress(ValueError):
@@ -1740,7 +1743,7 @@
             moduleType = PTL_SOURCE
         elif extension == ".rb":
             moduleType = RB_SOURCE
-        elif extension in _extensions:
+        elif extension in _extensions:  # noqa: Y106
             moduleType = PY_SOURCE
         else:
             raise ImportError
@@ -1777,15 +1780,14 @@
 
     mod = Module(modname, file, moduleType)
     with contextlib.suppress(UnicodeError, OSError):
-        if isRemoteFileName:
-            src = (
-                ericApp()
-                .getObject("EricServer")
-                .getServiceInterface("FileSystem")
-                .readEncodedFile(file)[0]
-            )
-        else:
-            src = Utilities.readEncodedFile(file)[0]
+        src = (
+            ericApp()
+            .getObject("EricServer")
+            .getServiceInterface("FileSystem")
+            .readEncodedFile(file)[0]
+            if isRemoteFileName
+            else Utilities.readEncodedFile(file)[0]
+        )
         mod.scan(src)
     if caching:
         _modules[modname] = mod
--- a/src/eric7/ViewManager/ViewManager.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/ViewManager/ViewManager.py	Thu Feb 22 16:26:46 2024 +0100
@@ -149,7 +149,7 @@
         self.__watcher = QFileSystemWatcher(self)
         self.__watcher.fileChanged.connect(self.__watchedFileChanged)
 
-    def setReferences(self, ui, dbs):
+    def setReferences(self, ui, dbs, remoteServerInterface):
         """
         Public method to set some references needed later on.
 
@@ -157,11 +157,16 @@
         @type UserInterface
         @param dbs reference to the debug server object
         @type DebugServer
+        @param remoteServerInterface reference to the 'eric-ide' server interface
+        @type EricServerInterface
         """
         from eric7.QScintilla.SearchReplaceWidget import SearchReplaceSlidingWidget
 
         self.ui = ui
         self.dbs = dbs
+        self.__remotefsInterface = remoteServerInterface.getServiceInterface(
+            "FileSystem"
+        )
 
         self.__searchReplaceWidget = SearchReplaceSlidingWidget(self, ui)
 
@@ -6114,9 +6119,15 @@
         filenames = []
         for editor in self.editors:
             fn = editor.getFileName()
-            if fn is not None and fn not in filenames and os.path.exists(fn):
+            if fn is not None and fn not in filenames:
                 # only return names of existing files
-                filenames.append(fn)
+                exists = (
+                    self.__remotefsInterface.exists(fn)
+                    if FileSystemUtilities.isRemoteFileName(fn)
+                    else os.path.exists(fn)
+                )
+                if exists:
+                    filenames.append(fn)
 
         return filenames
 
--- a/src/eric7/ViewManager/__init__.py	Mon Feb 19 19:37:00 2024 +0100
+++ b/src/eric7/ViewManager/__init__.py	Thu Feb 22 16:26:46 2024 +0100
@@ -23,7 +23,7 @@
 ######################################################################
 
 
-def factory(ui, dbs, pluginManager):
+def factory(ui, dbs, remoteServerInterface, pluginManager):
     """
     Modul factory function to generate the right viewmanager type.
 
@@ -34,6 +34,8 @@
     @type UserInterface
     @param dbs reference to the debug server object
     @type DebugServer
+    @param remoteServerInterface reference to the 'eric-ide' server interface
+    @type EricServerInterface
     @param pluginManager reference to the plugin manager object
     @type PluginManager
     @return the instantiated viewmanager
@@ -48,5 +50,5 @@
         if vm is None:
             raise RuntimeError(f"Could not create a viemanager object.\nError: {err}")
         Preferences.setViewManager("tabview")
-    vm.setReferences(ui, dbs)
+    vm.setReferences(ui, dbs, remoteServerInterface)
     return vm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/projectOpen-remote.svg	Thu Feb 22 16:26:46 2024 +0100
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg10"
+   sodipodi:docname="projectOpen-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview12"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="12.028238"
+     inkscape:cy="11.546251"
+     inkscape:window-width="2580"
+     inkscape:window-height="1285"
+     inkscape:window-x="861"
+     inkscape:window-y="96"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg10" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g4755"
+     transform="matrix(0.75,0,0,0.75,2.75,0.25)">
+    <path
+       d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+       color="#eff0f1"
+       fill="currentColor"
+       id="path6" />
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z"
+       color="#eff0f1"
+       fill="currentColor"
+       id="path8" />
+  </g>
+  <path
+     style="color:#eff0f1;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999998,17 v 1.333333 H 1 v 1.333334 H 8.4999998 V 21 H 13.499999 V 19.666667 H 21 V 18.333333 H 13.499999 V 17 H 8.4999998 M 11,17.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2500002,-0.594666 -1.2500002,-1.333333 0,-0.738667 0.5575002,-1.333333 1.2500002,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/projectOpen-remote.svg	Thu Feb 22 16:26:46 2024 +0100
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg1062"
+   sodipodi:docname="projectOpen-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1064"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.289192"
+     inkscape:cy="11.803311"
+     inkscape:window-width="1857"
+     inkscape:window-height="1337"
+     inkscape:window-x="62"
+     inkscape:window-y="17"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1062" />
+  <defs
+     id="defs1056">
+    <style
+       type="text/css"
+       id="style1054">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g9300"
+     transform="matrix(0.75,0,0,0.75,2.75,0.25)">
+    <path
+       d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path1058" />
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path1060" />
+  </g>
+  <path
+     style="color:#232629;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.7679983,17 v 1.333333 H 1.268 v 1.333334 H 8.7679983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.767997 V 17 H 8.7679983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.25,-0.594666 -1.25,-1.333333 0,-0.738667 0.5575,-1.333333 1.25,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
Binary file src/eric7/icons/oxygen/fileSaveAsRemote.png has changed
Binary file src/eric7/icons/oxygen/projectOpen-remote.png has changed

eric ide

mercurial