src/eric7/Project/Project.py

branch
eric7-maintenance
changeset 10814
ba20efe10336
parent 10694
f46c1e224e8a
parent 10806
2f6df822e3b9
child 10873
4e8e63df7893
--- a/src/eric7/Project/Project.py	Sun Jun 02 09:51:47 2024 +0200
+++ b/src/eric7/Project/Project.py	Wed Jul 03 09:20:41 2024 +0200
@@ -31,7 +31,7 @@
 from PyQt6.QtGui import QAction, QKeySequence
 from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu, QToolBar
 
-from eric7 import Globals, Preferences, Utilities
+from eric7 import EricUtilities, Preferences, Utilities
 from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction
 from eric7.CodeFormatting.BlackUtilities import aboutBlack
 from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction
@@ -44,6 +44,7 @@
 from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
 from eric7.Globals import recentNameProject
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.Sessions.SessionFile import SessionFile
 from eric7.SystemUtilities import (
     FileSystemUtilities,
@@ -158,18 +159,22 @@
     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.__remoteServer = remoteServer
+        self.__remotefsInterface = remoteServer.getServiceInterface("FileSystem")
 
         self.__progLanguages = [
             "Python3",
@@ -210,7 +215,7 @@
         else:
             self.vcs = self.initVCS()
 
-        self.__model = ProjectBrowserModel(self)
+        self.__model = ProjectBrowserModel(self, fsInterface=self.__remotefsInterface)
 
         self.codemetrics = None
         self.codecoverage = None
@@ -508,7 +513,6 @@
         self.opened = False
         self.subdirs = []
         # record the project dir as a relative path (i.e. empty path)
-        self.otherssubdirs = []
         self.vcs = None
         self.vcsRequested = False
         self.dbgVirtualEnv = ""
@@ -976,7 +980,13 @@
         rp = Preferences.Prefs.rsettings.value(recentNameProject)
         if rp is not None:
             for f in rp:
-                if pathlib.Path(f).exists():
+                if (
+                    FileSystemUtilities.isRemoteFileName(f)
+                    and (
+                        not self.__remoteServer.isServerConnected()
+                        or self.__remotefsInterface.exists(f)
+                    )
+                ) or pathlib.Path(f).exists():
                     self.recent.append(f)
 
     def __saveRecent(self):
@@ -1098,10 +1108,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:
@@ -1110,7 +1128,7 @@
 
     def __readProject(self, fn):
         """
-        Private method to read in a project (.epj) file.
+        Private method to read in a project file (.epj).
 
         @param fn filename of the project file to be read
         @type str
@@ -1121,8 +1139,17 @@
             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]
+                self.__remotefsInterface.populateFsCache(self.ppath)
+            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()
@@ -1133,15 +1160,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():
@@ -1150,15 +1184,21 @@
             # 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(self.ppath)
+                        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:
-                    self.otherssubdirs.append(dn)
+                dn = (
+                    self.__remotefsInterface.dirname(fn)
+                    if FileSystemUtilities.isRemoteFileName(fn)
+                    else os.path.dirname(fn)
+                )
 
         return res
 
@@ -1196,9 +1236,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
@@ -1213,10 +1260,22 @@
         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):
-            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:
+            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):
         """
@@ -1225,8 +1284,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)
@@ -1239,9 +1306,18 @@
         if self.pfile is None:
             enable = False
         else:
-            fn, ext = os.path.splitext(os.path.basename(self.pfile))
-            fn_sess = os.path.join(self.getProjectManagementDir(), "{0}.esj".format(fn))
-            enable = os.path.exists(fn_sess)
+            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)
 
@@ -1265,12 +1341,22 @@
                 )
             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):
-            self.__sessionFile.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}{indicator}.esj"
+            )
+            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=""):
@@ -1292,10 +1378,16 @@
                 )
             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)
 
@@ -1311,21 +1403,32 @@
             )
             return
 
-        fname, ext = os.path.splitext(os.path.basename(self.pfile))
-
-        fn = os.path.join(self.getProjectManagementDir(), f"{fname}.esj")
-        if os.path.exists(fn):
-            try:
-                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),
+        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,
+                title,
+                self.tr(
+                    "<p>The project session file <b>{0}</b> could"
+                    " not be deleted.</p>"
+                ).format(fn),
+            )
 
     def __readTasks(self):
         """
@@ -1339,10 +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):
-            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:
+            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):
         """
@@ -1351,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):
@@ -1364,9 +1487,18 @@
         if self.pfile is None:
             enable = False
         else:
-            fn, ext = os.path.splitext(os.path.basename(self.pfile))
-            fn = os.path.join(self.getProjectManagementDir(), "{0}.edj".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)
@@ -1392,9 +1524,21 @@
                 )
             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) and self.__debuggerPropertiesFile.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}.edj"
+            )
+            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}.edj")
+            if not os.path.exists(fn):
+                return
+        if self.__debuggerPropertiesFile.readFile(fn):
             self.debugPropertiesLoaded = True
             self.debugPropertiesChanged = False
 
@@ -1416,8 +1560,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)
@@ -1434,21 +1586,32 @@
             )
             return
 
-        fname, ext = os.path.splitext(os.path.basename(self.pfile))
-
-        fn = os.path.join(self.getProjectManagementDir(), f"{fname}.edj")
-        if os.path.exists(fn):
-            try:
-                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),
+        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,
+                title,
+                self.tr(
+                    "<p>The project debugger properties file"
+                    " <b>{0}</b> could not be deleted.</p>"
+                ).format(fn),
+            )
 
     def __initDebugProperties(self):
         """
@@ -1489,7 +1652,9 @@
         """
         from .DebuggerPropertiesDialog import DebuggerPropertiesDialog
 
-        dlg = DebuggerPropertiesDialog(self)
+        dlg = DebuggerPropertiesDialog(
+            self, isRemote=FileSystemUtilities.isRemoteFileName(self.ppath)
+        )
         if dlg.exec() == QDialog.DialogCode.Accepted:
             dlg.storeData()
 
@@ -1502,7 +1667,9 @@
         @return value of the property
         @rtype Any
         """
-        if key == "INTERPRETER":
+        if key == "INTERPRETER" and not FileSystemUtilities.isRemoteFileName(
+            self.ppath
+        ):
             return (
                 ericApp()
                 .getObject("VirtualEnvManager")
@@ -1669,16 +1836,34 @@
         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):
         """
@@ -1697,12 +1882,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)
@@ -1718,13 +1909,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>"
@@ -1775,14 +1971,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:
@@ -1823,8 +2027,6 @@
                 dirty = True
             else:
                 updateModel and self.repopulateItem(newfn)
-            if newdir not in self.otherssubdirs:
-                self.otherssubdirs.append(newdir)
 
         if dirty:
             self.setDirty(True)
@@ -1848,17 +2050,37 @@
         if dlg.exec() == QDialog.DialogCode.Accepted:
             fnames, target, isSource = dlg.getData()
             if target != "":
+                isRemote = FileSystemUtilities.isRemoteFileName(target)
                 for fn in fnames:
-                    targetfile = os.path.join(target, os.path.basename(fn))
-                    if not FileSystemUtilities.samepath(os.path.dirname(fn), target):
+                    targetfile = (
+                        self.__remotefsInterface.join(
+                            target, self.__remotefsInterface.basename(fn)
+                        )
+                        if isRemote
+                        else os.path.join(target, os.path.basename(fn))
+                    )
+                    if not FileSystemUtilities.samepath(
+                        (
+                            self.__remotefsInterface.dirname(fn)
+                            if isRemote
+                            else os.path.dirname(fn)
+                        ),
+                        target,
+                    ):
                         try:
-                            if not os.path.isdir(target):
-                                os.makedirs(target)
-
-                            if os.path.exists(targetfile):
+                            if isRemote:
+                                if not self.__remotefsInterface.isdir(target):
+                                    self.__remotefsInterface.makedirs(target)
+                            else:
+                                if not os.path.isdir(target):
+                                    os.makedirs(target)
+
+                            if (not isRemote and os.path.exists(targetfile)) or (
+                                isRemote and self.__remotefsInterface.exists(targetfile)
+                            ):
                                 res = EricMessageBox.yesNo(
                                     self.ui,
-                                    self.tr("Add file"),
+                                    self.tr("Add File"),
                                     self.tr(
                                         "<p>The file <b>{0}</b> already"
                                         " exists.</p><p>Overwrite it?</p>"
@@ -1868,11 +2090,14 @@
                                 if not res:
                                     return  # don't overwrite
 
-                            shutil.copy(fn, target)
+                            if isRemote:
+                                self.__remotefsInterface.shutilCopy(fn, target)
+                            else:
+                                shutil.copy(fn, target)
                         except OSError as why:
                             EricMessageBox.critical(
                                 self.ui,
-                                self.tr("Add file"),
+                                self.tr("Add File"),
                                 self.tr(
                                     "<p>The selected file <b>{0}</b> could"
                                     " not be added to <b>{1}</b>.</p>"
@@ -1913,15 +2138,20 @@
                 ignorePatterns.append(pattern)
 
         files = []
+        isRemote = FileSystemUtilities.isRemoteFileName(target)
         for pattern in patterns:
-            sstring = "{0}{1}{2}".format(source, os.sep, pattern)
-            files.extend(glob.glob(sstring))
+            if isRemote:
+                sstring = self.__remotefsInterface.join(source, pattern)
+                files.extend(self.__remotefsInterface.glob(sstring))
+            else:
+                sstring = os.path.join(source, pattern)
+                files.extend(glob.glob(sstring))
 
         if len(files) == 0:
             if not quiet:
                 EricMessageBox.information(
                     self.ui,
-                    self.tr("Add directory"),
+                    self.tr("Add Directory"),
                     self.tr(
                         "<p>The source directory doesn't contain"
                         " any files belonging to the selected category.</p>"
@@ -1929,15 +2159,19 @@
                 )
             return
 
-        if not FileSystemUtilities.samepath(target, source) and not os.path.isdir(
-            target
+        if not FileSystemUtilities.samepath(target, source) and not (
+            (not isRemote and os.path.isdir(target))
+            or (isRemote and self.__remotefsInterface.isdir(target))
         ):
             try:
-                os.makedirs(target)
+                if isRemote:
+                    self.__remotefsInterface.makedirs(target)
+                else:
+                    os.makedirs(target)
             except OSError as why:
                 EricMessageBox.critical(
                     self.ui,
-                    self.tr("Add directory"),
+                    self.tr("Add Directory"),
                     self.tr(
                         "<p>The target directory <b>{0}</b> could not be"
                         " created.</p><p>Reason: {1}</p>"
@@ -1950,13 +2184,21 @@
                 if fnmatch.fnmatch(file, pattern):
                     continue
 
-            targetfile = os.path.join(target, os.path.basename(file))
+            targetfile = (
+                self.__remotefsInterface.join(
+                    target, self.__remotefsInterface.basename(file)
+                )
+                if isRemote
+                else os.path.join(target, os.path.basename(file))
+            )
             if not FileSystemUtilities.samepath(target, source):
                 try:
-                    if os.path.exists(targetfile):
+                    if (not isRemote and os.path.exists(targetfile)) or (
+                        isRemote and self.__remotefsInterface.exists(targetfile)
+                    ):
                         res = EricMessageBox.yesNo(
                             self.ui,
-                            self.tr("Add directory"),
+                            self.tr("Add Directory"),
                             self.tr(
                                 "<p>The file <b>{0}</b> already exists.</p>"
                                 "<p>Overwrite it?</p>"
@@ -1967,7 +2209,10 @@
                             continue
                             # don't overwrite, carry on with next file
 
-                    shutil.copy(file, target)
+                    if isRemote:
+                        self.__remotefsInterface.shutilCopy(file, target)
+                    else:
+                        shutil.copy(file, target)
                 except OSError:
                     continue
             self.appendFile(targetfile)
@@ -2003,16 +2248,28 @@
             if filetype == "__IGNORE__"
         ]
 
-        # now recurse into subdirectories
-        with os.scandir(source) as dirEntriesIterator:
-            for dirEntry in dirEntriesIterator:
-                if dirEntry.is_dir() and not any(
-                    fnmatch.fnmatch(dirEntry.name, ignore_pattern)
+        # now recurs into subdirectories
+        if FileSystemUtilities.isRemoteFileName(target):
+            for entry in self.__remotefsInterface.listdir(source)[2]:
+                if entry["is_dir"] and not any(
+                    fnmatch.fnmatch(entry["name"], ignore_pattern)
                     for ignore_pattern in ignore_patterns
                 ):
                     self.__addRecursiveDirectory(
-                        filetype, dirEntry.path, os.path.join(target, dirEntry.name)
+                        filetype,
+                        entry["path"],
+                        self.__remotefsInterface.join(target, entry["name"]),
                     )
+        else:
+            with os.scandir(source) as dirEntriesIterator:
+                for dirEntry in dirEntriesIterator:
+                    if dirEntry.is_dir() and not any(
+                        fnmatch.fnmatch(dirEntry.name, ignore_pattern)
+                        for ignore_pattern in ignore_patterns
+                    ):
+                        self.__addRecursiveDirectory(
+                            filetype, dirEntry.path, os.path.join(target, dirEntry.name)
+                        )
 
     @pyqtSlot()
     def addDirectory(self, fileTypeFilter=None, startdir=None):
@@ -2065,11 +2322,17 @@
         @type str
         """
         if fn:
+            separator = (
+                self.__remotefsInterface.separator()
+                if FileSystemUtilities.isRemoteFileName(fn)
+                else os.sep
+            )
+
             # if it is below the project directory, make it relative to that
             fn = self.getRelativePath(fn)
 
             # if it ends with the directory separator character, remove it
-            if fn.endswith(os.sep):
+            if fn.endswith(separator):
                 fn = fn[:-1]
 
             if fn not in self.__pdata["OTHERS"]:
@@ -2077,9 +2340,6 @@
                 self.othersAdded(fn)
                 self.setDirty(True)
 
-            if os.path.isdir(fn) and fn not in self.otherssubdirs:
-                self.otherssubdirs.append(fn)
-
     def renameMainScript(self, oldfn, newfn):
         """
         Public method to rename the main script.
@@ -2109,22 +2369,36 @@
         @return flag indicating success
         @rtype bool
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(oldfn)
+
         fn = self.getRelativePath(oldfn)
         isSourceFile = fn in self.__pdata["SOURCES"]
 
         if newfn is None:
-            newfn = EricFileDialog.getSaveFileName(
-                None,
-                self.tr("Rename file"),
-                oldfn,
-                "",
-                options=EricFileDialog.DontConfirmOverwrite,
-            )
+            if isRemote:
+                newfn = EricServerFileDialog.getSaveFileName(
+                    None,
+                    self.tr("Rename File"),
+                    oldfn,
+                    "",
+                )
+            else:
+                newfn = EricFileDialog.getSaveFileName(
+                    None,
+                    self.tr("Rename File"),
+                    oldfn,
+                    "",
+                    options=EricFileDialog.DontConfirmOverwrite,
+                )
+                if newfn:
+                    newfn = FileSystemUtilities.toNativeSeparators(newfn)
+
             if not newfn:
                 return False
-            newfn = FileSystemUtilities.toNativeSeparators(newfn)
-
-        if os.path.exists(newfn):
+
+        if (not isRemote and os.path.exists(newfn)) or (
+            isRemote and self.__remotefsInterface.exists(newfn)
+        ):
             res = EricMessageBox.yesNo(
                 self.ui,
                 self.tr("Rename File"),
@@ -2138,7 +2412,10 @@
                 return False
 
         try:
-            os.rename(oldfn, newfn)
+            if isRemote:
+                self.__remotefsInterface.replace(oldfn, newfn)
+            else:
+                os.rename(oldfn, newfn)
         except OSError as msg:
             EricMessageBox.critical(
                 self.ui,
@@ -2167,8 +2444,15 @@
                 even if it doesn't have the source extension
         @type bool
         """
+        if FileSystemUtilities.isRemoteFileName(oldname):
+            oldDirName = self.__remotefsInterface.dirname(oldname)
+            newDirName = self.__remotefsInterface.dirname(newname)
+        else:
+            oldDirName = os.path.dirname(oldname)
+            newDirName = os.path.dirname(newname)
+
         fn = self.getRelativePath(oldname)
-        if os.path.dirname(oldname) == os.path.dirname(newname):
+        if oldDirName == newDirName:
             if self.__isInPdata(oldname):
                 self.removeFile(oldname, False)
                 self.appendFile(newname, isSourceFile, False)
@@ -2189,6 +2473,8 @@
         @return list of files starting with a common prefix
         @rtype list of str
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+
         filelist = []
         start = self.getRelativePath(start)
         for fileCategory in [
@@ -2196,7 +2482,11 @@
         ]:
             for entry in self.__pdata[fileCategory][:]:
                 if entry.startswith(start):
-                    filelist.append(os.path.join(self.ppath, entry))
+                    filelist.append(
+                        self.__remotefsInterface.join(self.ppath, entry)
+                        if isRemote
+                        else os.path.join(self.ppath, entry)
+                    )
         return filelist
 
     def __reorganizeFiles(self):
@@ -2204,8 +2494,9 @@
         Private method to reorganize files stored in the project.
         """
         reorganized = False
-
-        # init data store for the reorganization
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+
+        # initialize data store for the reorganization
         newPdata = {}
         for fileCategory in self.getFileCategories():
             newPdata[fileCategory] = []
@@ -2214,7 +2505,11 @@
         for fileCategory in self.getFileCategories():
             for fn in self.__pdata[fileCategory][:]:
                 filetype = fileCategory
-                bfn = os.path.basename(fn)
+                bfn = (
+                    self.__remotefsInterface.basename(fn)
+                    if isRemote
+                    else os.path.basename(fn)
+                )
                 for pattern in sorted(self.__pdata["FILETYPES"], reverse=True):
                     if fnmatch.fnmatch(bfn, pattern):
                         filetype = self.__pdata["FILETYPES"][pattern]
@@ -2243,6 +2538,8 @@
         @param newdn new directory name
         @type str
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+
         olddn = self.getRelativePath(olddn)
         newdn = self.getRelativePath(newdn)
         for fileCategory in [
@@ -2252,7 +2549,12 @@
                 if entry.startswith(olddn):
                     entry = entry.replace(olddn, newdn)
                     self.appendFile(
-                        os.path.join(self.ppath, entry), fileCategory == "SOURCES"
+                        (
+                            self.__remotefsInterface.join(self.ppath, entry)
+                            if isRemote
+                            else os.path.join(self.ppath, entry)
+                        ),
+                        fileCategory == "SOURCES",
                     )
         self.setDirty(True)
 
@@ -2278,12 +2580,8 @@
                     self.__pdata[fileCategory].remove(entry)
                     entry = entry.replace(olddn, newdn)
                     self.__pdata[fileCategory].append(entry)
-            if fileCategory == "OTHERS":
-                if newdn not in self.otherssubdirs:
-                    self.otherssubdirs.append(newdn)
-            else:
-                if newdn not in self.subdirs:
-                    self.subdirs.append(newdn)
+            if fileCategory != "OTHERS" and newdn not in self.subdirs:
+                self.subdirs.append(newdn)
         if typeStrings:
             # the directory is controlled by the project
             self.setDirty(True)
@@ -2325,13 +2623,19 @@
         @param dn directory name to be removed from the project
         @type str
         """
+        separator = (
+            self.__remotefsInterface.separator()
+            if FileSystemUtilities.isRemoteFileName(self.ppath)
+            else os.sep
+        )
+
         dirty = False
         dn = self.getRelativePath(dn)
         for entry in self.__pdata["OTHERS"][:]:
             if entry.startswith(dn):
                 self.__pdata["OTHERS"].remove(entry)
                 dirty = True
-        dn2 = dn if dn.endswith(os.sep) else dn + os.sep
+        dn2 = dn if dn.endswith(separator) else dn + separator
         for fileCategory in [c for c in self.getFileCategories() if c != "OTHERS"]:
             for entry in self.__pdata[fileCategory][:]:
                 if entry.startswith(dn2):
@@ -2352,26 +2656,38 @@
         @rtype bool
         """
         try:
-            os.remove(os.path.join(self.ppath, fn))
-            path, ext = os.path.splitext(fn)
-            if ext == ".ui":
-                fn2 = os.path.join(self.ppath, "{0}.h".format(fn))
-                if os.path.isfile(fn2):
-                    os.remove(fn2)
-            head, tail = os.path.split(path)
-            for ext in [".pyc", ".pyo"]:
-                fn2 = os.path.join(self.ppath, path + ext)
-                if os.path.isfile(fn2):
-                    os.remove(fn2)
-                pat = os.path.join(
-                    self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext)
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                self.__remotefsInterface.remove(
+                    self.__remotefsInterface.join(self.ppath, fn)
                 )
-                for f in glob.glob(pat):
-                    os.remove(f)
+                filepath = self.__remotefsInterface.splitext(fn)[0]
+                head, tail = self.__remotefsInterface.split(filepath)
+                for ext in [".pyc", ".pyo"]:
+                    fn2 = self.__remotefsInterface.join(self.ppath, filepath + ext)
+                    if self.__remotefsInterface.isfile(fn2):
+                        self.__remotefsInterface.remove(fn2)
+                    pat = self.__remotefsInterface.join(
+                        self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext)
+                    )
+                    for f in self.__remotefsInterface.glob(pat):
+                        self.__remotefsInterface.remove(f)
+            else:
+                os.remove(os.path.join(self.ppath, fn))
+                filepath = os.path.splitext(fn)[0]
+                head, tail = os.path.split(filepath)
+                for ext in [".pyc", ".pyo"]:
+                    fn2 = os.path.join(self.ppath, filepath + ext)
+                    if os.path.isfile(fn2):
+                        os.remove(fn2)
+                    pat = os.path.join(
+                        self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext)
+                    )
+                    for f in glob.glob(pat):
+                        os.remove(f)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
-                self.tr("Delete file"),
+                self.tr("Delete File"),
                 self.tr(
                     "<p>The selected file <b>{0}</b> could not be"
                     " deleted.</p><p>Reason: {1}</p>"
@@ -2380,8 +2696,6 @@
             return False
 
         self.removeFile(fn)
-        if ext == ".ui":
-            self.removeFile(fn + ".h")
         return True
 
     def deleteDirectory(self, dn):
@@ -2393,14 +2707,16 @@
         @return flag indicating success
         @rtype bool
         """
-        if not os.path.isabs(dn):
-            dn = os.path.join(self.ppath, dn)
+        dn = self.getAbsolutePath(dn)
         try:
-            shutil.rmtree(dn, ignore_errors=True)
+            if FileSystemUtilities.isRemoteFileName(dn):
+                self.__remotefsInterface.shutilRmtree(dn, ignore_errors=True)
+            else:
+                shutil.rmtree(dn, ignore_errors=True)
         except OSError as err:
             EricMessageBox.critical(
                 self.ui,
-                self.tr("Delete directory"),
+                self.tr("Delete Directory"),
                 self.tr(
                     "<p>The selected directory <b>{0}</b> could not be"
                     " deleted.</p><p>Reason: {1}</p>"
@@ -2434,6 +2750,7 @@
         This method displays the new project dialog and initializes
         the project object with the data entered.
         """
+        #       assume remote project without VCS if connected to server
         from eric7.VCS.CommandOptionsDialog import VcsCommandOptionsDialog
 
         from .PropertiesDialog import PropertiesDialog
@@ -2441,7 +2758,9 @@
         if not self.checkDirty():
             return
 
-        dlg = PropertiesDialog(self, new=True)
+        isRemote = self.__remoteServer.isServerConnected()
+
+        dlg = PropertiesDialog(self, new=True, isRemote=isRemote)
         if dlg.exec() == QDialog.DialogCode.Accepted:
             self.closeProject()
 
@@ -2458,9 +2777,13 @@
             self.reloadAct.setEnabled(True)
             self.closeAct.setEnabled(True)
             self.saveasAct.setEnabled(True)
+            self.saveasRemoteAct.setEnabled(
+                self.__remoteServer.isServerConnected()
+                and FileSystemUtilities.isRemoteFileName(self.pfile)
+            )
             self.actGrp2.setEnabled(True)
             self.propsAct.setEnabled(True)
-            self.userPropsAct.setEnabled(True)
+            self.userPropsAct.setEnabled(not isRemote)
             self.filetypesAct.setEnabled(True)
             self.lexersAct.setEnabled(True)
             self.sessActGrp.setEnabled(False)
@@ -2470,14 +2793,30 @@
             self.menuCheckAct.setEnabled(True)
             self.menuShowAct.setEnabled(True)
             self.menuDiagramAct.setEnabled(True)
-            self.menuApidocAct.setEnabled(True)
+            self.menuApidocAct.setEnabled(
+                not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
             self.menuPackagersAct.setEnabled(True)
-            self.pluginGrp.setEnabled(self.__pdata["PROJECTTYPE"] in ["E7Plugin"])
+            self.pluginGrp.setEnabled(
+                self.__pdata["PROJECTTYPE"] in ["E7Plugin"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
             self.addLanguageAct.setEnabled(bool(self.__pdata["TRANSLATIONPATTERN"]))
-            self.makeGrp.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"])
-            self.menuMakeAct.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"])
+            self.makeGrp.setEnabled(
+                self.__pdata["MAKEPARAMS"]["MakeEnabled"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
+            self.menuMakeAct.setEnabled(
+                self.__pdata["MAKEPARAMS"]["MakeEnabled"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
             self.menuOtherToolsAct.setEnabled(True)
-            self.menuFormattingAct.setEnabled(True)
+            self.menuFormattingAct.setEnabled(
+                not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
+            self.menuVcsAct.setEnabled(
+                not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
 
             self.projectAboutToBeCreated.emit()
 
@@ -2497,9 +2836,17 @@
                 }
 
             # create the project directory if it doesn't exist already
-            if not os.path.isdir(self.ppath):
+            ppathExists = (
+                self.__remotefsInterface.isdir(self.ppath)
+                if isRemote
+                else os.path.isdir(self.ppath)
+            )
+            if not ppathExists:
                 try:
-                    os.makedirs(self.ppath)
+                    if isRemote:
+                        self.__remotefsInterface.makedirs(self.ppath)
+                    else:
+                        os.makedirs(self.ppath)
                 except OSError:
                     EricMessageBox.critical(
                         self.ui,
@@ -2517,23 +2864,41 @@
                 # create an empty __init__.py file to make it a Python package
                 # (only for Python and Python3)
                 if self.__pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]:
-                    fn = os.path.join(self.ppath, "__init__.py")
-                    with open(fn, "w", encoding="utf-8"):
-                        pass
+                    if isRemote:
+                        fn = self.__remotefsInterface.join(self.ppath, "__init__.py")
+                        self.__remotefsInterface.writeFile(fn, b"")
+                    else:
+                        fn = os.path.join(self.ppath, "__init__.py")
+                        with open(fn, "w", encoding="utf-8"):
+                            pass
                     self.appendFile(fn, True)
 
                 # create an empty main script file, if a name was given
                 if self.__pdata["MAINSCRIPT"]:
-                    if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
-                        ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                    if isRemote:
+                        if not self.__remotefsInterface.isabs(
+                            self.__pdata["MAINSCRIPT"]
+                        ):
+                            ms = self.__remotefsInterface.join(
+                                self.ppath, self.__pdata["MAINSCRIPT"]
+                            )
+                        else:
+                            ms = self.__pdata["MAINSCRIPT"]
+                        self.__remotefsInterface.makedirs(
+                            self.__remotefsInterface.dirname(ms), exist_ok=True
+                        )
+                        self.__remotefsInterface.writeFile(ms, b"")
                     else:
-                        ms = self.__pdata["MAINSCRIPT"]
-                    os.makedirs(os.path.dirname(ms), exist_ok=True)
-                    with open(ms, "w"):
-                        pass
+                        if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
+                            ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                        else:
+                            ms = self.__pdata["MAINSCRIPT"]
+                        os.makedirs(os.path.dirname(ms), exist_ok=True)
+                        with open(ms, "w"):
+                            pass
                     self.appendFile(ms, True)
 
-                if self.__pdata["MAKEPARAMS"]["MakeEnabled"]:
+                if self.__pdata["MAKEPARAMS"]["MakeEnabled"] and not isRemote:
                     mf = self.__pdata["MAKEPARAMS"]["MakeFile"]
                     if mf:
                         if not os.path.isabs(mf):
@@ -2545,15 +2910,34 @@
                         pass
                     self.appendFile(mf)
 
-                tpd = os.path.join(self.ppath, self.translationsRoot)
-                if not self.translationsRoot.endswith(os.sep):
-                    tpd = os.path.dirname(tpd)
-                if not os.path.isdir(tpd):
-                    os.makedirs(tpd, exist_ok=True)
-                if self.__pdata["TRANSLATIONSBINPATH"]:
-                    tpd = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"])
+                if isRemote:
+                    tpd = self.__remotefsInterface.join(
+                        self.ppath, self.translationsRoot
+                    )
+                    if not self.translationsRoot.endswith(
+                        self.__remotefsInterface.separator()
+                    ):
+                        tpd = self.__remotefsInterface.dirname(tpd)
+                    if not self.__remotefsInterface.isdir(tpd):
+                        self.__remotefsInterface.makedirs(tpd, exist_ok=True)
+                    if self.__pdata["TRANSLATIONSBINPATH"]:
+                        tpd = self.__remotefsInterface.join(
+                            self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
+                        )
+                        if not self.__remotefsInterface.isdir(tpd):
+                            self.__remotefsInterface.makedirs(tpd, exist_ok=True)
+                else:
+                    tpd = os.path.join(self.ppath, self.translationsRoot)
+                    if not self.translationsRoot.endswith(os.sep):
+                        tpd = os.path.dirname(tpd)
                     if not os.path.isdir(tpd):
                         os.makedirs(tpd, exist_ok=True)
+                    if self.__pdata["TRANSLATIONSBINPATH"]:
+                        tpd = os.path.join(
+                            self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
+                        )
+                        if not os.path.isdir(tpd):
+                            os.makedirs(tpd, exist_ok=True)
 
                 # create management directory if not present
                 self.createProjectManagementDir()
@@ -2578,21 +2962,39 @@
                     return
 
                 if self.__pdata["MAINSCRIPT"]:
-                    if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
-                        ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                    if isRemote:
+                        if not self.__remotefsInterface.isabs(
+                            self.__pdata["MAINSCRIPT"]
+                        ):
+                            ms = self.__remotefsInterface.join(
+                                self.ppath, self.__pdata["MAINSCRIPT"]
+                            )
+                        else:
+                            ms = self.__pdata["MAINSCRIPT"]
+                        msExists = self.__remotefsInterface.exists(ms)
                     else:
-                        ms = self.__pdata["MAINSCRIPT"]
-                    if not os.path.exists(ms):
+                        if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
+                            ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                        else:
+                            ms = self.__pdata["MAINSCRIPT"]
+                        msExists = os.path.exists(ms)
+                    if not msExists:
                         try:
-                            os.makedirs(os.path.dirname(ms), exist_ok=True)
-                            with open(ms, "w"):
-                                pass
+                            if isRemote:
+                                self.__remotefsInterface.makedirs(
+                                    self.__remotefsInterface.dirname(ms), exist_ok=True
+                                )
+                                self.__remotefsInterface.writeFile(ms, b"")
+                            else:
+                                os.makedirs(os.path.dirname(ms), exist_ok=True)
+                                with open(ms, "w"):
+                                    pass
                         except OSError as err:
                             EricMessageBox.critical(
                                 self.ui,
                                 self.tr("Create main script"),
                                 self.tr(
-                                    "<p>The mainscript <b>{0}</b> could not"
+                                    "<p>The main script <b>{0}</b> could not"
                                     " be created.<br/>Reason: {1}</p>"
                                 ).format(ms, str(err)),
                             )
@@ -2600,7 +3002,7 @@
                 else:
                     ms = ""
 
-                if self.__pdata["MAKEPARAMS"]["MakeEnabled"]:
+                if self.__pdata["MAKEPARAMS"]["MakeEnabled"] and not isRemote:
                     mf = self.__pdata["MAKEPARAMS"]["MakeFile"]
                     if mf:
                         if not os.path.isabs(mf):
@@ -2631,89 +3033,102 @@
                     yesDefault=True,
                 )
                 if res:
-                    self.newProjectAddFiles(ms)
-                addAllToVcs = res
+                    self.newProjectAddFiles(ms, isRemote=isRemote)
+                addAllToVcs = res and not isRemote
+
                 # create an empty __init__.py file to make it a Python package
                 # if none exists (only for Python and Python3)
                 if self.__pdata["PROGLANGUAGE"] in ["Python3", "MicroPython"]:
-                    fn = os.path.join(self.ppath, "__init__.py")
-                    if not os.path.exists(fn):
-                        with open(fn, "w", encoding="utf-8"):
-                            pass
-                        self.appendFile(fn, True)
+                    if isRemote:
+                        fn = self.__remotefsInterface.join(self.ppath, "__init__.py")
+                        if not self.__remotefsInterface.exists(fn):
+                            self.__remotefsInterface.writeFile(fn, b"")
+                            self.appendFile(fn, True)
+                    else:
+                        fn = os.path.join(self.ppath, "__init__.py")
+                        if not os.path.exists(fn):
+                            with open(fn, "w", encoding="utf-8"):
+                                pass
+                            self.appendFile(fn, True)
                 self.saveProject()
 
                 # check, if the existing project directory is already under
                 # VCS control
-                pluginManager = ericApp().getObject("PluginManager")
-                for indicator, vcsData in list(
-                    pluginManager.getVcsSystemIndicators().items()
-                ):
-                    if os.path.exists(os.path.join(self.ppath, indicator)):
-                        if len(vcsData) > 1:
-                            vcsList = []
-                            for _vcsSystemStr, vcsSystemDisplay in vcsData:
-                                vcsList.append(vcsSystemDisplay)
-                            res, vcs_ok = QInputDialog.getItem(
-                                None,
-                                self.tr("New Project"),
-                                self.tr("Select Version Control System"),
-                                vcsList,
-                                0,
-                                False,
-                            )
-                            if vcs_ok:
-                                for vcsSystemStr, vcsSystemDisplay in vcsData:
-                                    if res == vcsSystemDisplay:
-                                        vcsSystem = vcsSystemStr
-                                        break
+                if not isRemote:
+                    pluginManager = ericApp().getObject("PluginManager")
+                    for indicator, vcsData in list(
+                        pluginManager.getVcsSystemIndicators().items()
+                    ):
+                        if os.path.exists(os.path.join(self.ppath, indicator)):
+                            if len(vcsData) > 1:
+                                vcsList = []
+                                for _vcsSystemStr, vcsSystemDisplay in vcsData:
+                                    vcsList.append(vcsSystemDisplay)
+                                res, vcs_ok = QInputDialog.getItem(
+                                    None,
+                                    self.tr("New Project"),
+                                    self.tr("Select Version Control System"),
+                                    vcsList,
+                                    0,
+                                    False,
+                                )
+                                if vcs_ok:
+                                    for vcsSystemStr, vcsSystemDisplay in vcsData:
+                                        if res == vcsSystemDisplay:
+                                            vcsSystem = vcsSystemStr
+                                            break
+                                    else:
+                                        vcsSystem = "None"
                                 else:
                                     vcsSystem = "None"
                             else:
-                                vcsSystem = "None"
-                        else:
-                            vcsSystem = vcsData[0][1]
-                        self.__pdata["VCS"] = vcsSystem
-                        self.vcs = self.initVCS()
-                        self.setDirty(True)
-                        if self.vcs is not None:
-                            # edit VCS command options
-                            if self.vcs.vcsSupportCommandOptions():
-                                vcores = EricMessageBox.yesNo(
-                                    self.ui,
-                                    self.tr("New Project"),
-                                    self.tr(
-                                        """Would you like to edit the VCS"""
-                                        """ command options?"""
-                                    ),
-                                )
+                                vcsSystem = vcsData[0][1]
+                            self.__pdata["VCS"] = vcsSystem
+                            self.vcs = self.initVCS()
+                            self.setDirty(True)
+                            if self.vcs is not None:
+                                # edit VCS command options
+                                if self.vcs.vcsSupportCommandOptions():
+                                    vcores = EricMessageBox.yesNo(
+                                        self.ui,
+                                        self.tr("New Project"),
+                                        self.tr(
+                                            """Would you like to edit the VCS"""
+                                            """ command options?"""
+                                        ),
+                                    )
+                                else:
+                                    vcores = False
+                                if vcores:
+                                    codlg = VcsCommandOptionsDialog(self.vcs)
+                                    if codlg.exec() == QDialog.DialogCode.Accepted:
+                                        self.vcs.vcsSetOptions(codlg.getOptions())
+                                # add project file to repository
+                                if res == 0:
+                                    apres = EricMessageBox.yesNo(
+                                        self.ui,
+                                        self.tr("New Project"),
+                                        self.tr(
+                                            "Shall the project file be added"
+                                            " to the repository?"
+                                        ),
+                                        yesDefault=True,
+                                    )
+                                    if apres:
+                                        self.saveProject()
+                                        self.vcs.vcsAdd(self.pfile)
                             else:
-                                vcores = False
-                            if vcores:
-                                codlg = VcsCommandOptionsDialog(self.vcs)
-                                if codlg.exec() == QDialog.DialogCode.Accepted:
-                                    self.vcs.vcsSetOptions(codlg.getOptions())
-                            # add project file to repository
-                            if res == 0:
-                                apres = EricMessageBox.yesNo(
-                                    self.ui,
-                                    self.tr("New project"),
-                                    self.tr(
-                                        "Shall the project file be added"
-                                        " to the repository?"
-                                    ),
-                                    yesDefault=True,
-                                )
-                                if apres:
-                                    self.saveProject()
-                                    self.vcs.vcsAdd(self.pfile)
-                        else:
-                            self.__pdata["VCS"] = "None"
-                        self.saveProject()
-                        break
+                                self.__pdata["VCS"] = "None"
+                            self.saveProject()
+                            break
 
             # put the project under VCS control
-            if self.vcs is None and self.vcsSoftwareAvailable() and self.vcsRequested:
+            if (
+                not isRemote
+                and self.vcs is None
+                and self.vcsSoftwareAvailable()
+                and self.vcsRequested
+            ):
                 vcsSystemsDict = (
                     ericApp()
                     .getObject("PluginManager")
@@ -2781,27 +3196,42 @@
             # set the auto save flag to its supposed value
             Preferences.setProject("AutoSaveProject", autoSaveProject)
 
-            if self.__pdata["EMBEDDED_VENV"]:
+            if self.__pdata[
+                "EMBEDDED_VENV"
+            ] and not FileSystemUtilities.isRemoteFileName(self.ppath):
                 self.__createEmbeddedEnvironment()
-            self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"])
+            self.menuEnvironmentAct.setEnabled(
+                self.__pdata["EMBEDDED_VENV"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
 
             self.projectOpenedHooks.emit()
             self.projectOpened.emit()
 
             # open the main script
             if self.__pdata["MAINSCRIPT"]:
-                if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
-                    ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                if isRemote:
+                    if not self.__remotefsInterface.isabs(self.__pdata["MAINSCRIPT"]):
+                        ms = self.__remotefsInterface.join(
+                            self.ppath, self.__pdata["MAINSCRIPT"]
+                        )
+                    else:
+                        ms = self.__pdata["MAINSCRIPT"]
                 else:
-                    ms = self.__pdata["MAINSCRIPT"]
+                    if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
+                        ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                    else:
+                        ms = self.__pdata["MAINSCRIPT"]
                 self.sourceFile.emit(ms)
 
-    def newProjectAddFiles(self, mainscript):
+    def newProjectAddFiles(self, mainscript, isRemote=False):
         """
         Public method to add files to a new project.
 
         @param mainscript name of the mainscript
         @type str
+        @param isRemote flag indicating a remote project (defaults to False)
+        @type bool (optional)
         """
         # Show the file type associations for the user to change
         self.__showFiletypeAssociations()
@@ -2810,54 +3240,113 @@
         if self.__pdata["EMBEDDED_VENV"]:
             environmentPath = self.__findEmbeddedEnvironment()
             if environmentPath:
-                # there is already an embeded venv; ignore thie whenn adding files
-                ignoreList = [os.path.split(environmentPath)[-1]]
+                # there is already an embedded venv; ignore this when adding files
+                ignoreList = (
+                    [self.__remotefsInterface.split(environmentPath)[-1]]
+                    if isRemote
+                    else [os.path.split(environmentPath)[-1]]
+                )
         with EricOverrideCursor():
             # search the project directory for files with known extensions
             for filespec in self.__pdata["FILETYPES"]:
-                files = FileSystemUtilities.direntries(
-                    self.ppath,
-                    filesonly=True,
-                    pattern=filespec,
-                    ignore=ignoreList,
+                files = (
+                    self.__remotefsInterface.direntries(
+                        self.ppath,
+                        filesonly=True,
+                        pattern=filespec,
+                        ignore=ignoreList,
+                    )
+                    if isRemote
+                    else FileSystemUtilities.direntries(
+                        self.ppath,
+                        filesonly=True,
+                        pattern=filespec,
+                        ignore=ignoreList,
+                    )
                 )
                 for file in files:
                     self.appendFile(file)
 
             # special handling for translation files
             if self.translationsRoot:
-                tpd = os.path.join(self.ppath, self.translationsRoot)
-                if not self.translationsRoot.endswith(os.sep):
-                    tpd = os.path.dirname(tpd)
+                if isRemote:
+                    tpd = self.__remotefsInterface.join(
+                        self.ppath, self.translationsRoot
+                    )
+                    if not self.translationsRoot.endswith(os.sep):
+                        tpd = self.__remotefsInterface.dirname(tpd)
+                else:
+                    tpd = os.path.join(self.ppath, self.translationsRoot)
+                    if not self.translationsRoot.endswith(os.sep):
+                        tpd = os.path.dirname(tpd)
             else:
                 tpd = self.ppath
             tslist = []
             if self.__pdata["TRANSLATIONPATTERN"]:
-                pattern = os.path.basename(self.__pdata["TRANSLATIONPATTERN"])
+                pattern = (
+                    self.__remotefsInterface.basename(
+                        self.__pdata["TRANSLATIONPATTERN"]
+                    )
+                    if isRemote
+                    else os.path.basename(self.__pdata["TRANSLATIONPATTERN"])
+                )
                 if "%language%" in pattern:
                     pattern = pattern.replace("%language%", "*")
                 else:
                     tpd = self.__pdata["TRANSLATIONPATTERN"].split("%language%")[0]
             else:
                 pattern = "*.ts"
-            tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern))
+            tslist.extend(
+                self.__remotefsInterface.direntries(tpd, True, pattern)
+                if isRemote
+                else FileSystemUtilities.direntries(tpd, True, pattern)
+            )
+
             pattern = self.__binaryTranslationFile(pattern)
             if pattern:
-                tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern))
+                tslist.extend(
+                    self.__remotefsInterface.direntries(tpd, True, pattern)
+                    if isRemote
+                    else FileSystemUtilities.direntries(tpd, True, pattern)
+                )
             if tslist:
-                if "_" in os.path.basename(tslist[0]):
-                    # the first entry determines the mainscript name
-                    mainscriptname = (
-                        os.path.splitext(mainscript)[0]
-                        or os.path.basename(tslist[0]).split("_")[0]
-                    )
-                    self.__pdata["TRANSLATIONPATTERN"] = os.path.join(
-                        os.path.dirname(tslist[0]),
-                        "{0}_%language%{1}".format(
-                            os.path.basename(tslist[0]).split("_")[0],
-                            os.path.splitext(tslist[0])[1],
-                        ),
-                    )
+                hasUnderscore = (
+                    "_" in self.__remotefsInterface.basename(tslist[0])
+                    if isRemote
+                    else "_" in os.path.basename(tslist[0])
+                )
+                if hasUnderscore:
+                    # the first entry determines the main script name
+                    if isRemote:
+                        mainscriptname = (
+                            self.__remotefsInterface.splitext(mainscript)[0]
+                            or self.__remotefsInterface.basename(tslist[0]).split("_")[
+                                0
+                            ]
+                        )
+                        self.__pdata["TRANSLATIONPATTERN"] = (
+                            self.__remotefsInterface.join(
+                                self.__remotefsInterface.dirname(tslist[0]),
+                                "{0}_%language%{1}".format(
+                                    self.__remotefsInterface.basename(tslist[0]).split(
+                                        "_"
+                                    )[0],
+                                    self.__remotefsInterface.splitext(tslist[0])[1],
+                                ),
+                            )
+                        )
+                    else:
+                        mainscriptname = (
+                            os.path.splitext(mainscript)[0]
+                            or os.path.basename(tslist[0]).split("_")[0]
+                        )
+                        self.__pdata["TRANSLATIONPATTERN"] = os.path.join(
+                            os.path.dirname(tslist[0]),
+                            "{0}_%language%{1}".format(
+                                os.path.basename(tslist[0]).split("_")[0],
+                                os.path.splitext(tslist[0])[1],
+                            ),
+                        )
                 else:
                     mainscriptname = ""
                     pattern, ok = QInputDialog.getText(
@@ -2885,12 +3374,20 @@
                             self.__pdata["TRANSLATIONS"].append(ts)
                             self.projectFileAdded.emit(ts, "TRANSLATIONS")
                     if self.__pdata["TRANSLATIONSBINPATH"]:
-                        tpd = os.path.join(
-                            self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
-                        )
-                        pattern = os.path.basename(
-                            self.__pdata["TRANSLATIONPATTERN"]
-                        ).replace("%language%", "*")
+                        if isRemote:
+                            tpd = self.__remotefsInterface.join(
+                                self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
+                            )
+                            pattern = self.__remotefsInterface.basename(
+                                self.__pdata["TRANSLATIONPATTERN"]
+                            ).replace("%language%", "*")
+                        else:
+                            tpd = os.path.join(
+                                self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
+                            )
+                            pattern = os.path.basename(
+                                self.__pdata["TRANSLATIONPATTERN"]
+                            ).replace("%language%", "*")
                         pattern = self.__binaryTranslationFile(pattern)
                         qmlist = FileSystemUtilities.direntries(tpd, True, pattern)
                         for qm in qmlist:
@@ -2909,30 +3406,55 @@
         """
         from .PropertiesDialog import PropertiesDialog
 
-        dlg = PropertiesDialog(self, new=False)
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+        dlg = PropertiesDialog(self, new=False, isRemote=isRemote)
         if dlg.exec() == QDialog.DialogCode.Accepted:
             fileTypesDict = copy.copy(self.__pdata["FILETYPES"])
             dlg.storeData()
             self.setDirty(True)
             if self.__pdata["MAINSCRIPT"]:
-                if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
-                    ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                if isRemote:
+                    if not self.__remotefsInterface.isabs(self.__pdata["MAINSCRIPT"]):
+                        ms = self.__remotefsInterface.join(
+                            self.ppath, self.__pdata["MAINSCRIPT"]
+                        )
+                    else:
+                        ms = self.__pdata["MAINSCRIPT"]
+                    if self.__remotefsInterface.exists(ms):
+                        self.appendFile(ms)
                 else:
-                    ms = self.__pdata["MAINSCRIPT"]
-                if os.path.exists(ms):
-                    self.appendFile(ms)
+                    if not os.path.isabs(self.__pdata["MAINSCRIPT"]):
+                        ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                    else:
+                        ms = self.__pdata["MAINSCRIPT"]
+                    if os.path.exists(ms):
+                        self.appendFile(ms)
 
             if self.__pdata["MAKEPARAMS"]["MakeEnabled"]:
                 mf = self.__pdata["MAKEPARAMS"]["MakeFile"]
-                if mf:
-                    if not os.path.isabs(mf):
-                        mf = os.path.join(self.ppath, mf)
+                if isRemote:
+                    if mf:
+                        if not self.__remotefsInterface.isabs(mf):
+                            mf = self.__remotefsInterface.join(self.ppath, mf)
+                    else:
+                        mf = self.__remotefsInterface.join(
+                            self.ppath, Project.DefaultMakefile
+                        )
+                    exists = self.__remotefsInterface.exists(mf)
                 else:
-                    mf = os.path.join(self.ppath, Project.DefaultMakefile)
-                if not os.path.exists(mf):
+                    if mf:
+                        if not os.path.isabs(mf):
+                            mf = os.path.join(self.ppath, mf)
+                    else:
+                        mf = os.path.join(self.ppath, Project.DefaultMakefile)
+                    exists = os.path.exists(mf)
+                if not exists:
                     try:
-                        with open(mf, "w"):
-                            pass
+                        if isRemote:
+                            self.__remotefsInterface.writeFile(mf, b"")
+                        else:
+                            with open(mf, "w"):
+                                pass
                     except OSError as err:
                         EricMessageBox.critical(
                             self.ui,
@@ -2945,20 +3467,38 @@
                 self.appendFile(mf)
 
             if self.translationsRoot:
-                tp = os.path.join(self.ppath, self.translationsRoot)
-                if not self.translationsRoot.endswith(os.sep):
-                    tp = os.path.dirname(tp)
+                if isRemote:
+                    tp = self.__remotefsInterface.join(
+                        self.ppath, self.translationsRoot
+                    )
+                    if not self.translationsRoot.endswith(
+                        self.__remotefsInterface.separator()
+                    ):
+                        tp = self.__remotefsInterface.dirname(tp)
+                    if not self.__remotefsInterface.isdir(tp):
+                        self.__remotefsInterface.makedirs(tp)
+                else:
+                    tp = os.path.join(self.ppath, self.translationsRoot)
+                    if not self.translationsRoot.endswith(os.sep):
+                        tp = os.path.dirname(tp)
+                    if not os.path.isdir(tp):
+                        os.makedirs(tp)
             else:
                 tp = self.ppath
-            if not os.path.isdir(tp):
-                os.makedirs(tp)
             if tp != self.ppath and tp not in self.subdirs:
                 self.subdirs.append(tp)
 
             if self.__pdata["TRANSLATIONSBINPATH"]:
-                tp = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"])
-                if not os.path.isdir(tp):
-                    os.makedirs(tp)
+                if isRemote:
+                    tp = self.__remotefsInterface.join(
+                        self.ppath, self.__pdata["TRANSLATIONSBINPATH"]
+                    )
+                    if not self.__remotefsInterface.isdir(tp):
+                        self.__remotefsInterface.makedirs(tp)
+                else:
+                    tp = os.path.join(self.ppath, self.__pdata["TRANSLATIONSBINPATH"])
+                    if not os.path.isdir(tp):
+                        os.makedirs(tp)
                 if tp != self.ppath and tp not in self.subdirs:
                     self.subdirs.append(tp)
 
@@ -2970,7 +3510,11 @@
             if self.__pdata["FILETYPES"] != fileTypesDict:
                 self.__reorganizeFiles()
 
-            if self.__pdata["EMBEDDED_VENV"] and not self.__findEmbeddedEnvironment():
+            if (
+                self.__pdata["EMBEDDED_VENV"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+                and not self.__findEmbeddedEnvironment()
+            ):
                 self.__createEmbeddedEnvironment()
 
     def __showUserProperties(self):
@@ -3110,7 +3654,7 @@
         if fn is None:
             fn = EricFileDialog.getOpenFileName(
                 self.parent(),
-                self.tr("Open project"),
+                self.tr("Open Project"),
                 Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir(),
                 self.tr("Project Files (*.epj)"),
             )
@@ -3145,53 +3689,65 @@
                 with EricOverrideCursor():
                     oldState = self.isDirty()
                     self.vcs = self.initVCS()
-                    if self.vcs is None and self.isDirty() == oldState:
-                        # check, if project is version controlled
-                        pluginManager = ericApp().getObject("PluginManager")
-                        for (
-                            indicator,
-                            vcsData,
-                        ) in pluginManager.getVcsSystemIndicators().items():
-                            if os.path.exists(os.path.join(self.ppath, indicator)):
-                                if len(vcsData) > 1:
-                                    vcsList = []
-                                    for _vcsSystemStr, vcsSystemDisplay in vcsData:
-                                        vcsList.append(vcsSystemDisplay)
-                                    with EricOverridenCursor():
-                                        res, vcs_ok = QInputDialog.getItem(
-                                            None,
-                                            self.tr("New Project"),
-                                            self.tr("Select Version Control System"),
-                                            vcsList,
-                                            0,
-                                            False,
-                                        )
-                                    if vcs_ok:
-                                        for vcsSystemStr, vcsSystemDisplay in vcsData:
-                                            if res == vcsSystemDisplay:
-                                                vcsSystem = vcsSystemStr
-                                                break
+                    if not FileSystemUtilities.isRemoteFileName(self.ppath):
+                        if self.vcs is None and self.isDirty() == oldState:
+                            # check, if project is version controlled
+                            pluginManager = ericApp().getObject("PluginManager")
+                            for (
+                                indicator,
+                                vcsData,
+                            ) in pluginManager.getVcsSystemIndicators().items():
+                                if os.path.exists(os.path.join(self.ppath, indicator)):
+                                    if len(vcsData) > 1:
+                                        vcsList = []
+                                        for _vcsSystemStr, vcsSystemDisplay in vcsData:
+                                            vcsList.append(vcsSystemDisplay)
+                                        with EricOverridenCursor():
+                                            res, vcs_ok = QInputDialog.getItem(
+                                                None,
+                                                self.tr("New Project"),
+                                                self.tr(
+                                                    "Select Version Control System"
+                                                ),
+                                                vcsList,
+                                                0,
+                                                False,
+                                            )
+                                        if vcs_ok:
+                                            for (
+                                                vcsSystemStr,
+                                                vcsSystemDisplay,
+                                            ) in vcsData:
+                                                if res == vcsSystemDisplay:
+                                                    vcsSystem = vcsSystemStr
+                                                    break
+                                            else:
+                                                vcsSystem = "None"
                                         else:
                                             vcsSystem = "None"
                                     else:
-                                        vcsSystem = "None"
-                                else:
-                                    vcsSystem = vcsData[0][0]
-                                self.__pdata["VCS"] = vcsSystem
-                                self.vcs = self.initVCS()
-                                self.setDirty(True)
-                    if self.vcs is not None and (
-                        self.vcs.vcsRegisteredState(self.ppath)
-                        != VersionControlState.Controlled
-                    ):
-                        self.__pdata["VCS"] = "None"
-                        self.vcs = self.initVCS()
+                                        vcsSystem = vcsData[0][0]
+                                    self.__pdata["VCS"] = vcsSystem
+                                    self.vcs = self.initVCS()
+                                    self.setDirty(True)
+                        if self.vcs is not None and (
+                            self.vcs.vcsRegisteredState(self.ppath)
+                            != VersionControlState.Controlled
+                        ):
+                            self.__pdata["VCS"] = "None"
+                            self.vcs = self.initVCS()
                     self.reloadAct.setEnabled(True)
                     self.closeAct.setEnabled(True)
                     self.saveasAct.setEnabled(True)
+                    self.saveasRemoteAct.setEnabled(
+                        self.__remoteServer.isServerConnected()
+                        and FileSystemUtilities.isRemoteFileName(self.pfile)
+                    )
                     self.actGrp2.setEnabled(True)
                     self.propsAct.setEnabled(True)
-                    self.userPropsAct.setEnabled(True)
+                    self.userPropsAct.setEnabled(
+                        not FileSystemUtilities.isRemoteFileName(self.pfile)
+                    )
                     self.filetypesAct.setEnabled(True)
                     self.lexersAct.setEnabled(True)
                     self.sessActGrp.setEnabled(True)
@@ -3201,20 +3757,34 @@
                     self.menuCheckAct.setEnabled(True)
                     self.menuShowAct.setEnabled(True)
                     self.menuDiagramAct.setEnabled(True)
-                    self.menuApidocAct.setEnabled(True)
-                    self.menuPackagersAct.setEnabled(True)
+                    self.menuApidocAct.setEnabled(
+                        not FileSystemUtilities.isRemoteFileName(self.ppath)
+                    )
+                    self.menuPackagersAct.setEnabled(
+                        not FileSystemUtilities.isRemoteFileName(self.ppath)
+                    )
                     self.pluginGrp.setEnabled(
                         self.__pdata["PROJECTTYPE"] in ["E7Plugin"]
+                        and not FileSystemUtilities.isRemoteFileName(self.ppath)
                     )
                     self.addLanguageAct.setEnabled(
                         bool(self.__pdata["TRANSLATIONPATTERN"])
                     )
-                    self.makeGrp.setEnabled(self.__pdata["MAKEPARAMS"]["MakeEnabled"])
+                    self.makeGrp.setEnabled(
+                        self.__pdata["MAKEPARAMS"]["MakeEnabled"]
+                        and not FileSystemUtilities.isRemoteFileName(self.ppath)
+                    )
                     self.menuMakeAct.setEnabled(
                         self.__pdata["MAKEPARAMS"]["MakeEnabled"]
+                        and not FileSystemUtilities.isRemoteFileName(self.ppath)
                     )
                     self.menuOtherToolsAct.setEnabled(True)
-                    self.menuFormattingAct.setEnabled(True)
+                    self.menuFormattingAct.setEnabled(
+                        not FileSystemUtilities.isRemoteFileName(self.ppath)
+                    )
+                    self.menuVcsAct.setEnabled(
+                        not FileSystemUtilities.isRemoteFileName(self.ppath)
+                    )
 
                     # open a project debugger properties file being quiet
                     # about errors
@@ -3223,7 +3793,9 @@
 
                     self.__model.projectOpened()
 
-                if self.__pdata["EMBEDDED_VENV"]:
+                if self.__pdata[
+                    "EMBEDDED_VENV"
+                ] and not FileSystemUtilities.isRemoteFileName(self.ppath):
                     envPath = self.__findEmbeddedEnvironment()
                     if bool(envPath):
                         self.__loadEnvironmentConfiguration()
@@ -3235,7 +3807,10 @@
                             self.__configureEnvironment(envPath)
                     else:
                         self.__createEmbeddedEnvironment()
-                self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"])
+                self.menuEnvironmentAct.setEnabled(
+                    self.__pdata["EMBEDDED_VENV"]
+                    and not FileSystemUtilities.isRemoteFileName(self.ppath)
+                )
 
                 self.projectOpenedHooks.emit()
                 self.projectOpened.emit()
@@ -3266,7 +3841,8 @@
                         self.__readSession(quiet=True)
 
                 # start the VCS monitor thread
-                self.__vcsConnectStatusMonitor()
+                if not FileSystemUtilities.isRemoteFileName(self.ppath):
+                    self.__vcsConnectStatusMonitor()
             else:
                 self.__initData()  # delete all invalid data read
 
@@ -3328,7 +3904,7 @@
             if fpath.exists():
                 res = EricMessageBox.yesNo(
                     self.ui,
-                    self.tr("Save File"),
+                    self.tr("Save Project"),
                     self.tr(
                         """<p>The file <b>{0}</b> already exists."""
                         """ Overwrite it?</p>"""
@@ -3338,7 +3914,6 @@
                 if not res:
                     return False
 
-            self.name = fpath.stem
             ok = self.__writeProject(str(fpath))
 
             if ok:
@@ -3451,7 +4026,10 @@
             return False
 
         # stop the VCS monitor thread
-        if self.vcs is not None:
+        if (
+            not FileSystemUtilities.isRemoteFileName(self.ppath)
+            and self.vcs is not None
+        ):
             self.vcs.stopStatusMonitor()
 
         # now save the tasks
@@ -3460,8 +4038,11 @@
         self.ui.taskViewer.clearProjectTasks()
         self.ui.taskViewer.setProjectOpen(False)
 
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            self.__remotefsInterface.removeFromFsCache(self.ppath)
+
         # now shutdown the vcs interface
-        if self.vcs:
+        if not FileSystemUtilities.isRemoteFileName(self.ppath) and self.vcs:
             self.vcs.vcsShutdown()
             self.vcs.deleteLater()
             self.vcs = None
@@ -3473,6 +4054,7 @@
         self.__initData()
         self.reloadAct.setEnabled(False)
         self.closeAct.setEnabled(False)
+        self.saveasRemoteAct.setEnabled(False)
         self.saveasAct.setEnabled(False)
         self.saveAct.setEnabled(False)
         self.actGrp2.setEnabled(False)
@@ -3526,7 +4108,7 @@
         if reportSyntaxErrors and filesWithSyntaxErrors > 0:
             EricMessageBox.critical(
                 self.ui,
-                self.tr("Syntax errors detected"),
+                self.tr("Syntax Errors Detected"),
                 self.tr(
                     """The project contains %n file(s) with syntax errors.""",
                     "",
@@ -3562,7 +4144,7 @@
         if reportSyntaxErrors and filesWithSyntaxErrors > 0:
             EricMessageBox.critical(
                 self.ui,
-                self.tr("Syntax errors detected"),
+                self.tr("Syntax Errors Detected"),
                 self.tr(
                     """The project contains %n file(s) with syntax errors.""",
                     "",
@@ -3587,7 +4169,12 @@
         """
         if self.__pdata["MAINSCRIPT"]:
             if normalized:
-                return os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
+                if FileSystemUtilities.isRemoteFileName(self.ppath):
+                    return self.__remotefsInterface.join(
+                        self.ppath, self.__pdata["MAINSCRIPT"]
+                    )
+                else:
+                    return os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
             else:
                 return self.__pdata["MAINSCRIPT"]
         else:
@@ -3620,7 +4207,13 @@
             raise ValueError("Given file type has incorrect value.")
 
         if normalized:
-            return [os.path.join(self.ppath, fn) for fn in self.__pdata[fileType]]
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                return [
+                    self.__remotefsInterface.join(self.ppath, fn)
+                    for fn in self.__pdata[fileType]
+                ]
+            else:
+                return [os.path.join(self.ppath, fn) for fn in self.__pdata[fileType]]
         else:
             return self.__pdata[fileType]
 
@@ -3715,16 +4308,18 @@
         @rtype tuple of (str, str)
         """
         pwl = ""
-        if self.__pdata["SPELLWORDS"]:
-            pwl = os.path.join(self.ppath, self.__pdata["SPELLWORDS"])
-            if not os.path.isfile(pwl):
-                pwl = ""
-
         pel = ""
-        if self.__pdata["SPELLEXCLUDES"]:
-            pel = os.path.join(self.ppath, self.__pdata["SPELLEXCLUDES"])
-            if not os.path.isfile(pel):
-                pel = ""
+
+        if not FileSystemUtilities.isRemoteFileName(self.ppath):
+            if self.__pdata["SPELLWORDS"]:
+                pwl = os.path.join(self.ppath, self.__pdata["SPELLWORDS"])
+                if not os.path.isfile(pwl):
+                    pwl = ""
+
+            if self.__pdata["SPELLEXCLUDES"]:
+                pel = os.path.join(self.ppath, self.__pdata["SPELLEXCLUDES"])
+                if not os.path.isfile(pel):
+                    pel = ""
 
         return (pwl, pel)
 
@@ -3750,25 +4345,30 @@
         """
         return self.ppath
 
-    def startswithProjectPath(self, path):
+    def startswithProjectPath(self, checkpath):
         """
         Public method to check, if a path starts with the project path.
 
-        @param path path to be checked
+        @param checkpath path to be checked
         @type str
         @return flag indicating that the path starts with the project path
         @rtype bool
         """
-        return bool(self.ppath) and (
-            path == self.ppath
-            or FileSystemUtilities.normcasepath(
-                FileSystemUtilities.toNativeSeparators(path)
-            ).startswith(
-                FileSystemUtilities.normcasepath(
-                    FileSystemUtilities.toNativeSeparators(self.ppath + "/")
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            return checkpath == self.ppath or checkpath.startswith(
+                self.ppath + self.__remotefsInterface.separator()
+            )
+        else:
+            return bool(self.ppath) and (
+                checkpath == self.ppath
+                or FileSystemUtilities.normcasepath(
+                    FileSystemUtilities.toNativeSeparators(checkpath)
+                ).startswith(
+                    FileSystemUtilities.normcasepath(
+                        FileSystemUtilities.toNativeSeparators(self.ppath + "/")
+                    )
                 )
             )
-        )
 
     def getProjectFile(self):
         """
@@ -3789,8 +4389,7 @@
         @rtype str
         """
         if self.pfile:
-            name = os.path.splitext(self.pfile)[0]
-            return os.path.basename(name)
+            return self.name
         else:
             return ""
 
@@ -3801,7 +4400,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):
         """
@@ -3811,8 +4413,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):
         """
@@ -3823,37 +4427,52 @@
         """
         return self.__pdata["HASH"]
 
-    def getRelativePath(self, path):
+    def getRelativePath(self, fullpath):
         """
         Public method to convert a file path to a project relative
         file path.
 
-        @param path file or directory name to convert
+        @param fullpath file or directory name to convert
         @type str
         @return project relative path or unchanged path, if path doesn't
             belong to the project
         @rtype str
         """
-        if path is None:
+        if fullpath is None:
             return ""
 
         try:
-            return str(pathlib.Path(path).relative_to(self.ppath))
+            if FileSystemUtilities.isRemoteFileName(self.ppath):
+                if self.__remotefsInterface.separator() == "\\":
+                    return str(
+                        pathlib.PureWindowsPath(fullpath).relative_to(self.ppath)
+                    )
+                else:
+                    return str(pathlib.PurePosixPath(fullpath).relative_to(self.ppath))
+            else:
+                return str(pathlib.PurePath(fullpath).relative_to(self.ppath))
         except ValueError:
-            return path
-
-    def getRelativeUniversalPath(self, path):
+            return fullpath
+
+    def getRelativeUniversalPath(self, fullpath):
         """
         Public method to convert a file path to a project relative
         file path with universal separators.
 
-        @param path file or directory name to convert
+        @param fullpath file or directory name to convert
         @type str
         @return project relative path or unchanged path, if path doesn't
             belong to the project
         @rtype str
         """
-        return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path))
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            return self.__remotefsInterface.fromNativeSeparators(
+                self.getRelativePath(fullpath)
+            )
+        else:
+            return FileSystemUtilities.fromNativeSeparators(
+                self.getRelativePath(fullpath)
+            )
 
     def getAbsolutePath(self, fn):
         """
@@ -3865,8 +4484,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):
@@ -3879,8 +4501,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):
@@ -3962,7 +4591,11 @@
         """
         venvName = (
             self.__venvConfiguration["name"]
-            if self.__pdata["EMBEDDED_VENV"] and bool(self.__venvConfiguration["name"])
+            if (
+                self.__pdata["EMBEDDED_VENV"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+                and bool(self.__venvConfiguration["name"])
+            )
             else self.getDebugProperty("VIRTUALENV")
         )
         if (
@@ -3981,7 +4614,9 @@
         @return path name of the embedded virtual environment
         @rtype str
         """
-        if self.__pdata["EMBEDDED_VENV"]:
+        if self.__pdata["EMBEDDED_VENV"] and not FileSystemUtilities.isRemoteFileName(
+            self.ppath
+        ):
             return self.__findEmbeddedEnvironment()
         else:
             return ""
@@ -3999,7 +4634,10 @@
         """
         interpreter = (
             self.__venvConfiguration["interpreter"]
-            if self.__pdata["EMBEDDED_VENV"]
+            if (
+                self.__pdata["EMBEDDED_VENV"]
+                and not FileSystemUtilities.isRemoteFileName(self.ppath)
+            )
             else ""
         )
         if not interpreter:
@@ -4070,7 +4708,11 @@
         @return flag indicating membership
         @rtype bool
         """
-        newfn = os.path.abspath(fn)
+        newfn = (
+            self.__remotefsInterface.abspath(fn)
+            if FileSystemUtilities.isRemoteFileName(self.ppath)
+            else os.path.abspath(fn)
+        )
         newfn = self.getRelativePath(newfn)
         return any(
             newfn in self.__pdata[category] for category in self.getFileCategories()
@@ -4103,7 +4745,11 @@
         @return flag indicating membership
         @rtype bool
         """
-        newfn = os.path.abspath(fn)
+        newfn = (
+            self.__remotefsInterface.abspath(fn)
+            if FileSystemUtilities.isRemoteFileName(fn)
+            else os.path.abspath(fn)
+        )
         newfn = self.getRelativePath(newfn)
         if newfn in self.__pdata[group] or (
             group == "OTHERS"
@@ -4111,7 +4757,10 @@
         ):
             return True
 
-        if OSUtilities.isWindowsPlatform():
+        if (
+            OSUtilities.isWindowsPlatform()
+            or self.__remotefsInterface.separator() == "\\"
+        ):
             # try the above case-insensitive
             newfn = newfn.lower()
             if any(entry.lower() == newfn for entry in self.__pdata[group]):
@@ -4189,6 +4838,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"),
@@ -4256,6 +4924,28 @@
         self.saveasAct.triggered.connect(self.saveProjectAs)
         self.actions.append(self.saveasAct)
 
+        self.saveasRemoteAct = EricAction(
+            self.tr("Save project as (Remote)"),
+            EricPixmapCache.getIcon("projectSaveAs-remote"),
+            self.tr("Save as (Remote)..."),
+            0,
+            0,
+            self,
+            "project_save_as_remote",
+        )
+        self.saveasRemoteAct.setStatusTip(
+            self.tr("Save the current project to a new remote file")
+        )
+        self.saveasRemoteAct.setWhatsThis(
+            self.tr(
+                """<b>Save as (Remote)</b>"""
+                """<p>This saves the current project to a new remote file.</p>"""
+            )
+        )
+        self.saveasRemoteAct.triggered.connect(self.__saveRemoteProjectAs)
+        self.actions.append(self.saveasRemoteAct)
+        self.saveasRemoteAct.setEnabled(False)  # server is not connected initially
+
         ###################################################################
         ## Project management actions
         ###################################################################
@@ -5248,6 +5938,7 @@
         """
         menu = QMenu(self.tr("&Project"), self.parent())
         self.recentMenu = QMenu(self.tr("Open &Recent Projects"), menu)
+        self.recentMenu.setIcon(EricPixmapCache.getIcon("projectOpenRecent"))
         self.sessionMenu = QMenu(self.tr("Session"), menu)
         self.debuggerMenu = QMenu(self.tr("Debugger"), menu)
         self.environmentMenu = QMenu(self.tr("Embedded Environment"), menu)
@@ -5363,6 +6054,7 @@
         menu.addSeparator()
         menu.addAction(self.saveAct)
         menu.addAction(self.saveasAct)
+        menu.addAction(self.saveasRemoteAct)
         menu.addSeparator()
         menu.addActions(self.actGrp2.actions())
         menu.addSeparator()
@@ -5379,7 +6071,7 @@
         # build the project tools menu
         toolsMenu.setTearOffEnabled(True)
         toolsMenu.addSeparator()
-        toolsMenu.addMenu(self.vcsMenu)
+        self.menuVcsAct = toolsMenu.addMenu(self.vcsMenu)
         toolsMenu.addSeparator()
         self.menuCheckAct = toolsMenu.addMenu(self.checksMenu)
         toolsMenu.addSeparator()
@@ -5435,6 +6127,7 @@
         tb.addSeparator()
         tb.addAction(self.saveAct)
         tb.addAction(self.saveasAct)
+        tb.addAction(self.saveasRemoteAct)
 
         toolbarManager.addToolBar(tb, tb.windowTitle())
         toolbarManager.addAction(self.addFilesAct, tb.windowTitle())
@@ -5452,7 +6145,10 @@
         Private method to set up the project menu.
         """
         self.menuRecentAct.setEnabled(len(self.recent) > 0)
-        self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"])
+        self.menuEnvironmentAct.setEnabled(
+            self.__pdata["EMBEDDED_VENV"]
+            and not FileSystemUtilities.isRemoteFileName(self.ppath)
+        )
 
         self.showMenu.emit("Main", self.__menus["Main"])
 
@@ -5462,7 +6158,9 @@
         with the central store.
         """
         for recent in self.recent[:]:
-            if FileSystemUtilities.samepath(self.pfile, recent):
+            if (
+                FileSystemUtilities.isRemoteFileName(recent) and recent == self.pfile
+            ) or FileSystemUtilities.samepath(self.pfile, recent):
                 self.recent.remove(recent)
         self.recent.insert(0, self.pfile)
         maxRecent = Preferences.getProject("RecentNumber")
@@ -5482,11 +6180,26 @@
             formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
             act = self.recentMenu.addAction(
                 formatStr.format(
-                    idx, FileSystemUtilities.compactPath(rp, self.ui.maxMenuFilePathLen)
+                    idx,
+                    (
+                        self.__remotefsInterface.compactPath(
+                            rp, self.ui.maxMenuFilePathLen
+                        )
+                        if FileSystemUtilities.isRemoteFileName(rp)
+                        else FileSystemUtilities.compactPath(
+                            rp, self.ui.maxMenuFilePathLen
+                        )
+                    ),
                 )
             )
             act.setData(rp)
-            act.setEnabled(pathlib.Path(rp).exists())
+            if FileSystemUtilities.isRemoteFileName(rp):
+                act.setEnabled(
+                    self.__remoteServer.isServerConnected
+                    and self.__remotefsInterface.exists(rp)
+                )
+            else:
+                act.setEnabled(pathlib.Path(rp).exists())
 
         self.recentMenu.addSeparator()
         self.recentMenu.addAction(self.tr("&Clear"), self.clearRecent)
@@ -5537,7 +6250,9 @@
             self.__findProjectFileDialog.sourceFile.connect(self.sourceFile)
             self.__findProjectFileDialog.designerFile.connect(self.designerFile)
             self.__findProjectFileDialog.linguistFile.connect(self.linguistFile)
-        self.__findProjectFileDialog.show()
+        self.__findProjectFileDialog.show(
+            FileSystemUtilities.isRemoteFileName(self.ppath)
+        )
         self.__findProjectFileDialog.raise_()
         self.__findProjectFileDialog.activateWindow()
 
@@ -5563,6 +6278,8 @@
         recursiveSearch = Preferences.getProject("SearchNewFilesRecursively")
         newFiles = []
 
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+
         ignore_patterns = [
             pattern
             for pattern, filetype in self.__pdata["FILETYPES"].items()
@@ -5577,9 +6294,17 @@
             ):
                 continue
 
-            curpath = os.path.join(self.ppath, directory)
+            curpath = (
+                self.__remotefsInterface.join(self.ppath, directory)
+                if isRemote
+                else os.path.join(self.ppath, directory)
+            )
             try:
-                newSources = os.listdir(curpath)
+                newSources = (
+                    [e["name"] for e in self.__remotefsInterface.listdir(curpath)[2]]
+                    if isRemote
+                    else os.listdir(curpath)
+                )
             except OSError:
                 newSources = []
             pattern = (
@@ -5596,11 +6321,24 @@
                 # set fn to project relative name
                 # then reset ns to fully qualified name for insertion,
                 # possibly.
-                fn = os.path.join(directory, ns) if directory else ns
-                ns = os.path.abspath(os.path.join(curpath, ns))
+                if isRemote:
+                    fn = (
+                        self.__remotefsInterface.join(directory, ns)
+                        if directory
+                        else ns
+                    )
+                    ns = self.__remotefsInterface.abspath(
+                        self.__remotefsInterface.join(curpath, ns)
+                    )
+
+                    isdir_ns = self.__remotefsInterface.isdir(ns)
+                else:
+                    fn = os.path.join(directory, ns) if directory else ns
+                    ns = os.path.abspath(os.path.join(curpath, ns))
+                    isdir_ns = os.path.isdir(ns)
 
                 # do not bother with dirs here...
-                if os.path.isdir(ns):
+                if isdir_ns:
                     if recursiveSearch:
                         d = self.getRelativePath(ns)
                         if d not in dirs:
@@ -5608,7 +6346,11 @@
                     continue
 
                 filetype = ""
-                bfn = os.path.basename(fn)
+                bfn = (
+                    self.__remotefsInterface.basename(fn)
+                    if isRemote
+                    else os.path.basename(fn)
+                )
 
                 # check against ignore patterns first (see issue 553)
                 if any(
@@ -5626,8 +6368,9 @@
                     filetype in self.getFileCategories()
                     and (
                         fn not in self.__pdata[filetype]
-                        and (
+                        or (
                             filetype == "OTHERS"
+                            and fn not in self.__pdata[filetype]
                             and os.path.dirname(fn) not in self.__pdata["OTHERS"]
                         )
                     )
@@ -5769,6 +6512,9 @@
         forProject = True
         override = False
 
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            return None
+
         if vcsSystem is None:
             if self.__pdata["VCS"] and self.__pdata["VCS"] != "None":
                 vcsSystem = self.__pdata["VCS"]
@@ -5945,11 +6691,19 @@
         """
         from eric7.DataViews.CodeMetricsDialog import CodeMetricsDialog
 
-        files = [
-            os.path.join(self.ppath, file)
-            for file in self.__pdata["SOURCES"]
-            if file.endswith(".py")
-        ]
+        files = (
+            [
+                self.__remotefsInterface.join(self.ppath, file)
+                for file in self.__pdata["SOURCES"]
+                if file.endswith(".py")
+            ]
+            if FileSystemUtilities.isRemoteFileName(self.ppath)
+            else [
+                os.path.join(self.ppath, file)
+                for file in self.__pdata["SOURCES"]
+                if file.endswith(".py")
+            ]
+        )
         self.codemetrics = CodeMetricsDialog()
         self.codemetrics.show()
         self.codemetrics.prepare(files)
@@ -5991,11 +6745,19 @@
         else:
             return
 
-        files = [
-            os.path.join(self.ppath, file)
-            for file in self.__pdata["SOURCES"]
-            if os.path.splitext(file)[1].startswith(".py")
-        ]
+        files = (
+            [
+                self.__remotefsInterface.join(self.ppath, file)
+                for file in self.__pdata["SOURCES"]
+                if self.__remotefsInterface.splitext(file)[1].startswith(".py")
+            ]
+            if FileSystemUtilities.isRemoteFileName(self.ppath)
+            else [
+                os.path.join(self.ppath, file)
+                for file in self.__pdata["SOURCES"]
+                if os.path.splitext(file)[1].startswith(".py")
+            ]
+        )
         self.codecoverage = PyCoverageDialog()
         self.codecoverage.show()
         self.codecoverage.start(fn, files)
@@ -6410,8 +7172,8 @@
                                     os.path.join(self.ppath, self.__pdata["MAINSCRIPT"])
                                 )
                                 if archiveVersion and (
-                                    Globals.versionToTuple(version)
-                                    < Globals.versionToTuple(archiveVersion)
+                                    EricUtilities.versionToTuple(version)
+                                    < EricUtilities.versionToTuple(archiveVersion)
                                 ):
                                     version = archiveVersion
                     except OSError as why:
@@ -6606,7 +7368,9 @@
         @return flag indicating enabled make support
         @rtype bool
         """
-        return self.__pdata["MAKEPARAMS"]["MakeEnabled"]
+        return self.__pdata["MAKEPARAMS"][
+            "MakeEnabled"
+        ] and not FileSystemUtilities.isRemoteFileName(self.ppath)
 
     @pyqtSlot()
     def __autoExecuteMake(self):
@@ -6614,7 +7378,9 @@
         Private slot to execute a project specific make run (auto-run)
         (execute or question).
         """
-        if Preferences.getProject("AutoExecuteMake"):
+        if Preferences.getProject(
+            "AutoExecuteMake"
+        ) and not FileSystemUtilities.isRemoteFileName(self.ppath):
             self.__executeMake(questionOnly=self.__pdata["MAKEPARAMS"]["MakeTestOnly"])
 
     @pyqtSlot()
@@ -6625,6 +7391,14 @@
         @param questionOnly flag indicating to ask make for changes only
         @type bool
         """
+        if FileSystemUtilities.isRemoteFileName(self.ppath):
+            EricMessageBox.critical(
+                self.ui,
+                self.tr("Execute Make"),
+                self.tr("'Make' is not supported for remote projects. Aborting..."),
+            )
+            return
+
         if (
             not self.__pdata["MAKEPARAMS"]["MakeEnabled"]
             or self.__makeProcess is not None
@@ -6850,6 +7624,10 @@
         """
         Private slot called before the 'Other Tools' menu is shown.
         """
+        self.createSBOMAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.ppath)
+        )
+
         self.showMenu.emit("OtherTools", self.othersMenu)
 
     @pyqtSlot()
@@ -6990,20 +7768,27 @@
         @rtype str
         """
         ppath = self.getProjectPath()
-        if ppath and os.path.exists(ppath):
-            with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator:
-                for dirEntry in ppathDirEntriesIterator:
-                    # potential venv directory; check for 'pyvenv.cfg'
-                    if dirEntry.is_dir() and os.path.exists(
-                        os.path.join(dirEntry.path, "pyvenv.cfg")
-                    ):
-                        return dirEntry.path
-
-            # check for some common names in case 'pyvenv.cfg' is missing
+        if FileSystemUtilities.isRemoteFileName(ppath):
+            # check for some common names
             for venvPathName in (".venv", "venv", ".env", "env"):
-                venvPath = os.path.join(self.getProjectPath(), venvPathName)
-                if os.path.isdir(venvPath):
+                venvPath = self.__remotefsInterface.join(ppath, venvPathName)
+                if self.__remotefsInterface.isdir(venvPath):
                     return venvPath
+        else:
+            if ppath and os.path.exists(ppath):
+                with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator:
+                    for dirEntry in ppathDirEntriesIterator:
+                        # potential venv directory; check for 'pyvenv.cfg'
+                        if dirEntry.is_dir() and os.path.exists(
+                            os.path.join(dirEntry.path, "pyvenv.cfg")
+                        ):
+                            return dirEntry.path
+
+                # check for some common names in case 'pyvenv.cfg' is missing
+                for venvPathName in (".venv", "venv", ".env", "env"):
+                    venvPath = os.path.join(ppath, venvPathName)
+                    if os.path.isdir(venvPath):
+                        return venvPath
 
         return ""
 
@@ -7209,6 +7994,91 @@
                 if dirEntry.is_dir():
                     self.__clearByteCodeCaches(dirEntry.path)
 
+    #############################################################################
+    ## 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)
+        self.saveasRemoteAct.setEnabled(
+            connected
+            and self.opened
+            and FileSystemUtilities.isRemoteFileName(self.pfile)
+        )
+        if not connected and FileSystemUtilities.isRemoteFileName(self.ppath):
+            self.closeProject(noSave=True)
+
+    @pyqtSlot()
+    def __openRemoteProject(self):
+        """
+        Private slot to open a project of an 'eric-ide' server.
+        """
+        fn = EricServerFileDialog.getOpenFileName(
+            self.parent(),
+            self.tr("Open Remote Project"),
+            "",
+            self.tr("Project Files (*.epj)"),
+        )
+        if fn:
+            self.openProject(fn=fn)
+
+    @pyqtSlot()
+    def __saveRemoteProjectAs(self):
+        """
+        Private slot to save the current remote project to different remote file.
+        """
+        defaultFilter = self.tr("Project Files (*.epj)")
+        defaultPath = self.ppath if self.ppath else ""
+        fn, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+            self.parent(),
+            self.tr("Save Remote Project"),
+            defaultPath,
+            self.tr("Project Files (*.epj)"),
+            defaultFilter,
+        )
+
+        if fn:
+            fname, ext = self.__remotefsInterface.splitext(fn)
+            if not ext:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fn = f"{fname}{ex}"
+            if self.__remotefsInterface.exists(fn):
+                res = EricMessageBox.yesNo(
+                    self.ui,
+                    self.tr("Save Remote Project"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> already exists."""
+                        """ Overwrite it?</p>"""
+                    ).format(fn),
+                    icon=EricMessageBox.Warning,
+                )
+                if not res:
+                    return
+
+            ok = self.__writeProject(fn)
+
+            if ok:
+                # create management directory if not present
+                self.createProjectManagementDir()
+
+                # now save the tasks
+                self.writeTasks()
+
+            self.sessActGrp.setEnabled(ok)
+            self.menuSessionAct.setEnabled(ok)
+            self.projectClosedHooks.emit()
+            self.projectClosed.emit(False)
+            self.projectOpenedHooks.emit()
+            self.projectOpened.emit()
+
 
 #
 # eflag: noqa = M601

eric ide

mercurial