Merged with branch 'eric7' in order to track these changes. server

Fri, 07 Jun 2024 13:58:16 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 07 Jun 2024 13:58:16 +0200
branch
server
changeset 10752
3cf5ee0c3e9f
parent 10748
d14a35b8dc2c (diff)
parent 10751
d4dbb6b75bdc (current diff)
child 10759
aeb98b3fa008

Merged with branch 'eric7' in order to track these changes.

--- a/eric7.epj	Fri Jun 07 13:51:43 2024 +0200
+++ b/eric7.epj	Fri Jun 07 13:58:16 2024 +0200
@@ -630,6 +630,7 @@
       "src/eric7/Preferences/ConfigurationPages/EditorSyntaxPage.ui",
       "src/eric7/Preferences/ConfigurationPages/EditorTypingPage.ui",
       "src/eric7/Preferences/ConfigurationPages/EmailPage.ui",
+      "src/eric7/Preferences/ConfigurationPages/EricServerPage.ui",
       "src/eric7/Preferences/ConfigurationPages/GraphicsPage.ui",
       "src/eric7/Preferences/ConfigurationPages/HelpDocumentationPage.ui",
       "src/eric7/Preferences/ConfigurationPages/HelpViewersPage.ui",
@@ -709,6 +710,9 @@
       "src/eric7/QtHelpInterface/QtHelpDocumentationConfigurationDialog.ui",
       "src/eric7/QtHelpInterface/QtHelpDocumentationSelectionDialog.ui",
       "src/eric7/QtHelpInterface/QtHelpDocumentationSettingsWidget.ui",
+      "src/eric7/RemoteServerInterface/EricServerConnectionDialog.ui",
+      "src/eric7/RemoteServerInterface/EricServerFileDialog.ui",
+      "src/eric7/RemoteServerInterface/EricServerProfilesDialog.ui",
       "src/eric7/Snapshot/SnapWidget.ui",
       "src/eric7/SqlBrowser/SqlBrowserWidget.ui",
       "src/eric7/SqlBrowser/SqlConnectionDialog.ui",
@@ -1190,6 +1194,7 @@
       "src/eric7/EricGraphics/EricGraphicsView.py",
       "src/eric7/EricGraphics/__init__.py",
       "src/eric7/EricGui/EricAction.py",
+      "src/eric7/EricGui/EricFileIconProvider.py",
       "src/eric7/EricGui/EricGenericDiffHighlighter.py",
       "src/eric7/EricGui/EricOverrideCursor.py",
       "src/eric7/EricGui/EricPixmapCache.py",
@@ -1920,6 +1925,7 @@
       "src/eric7/Preferences/ConfigurationPages/EditorSyntaxPage.py",
       "src/eric7/Preferences/ConfigurationPages/EditorTypingPage.py",
       "src/eric7/Preferences/ConfigurationPages/EmailPage.py",
+      "src/eric7/Preferences/ConfigurationPages/EricServerPage.py",
       "src/eric7/Preferences/ConfigurationPages/GraphicsPage.py",
       "src/eric7/Preferences/ConfigurationPages/HelpDocumentationPage.py",
       "src/eric7/Preferences/ConfigurationPages/HelpViewersPage.py",
@@ -2110,6 +2116,20 @@
       "src/eric7/QtHelpInterface/QtHelpDocumentationSettingsWidget.py",
       "src/eric7/QtHelpInterface/QtHelpSchemeHandler.py",
       "src/eric7/QtHelpInterface/__init__.py",
+      "src/eric7/RemoteServer/EricRequestCategory.py",
+      "src/eric7/RemoteServer/EricServer.py",
+      "src/eric7/RemoteServer/EricServerCoverageRequestHandler.py",
+      "src/eric7/RemoteServer/EricServerDebuggerRequestHandler.py",
+      "src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py",
+      "src/eric7/RemoteServer/__init__.py",
+      "src/eric7/RemoteServerInterface/EricServerConnectionDialog.py",
+      "src/eric7/RemoteServerInterface/EricServerCoverageInterface.py",
+      "src/eric7/RemoteServerInterface/EricServerDebuggerInterface.py",
+      "src/eric7/RemoteServerInterface/EricServerFileDialog.py",
+      "src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py",
+      "src/eric7/RemoteServerInterface/EricServerInterface.py",
+      "src/eric7/RemoteServerInterface/EricServerProfilesDialog.py",
+      "src/eric7/RemoteServerInterface/__init__.py",
       "src/eric7/Sessions/SessionFile.py",
       "src/eric7/Sessions/__init__.py",
       "src/eric7/Snapshot/SnapWidget.py",
@@ -2495,6 +2515,7 @@
       "src/eric7/eric7_qregularexpression.pyw",
       "src/eric7/eric7_re.py",
       "src/eric7/eric7_re.pyw",
+      "src/eric7/eric7_server.py",
       "src/eric7/eric7_shell.py",
       "src/eric7/eric7_shell.pyw",
       "src/eric7/eric7_snap.py",
--- a/pyproject.toml	Fri Jun 07 13:51:43 2024 +0200
+++ b/pyproject.toml	Fri Jun 07 13:58:16 2024 +0200
@@ -102,6 +102,7 @@
 eric7_api = "eric7.eric7_api:main"
 eric7_doc = "eric7.eric7_doc:main"
 eric7_post_install = "eric7.eric7_post_install:main"
+eric7_server =  "eric7.eric7_server:main"
 
 [project.gui-scripts]
 eric7_browser = "eric7.eric7_browser:main"
--- a/src/eric7/DataViews/CodeMetrics.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/DataViews/CodeMetrics.py	Fri Jun 07 13:58:16 2024 +0200
@@ -24,6 +24,8 @@
 from dataclasses import dataclass
 
 from eric7 import Utilities
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 KEYWORD = token.NT_OFFSET + 1
 COMMENT = tokenize.COMMENT
@@ -225,7 +227,13 @@
     @rtype SourceStat
     """
     try:
-        text = Utilities.readEncodedFile(filename)[0]
+        if FileSystemUtilities.isRemoteFileName(filename):
+            remotefsInterface = (
+                ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            )
+            text = remotefsInterface.readEncodedFile(filename)[0]
+        else:
+            text = Utilities.readEncodedFile(filename)[0]
     except (OSError, UnicodeError):
         return SourceStat()
 
--- a/src/eric7/DataViews/CodeMetricsDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/DataViews/CodeMetricsDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -56,6 +56,10 @@
 
         self.cancelled = False
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         self.__menu = QMenu(self)
         self.__menu.addAction(self.tr("Collapse All"), self.__resultCollapse)
         self.__menu.addAction(self.tr("Expand All"), self.__resultExpand)
@@ -153,15 +157,23 @@
         loc = QLocale()
         if isinstance(fn, list):
             files = fn
-        elif os.path.isdir(fn):
+        elif FileSystemUtilities.isRemoteFileName(
+            fn
+        ) and self.__remotefsInterface.isdir(fn):
+            files = self.__remotefsInterface.direntries(fn, True, "*.py", False)
+        elif FileSystemUtilities.isPlainFileName(fn) and os.path.isdir(fn):
             files = FileSystemUtilities.direntries(fn, True, "*.py", False)
         else:
             files = [fn]
         files.sort()
         # check for missing files
         for f in files[:]:
-            if not os.path.exists(f):
-                files.remove(f)
+            if FileSystemUtilities.isRemoteFileName(f):
+                if not self.__remotefsInterface.exists(f):
+                    files.remove(f)
+            else:
+                if not os.path.exists(f):
+                    files.remove(f)
 
         self.checkProgress.setMaximum(len(files))
         QApplication.processEvents()
--- a/src/eric7/DataViews/PyCoverageDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/DataViews/PyCoverageDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -25,6 +25,9 @@
 
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.RemoteServerInterface.EricServerCoverageInterface import (
+    EricServerCoverageError,
+)
 from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_PyCoverageDialog import Ui_PyCoverageDialog
@@ -57,7 +60,6 @@
         self.resultList.headerItem().setText(self.resultList.columnCount(), "")
 
         self.cancelled = False
-        self.path = "."
         self.reload = False
 
         self.excludeList = ["# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]"]
@@ -78,6 +80,14 @@
         self.resultList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
         self.resultList.customContextMenuRequested.connect(self.__showContextMenu)
 
+        # eric-ide server interfaces
+        self.__serverCoverageInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("Coverage")
+        )
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def __format_lines(self, lines):
         """
         Private method to format a list of integers into string by coalescing
@@ -143,7 +153,7 @@
         @param coverage percent of coverage
         @type int
         @param excluded list of excluded lines
-        @type str
+        @type list of int
         @param missing list of lines without coverage
         @type str
         """
@@ -154,7 +164,7 @@
                 str(statements),
                 str(executed),
                 "{0:.0f}%".format(coverage),
-                excluded,
+                excluded and self.__format_lines(excluded) or "",
                 missing,
             ],
         )
@@ -194,18 +204,16 @@
 
         if isinstance(fn, list):
             files = fn
-            self.path = os.path.dirname(cfn)
-        elif os.path.isdir(fn):
+        elif FileSystemUtilities.isRemoteFileName(
+            self.cfn
+        ) and self.__remotefsInterface.isdir(fn):
+            files = self.__remotefsInterface.direntries(fn, True, "*.py", False)
+        elif FileSystemUtilities.isPlainFileName(self.cfn) and os.path.isdir(fn):
             files = FileSystemUtilities.direntries(fn, True, "*.py", False)
-            self.path = fn
         else:
             files = [fn]
-            self.path = os.path.dirname(cfn)
         files.sort()
 
-        cover = Coverage(data_file=self.cfn)
-        cover.load()
-
         # set the exclude pattern
         self.excludeCombo.clear()
         self.excludeCombo.addItems(self.excludeList)
@@ -217,7 +225,23 @@
         total_executed = 0
         total_exceptions = 0
 
-        cover.exclude(self.excludeList[0])
+        if FileSystemUtilities.isRemoteFileName(self.cfn):
+            ok, error = self.__serverCoverageInterface.loadCoverageData(
+                self.cfn, self.excludeList[0]
+            )
+            if not ok:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Load Coverage Data"),
+                    self.tr(
+                        "<p>The coverage data could not be loaded from file"
+                        " <b>{0}</b>.</p><p>Reason: {1}</p>"
+                    ).format(self.cfn, error),
+                )
+        else:
+            cover = Coverage(data_file=self.cfn)
+            cover.load()
+            cover.exclude(self.excludeList[0])
 
         try:
             # disable updates of the list for speed
@@ -231,18 +255,28 @@
                     return
 
                 try:
-                    statements, excluded, missing, readable = cover.analysis2(file)[1:]
-                    readableEx = excluded and self.__format_lines(excluded) or ""
+                    if FileSystemUtilities.isRemoteFileName(self.cfn):
+                        (
+                            file,
+                            statements,
+                            excluded,
+                            missing,
+                            readable,
+                        ) = self.__serverCoverageInterface.analyzeFile(file)
+                    else:
+                        statements, excluded, missing, readable = cover.analysis2(file)[
+                            1:
+                        ]
                     n = len(statements)
                     m = n - len(missing)
                     pc = 100.0 * m / n if n > 0 else 100.0
                     self.__createResultItem(
-                        file, str(n), str(m), pc, readableEx, readable
+                        file, str(n), str(m), pc, excluded, readable
                     )
 
                     total_statements += n
                     total_executed += m
-                except CoverageException:
+                except (CoverageException, EricServerCoverageError):
                     total_exceptions += 1
 
                 self.checkProgress.setValue(progress)
--- a/src/eric7/DataViews/PyProfileDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/DataViews/PyProfileDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -22,7 +22,8 @@
 )
 
 from eric7.EricWidgets import EricMessageBox
-from eric7.SystemUtilities import PythonUtilities
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities, PythonUtilities
 
 from .Ui_PyProfileDialog import Ui_PyProfileDialog
 
@@ -104,6 +105,11 @@
         self.summaryList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
         self.summaryList.customContextMenuRequested.connect(self.__showContextMenu)
 
+        # eric-ide server interface
+        self.__serverFsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def __createResultItem(
         self,
         calls,
@@ -265,7 +271,10 @@
         self.basename = os.path.splitext(pfn)[0]
 
         fname = "{0}.profile".format(self.basename)
-        if not os.path.exists(fname):
+        if (
+            FileSystemUtilities.isRemoteFileName(fname)
+            and not self.__serverFsInterface.exists(fname)
+        ) or (FileSystemUtilities.isPlainFileName(fname) and not os.path.exists(fname)):
             EricMessageBox.warning(
                 self,
                 self.tr("Profile Results"),
@@ -277,8 +286,12 @@
             self.close()
             return
         try:
-            with open(fname, "rb") as f:
-                self.stats = pickle.load(f)  # secok
+            if FileSystemUtilities.isRemoteFileName(fname):
+                data = self.__serverFsInterface.readFile(fname)
+                self.stats = pickle.loads(data)
+            else:
+                with open(fname, "rb") as f:
+                    self.stats = pickle.load(f)  # secok
         except (EOFError, OSError, pickle.PickleError):
             EricMessageBox.critical(
                 self,
@@ -291,7 +304,10 @@
             self.close()
             return
 
-        self.file = fn
+        if FileSystemUtilities.isRemoteFileName(fname) and fn is not None:
+            self.file = FileSystemUtilities.plainFileName(fn)
+        else:
+            self.file = fn
         self.__populateLists()
         self.__finish()
 
--- a/src/eric7/DebugClients/Python/DebugClientBase.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/DebugClients/Python/DebugClientBase.py	Fri Jun 07 13:58:16 2024 +0200
@@ -1106,6 +1106,7 @@
         self.eventExit = False
         self.pollingDisabled = disablePolling
         selectErrors = 0
+        self.rawLine = ""
 
         while not self.eventExit:
             wrdy = []
--- a/src/eric7/Debugger/DebugServer.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Debugger/DebugServer.py	Fri Jun 07 13:58:16 2024 +0200
@@ -18,6 +18,7 @@
 from eric7 import Preferences
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 from . import DebugClientCapabilities
 from .BreakPointModel import BreakPointModel
@@ -241,6 +242,8 @@
         self.__autoClearShell = False
         self.__forProject = False
 
+        self.__ericServerDebugging = False
+
         self.clientClearBreak.connect(self.__clientClearBreakPoint)
         self.clientClearWatch.connect(self.__clientClearWatchPoint)
         self.newConnection.connect(self.__newConnection)
@@ -492,28 +495,32 @@
         forProject=False,
         runInConsole=False,
         venvName="",
-        workingDir=None,
+        workingDir="",
         configOverride=None,
+        startRemote=None,
     ):
         """
         Public method to start a debug client.
 
-        @param unplanned flag indicating that the client has died
-        @type bool
-        @param clType type of client to be started
-        @type str
-        @param forProject flag indicating a project related action
-        @type bool
+        @param unplanned flag indicating that the client has died (defaults to True)
+        @type bool (optional)
+        @param clType type of client to be started (defaults to None)
+        @type str (optional)
+        @param forProject flag indicating a project related action (defaults to False)
+        @type bool (optional)
         @param runInConsole flag indicating to start the debugger in a
-            console window
-        @type bool
-        @param venvName name of the virtual environment to be used
-        @type str
-        @param workingDir directory to start the debugger client in
-        @type str
-        @param configOverride dictionary containing the global config override
-            data
-        @type dict
+            console window (defaults to False)
+        @type bool (optional)
+        @param venvName name of the virtual environment to be used (defaults to "")
+        @type str (optional)
+        @param workingDir directory to start the debugger client in (defaults to "")
+        @type str (optional)
+        @param configOverride dictionary containing the global config override data
+            (defaults to None)
+        @type dict (optional)
+        @param startRemote flag indicating to start the client via an eric-ide server
+            (defaults to None)
+        @type bool (optional)
         """
         self.running = False
 
@@ -560,6 +567,7 @@
                         self.__originalPathString,
                         workingDir=workingDir,
                         configOverride=configOverride,
+                        startRemote=startRemote,
                     )
                 else:
                     if not venvName and project.getProjectData(dataKey="EMBEDDED_VENV"):
@@ -576,6 +584,7 @@
                         self.__originalPathString,
                         workingDir=workingDir,
                         configOverride=configOverride,
+                        startRemote=startRemote,
                     )
             else:
                 (
@@ -589,6 +598,7 @@
                     self.__originalPathString,
                     workingDir=workingDir,
                     configOverride=configOverride,
+                    startRemote=startRemote,
                 )
 
             if self.clientProcess:
@@ -606,6 +616,14 @@
                 elif self.__autoClearShell:
                     self.__autoClearShell = False
                     self.remoteBanner()
+            elif startRemote:
+                self.__ericServerDebugging = True
+                if self.lastClientType != self.clientType:
+                    self.lastClientType = self.clientType
+                    self.remoteBanner()
+                elif self.__autoClearShell:
+                    self.__autoClearShell = False
+                    self.remoteBanner()
             else:
                 if clType and self.lastClientType:
                     self.__setClientType(self.lastClientType)
@@ -729,11 +747,20 @@
                 if (fn, lineno) in self.__reportedBreakpointIssues:
                     self.__reportedBreakpointIssues.remove((fn, lineno))
 
-                self.remoteBreakpoint(debuggerId, fn, lineno, True, cond, temp)
-                if not enabled:
-                    self.__remoteBreakpointEnable(debuggerId, fn, lineno, False)
-                if ignorecount:
-                    self.__remoteBreakpointIgnore(debuggerId, fn, lineno, ignorecount)
+                if (
+                    self.__ericServerDebugging
+                    and FileSystemUtilities.isRemoteFileName(fn)
+                ) or (
+                    not self.__ericServerDebugging
+                    and FileSystemUtilities.isPlainFileName(fn)
+                ):
+                    self.remoteBreakpoint(debuggerId, fn, lineno, True, cond, temp)
+                    if not enabled:
+                        self.__remoteBreakpointEnable(debuggerId, fn, lineno, False)
+                    if ignorecount:
+                        self.__remoteBreakpointIgnore(
+                            debuggerId, fn, lineno, ignorecount
+                        )
 
     def __makeWatchCondition(self, cond, special):
         """
@@ -904,12 +931,13 @@
 
     def isClientProcessUp(self):
         """
-        Public method to check, if the debug client process is up.
+        Public method to check, if the debug client process is up or we are
+        doing debugging via the eric-ide server.
 
         @return flag indicating a running debug client process
         @rtype bool
         """
-        return self.clientProcess is not None
+        return self.clientProcess is not None or self.__ericServerDebugging
 
     def __newConnection(self):
         """
@@ -1091,6 +1119,7 @@
             runInConsole=runInConsole,
             venvName=venvName,
             configOverride=configOverride,
+            startRemote=FileSystemUtilities.isRemoteFileName(fn),
         )
 
         self.setCallTraceEnabled("", enableCallTrace)
@@ -1181,6 +1210,7 @@
             runInConsole=runInConsole,
             venvName=venvName,
             configOverride=configOverride,
+            startRemote=FileSystemUtilities.isRemoteFileName(fn),
         )
 
         self.remoteEnvironment(env)
@@ -1263,6 +1293,7 @@
             runInConsole=runInConsole,
             venvName=venvName,
             configOverride=configOverride,
+            startRemote=FileSystemUtilities.isRemoteFileName(fn),
         )
 
         self.remoteEnvironment(env)
@@ -1345,6 +1376,7 @@
             runInConsole=runInConsole,
             venvName=venvName,
             configOverride=configOverride,
+            startRemote=FileSystemUtilities.isRemoteFileName(fn),
         )
 
         self.remoteEnvironment(env)
@@ -1913,6 +1945,7 @@
             self.signalClientOutput(self.tr("\nNot connected\n"))
             self.signalClientStatement(False, "")
         self.running = False
+        self.__ericServerDebugging = False
 
     def signalClientClearBreak(self, filename, lineno, debuggerId):
         """
@@ -2224,3 +2257,14 @@
         except KeyError:
             # The project object is not present
             return ""
+
+    def getEricServerEnvironmentString(self):
+        """
+        Public method to get the string for an eric-ide server environment.
+
+        @return string for the eric-ide server environment
+        @rtype str
+        """
+        # TODO: make this more elaborate once server environments definitions
+        #       are supported
+        return "eric-ide Server"
--- a/src/eric7/Debugger/DebugUI.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Debugger/DebugUI.py	Fri Jun 07 13:58:16 2024 +0200
@@ -2045,6 +2045,13 @@
                     self.clientType = editor.determineFileType()
                 self.lastStartAction = 5
 
+            if (
+                FileSystemUtilities.isRemoteFileName(fn)
+                and not self.ui.isEricServerConnected()
+            ):
+                self.__showNotConnectedWarning(title=cap)
+                return
+
             # save the filename for use by the restart method
             self.lastDebuggedFile = fn
             self.restartAct.setEnabled(True)
@@ -2240,6 +2247,13 @@
                     self.clientType = editor.determineFileType()
                 self.lastStartAction = 7
 
+            if (
+                FileSystemUtilities.isRemoteFileName(fn)
+                and not self.ui.isEricServerConnected()
+            ):
+                self.__showNotConnectedWarning(title=cap)
+                return
+
             # save the filename for use by the restart method
             self.lastDebuggedFile = fn
             self.restartAct.setEnabled(True)
@@ -2430,6 +2444,13 @@
                     self.clientType = editor.determineFileType()
                 self.lastStartAction = 3
 
+            if (
+                FileSystemUtilities.isRemoteFileName(fn)
+                and not self.ui.isEricServerConnected()
+            ):
+                self.__showNotConnectedWarning(title=cap)
+                return
+
             # save the filename for use by the restart method
             self.lastDebuggedFile = fn
             self.restartAct.setEnabled(True)
@@ -2512,8 +2533,8 @@
         @param debugProject flag indicating debugging the current project
             (True) or script (False)
         @type bool
-        @param script name of a script (optional)
-        @type str
+        @param script name of a script (defaults to "")
+        @type str (optional)
         """
         from .StartDialog import StartDialog, StartDialogMode
 
@@ -2541,6 +2562,14 @@
             scriptName = self.lastDebuggedFile
         else:
             scriptName = ""
+        if (
+            scriptName
+            and FileSystemUtilities.isRemoteFileName(scriptName)
+            and not self.ui.isEricServerConnected()
+        ):
+            self.__showNotConnectedWarning(title=cap, name=scriptName)
+            return
+
         dlg = StartDialog(
             cap,
             self.lastUsedVenvName,
@@ -2633,6 +2662,13 @@
                     self.clientType = editor.determineFileType()
                 self.lastStartAction = 1
 
+            if (
+                FileSystemUtilities.isRemoteFileName(fn)
+                and not self.ui.isEricServerConnected()
+            ):
+                self.__showNotConnectedWarning(title=cap, name=fn)
+                return
+
             # save the filename for use by the restart method
             self.lastDebuggedFile = fn
             self.restartAct.setEnabled(True)
@@ -2825,6 +2861,13 @@
         self.viewmanager.unhighlight()
 
         if not doNotStart:
+            if (
+                FileSystemUtilities.isRemoteFileName(fn)
+                and not self.ui.isEricServerConnected()
+            ):
+                self.__showNotConnectedWarning(title=self.tr("Restart"))
+                return
+
             if forProject and self.project.getProjectType() in ["E7Plugin"]:
                 argv = '--plugin="{0}" {1}'.format(fn, argv)
                 fn = ""  # script name of the eric IDE is set in debug client
@@ -3166,3 +3209,21 @@
         @rtype str
         """
         return self.debugServer.getProjectEnvironmentString()
+
+    def __showNotConnectedWarning(self, title, name=""):
+        """
+        Private method to show a warning about a not connected eric-ide server.
+
+        @param title title for the dialog
+        @type str
+        @param name name of the file (defaults to "")
+        @type str (optional)
+        """
+        EricMessageBox.warning(
+            None,
+            title,
+            self.tr(
+                "<p>The selected file <b>{0}</b> is located on an eric-ide server but"
+                " no such server is connected. Aborting...</p>"
+            ).format(name),
+        )
--- a/src/eric7/Debugger/DebuggerInterfaceNone.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Debugger/DebuggerInterfaceNone.py	Fri Jun 07 13:58:16 2024 +0200
@@ -46,6 +46,7 @@
         originalPathString,
         workingDir=None,
         configOverride=None,
+        startRemote=False,
     ):
         """
         Public method to start a remote Python interpreter.
@@ -64,6 +65,9 @@
         @param configOverride dictionary containing the global config override
             data
         @type dict
+        @param startRemote flag indicating to start the client via an eric-ide server
+            (defaults to False)
+        @type bool (optional)
         @return client process object, a flag to indicate a network connection
             and the name of the interpreter in case of a local execution
         @rtype tuple of (QProcess, bool, str)
@@ -78,6 +82,7 @@
         originalPathString,
         workingDir=None,
         configOverride=None,
+        startRemote=False,
     ):
         """
         Public method to start a remote Python interpreter for a project.
@@ -96,6 +101,9 @@
         @param configOverride dictionary containing the global config override
             data
         @type dict
+        @param startRemote flag indicating to start the client via an eric-ide server
+            (defaults to False)
+        @type bool (optional)
         @return client process object, a flag to indicate a network connection
             and the name of the interpreter in case of a local execution
         @rtype tuple of (QProcess, bool, str)
--- a/src/eric7/Debugger/DebuggerInterfacePython.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Debugger/DebuggerInterfacePython.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,7 +16,7 @@
 import time
 import zlib
 
-from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer
+from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer, pyqtSlot
 
 from eric7 import Preferences, Utilities
 from eric7.EricWidgets import EricMessageBox
@@ -51,6 +51,23 @@
         self.__autoContinued = []
         self.__isStepCommand = False
 
+        self.__ericServerDebugging = False  # are we debugging via the eric-ide server?
+        try:
+            self.__ericServerDebuggerInterface = (
+                ericApp().getObject("EricServer").getServiceInterface("Debugger")
+            )
+            self.__ericServerDebuggerInterface.debugClientResponse.connect(
+                lambda jsonStr: self.handleJsonCommand(jsonStr, None)
+            )
+            self.__ericServerDebuggerInterface.debugClientDisconnected.connect(
+                self.__handleServerDebugClientDisconnected
+            )
+            self.__ericServerDebuggerInterface.lastClientExited.connect(
+                self.__handleServerLastClientExited
+            )
+        except KeyError:
+            self.__ericServerDebuggerInterface = None
+
         self.debugServer = debugServer
         self.passive = passive
         self.process = None
@@ -89,8 +106,9 @@
         @param fn filename to be translated
         @type str
         @param remote2local flag indicating the direction of translation
-            (False = local to remote, True = remote to local [default]) (unused)
-        @type bool
+            (False = local to remote, True = remote to local) (defaults to True)
+            (unused)
+        @type bool (optional)
         @return translated filename
         @rtype str
         """
@@ -103,8 +121,8 @@
         @param fn filename to be translated
         @type str
         @param remote2local flag indicating the direction of translation
-            (False = local to remote, True = remote to local [default])
-        @type bool
+            (False = local to remote, True = remote to local) (defaults to True)
+        @type bool (optional)
         @return translated filename
         @rtype str
         """
@@ -119,7 +137,24 @@
 
         return path
 
-    def __startProcess(self, program, arguments, environment=None, workingDir=None):
+    def __ericServerTranslation(self, fn, remote2local=True):
+        """
+        Private method to perform the eric-ide server path translation.
+
+        @param fn filename to be translated
+        @type str
+        @param remote2local flag indicating the direction of translation
+            (False = local to remote, True = remote to local) (defaults to True)
+        @type bool (optional)
+        @return translated filename
+        @rtype str
+        """
+        if remote2local:
+            return FileSystemUtilities.remoteFileName(fn)
+        else:
+            return FileSystemUtilities.plainFileName(fn)
+
+    def __startProcess(self, program, arguments, environment=None, workingDir=""):
         """
         Private method to start the debugger client process.
 
@@ -155,8 +190,9 @@
         runInConsole,
         venvName,
         originalPathString,
-        workingDir=None,
+        workingDir="",
         configOverride=None,
+        startRemote=None,
     ):
         """
         Public method to start a remote Python interpreter.
@@ -170,41 +206,61 @@
         @type str
         @param originalPathString original PATH environment variable
         @type str
-        @param workingDir directory to start the debugger client in
-        @type str
-        @param configOverride dictionary containing the global config override
-            data
-        @type dict
+        @param workingDir directory to start the debugger client in (defaults to "")
+        @type str (optional)
+        @param configOverride dictionary containing the global config override data
+            (defaults to None)
+        @type dict (optional)
+        @param startRemote flag indicating to start the client via an eric-ide server
+            (defaults to None)
+        @type bool (optional)
         @return client process object, a flag to indicate a network connection
             and the name of the interpreter in case of a local execution
         @rtype tuple of (QProcess, bool, str)
         """
         global origPathEnv
 
-        if not venvName:
-            venvName = Preferences.getDebugger("Python3VirtualEnv")
-        if venvName == self.debugServer.getProjectEnvironmentString():
-            project = ericApp().getObject("Project")
-            venvName = project.getProjectVenv()
-            execPath = project.getProjectExecPath()
-            interpreter = project.getProjectInterpreter()
+        if (
+            startRemote is True
+            or (
+                startRemote is None
+                and (
+                    venvName == self.debugServer.getEricServerEnvironmentString()
+                    or self.__ericServerDebugging
+                )
+            )
+        ) and ericApp().getObject("EricServer").isServerConnected():
+            # TODO change this once server environment definitions are supported
+            startRemote = True
+            venvName = self.debugServer.getEricServerEnvironmentString()
+            interpreter = ""  # use the interpreter of the server
         else:
-            venvManager = ericApp().getObject("VirtualEnvManager")
-            interpreter = venvManager.getVirtualenvInterpreter(venvName)
-            execPath = venvManager.getVirtualenvExecPath(venvName)
-        if interpreter == "":
-            # use the interpreter used to run eric for identical variants
-            interpreter = PythonUtilities.getPythonExecutable()
-        if interpreter == "":
-            EricMessageBox.critical(
-                None,
-                self.tr("Start Debugger"),
-                self.tr("""<p>No suitable Python3 environment configured.</p>"""),
-            )
-            return None, False, ""
+            if not venvName:
+                venvName = Preferences.getDebugger("Python3VirtualEnv")
+            if venvName == self.debugServer.getProjectEnvironmentString():
+                project = ericApp().getObject("Project")
+                venvName = project.getProjectVenv()
+                execPath = project.getProjectExecPath()
+                interpreter = project.getProjectInterpreter()
+            else:
+                venvManager = ericApp().getObject("VirtualEnvManager")
+                interpreter = venvManager.getVirtualenvInterpreter(venvName)
+                execPath = venvManager.getVirtualenvExecPath(venvName)
+            if interpreter == "":
+                # use the interpreter used to run eric for identical variants
+                interpreter = PythonUtilities.getPythonExecutable()
+            if interpreter == "":
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Start Debugger"),
+                    self.tr("""<p>No suitable Python3 environment configured.</p>"""),
+                )
+                return None, False, ""
 
         self.__inShutdown = False
 
+        self.__ericServerDebugging = False
+
         redirect = (
             str(configOverride["redirect"])
             if configOverride and configOverride["enable"]
@@ -291,6 +347,28 @@
                 )
                 return None, False, ""
 
+        elif startRemote and self.__ericServerDebuggerInterface is not None:
+            # debugging via an eric-ide server
+            self.translate = self.__ericServerTranslation
+            self.__ericServerDebugging = True
+
+            args = []
+            if noencoding:
+                args.append(noencoding)
+            if multiprocessEnabled:
+                args.append(multiprocessEnabled)
+            if callTraceOptimization:
+                args.append(callTraceOptimization)
+            self.__ericServerDebuggerInterface.startClient(
+                interpreter,
+                originalPathString,
+                args,
+                workingDir=workingDir,
+            )
+            self.__startedVenv = venvName
+
+            return None, self.__isNetworked, ""
+
         else:
             # local debugging code below
             debugClient = self.__determineDebugClient()
@@ -399,6 +477,7 @@
         originalPathString,
         workingDir=None,
         configOverride=None,
+        startRemote=False,
     ):
         """
         Public method to start a remote Python interpreter for a project.
@@ -417,6 +496,9 @@
         @param configOverride dictionary containing the global config override
             data
         @type dict
+        @param startRemote flag indicating to start the client via an eric-ide server
+            (defaults to False)
+        @type bool (optional)
         @return client process object, a flag to indicate a network connection
             and the name of the interpreter in case of a local execution
         @rtype tuple of (QProcess, bool, str)
@@ -443,24 +525,41 @@
             else ""
         )
 
-        if venvName and venvName != self.debugServer.getProjectEnvironmentString():
-            venvManager = ericApp().getObject("VirtualEnvManager")
-            interpreter = venvManager.getVirtualenvInterpreter(venvName)
-            execPath = venvManager.getVirtualenvExecPath(venvName)
+        if (
+            startRemote is True
+            or (
+                startRemote is None
+                and (
+                    venvName == self.debugServer.getEricServerEnvironmentString()
+                    or self.__ericServerDebugging
+                )
+            )
+        ) and ericApp().getObject("EricServer").isServerConnected():
+            # TODO change this once server environment definitions are supported
+            startRemote = True
+            venvName = self.debugServer.getEricServerEnvironmentString()
+            interpreter = ""  # use the interpreter of the server
         else:
-            venvName = project.getProjectVenv()
-            execPath = project.getProjectExecPath()
-            interpreter = project.getProjectInterpreter()
-        if interpreter == "":
-            EricMessageBox.critical(
-                None,
-                self.tr("Start Debugger"),
-                self.tr("""<p>No suitable Python3 environment configured.</p>"""),
-            )
-            return None, self.__isNetworked, ""
+            if venvName and venvName != self.debugServer.getProjectEnvironmentString():
+                venvManager = ericApp().getObject("VirtualEnvManager")
+                interpreter = venvManager.getVirtualenvInterpreter(venvName)
+                execPath = venvManager.getVirtualenvExecPath(venvName)
+            else:
+                venvName = project.getProjectVenv()
+                execPath = project.getProjectExecPath()
+                interpreter = project.getProjectInterpreter()
+            if interpreter == "":
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Start Debugger"),
+                    self.tr("""<p>No suitable Python3 environment configured.</p>"""),
+                )
+                return None, self.__isNetworked, ""
 
         self.__inShutdown = False
 
+        self.__ericServerDebugging = False
+
         if project.getDebugProperty("REMOTEDEBUGGER"):
             # remote debugging code
             ipaddr = self.debugServer.getHostAddress(False)
@@ -518,6 +617,28 @@
                 # remote shell command is missing
                 return None, self.__isNetworked, ""
 
+        elif startRemote and self.__ericServerDebuggerInterface is not None:
+            # debugging via an eric-ide server
+            self.translate = self.__ericServerTranslation
+            self.__ericServerDebugging = True
+
+            args = []
+            if noencoding:
+                args.append(noencoding)
+            if multiprocessEnabled:
+                args.append(multiprocessEnabled)
+            if callTraceOptimization:
+                args.append(callTraceOptimization)
+            self.__ericServerDebuggerInterface.startClient(
+                interpreter,
+                originalPathString,
+                args,
+                workingDir=workingDir,
+            )
+            self.__startedVenv = venvName
+
+            return None, self.__isNetworked, ""
+
         else:
             # local debugging code below
             debugClient = project.getDebugProperty("DEBUGCLIENT")
@@ -620,7 +741,7 @@
         """
         self.__pendingConnections.append(sock)
 
-        sock.readyRead.connect(lambda: self.__parseClientLine(sock))
+        sock.readyRead.connect(lambda: self.__receiveJson(sock))
         sock.disconnected.connect(lambda: self.__socketDisconnected(sock))
 
         return True
@@ -635,30 +756,30 @@
         @param debuggerId id of the connected debug client
         @type str
         """
-        if sock in self.__pendingConnections:
+        if sock and sock in self.__pendingConnections:
             self.__connections[debuggerId] = sock
             self.__pendingConnections.remove(sock)
 
-            if self.__mainDebugger is None:
-                self.__mainDebugger = debuggerId
-                # Get the remote clients capabilities
-                self.remoteCapabilities(debuggerId)
+        if self.__mainDebugger is None:
+            self.__mainDebugger = debuggerId
+            # Get the remote clients capabilities
+            self.remoteCapabilities(debuggerId)
 
-            self.debugServer.signalClientDebuggerId(debuggerId)
+        self.debugServer.signalClientDebuggerId(debuggerId)
 
-            if debuggerId == self.__mainDebugger:
-                self.__flush()
-                self.debugServer.mainClientConnected()
+        if debuggerId == self.__mainDebugger:
+            self.__flush()
+            self.debugServer.mainClientConnected()
 
-            self.debugServer.initializeClient(debuggerId)
+        self.debugServer.initializeClient(debuggerId)
 
-            # perform auto-continue except for main
-            if (
-                debuggerId != self.__mainDebugger
-                and self.__autoContinue
-                and not self.__isStepCommand
-            ):
-                QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId))
+        # perform auto-continue except for main
+        if (
+            debuggerId != self.__mainDebugger
+            and self.__autoContinue
+            and not self.__isStepCommand
+        ):
+            QTimer.singleShot(0, lambda: self.remoteContinue(debuggerId))
 
     def __socketDisconnected(self, sock):
         """
@@ -670,14 +791,7 @@
         for debuggerId in list(self.__connections):
             if self.__connections[debuggerId] is sock:
                 del self.__connections[debuggerId]
-                if debuggerId == self.__mainDebugger:
-                    self.__mainDebugger = None
-                if debuggerId in self.__autoContinued:
-                    self.__autoContinued.remove(debuggerId)
-                if not self.__inShutdown:
-                    with contextlib.suppress(RuntimeError):
-                        # can be ignored during a shutdown
-                        self.debugServer.signalClientDisconnected(debuggerId)
+                self.__handleServerDebugClientDisconnected(debuggerId)
                 break
         else:
             if sock in self.__pendingConnections:
@@ -685,13 +799,37 @@
 
         if not self.__connections:
             # no active connections anymore
+            self.__handleServerLastClientExited()
+
+    @pyqtSlot(str)
+    def __handleServerDebugClientDisconnected(self, debuggerId):
+        """
+        Private slot handling the disconnect of a debug client.
+
+        @param debuggerId ID of the disconnected debugger
+        @type str
+        """
+        if debuggerId == self.__mainDebugger:
+            self.__mainDebugger = None
+        if debuggerId in self.__autoContinued:
+            self.__autoContinued.remove(debuggerId)
+        if not self.__inShutdown:
             with contextlib.suppress(RuntimeError):
-                # debug server object might have been deleted already
-                # ignore this
+                # can be ignored during a shutdown
+                self.debugServer.signalClientDisconnected(debuggerId)
+
+    @pyqtSlot()
+    def __handleServerLastClientExited(self):
+        """
+        Private slot to handle the exit of the last debug client connected.
+        """
+        with contextlib.suppress(RuntimeError):
+            # debug server object might have been deleted already
+            # ignore this
+            self.__autoContinued.clear()
+            if not self.__inShutdown:
                 self.debugServer.signalLastClientExited()
-                self.__autoContinued.clear()
-                if not self.__inShutdown:
-                    self.debugServer.startClient()
+                self.debugServer.startClient()
 
     def getDebuggerIds(self):
         """
@@ -708,9 +846,15 @@
         """
         if self.__mainDebugger:
             # Send commands that were waiting for the connection.
-            conn = self.__connections[self.__mainDebugger]
-            for jsonStr in self.__commandQueue:
-                self.__writeJsonCommandToSocket(jsonStr, conn)
+            if self.__ericServerDebugging:
+                for jsonStr in self.__commandQueue:
+                    self.__ericServerDebuggerInterface.sendClientCommand(
+                        self.__mainDebugger, jsonStr
+                    )
+            else:
+                conn = self.__connections[self.__mainDebugger]
+                for jsonStr in self.__commandQueue:
+                    self.__writeJsonCommandToSocket(jsonStr, conn)
 
         self.__commandQueue.clear()
 
@@ -734,6 +878,8 @@
             sock = self.__pendingConnections.pop()
             self.__shutdownSocket(sock)
 
+        self.__ericServerDebuggerInterface.stopClient()
+
         # reinitialize
         self.__commandQueue.clear()
 
@@ -766,7 +912,7 @@
         @return flag indicating the connection status
         @rtype bool
         """
-        return bool(self.__connections)
+        return bool(self.__connections) or self.__ericServerDebugging
 
     def remoteEnvironment(self, env):
         """
@@ -811,12 +957,15 @@
             instead of unhandled exceptions only
         @type bool
         """
+        if FileSystemUtilities.isPlainFileName(fn):
+            fn = os.path.abspath(fn)
+
         self.__autoContinue = autoContinue
-        self.__scriptName = os.path.abspath(fn)
+        self.__scriptName = fn
         self.__isStepCommand = False
 
         wd = self.translate(wd, False)
-        fn = self.translate(os.path.abspath(fn), False)
+        fn = self.translate(fn, False)
         self.__sendJsonCommand(
             "RequestLoad",
             {
@@ -841,10 +990,13 @@
         @param wd working directory for the program
         @type str
         """
-        self.__scriptName = os.path.abspath(fn)
+        if FileSystemUtilities.isPlainFileName(fn):
+            fn = os.path.abspath(fn)
+
+        self.__scriptName = fn
 
         wd = self.translate(wd, False)
-        fn = self.translate(os.path.abspath(fn), False)
+        fn = self.translate(fn, False)
         self.__sendJsonCommand(
             "RequestRun",
             {
@@ -869,10 +1021,13 @@
             cleared first
         @type bool
         """
-        self.__scriptName = os.path.abspath(fn)
+        if FileSystemUtilities.isPlainFileName(fn):
+            fn = os.path.abspath(fn)
+
+        self.__scriptName = fn
 
         wd = self.translate(wd, False)
-        fn = self.translate(os.path.abspath(fn), False)
+        fn = self.translate(fn, False)
         self.__sendJsonCommand(
             "RequestCoverage",
             {
@@ -898,10 +1053,13 @@
             first
         @type bool
         """
-        self.__scriptName = os.path.abspath(fn)
+        if FileSystemUtilities.isPlainFileName(fn):
+            fn = os.path.abspath(fn)
+
+        self.__scriptName = fn
 
         wd = self.translate(wd, False)
-        fn = self.translate(os.path.abspath(fn), False)
+        fn = self.translate(fn, False)
         self.__sendJsonCommand(
             "RequestProfile",
             {
@@ -1043,7 +1201,12 @@
         @param temp flag indicating a temporary breakpoint
         @type bool
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             self.__sendJsonCommand(
                 "RequestBreakpoint",
@@ -1070,7 +1233,12 @@
         @param enable flag indicating enabling or disabling a breakpoint
         @type bool
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             self.__sendJsonCommand(
                 "RequestBreakpointEnable",
@@ -1095,7 +1263,12 @@
         @param count number of occurrences to ignore
         @type int
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             self.__sendJsonCommand(
                 "RequestBreakpointIgnore",
@@ -1120,7 +1293,12 @@
         @param temp flag indicating a temporary watch expression
         @type bool
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             # cond is combination of cond and special (s. watch expression
             # viewer)
@@ -1145,7 +1323,12 @@
         @param enable flag indicating enabling or disabling a watch expression
         @type bool
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             # cond is combination of cond and special (s. watch expression
             # viewer)
@@ -1170,7 +1353,12 @@
         @param count number of occurrences to ignore
         @type int
         """
-        debuggerList = [debuggerId] if debuggerId else list(self.__connections)
+        if debuggerId:
+            debuggerList = [debuggerId]
+        elif self.__ericServerDebugging:
+            debuggerList = ["<<all>>"]
+        else:
+            debuggerList = list(self.__connections)
         for debuggerId in debuggerList:
             # cond is combination of cond and special (s. watch expression
             # viewer)
@@ -1397,7 +1585,7 @@
             debuggerId,
         )
 
-    def __parseClientLine(self, sock):
+    def __receiveJson(self, sock):
         """
         Private method to handle data from the client.
 
@@ -1437,11 +1625,11 @@
             logging.debug("<Debug-Server> %s", jsonStr)
             ##print("Server: ", jsonStr)    ## debug       # __IGNORE_WARNING_M891__
 
-            self.__handleJsonCommand(jsonStr, sock)
+            self.handleJsonCommand(jsonStr, sock)
 
-    def __handleJsonCommand(self, jsonStr, sock):
+    def handleJsonCommand(self, jsonStr, sock):
         """
-        Private method to handle a command or response serialized as a
+        Public method to handle a command or response serialized as a
         JSON string.
 
         @param jsonStr string containing the command or response received
@@ -1567,6 +1755,7 @@
             self.debugServer.signalClientRawInput(
                 params["prompt"], params["echo"], params["debuggerId"]
             )
+            pass
 
         elif method == "ResponseBPConditionError":
             fn = self.translate(params["filename"], True)
@@ -1636,7 +1825,7 @@
         elif method == "ResponseExit":
             self.__scriptName = ""
             self.debugServer.signalClientExit(
-                params["program"],
+                self.translate(params["program"], True),
                 params["status"],
                 params["message"],
                 params["debuggerId"],
@@ -1677,14 +1866,26 @@
         }
         jsonStr = json.dumps(commandDict)
 
-        if debuggerId and debuggerId in self.__connections:
-            sock = self.__connections[debuggerId]
-        elif sock is None and self.__mainDebugger is not None:
-            sock = self.__connections[self.__mainDebugger]
-        if sock is not None:
-            self.__writeJsonCommandToSocket(jsonStr, sock)
+        if self.__ericServerDebugging:
+            # Debugging via the eric-ide server -> pass the command on to it
+            if self.__mainDebugger is None:
+                # debugger has not connected yet -> queue the command
+                self.__commandQueue.append(jsonStr)
+            else:
+                self.__ericServerDebuggerInterface.sendClientCommand(
+                    debuggerId, jsonStr
+                )
         else:
-            self.__commandQueue.append(jsonStr)
+            # Local debugging -> send the command to the client
+            if debuggerId and debuggerId in self.__connections:
+                sock = self.__connections[debuggerId]
+            elif sock is None and self.__mainDebugger is not None:
+                with contextlib.suppress(KeyError):
+                    sock = self.__connections[self.__mainDebugger]
+            if sock is not None:
+                self.__writeJsonCommandToSocket(jsonStr, sock)
+            else:
+                self.__commandQueue.append(jsonStr)
 
     def __writeJsonCommandToSocket(self, jsonCommand, sock):
         """
--- a/src/eric7/Debugger/StartDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Debugger/StartDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,6 +16,7 @@
 from eric7 import Preferences
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_StartDialog import Ui_StartDialog
 
@@ -203,6 +204,11 @@
         self.scriptnamePicker.addItems(scriptsList)
         self.scriptnamePicker.setText(scriptName)
 
+        self.scriptnamePicker.setRemote(
+            FileSystemUtilities.isRemoteFileName(scriptName)
+        )
+        self.workdirPicker.setRemote(FileSystemUtilities.isRemoteFileName(scriptName))
+
         if dialogMode == StartDialogMode.Debug:
             enableMultiprocessGlobal = Preferences.getDebugger("MultiProcessEnabled")
             self.tracePythonCheckBox.setChecked(tracePython)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/EricGui/EricFileIconProvider.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a file icon provider determining the icon based on file name.
+"""
+
+import fnmatch
+
+from PyQt6.QtGui import QImageReader
+
+from eric7.EricGui import EricPixmapCache
+
+
+class EricFileIconProvider:
+    """
+    Class implementing a file icon provider determining the icon based on file name.
+    """
+
+    def __init__(self):
+        """
+        Constructor
+        """
+        # pixmap icon names first because some are overwritten later
+        self.__iconMappings = {
+            "*.{0}".format(bytes(f).decode()): "filePixmap"
+            for f in QImageReader.supportedImageFormats()
+        }
+
+        # specific one next
+        self.__iconMappings.update(
+            {
+                "*.sh": "lexerBash",
+                "*.bash": "lexerBash",
+                "*.bat": "lexerBatch",
+                "*.cmd": "lexerBatch",
+                "*.cpp": "lexerCPP",
+                "*.cxx": "lexerCPP",
+                "*.cc": "lexerCPP",
+                "*.c": "lexerCPP",
+                "*.hpp": "lexerCPP",
+                "*.hh": "lexerCPP",
+                "*.h": "lexerCPP",
+                "*.cs": "lexerCsharp",
+                "CMakeLists.txt": "lexerCMake",
+                "*.cmake": "lexerCMake",
+                "*.cmake.in": "lexerCMake",
+                "*.ctest": "lexerCMake",
+                "*.ctest.in": "lexerCMake",
+                "*.css": "lexerCSS",
+                "*.qss": "lexerCSS",
+                "*.d": "lexerD",
+                "*.di": "lexerD",
+                "*.diff": "lexerDiff",
+                "*.patch": "lexerDiff",
+                "*.html": "lexerHTML",
+                "*.htm": "lexerHTML",
+                "*.asp": "lexerHTML",
+                "*.shtml": "lexerHTML",
+                "*.php": "lexerHTML",
+                "*.php3": "lexerHTML",
+                "*.php4": "lexerHTML",
+                "*.php5": "lexerHTML",
+                "*.phtml": "lexerHTML",
+                "*.docbook": "lexerHTML",
+                "*.ui": "fileDesigner",
+                "*.ts": "fileLinguist",
+                "*.qm": "fileLinguist2",
+                "*.qrc": "fileResource",
+                "*.kid": "lexerHTML",
+                "*.java": "lexerJava",
+                "*.js": "lexerJavaScript",
+                "*.lua": "lexerLua",
+                "*makefile": "lexerMakefile",
+                "Makefile*": "lexerMakefile",
+                "*.mak": "lexerMakefile",
+                "*.pl": "lexerPerl",
+                "*.pm": "lexerPerl",
+                "*.ph": "lexerPerl",
+                "*.pov": "lexerPovray",
+                "*.properties": "lexerProperties",
+                "*.ini": "lexerProperties",
+                "*.inf": "lexerProperties",
+                "*.reg": "lexerProperties",
+                "*.cfg": "lexerProperties",
+                "*.cnf": "lexerProperties",
+                "*.rc": "lexerProperties",
+                "*.py": "lexerPython3",
+                "*.pyw": "lexerPython3",
+                "*.py3": "lexerPython3",
+                "*.pyw3": "lexerPython3",
+                "*.pyx": "lexerCython",
+                "*.pxd": "lexerCython",
+                "*.pxi": "lexerCython",
+                "*.ptl": "lexerPython3",
+                "*.rb": "lexerRuby",
+                "*.rbw": "lexerRuby",
+                "*.sql": "lexerSQL",
+                "*.tex": "lexerTeX",
+                "*.sty": "lexerTeX",
+                "*.aux": "lexerTeX",
+                "*.toc": "lexerTeX",
+                "*.idx": "lexerTeX",
+                "*.vhd": "lexerVHDL",
+                "*.vhdl": "lexerVHDL",
+                "*.tcl": "lexerTCL",
+                "*.tk": "lexerTCL",
+                "*.f": "lexerFortran",
+                "*.for": "lexerFortran",
+                "*.f90": "lexerFortran",
+                "*.f95": "lexerFortran",
+                "*.f2k": "lexerFortran",
+                "*.dpr": "lexerPascal",
+                "*.dpk": "lexerPascal",
+                "*.pas": "lexerPascal",
+                "*.dfm": "lexerPascal",
+                "*.inc": "lexerPascal",
+                "*.pp": "lexerPascal",
+                "*.ps": "lexerPostScript",
+                "*.xml": "lexerXML",
+                "*.xsl": "lexerXML",
+                "*.svg": "fileSvg",
+                "*.xsd": "lexerXML",
+                "*.xslt": "lexerXML",
+                "*.dtd": "lexerXML",
+                "*.rdf": "lexerXML",
+                "*.xul": "lexerXML",
+                "*.yaml": "lexerYAML",
+                "*.yml": "lexerYAML",
+                "*.m": "lexerMatlab",
+                "*.m.matlab": "lexerMatlab",
+                "*.m.octave": "lexerOctave",
+                "*.e4c": "lexerXML",
+                "*.e4d": "lexerXML",
+                "*.e4k": "fileShortcuts",
+                "*.e4m": "fileMultiProject",
+                "*.e4p": "fileProject",
+                "*.e4q": "lexerXML",
+                "*.e4s": "lexerXML",
+                "*.e4t": "lexerXML",
+                "*.e5d": "lexerXML",
+                "*.e5g": "fileUML",
+                "*.e5k": "fileShortcuts",
+                "*.e5m": "fileMultiProject",
+                "*.e5p": "fileProject",
+                "*.e5q": "lexerXML",
+                "*.e5s": "lexerXML",
+                "*.e5t": "lexerXML",
+                "*.e6d": "lexerXML",
+                "*.e6k": "fileShortcuts",
+                "*.e6m": "fileMultiProject",
+                "*.e6p": "fileProject",
+                "*.e6q": "lexerXML",
+                "*.e6s": "lexerXML",
+                "*.e6t": "lexerXML",
+                "*.ecj": "lexerJSON",
+                "*.edj": "lexerJSON",
+                "*.egj": "fileUML",
+                "*.ehj": "lexerJSON",
+                "*.ekj": "fileShortcuts",
+                "*.emj": "fileMultiProject",
+                "*.epj": "fileProject",
+                "*.eqj": "lexerJSON",
+                "*.esj": "lexerJSON",
+                "*.etj": "lexerJSON",
+                "*.ethj": "lexerJSON",
+                "*.po": "lexerGettext",
+                "*.coffee": "lexerCoffeeScript",
+                "*.json": "lexerJSON",
+                "*.md": "lexerMarkdown",
+                "*.toml": "lexerProperties",
+                "Pipfile": "lexerProperties",
+                "poetry.lock": "lexerProperties",
+                "*.pdf": "pdfviewer",
+            }
+        )
+
+    def fileIcon(self, name):
+        """
+        Public method to get an icon for the given file name.
+
+        @param name file name
+        @type str
+        @return icon
+        @rtype QIcon
+        """
+        for pat in self.__iconMappings:
+            if fnmatch.fnmatch(name, pat):
+                return EricPixmapCache.getIcon(self.__iconMappings[pat])
+        else:
+            return EricPixmapCache.getIcon("fileMisc")
+
+    def fileIconName(self, name):
+        """
+        Public method to get an icon name for the given file name.
+
+        @param name file name
+        @type str
+        @return icon name
+        @rtype str
+        """
+        for pat in self.__iconMappings:
+            if fnmatch.fnmatch(name, pat):
+                return self.__iconMappings[pat]
+        else:
+            return "fileMisc"
--- a/src/eric7/EricWidgets/EricPathPicker.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/EricWidgets/EricPathPicker.py	Fri Jun 07 13:58:16 2024 +0200
@@ -22,8 +22,11 @@
 )
 
 from eric7.EricGui import EricPixmapCache
+from eric7.RemoteServerInterface import EricServerFileDialog
+from eric7.SystemUtilities import FileSystemUtilities
 
 from . import EricFileDialog
+from .EricApplication import ericApp
 from .EricCompleters import EricDirCompleter, EricFileCompleter
 
 
@@ -82,6 +85,8 @@
         self.__lineEditKind = useLineEdit
 
         self.__mode = EricPathPicker.DefaultMode
+        self.__remote = False
+        self.__remotefsInterface = None
 
         self._completer = None
         self.__filters = ""
@@ -186,6 +191,30 @@
         """
         return self.__mode
 
+    def setRemote(self, remote):
+        """
+        Public method to set the remote mode of the path picker.
+
+        @param remote flag indicating the remote mode
+        @type bool
+        """
+        self.__remote = remote
+        if remote:
+            self.__remotefsInterface = (
+                ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            )
+        else:
+            self.__remotefsInterface = None
+
+    def isRemote(self):
+        """
+        Public method to get the path picker remote mode.
+
+        @return flag indicating the remote mode
+        @rtype bool
+        """
+        return self.__remote
+
     def setPickerEnabled(self, enable):
         """
         Public method to set the enabled state of the file dialog button.
@@ -289,10 +318,15 @@
             else:
                 return self._editorText()
         else:
-            if toNative:
-                return os.path.expanduser(QDir.toNativeSeparators(self._editorText()))
+            if self.__remote:
+                return self.__remotefsInterface.expanduser(self._editorText())
             else:
-                return os.path.expanduser(self._editorText())
+                if toNative:
+                    return os.path.expanduser(
+                        QDir.toNativeSeparators(self._editorText())
+                    )
+                else:
+                    return os.path.expanduser(self._editorText())
 
     def setEditText(self, fpath, toNative=True):
         """
@@ -369,6 +403,39 @@
         """
         return self.paths()[-1]
 
+    def strPaths(self):
+        """
+        Public method to get the list of entered paths as strings.
+
+        @return entered paths
+        @rtype list of str
+        """
+        if self.__mode in (
+            EricPathPickerModes.OPEN_FILES_MODE,
+            EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+        ):
+            return self.text().split(";")
+        else:
+            return [self.text()]
+
+    def firstStrPath(self):
+        """
+        Public method to get the first path of a list of entered paths as a string.
+
+        @return first path
+        @rtype pathlib.Path
+        """
+        return self.strPaths()[0]
+
+    def lastStrPath(self):
+        """
+        Public method to get the last path of a list of entered paths as a string.
+
+        @return last path
+        @rtype pathlib.Path
+        """
+        return self.strPaths()[-1]
+
     def setEditorEnabled(self, enable):
         """
         Public method to set the path editor's enabled state.
@@ -533,76 +600,145 @@
         directory = self._editorText()
         if not directory and self.__defaultDirectory:
             directory = self.__defaultDirectory
-        directory = (
-            os.path.expanduser(directory.split(";")[0])
-            if self.__mode
-            in (
-                EricPathPickerModes.OPEN_FILES_MODE,
-                EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+        if self.__remote:
+            directory = (
+                self.__remotefsInterface.expanduser(directory.split(";")[0])
+                if self.__mode == EricPathPickerModes.OPEN_FILES_MODE
+                else self.__remotefsInterface.expanduser(directory)
             )
-            else os.path.expanduser(directory)
-        )
-        if not os.path.isabs(directory) and self.__defaultDirectory:
-            directory = os.path.join(self.__defaultDirectory, directory)
-        directory = QDir.fromNativeSeparators(directory)
+            if (
+                not self.__remotefsInterface.isabs(directory)
+                and self.__defaultDirectory
+            ):
+                directory = self.__remotefsInterface.join(
+                    self.__defaultDirectory, directory
+                )
+        else:
+            directory = (
+                os.path.expanduser(directory.split(";")[0])
+                if self.__mode
+                in (
+                    EricPathPickerModes.OPEN_FILES_MODE,
+                    EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE,
+                )
+                else os.path.expanduser(directory)
+            )
+            if not os.path.isabs(directory) and self.__defaultDirectory:
+                directory = os.path.join(self.__defaultDirectory, directory)
+            directory = QDir.fromNativeSeparators(directory)
 
         if self.__mode == EricPathPickerModes.OPEN_FILE_MODE:
-            fpath = EricFileDialog.getOpenFileName(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getOpenFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+            else:
+                fpath = EricFileDialog.getOpenFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.OPEN_FILES_MODE:
-            fpaths = EricFileDialog.getOpenFileNames(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
+            if self.__remote:
+                fpaths = EricServerFileDialog.getOpenFileNames(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = ";".join(fpaths)
+            else:
+                fpaths = EricFileDialog.getOpenFileNames(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
         elif self.__mode == EricPathPickerModes.OPEN_FILES_AND_DIRS_MODE:
+            # that is not supported for 'remote' pickers
             fpaths = EricFileDialog.getOpenFileAndDirNames(
                 self, windowTitle, directory, self.__filters
             )
             fpath = ";".join([QDir.toNativeSeparators(fpath) for fpath in fpaths])
         elif self.__mode == EricPathPickerModes.SAVE_FILE_MODE:
-            fpath = EricFileDialog.getSaveFileName(
-                self,
-                windowTitle,
-                directory,
-                self.__filters,
-                options=EricFileDialog.DontConfirmOverwrite,
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+            else:
+                fpath = EricFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                    options=EricFileDialog.DontConfirmOverwrite,
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE:
-            fpath, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
-                self,
-                windowTitle,
-                directory,
-                self.__filters,
-                None,
-                EricFileDialog.DontConfirmOverwrite,
-            )
-            fpath = pathlib.Path(fpath)
-            if not fpath.suffix:
-                ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
-                if ex:
-                    fpath = fpath.with_suffix(ex)
+            if self.__remote:
+                fpath, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+                fn, ext = self.__remotefsInterface.splitext(fpath)
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
+                    if ex:
+                        fpath = f"{fn}{ex}"
+            else:
+                fpath, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                    None,
+                    EricFileDialog.DontConfirmOverwrite,
+                )
+                fpath = pathlib.Path(fpath)
+                if not fpath.suffix:
+                    ex = selectedFilter.split("(*")[1].split(")")[0].split()[0]
+                    if ex:
+                        fpath = fpath.with_suffix(ex)
         elif self.__mode == EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE:
-            fpath = EricFileDialog.getSaveFileName(
-                self, windowTitle, directory, self.__filters
-            )
-            fpath = QDir.toNativeSeparators(fpath)
+            if self.__remote:
+                fpath = EricServerFileDialog.getSaveFileName(
+                    self,
+                    windowTitle,
+                    directory,
+                    self.__filters,
+                )
+            else:
+                fpath = EricFileDialog.getSaveFileName(
+                    self, windowTitle, directory, self.__filters
+                )
+                fpath = QDir.toNativeSeparators(fpath)
         elif self.__mode == EricPathPickerModes.DIRECTORY_MODE:
-            fpath = EricFileDialog.getExistingDirectory(
-                self, windowTitle, directory, EricFileDialog.ShowDirsOnly
-            )
-            fpath = QDir.toNativeSeparators(fpath)
-            while fpath.endswith(os.sep):
-                fpath = fpath[:-1]
+            if self.__remote:
+                fpath = EricServerFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, True
+                )
+                while fpath.endswith(self.__remotefsInterface.separator()):
+                    fpath = fpath[:-1]
+            else:
+                fpath = EricFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, EricFileDialog.ShowDirsOnly
+                )
+                fpath = QDir.toNativeSeparators(fpath)
+                while fpath.endswith(os.sep):
+                    fpath = fpath[:-1]
         elif self.__mode == EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE:
-            fpath = EricFileDialog.getExistingDirectory(
-                self, windowTitle, directory, EricFileDialog.DontUseNativeDialog
-            )
-            fpath = QDir.toNativeSeparators(fpath)
-            while fpath.endswith(os.sep):
-                fpath = fpath[:-1]
+            if self.__remote:
+                fpath = EricServerFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, False
+                )
+                while fpath.endswith(self.__remotefsInterface):
+                    fpath = fpath[:-1]
+            else:
+                fpath = EricFileDialog.getExistingDirectory(
+                    self, windowTitle, directory, EricFileDialog.DontUseNativeDialog
+                )
+                fpath = QDir.toNativeSeparators(fpath)
+                while fpath.endswith(os.sep):
+                    fpath = fpath[:-1]
 
         if fpath:
             self._setEditorText(str(fpath))
@@ -765,3 +901,17 @@
         for index in range(self._editor.count()):
             paths.append(pathlib.Path(self._editor.itemText(index)))
         return paths
+
+    def getRemotePathItems(self):
+        """
+        Public method to get the list of remembered remote paths.
+
+        @return list of remembered paths
+        @rtype list of str
+        """
+        paths = []
+        for index in range(self._editor.count()):
+            paths.append(
+                FileSystemUtilities.remoteFileName(self._editor.itemText(index))
+            )
+        return paths
--- a/src/eric7/EricWidgets/EricPathPickerDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/EricWidgets/EricPathPickerDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -78,6 +78,15 @@
         """
         self.__pathPicker.setMode(mode)
 
+    def setPickerRemote(self, remote):
+        """
+        Public method to set the remote mode of the path picker.
+
+        @param remote flag indicating the remote mode
+        @type bool
+        """
+        self.__pathPicker.setRemote(remote)
+
     def setPickerFilters(self, filters):
         """
         Public method to set the filters of the path picker.
@@ -134,6 +143,7 @@
     strPath=None,
     defaultDirectory=None,
     filters=None,
+    remote=False,
 ):
     """
     Function to get a file or directory path from the user.
@@ -153,6 +163,8 @@
     @type str (optional)
     @param filters list of file filters (defaults to None)
     @type list of str (optional)
+    @param remote flag indicating the remote mode (defaults to False)
+    @type bool (optional)
     @return tuple containing the entered path and a flag indicating that the
         user pressed the OK button
     @rtype tuple of (str, bool)
@@ -164,6 +176,7 @@
     if label:
         dlg.setLabelText(label)
     dlg.setPickerMode(mode)
+    dlg.setPickerRemote(remote)
     if strPath:
         dlg.setPickerPath(strPath)
     if defaultDirectory:
--- a/src/eric7/Globals/__init__.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Globals/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -197,7 +197,7 @@
     """
     if loc is None:
         if size < 1024:
-            return QCoreApplication.translate("Globals", "{0:4.2f} Bytes").format(size)
+            return QCoreApplication.translate("Globals", "{0:4d} Bytes").format(size)
         elif size < 1024 * 1024:
             size /= 1024
             return QCoreApplication.translate("Globals", "{0:4.2f} KiB").format(size)
@@ -211,9 +211,12 @@
             size /= 1024 * 1024 * 1024 * 1024
             return QCoreApplication.translate("Globals", "{0:4.2f} TiB").format(size)
     else:
+        if not isinstance(size, float):
+            size = float(size)
+
         if size < 1024:
             return QCoreApplication.translate("Globals", "{0} Bytes").format(
-                loc.toString(size, "f", 2)
+                loc.toString(size)
             )
         elif size < 1024 * 1024:
             size /= 1024
--- a/src/eric7/Graphics/ApplicationDiagramBuilder.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/ApplicationDiagramBuilder.py	Fri Jun 07 13:58:16 2024 +0200
@@ -15,6 +15,7 @@
 
 from eric7 import Globals, Preferences
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
 from eric7.SystemUtilities import FileSystemUtilities
 
@@ -49,6 +50,10 @@
             self.tr("Application Diagram {0}").format(self.project.getProjectName())
         )
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def __buildModulesDict(self):
         """
         Private method to build a dictionary of modules contained in the
@@ -64,9 +69,16 @@
         mods = self.project.getProjectData(dataKey="SOURCES")
         modules = []
         for module in mods:
-            modules.append(
-                FileSystemUtilities.normabsjoinpath(self.project.ppath, module)
-            )
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
+                modules.append(
+                    self.__remotefsInterface.join(self.project.getProjectPath(), module)
+                )
+            else:
+                modules.append(
+                    FileSystemUtilities.normabsjoinpath(
+                        self.project.getProjectPath(), module
+                    )
+                )
         tot = len(modules)
         progress = EricProgressDialog(
             self.tr("Parsing modules..."),
@@ -111,26 +123,48 @@
         @rtype str
         """
         candidates = []
-        path = self.project.getProjectPath()
-        init = os.path.join(path, "__init__.py")
-        if os.path.exists(init):
-            # project is a package
-            return path
+        ppath = self.project.getProjectPath()
+
+        if FileSystemUtilities.isRemoteFileName(ppath):
+            init = self.__remotefsInterface.join(ppath, "__init__.py")
+            if self.__remotefsInterface.exists(init):
+                # remote project is a package
+                return ppath
+            else:
+                # check, if any of the top directories is a package
+                for entry in self.__remotefsInterface.listdir(ppath)[2]:
+                    if (
+                        not entry["name"].startswith(".")
+                        and entry["is_dir"]
+                        and self.__remotefsInterface.exists(
+                            self.__remotefsInterface.join(entry["path"], "__init__.py")
+                        )
+                    ):
+                        candidates.append(entry["path"])
+
+                # check, if project uses the 'src' layout
+                srcPath = self.__remotefsInterface.join(ppath, "src")
+                if self.__remotefsInterface.exists(srcPath):
+                    for entry in self.__remotefsInterface.listdir(srcPath)[2]:
+                        if (
+                            not entry["name"].startswith(".")
+                            and entry["is_dir"]
+                            and self.__remotefsInterface.exists(
+                                self.__remotefsInterface.join(
+                                    entry["path"], "__init__.py"
+                                )
+                            )
+                        ):
+                            candidates.append(entry["path"])
+
         else:
-            # check, if any of the top directories is a package
-            with os.scandir(path) as dirEntriesIterator:
-                for entry in [
-                    e for e in dirEntriesIterator if not e.name.startswith(".")
-                ]:
-                    if entry.is_dir() and os.path.exists(
-                        os.path.join(entry.path, "__init__.py")
-                    ):
-                        candidates.append(entry.path)
-
-            # check, if project uses the 'src' layout
-            srcPath = os.path.join(path, "src")
-            if os.path.exists(srcPath):
-                with os.scandir(srcPath) as dirEntriesIterator:
+            init = os.path.join(ppath, "__init__.py")
+            if os.path.exists(init):
+                # project is a package
+                return ppath
+            else:
+                # check, if any of the top directories is a package
+                with os.scandir(ppath) as dirEntriesIterator:
                     for entry in [
                         e for e in dirEntriesIterator if not e.name.startswith(".")
                     ]:
@@ -139,28 +173,37 @@
                         ):
                             candidates.append(entry.path)
 
-            if len(candidates) == 1:
-                return candidates[0]
-            elif len(candidates) > 1:
-                root, ok = QInputDialog.getItem(
-                    None,
-                    self.tr("Application Diagram"),
-                    self.tr("Select the application directory:"),
-                    sorted(candidates),
-                    0,
-                    True,
-                )
-                if ok:
-                    return root
-            else:
-                EricMessageBox.warning(
-                    None,
-                    self.tr("Application Diagram"),
-                    self.tr(
-                        """No application package could be detected."""
-                        """ Aborting..."""
-                    ),
-                )
+                # check, if project uses the 'src' layout
+                srcPath = os.path.join(ppath, "src")
+                if os.path.exists(srcPath):
+                    with os.scandir(srcPath) as dirEntriesIterator:
+                        for entry in [
+                            e for e in dirEntriesIterator if not e.name.startswith(".")
+                        ]:
+                            if entry.is_dir() and os.path.exists(
+                                os.path.join(entry.path, "__init__.py")
+                            ):
+                                candidates.append(entry.path)
+
+        if len(candidates) == 1:
+            return candidates[0]
+        elif len(candidates) > 1:
+            root, ok = QInputDialog.getItem(
+                None,
+                self.tr("Application Diagram"),
+                self.tr("Select the application directory:"),
+                sorted(candidates),
+                0,
+                True,
+            )
+            if ok:
+                return root
+        else:
+            EricMessageBox.warning(
+                None,
+                self.tr("Application Diagram"),
+                self.tr("""No application package could be detected. Aborting..."""),
+            )
         return None
 
     def buildDiagram(self):
@@ -172,7 +215,13 @@
             # no root path detected
             return
 
-        root = os.path.splitdrive(rpath)[1].replace(os.sep, ".")[1:]
+        root = (
+            self.__remotefsInterface.splitdrive(rpath)[1][1:].replace(
+                self.__remotefsInterface.separator(), "."
+            )
+            if FileSystemUtilities.isRemoteFileName(rpath)
+            else os.path.splitdrive(rpath)[1][1:].replace(os.sep, ".")
+        )
 
         packages = {}
         self.__shapes = {}
--- a/src/eric7/Graphics/ImportsDiagramBuilder.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/ImportsDiagramBuilder.py	Fri Jun 07 13:58:16 2024 +0200
@@ -14,6 +14,7 @@
 from PyQt6.QtWidgets import QApplication, QGraphicsTextItem
 
 from eric7 import Globals, Preferences
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
 from eric7.SystemUtilities import FileSystemUtilities
 
@@ -49,7 +50,10 @@
         self.setObjectName("ImportsDiagram")
 
         self.showExternalImports = showExternalImports
-        self.packagePath = os.path.abspath(package)
+        if FileSystemUtilities.isRemoteFileName(package):
+            self.packagePath = package
+        else:
+            self.packagePath = os.path.abspath(package)
 
         self.__relPackagePath = (
             self.project.getRelativePath(self.packagePath)
@@ -57,16 +61,33 @@
             else ""
         )
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def initialize(self):
         """
         Public method to initialize the object.
         """
-        self.package = os.path.splitdrive(self.packagePath)[1].replace(os.sep, ".")[1:]
         hasInit = True
         ppath = self.packagePath
-        while hasInit:
-            ppath = os.path.dirname(ppath)
-            hasInit = len(glob.glob(os.path.join(ppath, "__init__.*"))) > 0
+
+        if FileSystemUtilities.isRemoteFileName(self.packagePath):
+            self.package = self.__remotefsInterface.splitdrive(self.packagePath)[1][
+                1:
+            ].replace(self.__remotefsInterface.separator(), ".")
+            while hasInit:
+                ppath = self.__remotefsInterface.dirname(ppath)
+                globPattern = self.__remotefsInterface.join(ppath, "__init__.*")
+                hasInit = len(self.__remotefsInterface.glob(globPattern)) > 0
+        else:
+            self.package = os.path.splitdrive(self.packagePath)[1][1:].replace(
+                os.sep, "."
+            )
+            while hasInit:
+                ppath = os.path.dirname(ppath)
+                hasInit = len(glob.glob(os.path.join(ppath, "__init__.*"))) > 0
+
         self.shortPackage = self.packagePath.replace(ppath, "").replace(os.sep, ".")[1:]
 
         pname = self.project.getProjectName()
@@ -93,13 +114,18 @@
         moduleDict = {}
         modules = []
         for ext in Preferences.getPython("Python3Extensions"):
-            modules.extend(
-                glob.glob(
-                    FileSystemUtilities.normjoinpath(
-                        self.packagePath, "*{0}".format(ext)
+            if FileSystemUtilities.isRemoteFileName(self.packagePath):
+                modules.extend(
+                    self.__remotefsInterface.glob(
+                        self.__remotefsInterface.join(self.packagePath, f"*{ext}")
                     )
                 )
-            )
+            else:
+                modules.extend(
+                    glob.glob(
+                        FileSystemUtilities.normjoinpath(self.packagePath, f"*{ext}")
+                    )
+                )
 
         tot = len(modules)
         progress = EricProgressDialog(
@@ -141,7 +167,11 @@
         """
         Public method to build the modules shapes of the diagram.
         """
-        initlist = glob.glob(os.path.join(self.packagePath, "__init__.*"))
+        if FileSystemUtilities.isRemoteFileName(self.packagePath):
+            globPattern = self.__remotefsInterface.join(self.packagePath, "__init__.*")
+            initlist = self.__remotefsInterface.glob(globPattern)
+        else:
+            initlist = glob.glob(os.path.join(self.packagePath, "__init__.*"))
         if len(initlist) == 0:
             ct = QGraphicsTextItem(None)
             ct.setHtml(
--- a/src/eric7/Graphics/PackageDiagramBuilder.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/PackageDiagramBuilder.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,6 +16,7 @@
 from PyQt6.QtWidgets import QApplication, QGraphicsTextItem
 
 from eric7 import Globals, Preferences
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
 from eric7.SystemUtilities import FileSystemUtilities
 
@@ -45,7 +46,10 @@
         super().__init__(dialog, view, project)
         self.setObjectName("PackageDiagram")
 
-        self.package = os.path.abspath(package)
+        if FileSystemUtilities.isRemoteFileName(package):
+            self.package = package
+        else:
+            self.package = os.path.abspath(package)
         self.noAttrs = noAttrs
 
         self.__relPackage = (
@@ -54,6 +58,10 @@
             else ""
         )
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def initialize(self):
         """
         Public method to initialize the object.
@@ -97,9 +105,16 @@
         moduleDict = {}
         modules = []
         for ext in supportedExt:
-            modules.extend(
-                glob.glob(FileSystemUtilities.normjoinpath(self.package, ext))
-            )
+            if FileSystemUtilities.isRemoteFileName(self.package):
+                modules.extend(
+                    self.__remotefsInterface.glob(
+                        self.__remotefsInterface.join(self.package, ext)
+                    )
+                )
+            else:
+                modules.extend(
+                    glob.glob(FileSystemUtilities.normjoinpath(self.package, ext))
+                )
         tot = len(modules)
         progress = EricProgressDialog(
             self.tr("Parsing modules..."),
@@ -154,19 +169,45 @@
         subpackagesDict = {}
         subpackagesList = []
 
-        with os.scandir(self.package) as dirEntriesIterator:
-            for subpackage in dirEntriesIterator:
+        if FileSystemUtilities.isRemoteFileName(self.package):
+            for subpackage in self.__remotefsInterface.listdir(self.package)[2]:
                 if (
-                    subpackage.is_dir()
-                    and subpackage.name != "__pycache__"
-                    and len(glob.glob(os.path.join(subpackage.path, "__init__.*"))) != 0
+                    subpackage["is_dir"]
+                    and subpackage["name"] != "__pycache__"
+                    and len(
+                        self.__remotefsInterface.glob(
+                            self.__remotefsInterface.join(
+                                subpackage["path"], "__init__.*"
+                            )
+                        )
+                    )
+                    != 0
                 ):
-                    subpackagesList.append(subpackage.path)
+                    subpackagesList.append(subpackage["path"])
+        else:
+            with os.scandir(self.package) as dirEntriesIterator:
+                for subpackage in dirEntriesIterator:
+                    if (
+                        subpackage.is_dir()
+                        and subpackage.name != "__pycache__"
+                        and len(glob.glob(os.path.join(subpackage.path, "__init__.*")))
+                        != 0
+                    ):
+                        subpackagesList.append(subpackage.path)
 
         tot = 0
         for ext in supportedExt:
             for subpackage in subpackagesList:
-                tot += len(glob.glob(FileSystemUtilities.normjoinpath(subpackage, ext)))
+                if FileSystemUtilities.isRemoteFileName(subpackage):
+                    tot += len(
+                        self.__remotefsInterface.glob(
+                            self.__remotefsInterface.join(subpackage, ext)
+                        )
+                    )
+                else:
+                    tot += len(
+                        glob.glob(FileSystemUtilities.normjoinpath(subpackage, ext))
+                    )
         progress = EricProgressDialog(
             self.tr("Parsing modules..."),
             None,
@@ -187,9 +228,16 @@
                 subpackagesDict[packageName] = []
                 modules = []
                 for ext in supportedExt:
-                    modules.extend(
-                        glob.glob(FileSystemUtilities.normjoinpath(subpackage, ext))
-                    )
+                    if FileSystemUtilities.isRemoteFileName(subpackage):
+                        modules.extend(
+                            self.__remotefsInterface.glob(
+                                self.__remotefsInterface.join(subpackage, ext)
+                            )
+                        )
+                    else:
+                        modules.extend(
+                            glob.glob(FileSystemUtilities.normjoinpath(subpackage, ext))
+                        )
                 for prog, module in enumerate(modules, start=start):
                     progress.setValue(prog)
                     if time.monotonic() - now > 0.01:
@@ -225,7 +273,12 @@
         """
         self.allClasses = {}
 
-        initlist = glob.glob(os.path.join(self.package, "__init__.*"))
+        globPattern = os.path.join(self.package, "__init__.*")
+        initlist = (
+            self.__remotefsInterface.glob(globPattern)
+            if FileSystemUtilities.isRemoteFileName(self.package)
+            else glob.glob(globPattern)
+        )
         if len(initlist) == 0:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
--- a/src/eric7/Graphics/PixmapDiagram.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/PixmapDiagram.py	Fri Jun 07 13:58:16 2024 +0200
@@ -8,24 +8,17 @@
 """
 
 from PyQt6.QtCore import QEvent, QMarginsF, QSize, Qt
-from PyQt6.QtGui import (
-    QAction,
-    QColor,
-    QFont,
-    QImage,
-    QPageLayout,
-    QPainter,
-    QPalette,
-    QPixmap,
-)
+from PyQt6.QtGui import QAction, QColor, QFont, QPageLayout, QPainter, QPalette, QPixmap
 from PyQt6.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog
 from PyQt6.QtWidgets import QLabel, QMenu, QScrollArea, QSizePolicy, QToolBar
 
 from eric7 import Preferences
 from eric7.EricGui import EricPixmapCache
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
+from eric7.SystemUtilities import FileSystemUtilities
 
 
 class PixmapDiagram(EricMainWindow):
@@ -186,8 +179,30 @@
         @return flag indicating success
         @rtype bool
         """
-        image = QImage(filename)
-        if image.isNull():
+        pixmap = QPixmap()
+        if FileSystemUtilities.isRemoteFileName(filename):
+            try:
+                data = (
+                    ericApp()
+                    .getObject("EricServer")
+                    .getServiceInterface("FileSystem")
+                    .readFile(filename)
+                )
+                pixmap.loadFromData(data)
+            except OSError as err:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("Pixmap-Viewer"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> cannot be loaded.</p>"""
+                        """<p>Reason: {1}</p>"""
+                    ).format(filename, str(err)),
+                )
+                return False
+        else:
+            pixmap.load(filename)
+
+        if pixmap.isNull():
             EricMessageBox.warning(
                 self,
                 self.tr("Pixmap-Viewer"),
@@ -198,7 +213,7 @@
             )
             return False
 
-        self.pixmapLabel.setPixmap(QPixmap.fromImage(image))
+        self.pixmapLabel.setPixmap(pixmap)
         self.pixmapLabel.adjustSize()
         return True
 
--- a/src/eric7/Graphics/SvgDiagram.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/SvgDiagram.py	Fri Jun 07 13:58:16 2024 +0200
@@ -15,8 +15,11 @@
 
 from eric7 import Preferences
 from eric7.EricGui import EricPixmapCache
+from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
+from eric7.SystemUtilities import FileSystemUtilities
 
 
 class SvgDiagram(EricMainWindow):
@@ -102,8 +105,7 @@
         self.resize(QSize(800, 600).expandedTo(self.minimumSizeHint()))
 
         self.zoom = 1.0
-        self.svgFile = svgFile
-        self.svgWidget.load(self.svgFile)
+        self.__loadSvgFile(svgFile)
         self.svgWidget.resize(self.svgWidget.renderer().defaultSize())
 
         self.__initActions()
@@ -112,6 +114,36 @@
 
         self.grabGesture(Qt.GestureType.PinchGesture)
 
+    def __loadSvgFile(self, svgFile):
+        """
+        Private method to load a given SVG file.
+
+        @param svgFile file path of the SVG file
+        @type str
+        """
+        self.svgFile = svgFile
+        if FileSystemUtilities.isRemoteFileName(svgFile):
+            try:
+                data = (
+                    ericApp()
+                    .getObject("EricServer")
+                    .getServiceInterface("FileSystem")
+                    .readFile(svgFile)
+                )
+                self.svgWidget.load(data)
+            except OSError as err:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("SVG-Viewer"),
+                    self.tr(
+                        "<p>The SVG file <b>{0}</b> could not be loaded.</p>"
+                        "<p>Reason: {1}</p>"
+                    ).format(svgFile, str(err)),
+                )
+
+        else:
+            self.svgWidget.load(self.svgFile)
+
     def __initActions(self):
         """
         Private method to initialize the view actions.
--- a/src/eric7/Graphics/UMLDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Graphics/UMLDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -17,7 +17,10 @@
 
 from eric7.EricGui import EricPixmapCache
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
+from eric7.RemoteServerInterface import EricServerFileDialog
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .ApplicationDiagramBuilder import ApplicationDiagramBuilder
 from .ImportsDiagramBuilder import ImportsDiagramBuilder
@@ -86,6 +89,9 @@
 
         self.__project = project
         self.__diagramType = diagramType
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
 
         self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0)
         self.umlView = UMLGraphicsView(self.scene, parent=self)
@@ -243,34 +249,55 @@
         @type str
         """
         if not filename:
-            fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
-                self,
-                self.tr("Save Diagram"),
-                "",
-                self.tr("Eric Graphics File (*.egj);;All Files (*)"),
-                "",
-                EricFileDialog.DontConfirmOverwrite,
-            )
-            if not fname:
-                return
+            if FileSystemUtilities.isRemoteFileName(self.__project.getProjectPath()):
+                fname, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    self.tr("Save Diagram"),
+                    self.__project.getProjectPath(),
+                    self.tr("Eric Graphics File (*.egj);;All Files (*)"),
+                    "",
+                )
+                if not fname:
+                    return
 
-            fpath = pathlib.Path(fname)
-            if not fpath.suffix:
-                ex = selectedFilter.split("(*")[1].split(")")[0]
-                if ex:
-                    fpath = fpath.with_suffix(ex)
-            if fpath.exists():
+                ext = self.__remotefsInterface.splitext(fname)[1]
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0]
+                    if ex:
+                        fname += ex
+                filename = fname
+                fileExists = self.__remotefsInterface.exists(filename)
+            else:
+                fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                    self,
+                    self.tr("Save Diagram"),
+                    self.__project.getProjectPath(),
+                    self.tr("Eric Graphics File (*.egj);;All Files (*)"),
+                    "",
+                    EricFileDialog.DontConfirmOverwrite,
+                )
+                if not fname:
+                    return
+
+                fpath = pathlib.Path(fname)
+                if not fpath.suffix:
+                    ex = selectedFilter.split("(*")[1].split(")")[0]
+                    if ex:
+                        fpath = fpath.with_suffix(ex)
+                fileExists = fpath.exists()
+                filename = str(fpath)
+
+            if fileExists:
                 res = EricMessageBox.yesNo(
                     self,
                     self.tr("Save Diagram"),
                     self.tr(
-                        "<p>The file <b>{0}</b> already exists. Overwrite it?</p>"
-                    ).format(fpath),
+                        "<p>The file <b>{0}</b> exists already. Overwrite it?</p>"
+                    ).format(filename),
                     icon=EricMessageBox.Warning,
                 )
                 if not res:
                     return
-            filename = str(fpath)
 
         res = self.__writeJsonGraphicsFile(filename)
 
@@ -289,12 +316,20 @@
         @rtype bool
         """
         if not filename:
-            filename = EricFileDialog.getOpenFileName(
-                self,
-                self.tr("Load Diagram"),
-                "",
-                self.tr("Eric Graphics File (*.egj);;All Files (*)"),
-            )
+            if FileSystemUtilities.isRemoteFileName(self.__project.getProjectPath()):
+                filename = EricServerFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Load Diagram"),
+                    self.__project.getProjectPath(),
+                    self.tr("Eric Graphics File (*.egj);;All Files (*)"),
+                )
+            else:
+                filename = EricFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Load Diagram"),
+                    self.__project.getProjectPath(),
+                    self.tr("Eric Graphics File (*.egj);;All Files (*)"),
+                )
             if not filename:
                 # Canceled by user
                 return False
@@ -343,8 +378,11 @@
 
         try:
             jsonString = json.dumps(data, indent=2)
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                self.__remotefsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                with open(filename, "w") as f:
+                    f.write(jsonString)
             return True
         except (OSError, TypeError) as err:
             EricMessageBox.critical(
@@ -368,8 +406,12 @@
         @rtype bool
         """
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                bdata = self.__remotefsInterface.readFile(filename)
+                jsonString = bdata.decode("utf-8")
+            else:
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             data = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
--- a/src/eric7/HexEdit/HexEditMainWindow.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/HexEdit/HexEditMainWindow.py	Fri Jun 07 13:58:16 2024 +0200
@@ -27,9 +27,11 @@
 from eric7.EricGui import EricPixmapCache
 from eric7.EricGui.EricAction import EricAction
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricClickableLabel import EricClickableLabel
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.Globals import recentNameHexFiles, strGroup
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.SystemUtilities import FileSystemUtilities
 
 from .HexEditGotoWidget import HexEditGotoWidget
@@ -82,6 +84,12 @@
         if not self.__fromEric:
             self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            if self.__fromEric
+            else None
+        )
+
         self.__editor = HexEditWidget()
         self.__searchWidget = HexEditSearchReplaceWidget(self.__editor, self, False)
         self.__replaceWidget = HexEditSearchReplaceWidget(self.__editor, self, True)
@@ -1056,11 +1064,20 @@
         ):
             self.__lastOpenPath = self.__project.getProjectPath()
 
-        fileName = EricFileDialog.getOpenFileName(
-            self,
-            self.tr("Open binary file in new window"),
-            self.__lastOpenPath,
-            self.tr("All Files (*)"),
+        fileName = (
+            EricServerFileDialog.getOpenFileName(
+                self,
+                self.tr("Open binary file"),
+                self.__lastOpenPath,
+                self.tr("All Files (*)"),
+            )
+            if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath)
+            else EricFileDialog.getOpenFileName(
+                self,
+                self.tr("Open binary file in new window"),
+                self.__lastOpenPath,
+                self.tr("All Files (*)"),
+            )
         )
         if fileName:
             he = HexEditMainWindow(
@@ -1097,17 +1114,12 @@
         @param fileName name of the binary file to load
         @type str
         """
-        if not os.path.exists(fileName):
-            EricMessageBox.warning(
-                self,
-                self.tr("eric Hex Editor"),
-                self.tr("The file '{0}' does not exist.").format(fileName),
-            )
-            return
-
         try:
-            with open(fileName, "rb") as f:
-                data = f.read()
+            if FileSystemUtilities.isRemoteFileName(fileName):
+                data = self.__remotefsInterface.readFile(fileName)
+            else:
+                with open(fileName, "rb") as f:
+                    data = f.read()
         except OSError as err:
             EricMessageBox.warning(
                 self,
@@ -1118,7 +1130,11 @@
             )
             return
 
-        self.__lastOpenPath = os.path.dirname(fileName)
+        self.__lastOpenPath = (
+            self.__remotefsInterface.dirname(fileName)
+            if FileSystemUtilities.isRemoteFileName(fileName)
+            else os.path.dirname(fileName)
+        )
         self.__editor.setData(data)
         self.__setCurrentFile(fileName)
 
@@ -1138,11 +1154,20 @@
             ):
                 self.__lastOpenPath = self.__project.getProjectPath()
 
-            fileName = EricFileDialog.getOpenFileName(
-                self,
-                self.tr("Open binary file"),
-                self.__lastOpenPath,
-                self.tr("All Files (*)"),
+            fileName = (
+                EricServerFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Open binary file"),
+                    self.__lastOpenPath,
+                    self.tr("All Files (*)"),
+                )
+                if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath)
+                else EricFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Open binary file"),
+                    self.__lastOpenPath,
+                    self.tr("All Files (*)"),
+                )
             )
             if fileName:
                 self.__loadHexFile(fileName)
@@ -1190,17 +1215,32 @@
         if not self.__lastSavePath and self.__lastOpenPath:
             self.__lastSavePath = self.__lastOpenPath
 
-        fileName = EricFileDialog.getSaveFileName(
-            self,
-            self.tr("Save binary file"),
-            self.__lastSavePath,
-            self.tr("All Files (*)"),
-            options=EricFileDialog.DontConfirmOverwrite,
+        fileName = (
+            EricServerFileDialog.getSaveFileName(
+                self,
+                self.tr("Save binary file"),
+                self.__lastSavePath,
+                self.tr("All Files (*)"),
+            )
+            if FileSystemUtilities.isRemoteFileName(self.__lastSavePath)
+            else EricFileDialog.getSaveFileName(
+                self,
+                self.tr("Save binary file"),
+                self.__lastSavePath,
+                self.tr("All Files (*)"),
+                options=EricFileDialog.DontConfirmOverwrite,
+            )
         )
         if not fileName:
             return False
 
-        if pathlib.Path(fileName).exists():
+        if (
+            FileSystemUtilities.isRemoteFileName(fileName)
+            and self.__remotefsInterface.exists(fileName)
+        ) or (
+            FileSystemUtilities.isPlainFileName(fileName)
+            and pathlib.Path(fileName).exists()
+        ):
             res = EricMessageBox.yesNo(
                 self,
                 self.tr("Save binary file"),
@@ -1226,8 +1266,12 @@
         @rtype bool
         """
         try:
-            with open(fileName, "wb") as f:
-                f.write(self.__editor.data())
+            data = self.__editor.data()
+            if FileSystemUtilities.isRemoteFileName(fileName):
+                self.__remotefsInterface.writeFile(fileName, data)
+            else:
+                with open(fileName, "wb") as f:
+                    f.write(data)
         except OSError as err:
             EricMessageBox.warning(
                 self,
@@ -1261,29 +1305,50 @@
         if not savePath and self.__lastOpenPath:
             savePath = self.__lastOpenPath
 
-        fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
-            self,
-            self.tr("Save to readable file"),
-            savePath,
-            self.tr("Text Files (*.txt);;All Files (*)"),
-            self.tr("Text Files (*.txt)"),
-            EricFileDialog.DontConfirmOverwrite,
-        )
-        if not fileName:
-            return
+        if FileSystemUtilities.isRemoteFileName(savePath):
+            fileName, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+                self,
+                self.tr("Save to readable file"),
+                savePath,
+                self.tr("Text Files (*.txt);;All Files (*)"),
+                self.tr("Text Files (*.txt)"),
+            )
+            if not fileName:
+                return
 
-        fpath = pathlib.Path(fileName)
-        if not fpath.suffix:
-            ex = selectedFilter.split("(*")[1].split(")")[0]
-            if ex:
-                fpath = fpath.with_suffix(ex)
-        if fpath.exists():
+            ext = self.__remotefsInterface.splitext(fileName)[1]
+            if not ext:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fileName += ex
+            fileExists = self.__remotefsInterface.exists(fileName)
+        else:
+            fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                self,
+                self.tr("Save to readable file"),
+                savePath,
+                self.tr("Text Files (*.txt);;All Files (*)"),
+                self.tr("Text Files (*.txt)"),
+                EricFileDialog.DontConfirmOverwrite,
+            )
+            if not fileName:
+                return
+
+            fpath = pathlib.Path(fileName)
+            if not fpath.suffix:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fpath = fpath.with_suffix(ex)
+            fileExists = fpath.exists()
+            fileName = str(fpath)
+
+        if fileExists:
             res = EricMessageBox.yesNo(
                 self,
                 self.tr("Save to readable file"),
                 self.tr(
                     "<p>The file <b>{0}</b> already exists. Overwrite it?</p>"
-                ).format(fpath),
+                ).format(fileName),
                 icon=EricMessageBox.Warning,
             )
             if not res:
@@ -1295,8 +1360,13 @@
             else self.__editor.toReadableString()
         )
         try:
-            with fpath.open("w", encoding="latin1") as f:
-                f.write(readableData)
+            if FileSystemUtilities.isRemoteFileName(fileName):
+                self.__remotefsInterface.writeFile(
+                    fileName, readableData.encode("latin1")
+                )
+            else:
+                with open(fileName, "w", encoding="latin1") as f:
+                    f.write(readableData)
         except OSError as err:
             EricMessageBox.warning(
                 self,
@@ -1535,6 +1605,10 @@
 
         self.__recentMenu.clear()
 
+        connected = (
+            self.__fromEric and ericApp().getObject("EricServer").isServerConnected()
+        )
+
         for idx, rs in enumerate(self.__recent, start=1):
             formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
             act = self.__recentMenu.addAction(
@@ -1546,7 +1620,10 @@
                 )
             )
             act.setData(rs)
-            act.setEnabled(pathlib.Path(rs).exists())
+            act.setEnabled(
+                (FileSystemUtilities.isRemoteFileName(rs) and connected)
+                or pathlib.Path(rs).exists()
+            )
 
         self.__recentMenu.addSeparator()
         self.__recentMenu.addAction(self.tr("&Clear"), self.__clearRecent)
@@ -1581,7 +1658,7 @@
         rs = Preferences.Prefs.rsettings.value(recentNameHexFiles)
         if rs is not None:
             for f in Preferences.toList(rs):
-                if pathlib.Path(f).exists():
+                if FileSystemUtilities.isRemoteFileName(f) or pathlib.Path(f).exists():
                     self.__recent.append(f)
 
     def __saveRecent(self):
--- a/src/eric7/IconEditor/IconEditorWindow.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/IconEditor/IconEditorWindow.py	Fri Jun 07 13:58:16 2024 +0200
@@ -11,7 +11,15 @@
 import os
 import pathlib
 
-from PyQt6.QtCore import QEvent, QSignalMapper, QSize, Qt, pyqtSignal
+from PyQt6.QtCore import (
+    QBuffer,
+    QByteArray,
+    QEvent,
+    QSignalMapper,
+    QSize,
+    Qt,
+    pyqtSignal,
+)
 from PyQt6.QtGui import QImage, QImageReader, QImageWriter, QKeySequence, QPalette
 from PyQt6.QtWidgets import QDockWidget, QLabel, QScrollArea, QWhatsThis
 
@@ -19,8 +27,10 @@
 from eric7.EricGui import EricPixmapCache
 from eric7.EricGui.EricAction import EricAction, createActionGroup
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .IconEditorGrid import IconEditorGrid, IconEditorTool
 
@@ -1282,7 +1292,28 @@
         @param fileName name of the icon file to load
         @type str
         """
-        img = QImage(fileName)
+        img = QImage()
+        if self.fromEric and FileSystemUtilities.isRemoteFileName(fileName):
+            try:
+                data = (
+                    ericApp()
+                    .getObject("EricServer")
+                    .getServiceInterface("FileSystem")
+                    .readFile(fileName)
+                )
+                img.loadFromData(data)
+            except OSError as err:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("eric Icon Editor"),
+                    self.tr(
+                        """<p>The file <b>{0}</b> cannot be loaded.</p>"""
+                        """<p>Reason: {1}</p>"""
+                    ).format(fileName, str(err)),
+                )
+                return
+        else:
+            img.load(fileName)
         if img.isNull():
             EricMessageBox.warning(
                 self,
@@ -1303,7 +1334,30 @@
         @rtype bool
         """
         img = self.__editor.iconImage()
-        res = img.save(fileName)
+        if self.fromEric and FileSystemUtilities.isRemoteFileName(fileName):
+            fsInterface = (
+                ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            )
+            imageFormat = fsInterface.splitext(fileName)[1][1:].upper()
+            data = QByteArray()
+            buffer = QBuffer(data)
+            res = img.save(buffer, imageFormat)
+            if res:
+                try:
+                    fsInterface.writeFile(fileName, data)
+                except OSError as err:
+                    EricMessageBox.warning(
+                        self,
+                        self.tr("eric Icon Editor"),
+                        self.tr(
+                            """<p>The file <b>{0}</b> cannot be written.</p>"""
+                            """<p>Reason: {1}</p>"""
+                        ).format(fileName, str(err)),
+                    )
+                    self.__checkActions()
+                    return False
+        else:
+            res = img.save(fileName)
 
         if not res:
             EricMessageBox.warning(
@@ -1311,9 +1365,7 @@
                 self.tr("eric Icon Editor"),
                 self.tr("Cannot write file '{0}'.").format(fileName),
             )
-
             self.__checkActions()
-
             return False
 
         self.__editor.setDirty(False, setCleanState=True)
--- a/src/eric7/JediInterface/AssistantJedi.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/JediInterface/AssistantJedi.py	Fri Jun 07 13:58:16 2024 +0200
@@ -13,6 +13,7 @@
 from PyQt6.QtWidgets import QMenu
 
 from eric7 import Preferences
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .JediServer import JediServer
 
@@ -254,4 +255,7 @@
         @type Editor
         """
         if menuName == "Main":
-            self.__menu.setEnabled(editor.hasSelectedText())
+            self.__menu.setEnabled(
+                not FileSystemUtilities.isRemoteFileName(editor.getFileName())
+                and editor.hasSelectedText()
+            )
--- a/src/eric7/PdfViewer/PdfViewerWindow.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/PdfViewer/PdfViewerWindow.py	Fri Jun 07 13:58:16 2024 +0200
@@ -11,7 +11,16 @@
 import os
 import pathlib
 
-from PyQt6.QtCore import QPointF, QSize, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtCore import (
+    QBuffer,
+    QByteArray,
+    QIODevice,
+    QPointF,
+    QSize,
+    Qt,
+    pyqtSignal,
+    pyqtSlot,
+)
 from PyQt6.QtGui import QAction, QActionGroup, QClipboard, QGuiApplication, QKeySequence
 from PyQt6.QtPdf import QPdfDocument, QPdfLink
 from PyQt6.QtPdfWidgets import QPdfView
@@ -30,9 +39,11 @@
 from eric7.EricGui import EricPixmapCache
 from eric7.EricGui.EricAction import EricAction
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.EricWidgets.EricStretchableSpacer import EricStretchableSpacer
 from eric7.Globals import recentNamePdfFiles
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities
 
 from .PdfInfoWidget import PdfInfoWidget
@@ -79,6 +90,12 @@
         if not self.__fromEric:
             self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+            if self.__fromEric
+            else None
+        )
+
         self.__pdfDocument = QPdfDocument(self)
 
         self.__cw = QSplitter(Qt.Orientation.Horizontal, self)
@@ -961,8 +978,31 @@
         """
         canceled = False
         err = QPdfDocument.Error.IncorrectPassword
+
+        if FileSystemUtilities.isRemoteFileName(fileName):
+            try:
+                data = QByteArray(self.__remotefsInterface.readFile(fileName))
+                buffer = QBuffer(data)
+            except OSError as err:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("OpenPDF File"),
+                    self.tr(
+                        "<p>The PDF file <b>{0}</b> could not be read.</p>"
+                        "<p>Reason: {1}</p>"
+                    ).format(fileName, str(err)),
+                )
+                return
+        else:
+            buffer = None
+
         while not canceled and err == QPdfDocument.Error.IncorrectPassword:
-            err = self.__pdfDocument.load(fileName)
+            if FileSystemUtilities.isRemoteFileName(fileName):
+                buffer.open(QIODevice.OpenModeFlag.ReadOnly)
+                self.__pdfDocument.load(buffer)
+                err = QPdfDocument.Error.None_
+            else:
+                err = self.__pdfDocument.load(fileName)
             if err == QPdfDocument.Error.IncorrectPassword:
                 password, ok = QInputDialog.getText(
                     self,
@@ -986,7 +1026,11 @@
             self.__documentInfoWidget.setFileName("")
             return
 
-        self.__lastOpenPath = os.path.dirname(fileName)
+        self.__lastOpenPath = (
+            self.__remotefsInterface.dirname(fileName)
+            if FileSystemUtilities.isRemoteFileName(fileName)
+            else os.path.dirname(fileName)
+        )
         self.__setCurrentFile(fileName)
 
         documentTitle = self.__pdfDocument.metaData(QPdfDocument.MetaDataField.Title)
@@ -1019,11 +1063,20 @@
         ):
             self.__lastOpenPath = self.__project.getProjectPath()
 
-        fileName = EricFileDialog.getOpenFileName(
-            self,
-            self.tr("Open PDF File"),
-            self.__lastOpenPath,
-            self.tr("PDF Files (*.pdf);;All Files (*)"),
+        fileName = (
+            EricServerFileDialog.getOpenFileName(
+                self,
+                self.tr("Open PDF File"),
+                self.__lastOpenPath,
+                self.tr("PDF Files (*.pdf);;All Files (*)"),
+            )
+            if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath)
+            else EricFileDialog.getOpenFileName(
+                self,
+                self.tr("Open PDF File"),
+                self.__lastOpenPath,
+                self.tr("PDF Files (*.pdf);;All Files (*)"),
+            )
         )
         if fileName:
             self.__loadPdfFile(fileName)
@@ -1044,11 +1097,20 @@
             self.__lastOpenPath = self.__project.getProjectPath()
 
         if not fileName:
-            fileName = EricFileDialog.getOpenFileName(
-                self,
-                self.tr("Open PDF File"),
-                self.__lastOpenPath,
-                self.tr("PDF Files (*.pdf);;All Files (*)"),
+            fileName = (
+                EricServerFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Open PDF File"),
+                    self.__lastOpenPath,
+                    self.tr("PDF Files (*.pdf);;All Files (*)"),
+                )
+                if FileSystemUtilities.isRemoteFileName(self.__lastOpenPath)
+                else EricFileDialog.getOpenFileName(
+                    self,
+                    self.tr("Open PDF File"),
+                    self.__lastOpenPath,
+                    self.tr("PDF Files (*.pdf);;All Files (*)"),
+                )
             )
         if fileName:
             viewer = PdfViewerWindow(
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -202,6 +202,10 @@
         self.mainWidget.setCurrentWidget(self.configureTab)
         self.optionsTabWidget.setCurrentWidget(self.globalOptionsTab)
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def __defaultStatistics(self):
         """
         Private method to return the default statistics entry.
@@ -840,13 +844,18 @@
 
         if isinstance(fn, list):
             self.files = fn[:]
-        elif os.path.isdir(fn):
-            self.files = []
+        elif FileSystemUtilities.isRemoteFileName(
+            fn
+        ) and self.__remotefsInterface.isdir(fn):
             extensions = set(Preferences.getPython("Python3Extensions"))
-            for ext in extensions:
-                self.files.extend(
-                    FileSystemUtilities.direntries(fn, True, "*{0}".format(ext), 0)
-                )
+            self.files = self.__remotefsInterface.direntries(
+                fn, True, [f"*{ext}" for ext in extensions], False
+            )
+        elif FileSystemUtilities.isPlainFileName(fn) and os.path.isdir(fn):
+            extensions = set(Preferences.getPython("Python3Extensions"))
+            self.files = FileSystemUtilities.direntries(
+                fn, True, [f"*{ext}" for ext in extensions], False
+            )
         else:
             self.files = [fn]
 
@@ -1085,7 +1094,12 @@
             encoding = Utilities.get_coding(source)
         else:
             try:
-                source, encoding = Utilities.readEncodedFile(self.filename)
+                if FileSystemUtilities.isRemoteFileName(self.filename):
+                    source, encoding = self.__remotefsInterface.readEncodedFile(
+                        self.filename
+                    )
+                else:
+                    source, encoding = Utilities.readEncodedFile(self.filename)
                 source = source.splitlines(True)
             except (OSError, UnicodeError) as msg:
                 self.results = CodeStyleCheckerDialog.hasResults
@@ -1131,7 +1145,12 @@
                 self.__timenow = time.monotonic()
 
             try:
-                source, encoding = Utilities.readEncodedFile(filename)
+                if FileSystemUtilities.isRemoteFileName(filename):
+                    source, encoding = self.__remotefsInterface.readEncodedFile(
+                        filename
+                    )
+                else:
+                    source, encoding = Utilities.readEncodedFile(filename)
                 source = source.splitlines(True)
             except (OSError, UnicodeError) as msg:
                 self.results = CodeStyleCheckerDialog.hasResults
@@ -1560,7 +1579,7 @@
             return
 
         if item.parent():
-            fn = os.path.abspath(item.data(0, self.filenameRole))
+            fn = item.data(0, self.filenameRole)
             lineno = item.data(0, self.lineRole)
             position = item.data(0, self.positionRole)
             message = item.data(0, self.messageRole)
@@ -1602,7 +1621,7 @@
         for index in selectedIndexes:
             itm = self.resultList.topLevelItem(index)
             if itm.data(0, self.filenameRole) is not None:
-                fn = os.path.abspath(itm.data(0, self.filenameRole))
+                fn = itm.data(0, self.filenameRole)
                 vm.openSourceFile(fn, 1)
                 editor = vm.getOpenEditor(fn)
                 editor.clearStyleWarnings()
@@ -1620,7 +1639,7 @@
         errorFiles = []
         for index in range(self.resultList.topLevelItemCount()):
             itm = self.resultList.topLevelItem(index)
-            errorFiles.append(os.path.abspath(itm.data(0, self.filenameRole)))
+            errorFiles.append(itm.data(0, self.filenameRole))
         for file in openFiles:
             if file not in errorFiles:
                 editor = vm.getOpenEditor(file)
--- a/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -87,6 +87,10 @@
             self.syntaxCheckService = None
         self.filename = None
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
     def __resort(self):
         """
         Private method to resort the tree.
@@ -294,18 +298,35 @@
         @param fn file or list of files or directory to be checked
         @type str or list of str
         """
+        isdir = isinstance(fn, str) and (
+            self.__remotefsInterface.isdir(fn)
+            if FileSystemUtilities.isRemoteFileName(fn)
+            else os.path.isdir(fn)
+        )
+
         if isinstance(fn, list):
             files = fn
-        elif os.path.isdir(fn):
-            files = FileSystemUtilities.direntries(
-                fn,
-                filesonly=True,
-                pattern=[
-                    "*{0}".format(ext)
-                    for ext in self.syntaxCheckService.getExtensions()
-                ],
-                followsymlinks=False,
-            )
+        elif isdir:
+            if FileSystemUtilities.isRemoteFileName(fn):
+                files = self.__remotefsInterface.direntries(
+                    fn,
+                    filesonly=True,
+                    pattern=[
+                        "*{0}".format(ext)
+                        for ext in self.syntaxCheckService.getExtensions()
+                    ],
+                    followsymlinks=False,
+                )
+            else:
+                files = FileSystemUtilities.direntries(
+                    fn,
+                    filesonly=True,
+                    pattern=[
+                        "*{0}".format(ext)
+                        for ext in self.syntaxCheckService.getExtensions()
+                    ],
+                    followsymlinks=False,
+                )
         else:
             files = [fn]
 
@@ -360,13 +381,25 @@
             self.checkProgress.setVisible(True)
             QApplication.processEvents()
 
+            isdir = isinstance(fn, str) and (
+                self.__remotefsInterface.isdir(fn)
+                if FileSystemUtilities.isRemoteFileName(fn)
+                else os.path.isdir(fn)
+            )
+
             if isinstance(fn, list):
                 self.files = fn
-            elif os.path.isdir(fn):
+            elif isdir:
                 self.files = []
                 for ext in self.syntaxCheckService.getExtensions():
                     self.files.extend(
-                        FileSystemUtilities.direntries(fn, True, "*{0}".format(ext), 0)
+                        self.__remotefsInterface.direntries(
+                            fn, True, "*{0}".format(ext), False
+                        )
+                        if FileSystemUtilities.isRemoteFileName(fn)
+                        else FileSystemUtilities.direntries(
+                            fn, True, "*{0}".format(ext), False
+                        )
                     )
             else:
                 self.files = [fn]
@@ -421,7 +454,12 @@
             self.source = Utilities.normalizeCode(codestring)
         else:
             try:
-                self.source = Utilities.readEncodedFile(self.filename)[0]
+                if FileSystemUtilities.isRemoteFileName(self.filename):
+                    self.source = self.__remotefsInterface.readEncodedFile(
+                        self.filename
+                    )[0]
+                else:
+                    self.source = Utilities.readEncodedFile(self.filename)[0]
                 self.source = Utilities.normalizeCode(self.source)
             except (OSError, UnicodeError) as msg:
                 self.noResults = False
@@ -458,7 +496,11 @@
                 self.__timenow = time.monotonic()
 
             try:
-                source = Utilities.readEncodedFile(filename)[0]
+                source = (
+                    self.__remotefsInterface.readEncodedFile(filename)[0]
+                    if FileSystemUtilities.isRemoteFileName(filename)
+                    else Utilities.readEncodedFile(filename)[0]
+                )
                 source = Utilities.normalizeCode(source)
             except (OSError, UnicodeError) as msg:
                 self.noResults = False
@@ -686,7 +728,7 @@
         vm = ericApp().getObject("ViewManager")
 
         if itm.parent():
-            fn = os.path.abspath(itm.data(0, self.filenameRole))
+            fn = itm.data(0, self.filenameRole)
             lineno = itm.data(0, self.lineRole)
             index = itm.data(0, self.indexRole)
             error = itm.data(0, self.errorRole)
@@ -699,7 +741,7 @@
             else:
                 editor.toggleSyntaxError(lineno, index, True, error, show=True)
         else:
-            fn = os.path.abspath(itm.data(0, self.filenameRole))
+            fn = itm.data(0, self.filenameRole)
             vm.openSourceFile(fn)
             editor = vm.getOpenEditor(fn)
             for index in range(itm.childCount()):
@@ -731,7 +773,7 @@
         for index in selectedIndexes:
             itm = self.resultList.topLevelItem(index)
             if itm.data(0, self.filenameRole) is not None:
-                fn = os.path.abspath(itm.data(0, self.filenameRole))
+                fn = itm.data(0, self.filenameRole)
                 vm.openSourceFile(fn, 1)
                 editor = vm.getOpenEditor(fn)
                 editor.clearSyntaxError()
@@ -751,7 +793,7 @@
         errorFiles = []
         for index in range(self.resultList.topLevelItemCount()):
             itm = self.resultList.topLevelItem(index)
-            errorFiles.append(os.path.abspath(itm.data(0, self.filenameRole)))
+            errorFiles.append(itm.data(0, self.filenameRole))
         for fn in vm.getOpenFilenames():
             if fn not in errorFiles:
                 editor = vm.getOpenEditor(fn)
--- a/src/eric7/Preferences/ConfigurationDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Preferences/ConfigurationDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -210,6 +210,13 @@
                     None,
                     None,
                 ],
+                "ericServerPage": [
+                    self.tr("eric-ide Server"),
+                    "preferences-eric-server",
+                    "EricServerPage",
+                    None,
+                    None,
+                ],
                 "graphicsPage": [
                     self.tr("Graphics"),
                     "preferences-graphics",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Preferences/ConfigurationPages/EricServerPage.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the eric-ide server related settings.
+"""
+
+from eric7 import Preferences
+
+from .ConfigurationPageBase import ConfigurationPageBase
+from .Ui_EricServerPage import Ui_EricServerPage
+
+
+class EricServerPage(ConfigurationPageBase, Ui_EricServerPage):
+    """
+    Class implementing the eric-ide server related settings.
+    """
+
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__()
+        self.setupUi(self)
+        self.setObjectName("EricServerPage")
+
+        # set initial values
+        self.timeoutSpinBox.setValue(Preferences.getEricServer("ConnectionTimeout"))
+        self.startShellCheckBox.setChecked(Preferences.getEricServer("AutostartShell"))
+
+    def save(self):
+        """
+        Public slot to save the Cooperation configuration.
+        """
+        Preferences.setEricServer("ConnectionTimeout", self.timeoutSpinBox.value())
+        Preferences.setEricServer("AutostartShell", self.startShellCheckBox.isChecked())
+
+
+def create(dlg):  # noqa: U100
+    """
+    Module function to create the configuration page.
+
+    @param dlg reference to the configuration dialog
+    @type ConfigurationDialog
+    @return reference to the instantiated page
+    @rtype ConfigurationPageBase
+    """
+    page = EricServerPage()
+    return page
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Preferences/ConfigurationPages/EricServerPage.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EricServerPage</class>
+ <widget class="QWidget" name="EricServerPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>510</width>
+    <height>452</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <widget class="QLabel" name="headerLabel">
+     <property name="text">
+      <string>&lt;b&gt;Configure eric-ide Server Settings&lt;/b&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line11">
+     <property name="frameShape">
+      <enum>QFrame::HLine</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>&lt;b&gt;Note:&lt;/b&gt; The eric-ide server is configured via command line parameters. The parameters of this page configure the interface to the eric-ide server.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Server Connection</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Default Timeout:</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="timeoutSpinBox">
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+        <property name="suffix">
+         <string> s</string>
+        </property>
+        <property name="minimum">
+         <number>5</number>
+        </property>
+        <property name="maximum">
+         <number>60</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>294</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>Shell</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QCheckBox" name="startShellCheckBox">
+        <property name="toolTip">
+         <string>Select this to start an eric-ide Server Shell when a connection to an eric-ide Server is established.</string>
+        </property>
+        <property name="text">
+         <string>Start server Shell when server is conncted</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>87</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>timeoutSpinBox</tabstop>
+  <tabstop>startShellCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Preferences/ConfigurationPages/Ui_EricServerPage.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,71 @@
+# Form implementation generated from reading ui file 'src/eric7/Preferences/ConfigurationPages/EricServerPage.ui'
+#
+# Created by: PyQt6 UI code generator 6.7.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_EricServerPage(object):
+    def setupUi(self, EricServerPage):
+        EricServerPage.setObjectName("EricServerPage")
+        EricServerPage.resize(510, 452)
+        self.verticalLayout_2 = QtWidgets.QVBoxLayout(EricServerPage)
+        self.verticalLayout_2.setObjectName("verticalLayout_2")
+        self.headerLabel = QtWidgets.QLabel(parent=EricServerPage)
+        self.headerLabel.setObjectName("headerLabel")
+        self.verticalLayout_2.addWidget(self.headerLabel)
+        self.line11 = QtWidgets.QFrame(parent=EricServerPage)
+        self.line11.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+        self.line11.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+        self.line11.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+        self.line11.setObjectName("line11")
+        self.verticalLayout_2.addWidget(self.line11)
+        self.label_2 = QtWidgets.QLabel(parent=EricServerPage)
+        self.label_2.setWordWrap(True)
+        self.label_2.setObjectName("label_2")
+        self.verticalLayout_2.addWidget(self.label_2)
+        self.groupBox = QtWidgets.QGroupBox(parent=EricServerPage)
+        self.groupBox.setObjectName("groupBox")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.label = QtWidgets.QLabel(parent=self.groupBox)
+        self.label.setObjectName("label")
+        self.horizontalLayout.addWidget(self.label)
+        self.timeoutSpinBox = QtWidgets.QSpinBox(parent=self.groupBox)
+        self.timeoutSpinBox.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
+        self.timeoutSpinBox.setMinimum(5)
+        self.timeoutSpinBox.setMaximum(60)
+        self.timeoutSpinBox.setObjectName("timeoutSpinBox")
+        self.horizontalLayout.addWidget(self.timeoutSpinBox)
+        spacerItem = QtWidgets.QSpacerItem(294, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+        self.horizontalLayout.addItem(spacerItem)
+        self.verticalLayout_2.addWidget(self.groupBox)
+        self.groupBox_2 = QtWidgets.QGroupBox(parent=EricServerPage)
+        self.groupBox_2.setObjectName("groupBox_2")
+        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_2)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.startShellCheckBox = QtWidgets.QCheckBox(parent=self.groupBox_2)
+        self.startShellCheckBox.setObjectName("startShellCheckBox")
+        self.verticalLayout.addWidget(self.startShellCheckBox)
+        self.verticalLayout_2.addWidget(self.groupBox_2)
+        spacerItem1 = QtWidgets.QSpacerItem(20, 87, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+        self.verticalLayout_2.addItem(spacerItem1)
+
+        self.retranslateUi(EricServerPage)
+        QtCore.QMetaObject.connectSlotsByName(EricServerPage)
+        EricServerPage.setTabOrder(self.timeoutSpinBox, self.startShellCheckBox)
+
+    def retranslateUi(self, EricServerPage):
+        _translate = QtCore.QCoreApplication.translate
+        self.headerLabel.setText(_translate("EricServerPage", "<b>Configure eric-ide Server Settings</b>"))
+        self.label_2.setText(_translate("EricServerPage", "<b>Note:</b> The eric-ide server is configured via command line parameters. The parameters of this page configure the interface to the eric-ide server."))
+        self.groupBox.setTitle(_translate("EricServerPage", "Server Connection"))
+        self.label.setText(_translate("EricServerPage", "Default Timeout:"))
+        self.timeoutSpinBox.setSuffix(_translate("EricServerPage", " s"))
+        self.groupBox_2.setTitle(_translate("EricServerPage", "Shell"))
+        self.startShellCheckBox.setToolTip(_translate("EricServerPage", "Select this to start an eric-ide Server Shell when a connection to an eric-ide Server is established."))
+        self.startShellCheckBox.setText(_translate("EricServerPage", "Start server Shell when server is conncted"))
--- a/src/eric7/Preferences/__init__.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Preferences/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -1724,7 +1724,7 @@
     else:
         jediDefaults["MouseClickGotoModifiers"] = Qt.KeyboardModifier.ControlModifier
 
-    # defaults for Hex Editor
+    # defaults for PDF viewer
     pdfViewerDefaults = {
         "PdfViewerState": QByteArray(),
         "PdfViewerSplitterState": QByteArray(),
@@ -1738,6 +1738,13 @@
         "PdfSearchHighlightAll": True,
     }
 
+    # defaults for the eric-ide server interface
+    ericServerDefaults = {
+        "ConnectionProfiles": "{}",  # JSON encoded dictionary
+        "ConnectionTimeout": 10,  # timeout in seconds
+        "AutostartShell": True,
+    }
+
 
 def readToolGroups():
     """
@@ -4163,7 +4170,7 @@
 
 def getPdfViewer(key):
     """
-    Module function to retrieve the Pdf Viewer related settings.
+    Module function to retrieve the PDF Viewer related settings.
 
     @param key the key of the value to get
     @type str
@@ -4196,7 +4203,7 @@
 
 def setPdfViewer(key, value):
     """
-    Module function to store the Pdf Viewer related settings.
+    Module function to store the PDF Viewer related settings.
 
     @param key the key of the setting to be set
     @type str
@@ -4208,6 +4215,52 @@
     Prefs.settings.setValue("PdfViewer/" + key, value)
 
 
+def getEricServer(key):
+    """
+    Module function to retrieve the eric-ide server interface related settings.
+
+    @param key the key of the value to get
+    @type str
+    @return the requested user setting
+    @rtype Any
+    """
+    prefix = "EricServer/"
+
+    if key in ("ConnectionTimeout",):
+        return int(
+            Prefs.settings.value(f"{prefix}{key}", Prefs.ericServerDefaults[key])
+        )
+    elif key in ("AutostartShell",):
+        return toBool(
+            Prefs.settings.value(f"{prefix}{key}", Prefs.ericServerDefaults[key])
+        )
+    elif key in ("ConnectionProfiles",):
+        jsonStr = Prefs.settings.value(f"{prefix}{key}", Prefs.ericServerDefaults[key])
+        if jsonStr:
+            return json.loads(jsonStr)
+        else:
+            return None
+    else:
+        return Prefs.settings.value(f"{prefix}{key}", Prefs.ericServerDefaults[key])
+
+
+def setEricServer(key, value):
+    """
+    Module function to store the eric-ide server interface related settings.
+
+    @param key the key of the setting to be set
+    @type str
+    @param value the value to be set
+    @type Any
+    """
+    prefix = "EricServer/"
+
+    if key in ("ConnectionProfiles",):
+        Prefs.settings.setValue(f"{prefix}{key}", json.dumps(value))
+    else:
+        Prefs.settings.setValue(f"{prefix}{key}", value)
+
+
 def getGeometry(key):
     """
     Module function to retrieve the display geometry.
--- a/src/eric7/Project/AddDirectoryDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/AddDirectoryDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -8,9 +8,10 @@
 """
 
 from PyQt6.QtCore import pyqtSlot
-from PyQt6.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
 
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_AddDirectoryDialog import Ui_AddDirectoryDialog
 
@@ -42,13 +43,23 @@
             self.setObjectName(name)
         self.setupUi(self)
 
+        self.__remoteMode = (
+            bool(startdir) and FileSystemUtilities.isRemoteFileName(startdir)
+        ) or FileSystemUtilities.isRemoteFileName(pro.getProjectPath())
+
         self.sourceDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.sourceDirPicker.setDefaultDirectory(startdir)
+        self.sourceDirPicker.setRemote(self.__remoteMode)
+
         self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirPicker.setDefaultDirectory(startdir)
+        self.targetDirPicker.setRemote(self.__remoteMode)
 
         self.__project = pro
-        self.targetDirPicker.setText(self.__project.getProjectPath())
+        if startdir:
+            self.targetDirPicker.setText(startdir)
+        else:
+            self.targetDirPicker.setText(pro.getProjectPath())
 
         if fileTypeFilter and fileTypeFilter != "TRANSLATIONS":
             self.filterComboBox.addItem(
@@ -65,6 +76,8 @@
                 )
         self.filterComboBox.setCurrentIndex(0)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
@@ -103,6 +116,10 @@
         if directory.startswith(self.__project.getProjectPath()):
             self.targetDirPicker.setText(directory)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            bool(directory)
+        )
+
     def getData(self):
         """
         Public slot to retrieve the dialogs data.
--- a/src/eric7/Project/AddFileDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/AddFileDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -10,9 +10,11 @@
 import os
 
 from PyQt6.QtCore import pyqtSlot
-from PyQt6.QtWidgets import QDialog
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
 
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Ui_AddFileDialog import Ui_AddFileDialog
 
@@ -42,9 +44,16 @@
             self.setObjectName(name)
         self.setupUi(self)
 
+        self.__remoteMode = (
+            bool(startdir) and FileSystemUtilities.isRemoteFileName(startdir)
+        ) or FileSystemUtilities.isRemoteFileName(pro.getProjectPath())
+
         self.sourceFilesPicker.setMode(EricPathPickerModes.OPEN_FILES_MODE)
+        self.sourceFilesPicker.setRemote(self.__remoteMode)
+
         self.targetDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirPicker.setDefaultDirectory(startdir)
+        self.targetDirPicker.setRemote(self.__remoteMode)
 
         if startdir:
             self.targetDirPicker.setText(startdir)
@@ -57,6 +66,8 @@
         if self.fileTypeFilter is not None:
             self.sourcecodeCheckBox.hide()
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
@@ -66,12 +77,16 @@
         Private slot to perform actions before the source files selection
         dialog is shown.
         """
-        path = self.targetDirPicker.text()
-        if not path:
-            path = self.startdir
-        self.sourceFilesPicker.setDefaultDirectory(path)
+        targetPath = self.targetDirPicker.text()
+        if not targetPath:
+            targetPath = self.startdir
+        self.sourceFilesPicker.setDefaultDirectory(targetPath)
 
-        caption = self.tr("Select Files")
+        caption = (
+            self.tr("Select Files")
+            if self.__remoteMode
+            else self.tr("Select Remote Files")
+        )
         if self.fileTypeFilter is None:
             dfilter = self.__project.getFileCategoryFilterString(withAll=True)
         elif (
@@ -103,14 +118,23 @@
         @param sfile the text of the source file picker
         @type str
         """
-        sfile = str(self.sourceFilesPicker.firstPath())
+        sfile = self.sourceFilesPicker.firstStrPath()
         if sfile.startswith(self.__project.getProjectPath()):
-            if os.path.isdir(sfile):
-                directory = sfile
+            if self.__remoteMode:
+                fsInterface = (
+                    ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+                )
+                directory = (
+                    sfile if fsInterface.isdir(sfile) else fsInterface.dirname(sfile)
+                )
             else:
-                directory = os.path.dirname(sfile)
+                directory = sfile if os.path.isdir(sfile) else os.path.dirname(sfile)
             self.targetDirPicker.setText(directory)
 
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            bool(sfile)
+        )
+
     def getData(self):
         """
         Public slot to retrieve the dialogs data.
@@ -120,7 +144,7 @@
         @rtype tuple of (list of string, string, boolean)
         """
         return (
-            [str(p) for p in self.sourceFilesPicker.paths()],
+            self.sourceFilesPicker.strPaths(),
             self.targetDirPicker.text(),
             self.sourcecodeCheckBox.isChecked(),
         )
--- a/src/eric7/Project/DebuggerPropertiesDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/DebuggerPropertiesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -28,16 +28,18 @@
     settings.
     """
 
-    def __init__(self, project, parent=None, name=None):
+    def __init__(self, project, isRemote=False, parent=None, name=None):
         """
         Constructor
 
         @param project reference to the project object
         @type Project
-        @param parent parent widget of this dialog
-        @type QWidget
-        @param name name of this dialog
-        @type str
+        @param isRemote flag indicating a remote project (defaults to False)
+        @type bool (optional)
+        @param parent parent widget of this dialog (defaults to None)
+        @type QWidget (optional)
+        @param name name of this dialog (defaults to None)
+        @type str (optional)
         """
         super().__init__(parent)
         if name:
@@ -45,6 +47,7 @@
         self.setupUi(self)
 
         self.project = project
+        self.__isRemote = isRemote
 
         debugClientsHistory = Preferences.getProject("DebugClientsHistory")
         self.debugClientPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
@@ -61,8 +64,11 @@
 
         venvManager = ericApp().getObject("VirtualEnvManager")
 
+        # Virtual Environment
         self.venvGroupBox.setVisible(
             not self.project.getProjectData(dataKey="EMBEDDED_VENV")
+            and not isRemote
+            # TODO: change once remote environments have been implemented
         )
         self.venvComboBox.addItem("")
         if self.project.getProjectData(dataKey="EMBEDDED_VENV"):
@@ -89,7 +95,12 @@
                 else:
                     venvIndex = 0
         self.venvComboBox.setCurrentIndex(venvIndex)
-        if self.project.debugProperties["DEBUGCLIENT"]:
+
+        # Debug Client
+        self.debugClientGroup.setVisible(not isRemote)
+        if isRemote:
+            self.debugClientPicker.clear()
+        elif self.project.debugProperties["DEBUGCLIENT"]:
             self.debugClientPicker.setText(
                 self.project.debugProperties["DEBUGCLIENT"], toNative=False
             )
@@ -101,12 +112,17 @@
             else:
                 debugClient = ""
             self.debugClientPicker.setText(debugClient, toNative=False)
+
+        # Debug Environment
         self.debugEnvironmentOverrideCheckBox.setChecked(
             self.project.debugProperties["ENVIRONMENTOVERRIDE"]
         )
         self.debugEnvironmentEdit.setText(
             self.project.debugProperties["ENVIRONMENTSTRING"]
         )
+
+        # Remote (ssh) Debugger
+        self.remoteDebuggerGroup.setVisible(not isRemote)
         self.remoteDebuggerGroup.setChecked(
             self.project.debugProperties["REMOTEDEBUGGER"]
         )
@@ -120,11 +136,20 @@
         )
         self.translationRemoteEdit.setText(self.project.debugProperties["REMOTEPATH"])
         self.translationLocalEdit.setText(self.project.debugProperties["LOCALPATH"])
+
+        # Console Debugger
+        self.consoleDebuggerGroup.setVisible(not isRemote)
         self.consoleDebuggerGroup.setChecked(
             self.project.debugProperties["CONSOLEDEBUGGER"]
         )
         self.consoleCommandEdit.setText(self.project.debugProperties["CONSOLECOMMAND"])
-        self.redirectCheckBox.setChecked(self.project.debugProperties["REDIRECT"])
+
+        # Redirect stdin/stdout/stderr
+        self.redirectCheckBox.setChecked(
+            self.project.debugProperties["REDIRECT"] or isRemote
+        )
+
+        # No encoding
         self.noEncodingCheckBox.setChecked(self.project.debugProperties["NOENCODING"])
 
         msh = self.minimumSizeHint()
@@ -148,17 +173,20 @@
         """
         self.project.debugProperties["VIRTUALENV"] = self.venvComboBox.currentText()
 
-        self.project.debugProperties["DEBUGCLIENT"] = self.debugClientPicker.text(
-            toNative=False
-        )
-        if not self.project.debugProperties["DEBUGCLIENT"]:
-            if self.project.getProjectData(dataKey="PROGLANGUAGE") == "Python3":
-                debugClient = os.path.join(
-                    getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py"
-                )
-            else:
-                debugClient = ""
-            self.project.debugProperties["DEBUGCLIENT"] = debugClient
+        if self.__isRemote:
+            self.project.debugProperties["DEBUGCLIENT"] = ""
+        else:
+            self.project.debugProperties["DEBUGCLIENT"] = self.debugClientPicker.text(
+                toNative=False
+            )
+            if not self.project.debugProperties["DEBUGCLIENT"]:
+                if self.project.getProjectData(dataKey="PROGLANGUAGE") == "Python3":
+                    debugClient = os.path.join(
+                        getConfig("ericDir"), "DebugClients", "Python", "DebugClient.py"
+                    )
+                else:
+                    debugClient = ""
+                self.project.debugProperties["DEBUGCLIENT"] = debugClient
 
         self.project.debugProperties["ENVIRONMENTOVERRIDE"] = (
             self.debugEnvironmentOverrideCheckBox.isChecked()
@@ -166,25 +194,43 @@
         self.project.debugProperties["ENVIRONMENTSTRING"] = (
             self.debugEnvironmentEdit.text()
         )
-        self.project.debugProperties["REMOTEDEBUGGER"] = (
-            self.remoteDebuggerGroup.isChecked()
-        )
-        self.project.debugProperties["REMOTEHOST"] = self.remoteHostEdit.text()
-        self.project.debugProperties["REMOTECOMMAND"] = self.remoteCommandEdit.text()
-        self.project.debugProperties["REMOTEDEBUGCLIENT"] = (
-            self.remoteDebugClientEdit.text()
+
+        if self.__isRemote:
+            self.project.debugProperties["REMOTEDEBUGGER"] = False
+        else:
+            self.project.debugProperties["REMOTEDEBUGGER"] = (
+                self.remoteDebuggerGroup.isChecked()
+            )
+            self.project.debugProperties["REMOTEHOST"] = self.remoteHostEdit.text()
+            self.project.debugProperties["REMOTECOMMAND"] = (
+                self.remoteCommandEdit.text()
+            )
+            self.project.debugProperties["REMOTEDEBUGCLIENT"] = (
+                self.remoteDebugClientEdit.text()
+            )
+            self.project.debugProperties["PATHTRANSLATION"] = (
+                self.pathTranslationGroup.isChecked()
+            )
+            self.project.debugProperties["REMOTEPATH"] = (
+                self.translationRemoteEdit.text()
+            )
+            self.project.debugProperties["LOCALPATH"] = self.translationLocalEdit.text()
+
+        if self.__isRemote:
+            self.project.debugProperties["CONSOLEDEBUGGER"] = False
+        else:
+            self.project.debugProperties["CONSOLEDEBUGGER"] = (
+                self.consoleDebuggerGroup.isChecked()
+            )
+            self.project.debugProperties["CONSOLECOMMAND"] = (
+                self.consoleCommandEdit.text()
+            )
+
+        self.project.debugProperties["REDIRECT"] = (
+            self.redirectCheckBox.isChecked() or self.__isRemote
         )
-        self.project.debugProperties["PATHTRANSLATION"] = (
-            self.pathTranslationGroup.isChecked()
-        )
-        self.project.debugProperties["REMOTEPATH"] = self.translationRemoteEdit.text()
-        self.project.debugProperties["LOCALPATH"] = self.translationLocalEdit.text()
-        self.project.debugProperties["CONSOLEDEBUGGER"] = (
-            self.consoleDebuggerGroup.isChecked()
-        )
-        self.project.debugProperties["CONSOLECOMMAND"] = self.consoleCommandEdit.text()
-        self.project.debugProperties["REDIRECT"] = self.redirectCheckBox.isChecked()
         self.project.debugProperties["NOENCODING"] = self.noEncodingCheckBox.isChecked()
+
         self.project.debugPropertiesLoaded = True
         self.project.debugPropertiesChanged = True
 
--- a/src/eric7/Project/DebuggerPropertiesDialog.ui	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/DebuggerPropertiesDialog.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -18,7 +18,7 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <widget class="QGroupBox" name="groupBox">
+    <widget class="QGroupBox" name="debugClientGroup">
      <property name="title">
       <string>Debug Client</string>
      </property>
--- a/src/eric7/Project/DebuggerPropertiesFile.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/DebuggerPropertiesFile.py	Fri Jun 07 13:58:16 2024 +0200
@@ -17,6 +17,8 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
 
@@ -48,6 +50,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         debuggerPropertiesDict = {
             "header": {
                 "comment": "eric debugger properties file for project {0}".format(
@@ -66,13 +72,18 @@
 
         try:
             jsonString = json.dumps(debuggerPropertiesDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Debugger Properties")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Debugger Properties")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Debugger Properties"),
+                    title,
                     self.tr(
                         "<p>The project debugger properties file"
                         " <b>{0}</b> could not be written.</p>"
@@ -93,14 +104,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Debugger Properties")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Debugger Properties")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             debuggerPropertiesDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Debugger Properties"),
+                title,
                 self.tr(
                     "<p>The project debugger properties file <b>{0}</b>"
                     " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/Project/Project.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/Project.py	Fri Jun 07 13:58:16 2024 +0200
@@ -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")
@@ -2783,25 +3198,38 @@
 
             if self.__pdata["EMBEDDED_VENV"]:
                 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 +3238,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 +3372,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 +3404,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 +3465,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)
 
@@ -3110,7 +3648,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 +3683,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 +3751,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
@@ -3235,7 +3799,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 +3833,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 +3896,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 +3906,6 @@
                 if not res:
                     return False
 
-            self.name = fpath.stem
             ok = self.__writeProject(str(fpath))
 
             if ok:
@@ -3451,7 +4018,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 +4030,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 +4046,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 +4100,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 +4136,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 +4161,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 +4199,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 +4300,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 +4337,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 +4381,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 +4392,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 +4405,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 +4419,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 +4476,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 +4493,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):
@@ -3960,6 +4581,7 @@
         @return name of the project's virtual environment
         @rtype str
         """
+        # TODO: remote server not supported yet
         venvName = (
             self.__venvConfiguration["name"]
             if self.__pdata["EMBEDDED_VENV"] and bool(self.__venvConfiguration["name"])
@@ -3981,6 +4603,7 @@
         @return path name of the embedded virtual environment
         @rtype str
         """
+        # TODO: remote server not supported yet
         if self.__pdata["EMBEDDED_VENV"]:
             return self.__findEmbeddedEnvironment()
         else:
@@ -3997,6 +4620,7 @@
         @return path of the project's interpreter
         @rtype str
         """
+        # TODO: remote server not supported yet
         interpreter = (
             self.__venvConfiguration["interpreter"]
             if self.__pdata["EMBEDDED_VENV"]
@@ -4022,6 +4646,7 @@
         @return executable search path prefix
         @rtype str
         """
+        # TODO: remote server not supported yet
         if self.__pdata["EMBEDDED_VENV"]:
             execPath = self.__venvConfiguration["exec_path"]
         else:
@@ -4043,6 +4668,7 @@
         @return testing framework name of the project
         @rtype str
         """
+        # TODO: remote server not supported yet
         try:
             return self.__pdata["TESTING_FRAMEWORK"]
         except KeyError:
@@ -4070,7 +4696,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 +4733,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 +4745,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 +4826,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 +4912,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
         ###################################################################
@@ -5363,6 +6041,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 +6058,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 +6114,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 +6132,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 +6145,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 +6167,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 +6237,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 +6265,8 @@
         recursiveSearch = Preferences.getProject("SearchNewFilesRecursively")
         newFiles = []
 
+        isRemote = FileSystemUtilities.isRemoteFileName(self.ppath)
+
         ignore_patterns = [
             pattern
             for pattern, filetype in self.__pdata["FILETYPES"].items()
@@ -5577,9 +6281,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 +6308,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 +6333,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(
@@ -5769,6 +6498,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 +6677,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 +6731,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)
@@ -6606,7 +7354,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 +7364,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 +7377,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 +7610,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()
@@ -7209,6 +7973,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
--- a/src/eric7/Project/ProjectBaseBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectBaseBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -269,7 +269,9 @@
         if self.backMenu is not None:
             self.backMenu.setEnabled(True)
 
-        if self.project.vcs is not None:
+        if self.project.vcs is not None and not FileSystemUtilities.isRemoteFileName(
+            self.project.getProjectPath()
+        ):
             self.vcsHelper = self.project.vcs.vcsGetProjectBrowserHelper(
                 self, self.project, self.isTranslationsBrowser
             )
@@ -280,6 +282,8 @@
                 self.dirMenu,
                 self.dirMultiMenu,
             )
+        else:
+            self.vcsHelper = None
 
     def _newProject(self):
         """
@@ -502,7 +506,7 @@
         if self.project.vcs is None:
             for act in self.menuActions:
                 act.setEnabled(True)
-        else:
+        elif self.vcsHelper is not None:
             self.vcsHelper.showContextMenu(menu, self.menuActions)
 
     def _showContextMenuMulti(self, menu):
@@ -519,7 +523,7 @@
         if self.project.vcs is None:
             for act in self.multiMenuActions:
                 act.setEnabled(True)
-        else:
+        elif self.vcsHelper is not None:
             self.vcsHelper.showContextMenuMulti(menu, self.multiMenuActions)
 
     def _showContextMenuDir(self, menu):
@@ -535,7 +539,7 @@
         if self.project.vcs is None:
             for act in self.dirMenuActions:
                 act.setEnabled(True)
-        else:
+        elif self.vcsHelper is not None:
             self.vcsHelper.showContextMenuDir(menu, self.dirMenuActions)
 
     def _showContextMenuDirMulti(self, menu):
@@ -551,7 +555,7 @@
         if self.project.vcs is None:
             for act in self.dirMultiMenuActions:
                 act.setEnabled(True)
-        else:
+        elif self.vcsHelper is not None:
             self.vcsHelper.showContextMenuDirMulti(menu, self.dirMultiMenuActions)
 
     def _showContextMenuBack(self, _menu):
--- a/src/eric7/Project/ProjectBrowserModel.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectBrowserModel.py	Fri Jun 07 13:58:16 2024 +0200
@@ -115,7 +115,7 @@
     items.
     """
 
-    def __init__(self, parent, projectType, text, path=""):
+    def __init__(self, parent, projectType, text, path="", fsInterface=None):
         """
         Constructor
 
@@ -127,8 +127,13 @@
         @type str
         @param path path of the directory
         @type str
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserSimpleDirectoryItem.__init__(self, parent, text, path=path)
+        BrowserSimpleDirectoryItem.__init__(
+            self, parent, text, path=path, fsInterface=fsInterface
+        )
         ProjectBrowserItemMixin.__init__(self, projectType)
 
         self.type_ = BrowserItemType.PbSimpleDirectory
@@ -139,7 +144,9 @@
     Class implementing the data structure for project browser directory items.
     """
 
-    def __init__(self, parent, dinfo, projectType, full=True, bold=False):
+    def __init__(
+        self, parent, dinfo, projectType, full=True, bold=False, fsInterface=None
+    ):
         """
         Constructor
 
@@ -153,8 +160,11 @@
         @type bool
         @param bold flag indicating a highlighted font
         @type bool
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserDirectoryItem.__init__(self, parent, dinfo, full)
+        BrowserDirectoryItem.__init__(self, parent, dinfo, full, fsInterface)
         ProjectBrowserItemMixin.__init__(self, projectType, bold)
 
         self.type_ = BrowserItemType.PbDirectory
@@ -166,7 +176,14 @@
     """
 
     def __init__(
-        self, parent, finfo, projectType, full=True, bold=False, sourceLanguage=""
+        self,
+        parent,
+        finfo,
+        projectType,
+        full=True,
+        bold=False,
+        sourceLanguage="",
+        fsInterface=None,
     ):
         """
         Constructor
@@ -183,8 +200,11 @@
         @type bool
         @param sourceLanguage source code language of the project
         @type str
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage)
+        BrowserFileItem.__init__(self, parent, finfo, full, sourceLanguage, fsInterface)
         ProjectBrowserItemMixin.__init__(self, projectType, bold)
 
         self.type_ = BrowserItemType.PbFile
@@ -199,12 +219,15 @@
 
     vcsStateChanged = pyqtSignal(str)
 
-    def __init__(self, parent):
+    def __init__(self, parent, fsInterface=None):
         """
         Constructor
 
         @param parent reference to parent object
         @type Project.Project
+        @param fsInterface reference to the 'eric-ide' server interface object
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
         super().__init__(parent, nopopulate=True)
 
@@ -216,7 +239,10 @@
         self.project = parent
         self.__projectBrowser = None
 
+        self.__remotefsInterface = fsInterface
+
         self.watchedDirItems = {}
+
         self.__watcherActive = True
         watcher = EricFileSystemWatcher.instance()
         watcher.directoryCreated.connect(lambda x: self.entryCreated(x, isDir=True))
@@ -321,61 +347,106 @@
         """
         self._addWatchedItem(parentItem)
 
-        qdir = QDir(parentItem.dirName())
+        dirName = parentItem.dirName()
+        if FileSystemUtilities.isPlainFileName(dirName):
+            qdir = QDir(parentItem.dirName())
 
-        fileFilter = (
-            (QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot)
-            if Preferences.getProject("BrowsersListHiddenFiles")
-            else QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot
-        )
-        entryInfoList = qdir.entryInfoList(fileFilter)
+            fileFilter = (
+                (
+                    QDir.Filter.AllEntries
+                    | QDir.Filter.Hidden
+                    | QDir.Filter.NoDotAndDotDot
+                )
+                if Preferences.getProject("BrowsersListHiddenFiles")
+                else QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot
+            )
+            entryInfoList = qdir.entryInfoList(fileFilter)
 
-        if len(entryInfoList) > 0:
-            if repopulate:
-                self.beginInsertRows(
-                    self.createIndex(parentItem.row(), 0, parentItem),
-                    0,
-                    len(entryInfoList) - 1,
-                )
-            states = {}
-            if self.project.vcs is not None:
-                for f in entryInfoList:
-                    fname = f.absoluteFilePath()
-                    states[os.path.normcase(fname)] = 0
-                dname = parentItem.dirName()
-                self.project.vcs.clearStatusCache()
-                states = self.project.vcs.vcsAllRegisteredStates(states, dname)
+            if len(entryInfoList) > 0:
+                if repopulate:
+                    self.beginInsertRows(
+                        self.createIndex(parentItem.row(), 0, parentItem),
+                        0,
+                        len(entryInfoList) - 1,
+                    )
+                states = {}
+                if self.project.vcs is not None:
+                    for f in entryInfoList:
+                        fname = f.absoluteFilePath()
+                        states[os.path.normcase(fname)] = 0
+                    dname = parentItem.dirName()
+                    self.project.vcs.clearStatusCache()
+                    states = self.project.vcs.vcsAllRegisteredStates(states, dname)
 
-            for f in entryInfoList:
-                node = (
-                    ProjectBrowserDirectoryItem(
-                        parentItem,
-                        FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
-                        parentItem.getProjectTypes()[0],
-                        False,
-                    )
-                    if f.isDir()
-                    else ProjectBrowserFileItem(
-                        parentItem,
-                        FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
-                        parentItem.getProjectTypes()[0],
+                for f in entryInfoList:
+                    node = (
+                        ProjectBrowserDirectoryItem(
+                            parentItem,
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
+                            parentItem.getProjectTypes()[0],
+                            False,
+                            fsInterface=self.__remotefsInterface,
+                        )
+                        if f.isDir()
+                        else ProjectBrowserFileItem(
+                            parentItem,
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
+                            parentItem.getProjectTypes()[0],
+                            fsInterface=self.__remotefsInterface,
+                        )
                     )
-                )
-                if self.project.vcs is not None:
-                    fname = f.absoluteFilePath()
-                    if (
-                        states[os.path.normcase(fname)]
-                        == VersionControlState.Controlled
-                    ):
-                        node.addVcsStatus(self.project.vcs.vcsName())
-                        self.project.clearStatusMonitorCachedState(f.absoluteFilePath())
+                    if self.project.vcs is not None:
+                        fname = f.absoluteFilePath()
+                        if (
+                            states[os.path.normcase(fname)]
+                            == VersionControlState.Controlled
+                        ):
+                            node.addVcsStatus(self.project.vcs.vcsName())
+                            self.project.clearStatusMonitorCachedState(
+                                f.absoluteFilePath()
+                            )
+                        else:
+                            node.addVcsStatus(self.tr("local"))
                     else:
-                        node.addVcsStatus(self.tr("local"))
-                else:
+                        node.addVcsStatus("")
+                    self._addItem(node, parentItem)
+                if repopulate:
+                    self.endInsertRows()
+
+        elif FileSystemUtilities.isRemoteFileName(dirName):
+            entriesList = self.__remotefsInterface.listdir(dirName)[2]
+            if len(entriesList) > 0:
+                if repopulate:
+                    self.beginInsertRows(
+                        self.createIndex(parentItem.row(), 0, parentItem),
+                        0,
+                        len(entryInfoList) - 1,
+                    )
+                for entry in entriesList:
+                    node = (
+                        ProjectBrowserDirectoryItem(
+                            parentItem,
+                            entry["path"],
+                            parentItem.getProjectTypes()[0],
+                            False,
+                            fsInterface=self.__remotefsInterface,
+                        )
+                        if entry["is_dir"]
+                        else ProjectBrowserFileItem(
+                            parentItem,
+                            entry["path"],
+                            parentItem.getProjectTypes()[0],
+                            fsInterface=self.__remotefsInterface,
+                        )
+                    )
                     node.addVcsStatus("")
-                self._addItem(node, parentItem)
-            if repopulate:
-                self.endInsertRows()
+                    self._addItem(node, parentItem)
+                if repopulate:
+                    self.endInsertRows()
 
     def projectClosed(self):
         """
@@ -405,7 +476,9 @@
         states = {}
         fileCategories = self.project.getFileCategories()
 
-        if self.project.vcs is not None:
+        if self.project.vcs is not None and not FileSystemUtilities.isRemoteFileName(
+            self.project.ppath
+        ):
             for fileCategory in fileCategories:
                 for fn in self.project.getProjectData(dataKey=fileCategory):
                     states[os.path.normcase(os.path.join(self.project.ppath, fn))] = 0
@@ -423,7 +496,16 @@
             )
 
             for fn in self.project.getProjectData(dataKey=fileCategory):
-                fname = os.path.join(self.project.ppath, fn)
+                fname = (
+                    self.__remotefsInterface.join(self.project.ppath, fn)
+                    if FileSystemUtilities.isRemoteFileName(self.project.ppath)
+                    else os.path.join(self.project.ppath, fn)
+                )
+                isdir = (
+                    self.__remotefsInterface.isdir(fname)
+                    if FileSystemUtilities.isRemoteFileName(fname)
+                    else os.path.isdir(fname)
+                )
                 parentItem, _dt = self.findParentItemByName(
                     self.__projectBrowser.getProjectBrowserFilter(fileCategory), fn
                 )
@@ -434,8 +516,9 @@
                         self.__projectBrowser.getProjectBrowserFilter(fileCategory),
                         False,
                         bold,
+                        fsInterface=self.__remotefsInterface,
                     )
-                    if os.path.isdir(fname)
+                    if isdir
                     else ProjectBrowserFileItem(
                         parentItem,
                         fname,
@@ -443,10 +526,14 @@
                         False,
                         bold,
                         sourceLanguage=sourceLanguage,
+                        fsInterface=self.__remotefsInterface,
                     )
                 )
                 self._addItem(itm, parentItem)
-                if self.project.vcs is not None:
+                if (
+                    self.project.vcs is not None
+                    and not FileSystemUtilities.isRemoteFileName(self.project.ppath)
+                ):
                     if (
                         states[os.path.normcase(fname)]
                         == VersionControlState.Controlled
@@ -490,7 +577,9 @@
                 itm = self.findChildItem(p, 0, olditem)
                 path = os.path.join(path, p)
                 if itm is None:
-                    itm = ProjectBrowserSimpleDirectoryItem(olditem, type_, p, path)
+                    itm = ProjectBrowserSimpleDirectoryItem(
+                        olditem, type_, p, path, self.__remotefsInterface
+                    )
                     self.__addVCSStatus(itm, path)
                     if self.inRefresh:
                         self._addItem(itm, olditem)
@@ -561,6 +650,7 @@
                 self.__projectBrowser.getProjectBrowserFilter(typeString),
                 False,
                 bold,
+                fsInterface=self.__remotefsInterface,
             )
         else:
             if typeString == "SOURCES":
@@ -574,6 +664,7 @@
                 False,
                 bold,
                 sourceLanguage=sourceLanguage,
+                fsInterface=self.__remotefsInterface,
             )
         self.__addVCSStatus(itm, fname)
         if additionalTypeStrings:
@@ -719,12 +810,14 @@
                     FileSystemUtilities.toNativeSeparators(path),
                     itm.getProjectTypes()[0],
                     False,
+                    fsInterface=self.__remotefsInterface,
                 )
                 if isDir
                 else ProjectBrowserFileItem(
                     itm,
                     FileSystemUtilities.toNativeSeparators(path),
                     itm.getProjectTypes()[0],
+                    fsInterface=self.__remotefsInterface,
                 )
             )
             self._addItem(node, itm)
--- a/src/eric7/Project/ProjectFile.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectFile.py	Fri Jun 07 13:58:16 2024 +0200
@@ -17,6 +17,7 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
@@ -48,6 +49,11 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+        isRemote = FileSystemUtilities.isRemoteFileName(filename)
+
         projectDict = {
             "header": {
                 "comment": "eric project file for project {0}".format(
@@ -85,19 +91,28 @@
             "SOURCESDIR",
         ):
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = FileSystemUtilities.fromNativeSeparators(
-                    projectDict["project"][key]
+                projectDict["project"][key] = (
+                    fsInterface.fromNativeSeparators(projectDict["project"][key])
+                    if isRemote
+                    else FileSystemUtilities.fromNativeSeparators(
+                        projectDict["project"][key]
+                    )
                 )
 
         try:
             jsonString = json.dumps(projectDict, indent=2, sort_keys=True) + "\n"
-            with open(filename, "w", newline="") as f:
-                f.write(jsonString)
+            if isRemote:
+                title = self.tr("Save Remote Project File")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Project File")
+                with open(filename, "w", newline="") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Project File"),
+                    title,
                     self.tr(
                         "<p>The project file <b>{0}</b> could not be "
                         "written.</p><p>Reason: {1}</p>"
@@ -116,14 +131,24 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
+        isRemote = FileSystemUtilities.isRemoteFileName(filename)
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if isRemote:
+                title = self.tr("Read Remote Project File")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Project File")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             projectDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Project File"),
+                title,
                 self.tr(
                     "<p>The project file <b>{0}</b> could not be "
                     "read.</p><p>Reason: {1}</p>"
@@ -134,10 +159,17 @@
         # modify paths to contain native separators
         for key in self.__project.getFileCategories() + ["TRANSLATIONEXCEPTIONS"]:
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = [
-                    FileSystemUtilities.toNativeSeparators(f)
-                    for f in projectDict["project"][key]
-                ]
+                projectDict["project"][key] = (
+                    [
+                        fsInterface.toNativeSeparators(f)
+                        for f in projectDict["project"][key]
+                    ]
+                    if isRemote
+                    else [
+                        FileSystemUtilities.toNativeSeparators(f)
+                        for f in projectDict["project"][key]
+                    ]
+                )
         for key in (
             "SPELLWORDS",
             "SPELLEXCLUDES",
@@ -148,8 +180,12 @@
             "SOURCESDIR",
         ):
             with contextlib.suppress(KeyError):
-                projectDict["project"][key] = FileSystemUtilities.toNativeSeparators(
-                    projectDict["project"][key]
+                projectDict["project"][key] = (
+                    fsInterface.toNativeSeparators(projectDict["project"][key])
+                    if isRemote
+                    else FileSystemUtilities.toNativeSeparators(
+                        projectDict["project"][key]
+                    )
                 )
 
         self.__project.setProjectData(projectDict["project"])
--- a/src/eric7/Project/ProjectFormsBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectFormsBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -22,7 +22,7 @@
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
 from eric7.Globals import getConfig
-from eric7.SystemUtilities import QtUtilities
+from eric7.SystemUtilities import FileSystemUtilities, QtUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 from eric7.UI.NotificationWidget import NotificationTypes
 
@@ -202,49 +202,57 @@
 
         self.menu = QMenu(self)
         if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
-            self.menu.addAction(self.tr("Compile form"), self.__compileForm)
-            self.menu.addAction(self.tr("Compile all forms"), self.__compileAllForms)
-            self.menu.addAction(
-                self.tr("Generate Dialog Code..."), self.__generateDialogCode
-            )
-            self.menu.addSeparator()
-            self.__pyuicConfigAct = self.menu.addAction(
-                self.tr("Configure uic Compiler"), self.__configureUicCompiler
-            )
-            self.menu.addSeparator()
-            self.menu.addAction(
-                self.tr("Open in Qt-Designer"), self.__openFile
-            ).setEnabled(QtUtilities.hasQtDesigner())
-            self.menu.addAction(self.tr("Open in Editor"), self.__openFileInEditor)
-            self.menu.addSeparator()
-            self.menu.addAction(self.tr("Preview form"), self.__UIPreview)
-            self.menu.addAction(self.tr("Preview translations"), self.__TRPreview)
-        else:
-            if self.hooks["compileForm"] is not None:
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
+                self.menu.addAction(self.tr("Open in Editor"), self.__openFileInEditor)
+            else:
+                self.menu.addAction(self.tr("Compile form"), self.__compileForm)
+                self.menu.addAction(
+                    self.tr("Compile all forms"), self.__compileAllForms
+                )
+                self.menu.addAction(
+                    self.tr("Generate Dialog Code..."), self.__generateDialogCode
+                )
+                self.menu.addSeparator()
+                self.__pyuicConfigAct = self.menu.addAction(
+                    self.tr("Configure uic Compiler"), self.__configureUicCompiler
+                )
+                self.menu.addSeparator()
                 self.menu.addAction(
-                    self.hooksMenuEntries.get("compileForm", self.tr("Compile form")),
-                    self.__compileForm,
-                )
-            if self.hooks["compileAllForms"] is not None:
-                self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllForms", self.tr("Compile all forms")
-                    ),
-                    self.__compileAllForms,
-                )
-            if self.hooks["generateDialogCode"] is not None:
-                self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateDialogCode", self.tr("Generate Dialog Code...")
-                    ),
-                    self.__generateDialogCode,
-                )
-            if (
-                self.hooks["compileForm"] is not None
-                or self.hooks["compileAllForms"] is not None
-                or self.hooks["generateDialogCode"] is not None
-            ):
+                    self.tr("Open in Qt-Designer"), self.__openFile
+                ).setEnabled(QtUtilities.hasQtDesigner())
+                self.menu.addAction(self.tr("Open in Editor"), self.__openFileInEditor)
                 self.menu.addSeparator()
+                self.menu.addAction(self.tr("Preview form"), self.__UIPreview)
+                self.menu.addAction(self.tr("Preview translations"), self.__TRPreview)
+        else:
+            if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+                if self.hooks["compileForm"] is not None:
+                    self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileForm", self.tr("Compile form")
+                        ),
+                        self.__compileForm,
+                    )
+                if self.hooks["compileAllForms"] is not None:
+                    self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllForms", self.tr("Compile all forms")
+                        ),
+                        self.__compileAllForms,
+                    )
+                if self.hooks["generateDialogCode"] is not None:
+                    self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateDialogCode", self.tr("Generate Dialog Code...")
+                        ),
+                        self.__generateDialogCode,
+                    )
+                if (
+                    self.hooks["compileForm"] is not None
+                    or self.hooks["compileAllForms"] is not None
+                    or self.hooks["generateDialogCode"] is not None
+                ):
+                    self.menu.addSeparator()
             if self.hooks["open"] is not None:
                 self.menu.addAction(
                     self.hooksMenuEntries.get("open", self.tr("Open")), self.__openFile
@@ -258,18 +266,22 @@
         act = self.menu.addAction(self.tr("Delete"), self.__deleteFile)
         self.menuActions.append(act)
         self.menu.addSeparator()
-        if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
-            self.menu.addAction(self.tr("New form..."), self.__newForm)
-        else:
-            if self.hooks["newForm"] is not None:
-                self.menu.addAction(
-                    self.hooksMenuEntries.get("newForm", self.tr("New form...")),
-                    self.__newForm,
-                )
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
+                self.menu.addAction(self.tr("New form..."), self.__newForm)
+            else:
+                if self.hooks["newForm"] is not None:
+                    self.menu.addAction(
+                        self.hooksMenuEntries.get("newForm", self.tr("New form...")),
+                        self.__newForm,
+                    )
         self.menu.addAction(self.tr("Add forms..."), self.__addFormFiles)
         self.menu.addAction(self.tr("Add forms directory..."), self.__addFormsDirectory)
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.menu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.menu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -278,37 +290,38 @@
         self.menu.addAction(self.tr("Configure..."), self._configure)
 
         self.backMenu = QMenu(self)
-        if (
-            projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]
-            or self.hooks["compileAllForms"] is not None
-        ):
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if (
+                projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]
+                or self.hooks["compileAllForms"] is not None
+            ):
+                self.backMenu.addAction(
+                    self.tr("Compile all forms"), self.__compileAllForms
+                )
+                self.backMenu.addSeparator()
+                self.__pyuicBackConfigAct = self.backMenu.addAction(
+                    self.tr("Configure uic Compiler"), self.__configureUicCompiler
+                )
+                self.backMenu.addSeparator()
+                self.backMenu.addAction(self.tr("New form..."), self.__newForm)
+            else:
+                if self.hooks["newForm"] is not None:
+                    self.backMenu.addAction(
+                        self.hooksMenuEntries.get("newForm", self.tr("New form...")),
+                        self.__newForm,
+                    )
             self.backMenu.addAction(
-                self.tr("Compile all forms"), self.__compileAllForms
+                self.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
             )
-            self.backMenu.addSeparator()
-            self.__pyuicBackConfigAct = self.backMenu.addAction(
-                self.tr("Configure uic Compiler"), self.__configureUicCompiler
+            self.backMenu.addAction(
+                self.tr("Add forms directory..."),
+                lambda: self.project.addDirectory("FORMS"),
             )
             self.backMenu.addSeparator()
-            self.backMenu.addAction(self.tr("New form..."), self.__newForm)
-        else:
-            if self.hooks["newForm"] is not None:
-                self.backMenu.addAction(
-                    self.hooksMenuEntries.get("newForm", self.tr("New form...")),
-                    self.__newForm,
-                )
-        self.backMenu.addAction(
-            self.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
-        )
-        self.backMenu.addAction(
-            self.tr("Add forms directory..."),
-            lambda: self.project.addDirectory("FORMS"),
-        )
-        self.backMenu.addSeparator()
-        self.backMenu.addAction(
-            self.tr("Show in File Manager"), self._showProjectInFileManager
-        )
-        self.backMenu.addSeparator()
+            self.backMenu.addAction(
+                self.tr("Show in File Manager"), self._showProjectInFileManager
+            )
+            self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.backMenu.addAction(
             self.tr("Collapse all directories"), self._collapseAllDirs
@@ -320,22 +333,34 @@
         # create the menu for multiple selected files
         self.multiMenu = QMenu(self)
         if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
-            self.multiMenu.addAction(
-                self.tr("Compile forms"), self.__compileSelectedForms
-            )
-            self.multiMenu.addSeparator()
-            self.__pyuicMultiConfigAct = self.multiMenu.addAction(
-                self.tr("Configure uic Compiler"), self.__configureUicCompiler
-            )
-            self.multiMenu.addSeparator()
-            self.multiMenu.addAction(
-                self.tr("Open in Qt-Designer"), self.__openFile
-            ).setEnabled(QtUtilities.hasQtDesigner())
-            self.multiMenu.addAction(self.tr("Open in Editor"), self.__openFileInEditor)
-            self.multiMenu.addSeparator()
-            self.multiMenu.addAction(self.tr("Preview translations"), self.__TRPreview)
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
+                self.multiMenu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+            else:
+                self.multiMenu.addAction(
+                    self.tr("Compile forms"), self.__compileSelectedForms
+                )
+                self.multiMenu.addSeparator()
+                self.__pyuicMultiConfigAct = self.multiMenu.addAction(
+                    self.tr("Configure uic Compiler"), self.__configureUicCompiler
+                )
+                self.multiMenu.addSeparator()
+                self.multiMenu.addAction(
+                    self.tr("Open in Qt-Designer"), self.__openFile
+                ).setEnabled(QtUtilities.hasQtDesigner())
+                self.multiMenu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+                self.multiMenu.addSeparator()
+                self.multiMenu.addAction(
+                    self.tr("Preview translations"), self.__TRPreview
+                )
         else:
-            if self.hooks["compileSelectedForms"] is not None:
+            if (
+                FileSystemUtilities.isPlainFileName(self.project.getProjectPath())
+                and self.hooks["compileSelectedForms"] is not None
+            ):
                 act = self.multiMenu.addAction(
                     self.hooksMenuEntries.get(
                         "compileSelectedForms", self.tr("Compile forms")
@@ -362,41 +387,47 @@
         self.multiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.dirMenu = QMenu(self)
-        if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
-            self.dirMenu.addAction(self.tr("Compile all forms"), self.__compileAllForms)
-            self.dirMenu.addSeparator()
-            self.__pyuicDirConfigAct = self.dirMenu.addAction(
-                self.tr("Configure uic Compiler"), self.__configureUicCompiler
-            )
-            self.dirMenu.addSeparator()
-        else:
-            if self.hooks["compileAllForms"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
                 self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllForms", self.tr("Compile all forms")
-                    ),
-                    self.__compileAllForms,
+                    self.tr("Compile all forms"), self.__compileAllForms
+                )
+                self.dirMenu.addSeparator()
+                self.__pyuicDirConfigAct = self.dirMenu.addAction(
+                    self.tr("Configure uic Compiler"), self.__configureUicCompiler
                 )
                 self.dirMenu.addSeparator()
+            else:
+                if self.hooks["compileAllForms"] is not None:
+                    self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllForms", self.tr("Compile all forms")
+                        ),
+                        self.__compileAllForms,
+                    )
+                    self.dirMenu.addSeparator()
         act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeDir)
         self.dirMenuActions.append(act)
         act = self.dirMenu.addAction(self.tr("Delete"), self._deleteDirectory)
         self.dirMenuActions.append(act)
         self.dirMenu.addSeparator()
-        if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
-            self.dirMenu.addAction(self.tr("New form..."), self.__newForm)
-        else:
-            if self.hooks["newForm"] is not None:
-                self.dirMenu.addAction(
-                    self.hooksMenuEntries.get("newForm", self.tr("New form...")),
-                    self.__newForm,
-                )
-        self.dirMenu.addAction(self.tr("Add forms..."), self.__addFormFiles)
-        self.dirMenu.addAction(
-            self.tr("Add forms directory..."), self.__addFormsDirectory
-        )
-        self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
+                self.dirMenu.addAction(self.tr("New form..."), self.__newForm)
+            else:
+                if self.hooks["newForm"] is not None:
+                    self.dirMenu.addAction(
+                        self.hooksMenuEntries.get("newForm", self.tr("New form...")),
+                        self.__newForm,
+                    )
+            self.dirMenu.addAction(self.tr("Add forms..."), self.__addFormFiles)
+            self.dirMenu.addAction(
+                self.tr("Add forms directory..."), self.__addFormsDirectory
+            )
+            self.dirMenu.addSeparator()
+            self.dirMenu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -407,32 +438,33 @@
         self.dirMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.dirMultiMenu = QMenu(self)
-        if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if projectType in ["PyQt5", "PyQt6", "E7Plugin", "PySide2", "PySide6"]:
+                self.dirMultiMenu.addAction(
+                    self.tr("Compile all forms"), self.__compileAllForms
+                )
+                self.dirMultiMenu.addSeparator()
+                self.__pyuicDirMultiConfigAct = self.dirMultiMenu.addAction(
+                    self.tr("Configure uic Compiler"), self.__configureUicCompiler
+                )
+                self.dirMultiMenu.addSeparator()
+            else:
+                if self.hooks["compileAllForms"] is not None:
+                    self.dirMultiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllForms", self.tr("Compile all forms")
+                        ),
+                        self.__compileAllForms,
+                    )
+                    self.dirMultiMenu.addSeparator()
             self.dirMultiMenu.addAction(
-                self.tr("Compile all forms"), self.__compileAllForms
+                self.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
             )
-            self.dirMultiMenu.addSeparator()
-            self.__pyuicDirMultiConfigAct = self.dirMultiMenu.addAction(
-                self.tr("Configure uic Compiler"), self.__configureUicCompiler
+            self.dirMultiMenu.addAction(
+                self.tr("Add forms directory..."),
+                lambda: self.project.addDirectory("FORMS"),
             )
             self.dirMultiMenu.addSeparator()
-        else:
-            if self.hooks["compileAllForms"] is not None:
-                self.dirMultiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllForms", self.tr("Compile all forms")
-                    ),
-                    self.__compileAllForms,
-                )
-                self.dirMultiMenu.addSeparator()
-        self.dirMultiMenu.addAction(
-            self.tr("Add forms..."), lambda: self.project.addFiles("FORMS")
-        )
-        self.dirMultiMenu.addAction(
-            self.tr("Add forms directory..."),
-            lambda: self.project.addDirectory("FORMS"),
-        )
-        self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
             self.tr("Expand all directories"), self._expandAllDirs
         )
@@ -459,12 +491,13 @@
         if not self.project.isOpen():
             return
 
-        enable = self.project.getProjectType() in ("PyQt5", "PyQt6", "E7Plugin")
-        self.__pyuicConfigAct.setEnabled(enable)
-        self.__pyuicMultiConfigAct.setEnabled(enable)
-        self.__pyuicDirConfigAct.setEnabled(enable)
-        self.__pyuicDirMultiConfigAct.setEnabled(enable)
-        self.__pyuicBackConfigAct.setEnabled(enable)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            enable = self.project.getProjectType() in ("PyQt5", "PyQt6", "E7Plugin")
+            self.__pyuicConfigAct.setEnabled(enable)
+            self.__pyuicMultiConfigAct.setEnabled(enable)
+            self.__pyuicDirConfigAct.setEnabled(enable)
+            self.__pyuicDirMultiConfigAct.setEnabled(enable)
+            self.__pyuicBackConfigAct.setEnabled(enable)
 
         with contextlib.suppress(Exception):  # secok
             categories = self.getSelectedItemsCountCategorized(
@@ -579,7 +612,9 @@
         itmList = self.getSelectedItems()
         for itm in itmList:
             if isinstance(itm, ProjectBrowserFileItem):
-                if itm.isDesignerFile():
+                if itm.isDesignerFile() and FileSystemUtilities.isPlainFileName(
+                    itm.fileName()
+                ):
                     self.designerFile.emit(itm.fileName())
                 else:
                     self.sourceFile.emit(itm.fileName())
--- a/src/eric7/Project/ProjectOthersBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectOthersBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -17,6 +17,7 @@
 from eric7 import Preferences
 from eric7.EricGui import EricPixmapCache
 from eric7.EricWidgets import EricMessageBox
+from eric7.SystemUtilities import FileSystemUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 from eric7.Utilities import MimeTypes
 
@@ -159,7 +160,9 @@
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Refresh"), self.__refreshItem)
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.menuFileManagerAct = self.menu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.menu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -180,7 +183,9 @@
         self.dirMenu.addAction(self.tr("Add files..."), self.__addOthersFiles)
         self.dirMenu.addAction(self.tr("Add directory..."), self.__addOthersDirectory)
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.dirMenuFileManagerAct = self.dirMenu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -198,7 +203,7 @@
             self.tr("Add directory..."), lambda: self.project.addDirectory("OTHERS")
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(
+        self.backMenuFileManagerAct = self.backMenu.addAction(
             self.tr("Show in File Manager"), self._showProjectInFileManager
         )
         self.backMenu.addSeparator()
@@ -241,6 +246,8 @@
         if not self.project.isOpen():
             return
 
+        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+
         with contextlib.suppress(Exception):  # secok
             cnt = self.getSelectedItemsCount(
                 [
@@ -272,18 +279,24 @@
                         self.openInEditorAct.setVisible(itm.isSvgFile())
                         self.openInPdfViewerAct.setVisible(itm.isPdfFile())
                         self.mimeTypeAct.setVisible(True)
+                        self.menuFileManagerAct.setVisible(not isRemote)
                         self.menu.popup(self.mapToGlobal(coord))
-                    elif isinstance(itm, ProjectBrowserDirectoryItem):
+                    elif isinstance(
+                        itm,
+                        (
+                            ProjectBrowserDirectoryItem,
+                            ProjectBrowserSimpleDirectoryItem,
+                        ),
+                    ):
                         self.removeDirAct.setVisible(True)
                         self.deleteDirAct.setVisible(True)
-                        self.dirMenu.popup(self.mapToGlobal(coord))
-                    elif isinstance(itm, ProjectBrowserSimpleDirectoryItem):
-                        self.removeDirAct.setVisible(False)
-                        self.deleteDirAct.setVisible(False)
+                        self.dirMenuFileManagerAct.setVisible(not isRemote)
                         self.dirMenu.popup(self.mapToGlobal(coord))
                     else:
+                        self.backMenuFileManagerAct.setVisible(not isRemote)
                         self.backMenu.popup(self.mapToGlobal(coord))
                 else:
+                    self.backMenuFileManagerAct.setVisible(not isRemote)
                     self.backMenu.popup(self.mapToGlobal(coord))
 
     def __showContextMenu(self):
@@ -336,7 +349,7 @@
                 itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
             ):
                 self.renameFileAct.setEnabled(False)
-        else:
+        elif self.vcsHelper is not None:
             self.vcsHelper.showContextMenu(menu, self.menuActions)
 
     def _editPixmap(self):
--- a/src/eric7/Project/ProjectResourcesBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectResourcesBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -19,7 +19,7 @@
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricProgressDialog import EricProgressDialog
-from eric7.SystemUtilities import QtUtilities
+from eric7.SystemUtilities import FileSystemUtilities, QtUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 from eric7.UI.NotificationWidget import NotificationTypes
 
@@ -139,7 +139,9 @@
         self.dirMultiMenuActions = []
 
         self.menu = QMenu(self)
-        if self.project.getProjectType() in [
+        if FileSystemUtilities.isPlainFileName(
+            self.project.getProjectPath()
+        ) and self.project.getProjectType() in [
             "PyQt5",
             "PyQt5C",
             "PySide2",
@@ -185,29 +187,33 @@
         act = self.menu.addAction(self.tr("Delete"), self.__deleteFile)
         self.menuActions.append(act)
         self.menu.addSeparator()
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            self.menu.addAction(self.tr("New resource..."), self.__newResource)
-        else:
-            if self.hooks["newResource"] is not None:
-                self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "newResource", self.tr("New resource...")
-                    ),
-                    self.__newResource,
-                )
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
+                self.menu.addAction(self.tr("New resource..."), self.__newResource)
+            else:
+                if self.hooks["newResource"] is not None:
+                    self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "newResource", self.tr("New resource...")
+                        ),
+                        self.__newResource,
+                    )
         self.menu.addAction(self.tr("Add resources..."), self.__addResourceFiles)
         self.menu.addAction(
             self.tr("Add resources directory..."), self.__addResourcesDirectory
         )
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.menu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.menu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -216,39 +222,40 @@
         self.menu.addAction(self.tr("Configure..."), self._configure)
 
         self.backMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            self.backMenu.addAction(
-                self.tr("Compile all resources"), self.__compileAllResources
-            )
-            self.backMenu.addSeparator()
-            self.backMenu.addAction(
-                self.tr("Configure rcc Compiler"), self.__configureRccCompiler
-            )
-            self.backMenu.addSeparator()
-            self.backMenu.addAction(self.tr("New resource..."), self.__newResource)
-        else:
-            if self.hooks["compileAllResources"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllResources", self.tr("Compile all resources")
-                    ),
-                    self.__compileAllResources,
+                    self.tr("Compile all resources"), self.__compileAllResources
+                )
+                self.backMenu.addSeparator()
+                self.backMenu.addAction(
+                    self.tr("Configure rcc Compiler"), self.__configureRccCompiler
                 )
                 self.backMenu.addSeparator()
-            if self.hooks["newResource"] is not None:
-                self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "newResource", self.tr("New resource...")
-                    ),
-                    self.__newResource,
-                )
+                self.backMenu.addAction(self.tr("New resource..."), self.__newResource)
+            else:
+                if self.hooks["compileAllResources"] is not None:
+                    self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllResources", self.tr("Compile all resources")
+                        ),
+                        self.__compileAllResources,
+                    )
+                    self.backMenu.addSeparator()
+                if self.hooks["newResource"] is not None:
+                    self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "newResource", self.tr("New resource...")
+                        ),
+                        self.__newResource,
+                    )
         self.backMenu.addAction(
             self.tr("Add resources..."), lambda: self.project.addFiles("RECOURCES")
         )
@@ -257,9 +264,10 @@
             lambda: self.project.addDirectory("RESOURCES"),
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(
-            self.tr("Show in File Manager"), self._showProjectInFileManager
-        )
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.backMenu.addAction(
+                self.tr("Show in File Manager"), self._showProjectInFileManager
+            )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.backMenu.addAction(
@@ -271,31 +279,32 @@
 
         # create the menu for multiple selected files
         self.multiMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            act = self.multiMenu.addAction(
-                self.tr("Compile resources"), self.__compileSelectedResources
-            )
-            self.multiMenu.addSeparator()
-            self.multiMenu.addAction(
-                self.tr("Configure rcc Compiler"), self.__configureRccCompiler
-            )
-            self.multiMenu.addSeparator()
-        else:
-            if self.hooks["compileSelectedResources"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileSelectedResources", self.tr("Compile resources")
-                    ),
-                    self.__compileSelectedResources,
+                    self.tr("Compile resources"), self.__compileSelectedResources
                 )
                 self.multiMenu.addSeparator()
+                self.multiMenu.addAction(
+                    self.tr("Configure rcc Compiler"), self.__configureRccCompiler
+                )
+                self.multiMenu.addSeparator()
+            else:
+                if self.hooks["compileSelectedResources"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileSelectedResources", self.tr("Compile resources")
+                        ),
+                        self.__compileSelectedResources,
+                    )
+                    self.multiMenu.addSeparator()
         self.multiMenu.addAction(self.tr("Open"), self.__openFile)
         self.multiMenu.addSeparator()
         act = self.multiMenu.addAction(self.tr("Remove from project"), self._removeFile)
@@ -311,43 +320,48 @@
         self.multiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.dirMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            self.dirMenu.addAction(
-                self.tr("Compile all resources"), self.__compileAllResources
-            )
-            self.dirMenu.addSeparator()
-            self.dirMenu.addAction(
-                self.tr("Configure rcc Compiler"), self.__configureRccCompiler
-            )
-            self.dirMenu.addSeparator()
-        else:
-            if self.hooks["compileAllResources"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllResources", self.tr("Compile all resources")
-                    ),
-                    self.__compileAllResources,
+                    self.tr("Compile all resources"), self.__compileAllResources
                 )
                 self.dirMenu.addSeparator()
+                self.dirMenu.addAction(
+                    self.tr("Configure rcc Compiler"), self.__configureRccCompiler
+                )
+                self.dirMenu.addSeparator()
+            else:
+                if self.hooks["compileAllResources"] is not None:
+                    self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllResources", self.tr("Compile all resources")
+                        ),
+                        self.__compileAllResources,
+                    )
+                    self.dirMenu.addSeparator()
         act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeDir)
         self.dirMenuActions.append(act)
         act = self.dirMenu.addAction(self.tr("Delete"), self._deleteDirectory)
         self.dirMenuActions.append(act)
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("New resource..."), self.__newResource)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.dirMenu.addAction(self.tr("New resource..."), self.__newResource)
         self.dirMenu.addAction(self.tr("Add resources..."), self.__addResourceFiles)
         self.dirMenu.addAction(
             self.tr("Add resources directory..."), self.__addResourcesDirectory
         )
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.dirMenu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -358,31 +372,32 @@
         self.dirMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.dirMultiMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            self.dirMultiMenu.addAction(
-                self.tr("Compile all resources"), self.__compileAllResources
-            )
-            self.dirMultiMenu.addSeparator()
-            self.dirMultiMenu.addAction(
-                self.tr("Configure rcc Compiler"), self.__configureRccCompiler
-            )
-            self.dirMultiMenu.addSeparator()
-        else:
-            if self.hooks["compileAllResources"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 self.dirMultiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "compileAllResources", self.tr("Compile all resources")
-                    ),
-                    self.__compileAllResources,
+                    self.tr("Compile all resources"), self.__compileAllResources
                 )
                 self.dirMultiMenu.addSeparator()
+                self.dirMultiMenu.addAction(
+                    self.tr("Configure rcc Compiler"), self.__configureRccCompiler
+                )
+                self.dirMultiMenu.addSeparator()
+            else:
+                if self.hooks["compileAllResources"] is not None:
+                    self.dirMultiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "compileAllResources", self.tr("Compile all resources")
+                        ),
+                        self.__compileAllResources,
+                    )
+                    self.dirMultiMenu.addSeparator()
         self.dirMultiMenu.addAction(
             self.tr("Add resources..."), lambda: self.project.addFiles("RECOURCES")
         )
--- a/src/eric7/Project/ProjectSourcesBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectSourcesBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -23,6 +23,7 @@
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPickerDialog import EricPathPickerModes
 from eric7.Graphics.UMLDialog import UMLDialog, UMLDialogType
+from eric7.SystemUtilities import FileSystemUtilities
 from eric7.UI.BrowserModel import (
     BrowserClassAttributeItem,
     BrowserClassItem,
@@ -318,12 +319,14 @@
         self.sourceMenu.addSeparator()
         self.sourceMenu.addMenu(self.graphicsMenu)
         self.sourceMenu.addMenu(self.checksMenu)
-        self.sourceMenu.addMenu(self.formattingMenu)
+        self.sourceMenuActions["Formatting"] = self.sourceMenu.addMenu(
+            self.formattingMenu
+        )
         self.sourceMenuActions["Show"] = self.sourceMenu.addMenu(self.menuShow)
         self.sourceMenu.addSeparator()
         self.__startAct = self.sourceMenu.addMenu(self.__startMenu)
         self.sourceMenu.addSeparator()
-        self.sourceMenu.addAction(
+        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.sourceMenu.addAction(
@@ -349,7 +352,9 @@
             self.tr("Add source directory..."), self.__addSourceDirectory
         )
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__menuFileManagerAct = self.menu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
@@ -379,7 +384,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
-        self.attributeMenu.addAction(
+        self.__attributeMenuFileManagerAct = self.attributeMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.attributeMenu.addSeparator()
@@ -408,7 +413,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(
+        self.__backMenuFileManagerAct = self.backMenu.addAction(
             self.tr("Show in File Manager"), self._showProjectInFileManager
         )
         self.backMenu.addSeparator()
@@ -428,7 +433,7 @@
         self.multiMenuActions.append(act)
         self.multiMenu.addSeparator()
         self.multiMenu.addMenu(self.checksMenu)
-        self.multiMenu.addMenu(self.formattingMenu)
+        self.__multiMenuFormattingAct = self.multiMenu.addMenu(self.formattingMenu)
         self.multiMenu.addSeparator()
         self.multiMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.multiMenu.addAction(
@@ -454,9 +459,11 @@
         self.dirMenu.addSeparator()
         act = self.dirMenu.addMenu(self.graphicsMenu)
         self.dirMenu.addMenu(self.checksMenu)
-        self.dirMenu.addMenu(self.formattingMenu)
+        self.__dirMenuFormattingAct = self.dirMenu.addMenu(self.formattingMenu)
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -481,6 +488,8 @@
         self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
+        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
+        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
         self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
         self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
         self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
@@ -525,7 +534,7 @@
         self.sourceMenu.addSeparator()
         act = self.sourceMenu.addMenu(self.graphicsMenu)
         self.sourceMenu.addSeparator()
-        self.sourceMenu.addAction(
+        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.sourceMenu.addSeparator()
@@ -545,7 +554,9 @@
             self.tr("Add source directory..."), self.__addSourceDirectory
         )
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__menuFileManagerAct = self.menu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
@@ -570,7 +581,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
-        self.attributeMenu.addAction(
+        self.__attributeMenuFileManagerAct = self.attributeMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.attributeMenu.addSeparator()
@@ -596,7 +607,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(
+        self.__backMenuFileManagerAct = self.backMenu.addAction(
             self.tr("Show in File Manager"), self._showProjectInFileManager
         )
         self.backMenu.addSeparator()
@@ -634,7 +645,9 @@
         self.dirMenu.addSeparator()
         act = self.dirMenu.addMenu(self.graphicsMenu)
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.dirMenu.addAction(
@@ -658,6 +671,8 @@
         self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
+        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
+        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
         self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
         self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
         self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
@@ -688,7 +703,7 @@
         self.sourceMenu.addSeparator()
         self.sourceMenu.addMenu(self.checksMenu)
         self.sourceMenu.addSeparator()
-        self.sourceMenu.addAction(
+        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.sourceMenu.addAction(
@@ -711,7 +726,9 @@
             self.tr("Add source directory..."), self.__addSourceDirectory
         )
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__menuFileManagerAct = self.menu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
         self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
@@ -736,7 +753,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.attributeMenu.addSeparator()
-        self.attributeMenu.addAction(
+        self.__attrMenuFileManagerAct = self.attributeMenu.addAction(
             self.tr("Show in File Manager"), self._showInFileManager
         )
         self.attributeMenu.addSeparator()
@@ -762,7 +779,7 @@
             lambda: self.project.addDirectory("SOURCES"),
         )
         self.backMenu.addSeparator()
-        self.backMenu.addAction(
+        self.__backMenuFileManagerAct = self.backMenu.addAction(
             self.tr("Show in File Manager"), self._showProjectInFileManager
         )
         self.backMenu.addSeparator()
@@ -804,7 +821,9 @@
         self.dirMenu.addSeparator()
         self.dirMenu.addMenu(self.checksMenu)
         self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
+            self.tr("Show in File Manager"), self._showInFileManager
+        )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
@@ -829,6 +848,8 @@
         self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
+        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
+        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
         self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
         self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
         self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
@@ -956,14 +977,46 @@
         else:
             self.__startAct.setEnabled(False)
 
+        self.sourceMenuActions["Formatting"].setEnabled(
+            self.sourceMenuActions["Formatting"].isEnabled()
+            and not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+        self.__sourceMenuFileManagerAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
         self.showMenu.emit("Main", self.sourceMenu)
 
+    def __showContextMenuGeneral(self):
+        """
+        Private slot called by the menu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenu(self, self.menu)
+
+        self.__menuFileManagerAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
+    def __showContextMenuAttribute(self):
+        """
+        Private slot called by the attributeMenu aboutToShow signal.
+        """
+        ProjectBaseBrowser._showContextMenu(self, self.menu)
+
+        self.__attributeMenuFileManagerAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
     def __showContextMenuMulti(self):
         """
         Private slot called by the multiMenu aboutToShow signal.
         """
         ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)
 
+        self.__multiMenuFormattingAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
         self.showMenu.emit("MainMulti", self.multiMenu)
 
     def __showContextMenuDir(self):
@@ -972,6 +1025,13 @@
         """
         ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)
 
+        self.__dirMenuFormattingAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+        self.__dirMenuFileManagerAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
         self.showMenu.emit("MainDir", self.dirMenu)
 
     def __showContextMenuDirMulti(self):
@@ -988,6 +1048,10 @@
         """
         ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)
 
+        self.__backMenuFileManagerAct.setEnabled(
+            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        )
+
         self.showMenu.emit("MainBack", self.backMenu)
 
     def __showContextMenuShow(self):
@@ -1065,8 +1129,14 @@
         """
         from .NewPythonPackageDialog import NewPythonPackageDialog
 
+        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+        separator = remotefsInterface.separator() if isRemote else os.sep
+
         dn = self.currentDirectory(relative=True)
-        if dn.startswith(os.sep):
+        if dn.startswith(separator):
             dn = dn[1:]
         dlg = NewPythonPackageDialog(dn, self)
         if dlg.exec() == QDialog.DialogCode.Accepted:
@@ -1075,10 +1145,22 @@
             packagePath = self.project.ppath
             packageFile = ""
             for name in nameParts:
-                packagePath = os.path.join(packagePath, name)
-                if not os.path.exists(packagePath):
+                packagePath = (
+                    remotefsInterface.join(packagePath, name)
+                    if isRemote
+                    else os.path.join(packagePath, name)
+                )
+                exists = (
+                    remotefsInterface.exists(packagePath)
+                    if isRemote
+                    else os.path.exists(packagePath)
+                )
+                if not exists:
                     try:
-                        os.mkdir(packagePath)
+                        if isRemote:
+                            remotefsInterface.mkdir(packagePath)
+                        else:
+                            os.mkdir(packagePath)
                     except OSError as err:
                         EricMessageBox.critical(
                             self,
@@ -1090,11 +1172,23 @@
                             ).format(packagePath, str(err)),
                         )
                         return
-                packageFile = os.path.join(packagePath, "__init__.py")
-                if not os.path.exists(packageFile):
+                packageFile = (
+                    remotefsInterface.join(packagePath, "__init__.py")
+                    if isRemote
+                    else os.path.join(packagePath, "__init__.py")
+                )
+                exists = (
+                    remotefsInterface.exists(packageFile)
+                    if isRemote
+                    else os.path.exists(packageFile)
+                )
+                if not exists:
                     try:
-                        with open(packageFile, "w", encoding="utf-8"):
-                            pass
+                        if isRemote:
+                            remotefsInterface.writeFile(packageFile, b"")
+                        else:
+                            with open(packageFile, "w", encoding="utf-8"):
+                                pass
                     except OSError as err:
                         EricMessageBox.critical(
                             self,
@@ -1114,6 +1208,11 @@
         """
         Private method to add a new source file to the project.
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         dn = self.currentDirectory()
         filename, ok = EricPathPickerDialog.getStrPath(
             self,
@@ -1125,9 +1224,15 @@
             filters=self.project.getFileCategoryFilters(
                 categories=["SOURCES"], withAll=False
             ),
+            remote=isRemote,
         )
         if ok:
-            if os.path.exists(filename):
+            exists = (
+                remotefsInterface.exists(filename)
+                if isRemote
+                else os.path.exists(filename)
+            )
+            if exists:
                 EricMessageBox.critical(
                     self,
                     self.tr("New source file"),
@@ -1142,9 +1247,16 @@
                 newline = (
                     None if self.project.useSystemEol() else self.project.getEolString()
                 )
-                with open(filename, "w", newline=newline) as f:
-                    f.write("# -*- coding: utf-8 -*-\n")
-                    f.write("# {0}\n".format(self.project.getRelativePath(filename)))
+                header = "# -*- coding: utf-8 -*-\n# {0}\n".format(
+                    self.project.getRelativePath(filename)
+                )
+                if isRemote:
+                    remotefsInterface.writeFile(
+                        filename, header.encode("utf-8"), newline=newline
+                    )
+                else:
+                    with open(filename, "w", newline=newline) as f:
+                        f.write(header)
             except OSError as err:
                 EricMessageBox.critical(
                     self,
@@ -1345,12 +1457,25 @@
         """
         Private method to handle the imports diagram context menu action.
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         itm = self.model().item(self.currentIndex())
         try:
             fn = itm.fileName()
         except AttributeError:
             fn = itm.dirName()
-        package = fn if os.path.isdir(fn) else os.path.dirname(fn)
+        package = (
+            fn
+            if remotefsInterface.isdir(fn)
+            else (
+                remotefsInterface.dirname(fn)
+                if isRemote
+                else fn if os.path.isdir(fn) else os.path.dirname(fn)
+            )
+        )
         res = EricMessageBox.yesNo(
             self,
             self.tr("Imports Diagram"),
@@ -1370,12 +1495,25 @@
         """
         Private method to handle the package diagram context menu action.
         """
+        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
+        remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         itm = self.model().item(self.currentIndex())
         try:
             fn = itm.fileName()
         except AttributeError:
             fn = itm.dirName()
-        package = fn if os.path.isdir(fn) else os.path.dirname(fn)
+        package = (
+            fn
+            if remotefsInterface.isdir(fn)
+            else (
+                remotefsInterface.dirname(fn)
+                if isRemote
+                else fn if os.path.isdir(fn) else os.path.dirname(fn)
+            )
+        )
         res = EricMessageBox.yesNo(
             self,
             self.tr("Package Diagram"),
--- a/src/eric7/Project/ProjectTranslationsBrowser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/ProjectTranslationsBrowser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -23,7 +23,7 @@
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
-from eric7.SystemUtilities import OSUtilities, QtUtilities
+from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, QtUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 from eric7.UI.NotificationWidget import NotificationTypes
 
@@ -177,127 +177,144 @@
             "PySide6",
             "PySide6C",
         ]:
-            act = self.menu.addAction(
-                self.tr("Generate translation"), self.__generateSelected
-            )
-            self.tsMenuActions.append(act)
-            self.tsprocMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Generate translation (with obsolete)"),
-                self.__generateObsoleteSelected,
-            )
-            self.tsMenuActions.append(act)
-            self.tsprocMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Generate all translations"), self.__generateAll
-            )
-            self.tsprocMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Generate all translations (with obsolete)"),
-                self.__generateObsoleteAll,
-            )
-            self.tsprocMenuActions.append(act)
-            self.menu.addSeparator()
-            self.__qtLinguistAct = self.menu.addAction(
-                self.tr("Open in Qt-Linguist"), self._openItem
-            )
-            self.tsMenuActions.append(self.__qtLinguistAct)
-            act = self.menu.addAction(
-                self.tr("Open in Editor"), self.__openFileInEditor
-            )
-            self.tsMenuActions.append(act)
-            self.menu.addSeparator()
-            act = self.menu.addAction(
-                self.tr("Release translation"), self.__releaseSelected
-            )
-            self.tsMenuActions.append(act)
-            self.qmprocMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Release all translations"), self.__releaseAll
-            )
-            self.qmprocMenuActions.append(act)
-            self.menu.addSeparator()
-            act = self.menu.addAction(self.tr("Preview translation"), self.__TRPreview)
-            self.qmMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Preview all translations"), self.__TRPreviewAll
-            )
-            self.menu.addSeparator()
-        else:
-            if self.hooks["extractMessages"] is not None:
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "extractMessages", self.tr("Extract messages")
-                    ),
-                    self.__extractMessages,
+                    self.tr("Open in Editor"), self.__openFileInEditor
                 )
-                self.menuActions.append(act)
+                self.tsMenuActions.append(act)
                 self.menu.addSeparator()
-            if self.hooks["generateSelected"] is not None:
+            else:
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateSelected", self.tr("Generate translation")
-                    ),
-                    self.__generateSelected,
+                    self.tr("Generate translation"), self.__generateSelected
                 )
                 self.tsMenuActions.append(act)
                 self.tsprocMenuActions.append(act)
-            if self.hooks["generateSelectedWithObsolete"] is not None:
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateSelectedWithObsolete",
-                        self.tr("Generate translation (with obsolete)"),
-                    ),
+                    self.tr("Generate translation (with obsolete)"),
                     self.__generateObsoleteSelected,
                 )
                 self.tsMenuActions.append(act)
                 self.tsprocMenuActions.append(act)
-            if self.hooks["generateAll"] is not None:
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAll", self.tr("Generate all translations")
-                    ),
-                    self.__generateAll,
+                    self.tr("Generate all translations"), self.__generateAll
                 )
                 self.tsprocMenuActions.append(act)
-            if self.hooks["generateAllWithObsolete"] is not None:
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAllWithObsolete",
-                        self.tr("Generate all translations (with obsolete)"),
-                    ),
+                    self.tr("Generate all translations (with obsolete)"),
                     self.__generateObsoleteAll,
                 )
                 self.tsprocMenuActions.append(act)
-            self.menu.addSeparator()
-            if self.hooks["open"] is not None:
+                self.menu.addSeparator()
+                self.__qtLinguistAct = self.menu.addAction(
+                    self.tr("Open in Qt-Linguist"), self._openItem
+                )
+                self.tsMenuActions.append(self.__qtLinguistAct)
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get("open", self.tr("Open")), self._openItem
+                    self.tr("Open in Editor"), self.__openFileInEditor
                 )
                 self.tsMenuActions.append(act)
-            act = self.menu.addAction(
-                self.tr("Open in Editor"), self.__openFileInEditor
-            )
-            self.tsMenuActions.append(act)
-            self.menu.addSeparator()
-            if self.hooks["releaseSelected"] is not None:
+                self.menu.addSeparator()
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "releaseSelected", self.tr("Release translation")
-                    ),
-                    self.__releaseSelected,
+                    self.tr("Release translation"), self.__releaseSelected
                 )
                 self.tsMenuActions.append(act)
                 self.qmprocMenuActions.append(act)
-            if self.hooks["releaseAll"] is not None:
                 act = self.menu.addAction(
-                    self.hooksMenuEntries.get(
-                        "releaseAll", self.tr("Release all translations")
-                    ),
-                    self.__releaseAll,
+                    self.tr("Release all translations"), self.__releaseAll
                 )
                 self.qmprocMenuActions.append(act)
-            self.menu.addSeparator()
+                self.menu.addSeparator()
+                act = self.menu.addAction(
+                    self.tr("Preview translation"), self.__TRPreview
+                )
+                self.qmMenuActions.append(act)
+                act = self.menu.addAction(
+                    self.tr("Preview all translations"), self.__TRPreviewAll
+                )
+                self.menu.addSeparator()
+        else:
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
+                act = self.menu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+                self.tsMenuActions.append(act)
+                self.menu.addSeparator()
+            else:
+                if self.hooks["extractMessages"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "extractMessages", self.tr("Extract messages")
+                        ),
+                        self.__extractMessages,
+                    )
+                    self.menuActions.append(act)
+                    self.menu.addSeparator()
+                if self.hooks["generateSelected"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateSelected", self.tr("Generate translation")
+                        ),
+                        self.__generateSelected,
+                    )
+                    self.tsMenuActions.append(act)
+                    self.tsprocMenuActions.append(act)
+                if self.hooks["generateSelectedWithObsolete"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateSelectedWithObsolete",
+                            self.tr("Generate translation (with obsolete)"),
+                        ),
+                        self.__generateObsoleteSelected,
+                    )
+                    self.tsMenuActions.append(act)
+                    self.tsprocMenuActions.append(act)
+                if self.hooks["generateAll"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAll", self.tr("Generate all translations")
+                        ),
+                        self.__generateAll,
+                    )
+                    self.tsprocMenuActions.append(act)
+                if self.hooks["generateAllWithObsolete"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAllWithObsolete",
+                            self.tr("Generate all translations (with obsolete)"),
+                        ),
+                        self.__generateObsoleteAll,
+                    )
+                    self.tsprocMenuActions.append(act)
+                self.menu.addSeparator()
+                if self.hooks["open"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get("open", self.tr("Open")),
+                        self._openItem,
+                    )
+                    self.tsMenuActions.append(act)
+                act = self.menu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+                self.tsMenuActions.append(act)
+                self.menu.addSeparator()
+                if self.hooks["releaseSelected"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "releaseSelected", self.tr("Release translation")
+                        ),
+                        self.__releaseSelected,
+                    )
+                    self.tsMenuActions.append(act)
+                    self.qmprocMenuActions.append(act)
+                if self.hooks["releaseAll"] is not None:
+                    act = self.menu.addAction(
+                        self.hooksMenuEntries.get(
+                            "releaseAll", self.tr("Release all translations")
+                        ),
+                        self.__releaseAll,
+                    )
+                    self.qmprocMenuActions.append(act)
+                self.menu.addSeparator()
         act = self.menu.addAction(
             self.tr("Remove from project"), self.__removeLanguageFile
         )
@@ -312,75 +329,79 @@
             self.tr("Add translation files..."), self.__addTranslationFiles
         )
         self.menu.addSeparator()
-        self.menu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.menu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.menu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.menu.addSeparator()
         self.menu.addAction(self.tr("Configure..."), self._configure)
 
         self.backMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PyQt6",
-            "PyQt6C",
-            "E7Plugin",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            act = self.backMenu.addAction(
-                self.tr("Generate all translations"), self.__generateAll
-            )
-            self.tsprocBackMenuActions.append(act)
-            act = self.backMenu.addAction(
-                self.tr("Generate all translations (with obsolete)"),
-                self.__generateObsoleteAll,
-            )
-            self.tsprocBackMenuActions.append(act)
-            act = self.backMenu.addAction(
-                self.tr("Release all translations"), self.__releaseAll
-            )
-            self.qmprocBackMenuActions.append(act)
-            self.backMenu.addSeparator()
-            act = self.backMenu.addAction(
-                self.tr("Preview all translations"), self.__TRPreview
-            )
-        else:
-            if self.hooks["extractMessages"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PyQt6",
+                "PyQt6C",
+                "E7Plugin",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 act = self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "extractMessages", self.tr("Extract messages")
-                    ),
-                    self.__extractMessages,
-                )
-                self.backMenu.addSeparator()
-            if self.hooks["generateAll"] is not None:
-                act = self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAll", self.tr("Generate all translations")
-                    ),
-                    self.__generateAll,
+                    self.tr("Generate all translations"), self.__generateAll
                 )
                 self.tsprocBackMenuActions.append(act)
-            if self.hooks["generateAllWithObsolete"] is not None:
                 act = self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAllWithObsolete",
-                        self.tr("Generate all translations (with obsolete)"),
-                    ),
+                    self.tr("Generate all translations (with obsolete)"),
                     self.__generateObsoleteAll,
                 )
                 self.tsprocBackMenuActions.append(act)
-            if self.hooks["releaseAll"] is not None:
                 act = self.backMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "releaseAll", self.tr("Release all translations")
-                    ),
-                    self.__releaseAll,
+                    self.tr("Release all translations"), self.__releaseAll
                 )
                 self.qmprocBackMenuActions.append(act)
-        self.backMenu.addSeparator()
+                self.backMenu.addSeparator()
+                act = self.backMenu.addAction(
+                    self.tr("Preview all translations"), self.__TRPreview
+                )
+            else:
+                if self.hooks["extractMessages"] is not None:
+                    act = self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "extractMessages", self.tr("Extract messages")
+                        ),
+                        self.__extractMessages,
+                    )
+                    self.backMenu.addSeparator()
+                if self.hooks["generateAll"] is not None:
+                    act = self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAll", self.tr("Generate all translations")
+                        ),
+                        self.__generateAll,
+                    )
+                    self.tsprocBackMenuActions.append(act)
+                if self.hooks["generateAllWithObsolete"] is not None:
+                    act = self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAllWithObsolete",
+                            self.tr("Generate all translations (with obsolete)"),
+                        ),
+                        self.__generateObsoleteAll,
+                    )
+                    self.tsprocBackMenuActions.append(act)
+                if self.hooks["releaseAll"] is not None:
+                    act = self.backMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "releaseAll", self.tr("Release all translations")
+                        ),
+                        self.__releaseAll,
+                    )
+                    self.qmprocBackMenuActions.append(act)
+            self.backMenu.addSeparator()
         self.__addTranslationBackAct = self.backMenu.addAction(
             self.tr("New translation..."), self.project.addLanguage
         )
@@ -408,86 +429,101 @@
             "PySide6",
             "PySide6C",
         ]:
-            act = self.multiMenu.addAction(
-                self.tr("Generate translations"), self.__generateSelected
-            )
-            self.tsMultiMenuActions.append(act)
-            self.tsprocMultiMenuActions.append(act)
-            act = self.multiMenu.addAction(
-                self.tr("Generate translations (with obsolete)"),
-                self.__generateObsoleteSelected,
-            )
-            self.tsMultiMenuActions.append(act)
-            self.tsprocMultiMenuActions.append(act)
-            self.multiMenu.addSeparator()
-            self.__qtLinguistMultiAct = self.multiMenu.addAction(
-                self.tr("Open in Qt-Linguist"), self._openItem
-            )
-            self.tsMultiMenuActions.append(self.__qtLinguistMultiAct)
-            act = self.multiMenu.addAction(
-                self.tr("Open in Editor"), self.__openFileInEditor
-            )
-            self.tsMultiMenuActions.append(act)
-            self.multiMenu.addSeparator()
-            act = self.multiMenu.addAction(
-                self.tr("Release translations"), self.__releaseSelected
-            )
-            self.tsMultiMenuActions.append(act)
-            self.qmprocMultiMenuActions.append(act)
-            self.multiMenu.addSeparator()
-            act = self.multiMenu.addAction(
-                self.tr("Preview translations"), self.__TRPreview
-            )
-            self.qmMultiMenuActions.append(act)
-        else:
-            if self.hooks["extractMessages"] is not None:
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "extractMessages", self.tr("Extract messages")
-                    ),
-                    self.__extractMessages,
+                    self.tr("Open in Editor"), self.__openFileInEditor
                 )
-                self.multiMenuActions.append(act)
+                self.tsMultiMenuActions.append(act)
                 self.multiMenu.addSeparator()
-            if self.hooks["generateSelected"] is not None:
+            else:
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateSelected", self.tr("Generate translations")
-                    ),
-                    self.__generateSelected,
+                    self.tr("Generate translations"), self.__generateSelected
                 )
                 self.tsMultiMenuActions.append(act)
                 self.tsprocMultiMenuActions.append(act)
-            if self.hooks["generateSelectedWithObsolete"] is not None:
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateSelectedWithObsolete",
-                        self.tr("Generate translations (with obsolete)"),
-                    ),
+                    self.tr("Generate translations (with obsolete)"),
                     self.__generateObsoleteSelected,
                 )
                 self.tsMultiMenuActions.append(act)
                 self.tsprocMultiMenuActions.append(act)
-            self.multiMenu.addSeparator()
-            if self.hooks["open"] is not None:
+                self.multiMenu.addSeparator()
+                self.__qtLinguistMultiAct = self.multiMenu.addAction(
+                    self.tr("Open in Qt-Linguist"), self._openItem
+                )
+                self.tsMultiMenuActions.append(self.__qtLinguistMultiAct)
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get("open", self.tr("Open")), self._openItem
+                    self.tr("Open in Editor"), self.__openFileInEditor
                 )
                 self.tsMultiMenuActions.append(act)
-            act = self.multiMenu.addAction(
-                self.tr("Open in Editor"), self.__openFileInEditor
-            )
-            self.tsMultiMenuActions.append(act)
-            self.multiMenu.addSeparator()
-            if self.hooks["releaseSelected"] is not None:
+                self.multiMenu.addSeparator()
                 act = self.multiMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "releaseSelected", self.tr("Release translations")
-                    ),
-                    self.__releaseSelected,
+                    self.tr("Release translations"), self.__releaseSelected
                 )
                 self.tsMultiMenuActions.append(act)
                 self.qmprocMultiMenuActions.append(act)
+                self.multiMenu.addSeparator()
+                act = self.multiMenu.addAction(
+                    self.tr("Preview translations"), self.__TRPreview
+                )
+                self.qmMultiMenuActions.append(act)
+        else:
+            if FileSystemUtilities.isRemoteFileName(self.project.getProjectPath()):
+                act = self.multiMenu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+                self.tsMultiMenuActions.append(act)
+                self.multiMenu.addSeparator()
+            else:
+                if self.hooks["extractMessages"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "extractMessages", self.tr("Extract messages")
+                        ),
+                        self.__extractMessages,
+                    )
+                    self.multiMenuActions.append(act)
+                    self.multiMenu.addSeparator()
+                if self.hooks["generateSelected"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateSelected", self.tr("Generate translations")
+                        ),
+                        self.__generateSelected,
+                    )
+                    self.tsMultiMenuActions.append(act)
+                    self.tsprocMultiMenuActions.append(act)
+                if self.hooks["generateSelectedWithObsolete"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateSelectedWithObsolete",
+                            self.tr("Generate translations (with obsolete)"),
+                        ),
+                        self.__generateObsoleteSelected,
+                    )
+                    self.tsMultiMenuActions.append(act)
+                    self.tsprocMultiMenuActions.append(act)
+                self.multiMenu.addSeparator()
+                if self.hooks["open"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get("open", self.tr("Open")),
+                        self._openItem,
+                    )
+                    self.tsMultiMenuActions.append(act)
+                act = self.multiMenu.addAction(
+                    self.tr("Open in Editor"), self.__openFileInEditor
+                )
+                self.tsMultiMenuActions.append(act)
+                self.multiMenu.addSeparator()
+                if self.hooks["releaseSelected"] is not None:
+                    act = self.multiMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "releaseSelected", self.tr("Release translations")
+                        ),
+                        self.__releaseSelected,
+                    )
+                    self.tsMultiMenuActions.append(act)
+                    self.qmprocMultiMenuActions.append(act)
         self.multiMenu.addSeparator()
         act = self.multiMenu.addAction(
             self.tr("Remove from project"), self.__removeLanguageFile
@@ -499,81 +535,85 @@
         self.multiMenu.addAction(self.tr("Configure..."), self._configure)
 
         self.dirMenu = QMenu(self)
-        if self.project.getProjectType() in [
-            "PyQt5",
-            "PyQt5C",
-            "PyQt6",
-            "PyQt6C",
-            "E7Plugin",
-            "PySide2",
-            "PySide2C",
-            "PySide6",
-            "PySide6C",
-        ]:
-            act = self.dirMenu.addAction(
-                self.tr("Generate all translations"), self.__generateAll
-            )
-            self.tsprocDirMenuActions.append(act)
-            act = self.dirMenu.addAction(
-                self.tr("Generate all translations (with obsolete)"),
-                self.__generateObsoleteAll,
-            )
-            self.tsprocDirMenuActions.append(act)
-            act = self.dirMenu.addAction(
-                self.tr("Release all translations"), self.__releaseAll
-            )
-            self.qmprocDirMenuActions.append(act)
-            self.dirMenu.addSeparator()
-            act = self.dirMenu.addAction(
-                self.tr("Preview all translations"), self.__TRPreview
-            )
-        else:
-            if self.hooks["extractMessages"] is not None:
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            if self.project.getProjectType() in [
+                "PyQt5",
+                "PyQt5C",
+                "PyQt6",
+                "PyQt6C",
+                "E7Plugin",
+                "PySide2",
+                "PySide2C",
+                "PySide6",
+                "PySide6C",
+            ]:
                 act = self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "extractMessages", self.tr("Extract messages")
-                    ),
-                    self.__extractMessages,
-                )
-                self.dirMenuActions.append(act)
-                self.dirMenu.addSeparator()
-            if self.hooks["generateAll"] is not None:
-                act = self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAll", self.tr("Generate all translations")
-                    ),
-                    self.__generateAll,
+                    self.tr("Generate all translations"), self.__generateAll
                 )
                 self.tsprocDirMenuActions.append(act)
-            if self.hooks["generateAllWithObsolete"] is not None:
                 act = self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "generateAllWithObsolete",
-                        self.tr("Generate all translations (with obsolete)"),
-                    ),
+                    self.tr("Generate all translations (with obsolete)"),
                     self.__generateObsoleteAll,
                 )
                 self.tsprocDirMenuActions.append(act)
-            if self.hooks["releaseAll"] is not None:
                 act = self.dirMenu.addAction(
-                    self.hooksMenuEntries.get(
-                        "releaseAll", self.tr("Release all translations")
-                    ),
-                    self.__releaseAll,
+                    self.tr("Release all translations"), self.__releaseAll
                 )
                 self.qmprocDirMenuActions.append(act)
+                self.dirMenu.addSeparator()
+                act = self.dirMenu.addAction(
+                    self.tr("Preview all translations"), self.__TRPreview
+                )
+            else:
+                if self.hooks["extractMessages"] is not None:
+                    act = self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "extractMessages", self.tr("Extract messages")
+                        ),
+                        self.__extractMessages,
+                    )
+                    self.dirMenuActions.append(act)
+                    self.dirMenu.addSeparator()
+                if self.hooks["generateAll"] is not None:
+                    act = self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAll", self.tr("Generate all translations")
+                        ),
+                        self.__generateAll,
+                    )
+                    self.tsprocDirMenuActions.append(act)
+                if self.hooks["generateAllWithObsolete"] is not None:
+                    act = self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "generateAllWithObsolete",
+                            self.tr("Generate all translations (with obsolete)"),
+                        ),
+                        self.__generateObsoleteAll,
+                    )
+                    self.tsprocDirMenuActions.append(act)
+                if self.hooks["releaseAll"] is not None:
+                    act = self.dirMenu.addAction(
+                        self.hooksMenuEntries.get(
+                            "releaseAll", self.tr("Release all translations")
+                        ),
+                        self.__releaseAll,
+                    )
+                    self.qmprocDirMenuActions.append(act)
         self.dirMenu.addSeparator()
         act = self.dirMenu.addAction(self.tr("Delete"), self._deleteDirectory)
         self.dirMenuActions.append(act)
         self.dirMenu.addSeparator()
-        self.__addTranslationDirAct = self.dirMenu.addAction(
-            self.tr("New translation..."), self.project.addLanguage
-        )
-        self.dirMenu.addAction(
-            self.tr("Add translation files..."), self.__addTranslationFiles
-        )
-        self.dirMenu.addSeparator()
-        self.dirMenu.addAction(self.tr("Show in File Manager"), self._showInFileManager)
+        if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+            self.__addTranslationDirAct = self.dirMenu.addAction(
+                self.tr("New translation..."), self.project.addLanguage
+            )
+            self.dirMenu.addAction(
+                self.tr("Add translation files..."), self.__addTranslationFiles
+            )
+            self.dirMenu.addSeparator()
+            self.dirMenu.addAction(
+                self.tr("Show in File Manager"), self._showInFileManager
+            )
         self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
         self.dirMenu.addSeparator()
         self.dirMenu.addAction(self.tr("Configure..."), self._configure)
@@ -655,8 +695,9 @@
             elif tsFiles > 0:
                 for act in self.tsMenuActions:
                     act.setEnabled(True)
-                self.__qtLinguistAct.setEnabled(QtUtilities.hasQtLinguist())
-                self.__qtLinguistMultiAct.setEnabled(QtUtilities.hasQtLinguist())
+                if FileSystemUtilities.isPlainFileName(self.project.getProjectPath()):
+                    self.__qtLinguistAct.setEnabled(QtUtilities.hasQtLinguist())
+                    self.__qtLinguistMultiAct.setEnabled(QtUtilities.hasQtLinguist())
                 for act in self.qmMenuActions:
                     act.setEnabled(False)
             elif qmFiles > 0:
@@ -727,7 +768,9 @@
         """
         Private slot called by the dirMenu aboutToShow signal.
         """
-        if self.project.getProjectType() in [
+        if FileSystemUtilities.isPlainFileName(
+            self.project.getProjectPath()
+        ) and self.project.getProjectType() in [
             "PyQt5",
             "PyQt5C",
             "PyQt6",
@@ -756,7 +799,9 @@
         """
         Private slot called by the backMenu aboutToShow signal.
         """
-        if self.project.getProjectType() in [
+        if FileSystemUtilities.isPlainFileName(
+            self.project.getProjectPath()
+        ) and self.project.getProjectType() in [
             "PyQt5",
             "PyQt5C",
             "PyQt6",
@@ -795,7 +840,9 @@
                 # hook support
                 if self.hooks["open"] is not None:
                     self.hooks["open"](itm.fileName())
-                elif itm.isLinguistFile():
+                elif itm.isLinguistFile() and FileSystemUtilities.isPlainFileName(
+                    itm.fileName()
+                ):
                     if itm.fileExt() == ".ts":
                         self.linguistFile.emit(itm.fileName())
                     else:
--- a/src/eric7/Project/PropertiesDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/PropertiesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -31,7 +31,7 @@
     Class implementing the project properties dialog.
     """
 
-    def __init__(self, project, new=True, parent=None, name=None):
+    def __init__(self, project, new=True, isRemote=False, parent=None, name=None):
         """
         Constructor
 
@@ -40,6 +40,8 @@
         @param new flag indicating the generation of a new project
             (defaults to True)
         @type bool (optional)
+        @param isRemote flag indicating a remote project (defaults to False)
+        @type bool (optional)
         @param parent parent widget of this dialog (defaults to None)
         @type QWidget (optional)
         @param name name of this dialog (defaults to None)
@@ -50,9 +52,19 @@
             self.setObjectName(name)
         self.setupUi(self)
 
+        self.__remoteProject = isRemote
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         self.dirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
+        self.dirPicker.setRemote(isRemote)
+
         self.srcDirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
+        self.srcDirPicker.setRemote(isRemote)
+
         self.mainscriptPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
+        self.mainscriptPicker.setRemote(isRemote)
 
         self.makeButton.setIcon(EricPixmapCache.getIcon("makefile"))
 
@@ -62,11 +74,19 @@
 
         self.project = project
         self.newProject = new
+
         self.transPropertiesDlg = None
         self.spellPropertiesDlg = None
         self.makePropertiesDlg = None
         self.__fileTypesDict = {}
 
+        if self.__remoteProject:
+            # some stuff is not supported for remote projects
+            self.makeCheckBox.setEnabled(False)
+            self.makeButton.setEnabled(False)
+            self.testingFrameworkComboBox.setEnabled(False)
+            self.embeddedVenvCheckBox.setEnabled(False)
+            self.spellPropertiesButton.setEnabled(False)
         self.languageComboBox.addItems(project.getProgrammingLanguages())
 
         projectTypes = []
@@ -76,18 +96,28 @@
         for projectType in sorted(projectTypes):
             self.projectTypeComboBox.addItem(projectType[0], projectType[1])
 
-        ipath = Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()
-        self.__initPaths = [
-            FileSystemUtilities.fromNativeSeparators(ipath),
-            FileSystemUtilities.fromNativeSeparators(ipath) + "/",
-        ]
+        if self.__remoteProject:
+            self.__initPaths = [self.__remotefsInterface.getcwd()]
+        else:
+            ipath = Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()
+            self.__initPaths = [
+                FileSystemUtilities.fromNativeSeparators(ipath),
+                FileSystemUtilities.fromNativeSeparators(ipath) + "/",
+            ]
 
         self.licenseComboBox.lineEdit().setClearButtonEnabled(True)
         self.__populateLicenseComboBox()
 
         if not new:
-            name = os.path.splitext(self.project.pfile)[0]
-            self.nameEdit.setText(os.path.basename(name))
+            self.nameEdit.setReadOnly(True)
+            self.dirPicker.setReadOnly(True)
+
+            if self.__remoteProject:
+                name = self.__remotefsInterface.splitext(self.project.pfile)[0]
+                self.nameEdit.setText(self.__remotefsInterface.basename(name))
+            else:
+                name = os.path.splitext(self.project.pfile)[0]
+                self.nameEdit.setText(os.path.basename(name))
             self.nameEdit.setReadOnly(True)
             self.languageComboBox.setCurrentIndex(
                 self.languageComboBox.findText(
@@ -116,40 +146,53 @@
             )
             self.eolComboBox.setCurrentIndex(self.project.getProjectData(dataKey="EOL"))
             self.vcsLabel.show()
-            if self.project.vcs is not None:
-                vcsSystemsDict = (
-                    ericApp()
-                    .getObject("PluginManager")
-                    .getPluginDisplayStrings("version_control")
+            if not self.__remoteProject:
+                # VCS not supported for remote projects
+                if self.project.vcs is not None:
+                    vcsSystemsDict = (
+                        ericApp()
+                        .getObject("PluginManager")
+                        .getPluginDisplayStrings("version_control")
+                    )
+                    try:
+                        vcsSystemDisplay = vcsSystemsDict[
+                            self.project.getProjectData(dataKey="VCS")
+                        ]
+                    except KeyError:
+                        vcsSystemDisplay = "None"
+                    self.vcsLabel.setText(
+                        self.tr(
+                            "The project is version controlled by <b>{0}</b>."
+                        ).format(vcsSystemDisplay)
+                    )
+                    self.vcsInfoButton.show()
+                else:
+                    self.vcsLabel.setText(
+                        self.tr("The project is not version controlled.")
+                    )
+                    self.vcsInfoButton.hide()
+            else:
+                self.vcsLabel.setText(
+                    self.tr("Version control is not available for remote projects.")
                 )
-                try:
-                    vcsSystemDisplay = vcsSystemsDict[
-                        self.project.getProjectData(dataKey="VCS")
-                    ]
-                except KeyError:
-                    vcsSystemDisplay = "None"
-                self.vcsLabel.setText(
-                    self.tr("The project is version controlled by <b>{0}</b>.").format(
-                        vcsSystemDisplay
-                    )
-                )
-                self.vcsInfoButton.show()
-            else:
-                self.vcsLabel.setText(self.tr("The project is not version controlled."))
                 self.vcsInfoButton.hide()
             self.vcsCheckBox.hide()
-            self.makeCheckBox.setChecked(
-                self.project.getProjectData(dataKey="MAKEPARAMS")["MakeEnabled"]
-            )
+            if self.__remoteProject:
+                self.makeCheckBox.setChecked(False)
+            else:
+                self.makeCheckBox.setChecked(
+                    self.project.getProjectData(dataKey="MAKEPARAMS")["MakeEnabled"]
+                )
             cindex = self.docstringStyleComboBox.findData(
                 self.project.getProjectData(dataKey="DOCSTRING")
             )
             self.docstringStyleComboBox.setCurrentIndex(cindex)
-            with contextlib.suppress(KeyError):
-                cindex = self.testingFrameworkComboBox.findData(
-                    self.project.getProjectData(dataKey="TESTING_FRAMEWORK")
-                )
-                self.testingFrameworkComboBox.setCurrentIndex(cindex)
+            if not self.__remoteProject:
+                with contextlib.suppress(KeyError):
+                    cindex = self.testingFrameworkComboBox.findData(
+                        self.project.getProjectData(dataKey="TESTING_FRAMEWORK")
+                    )
+                    self.testingFrameworkComboBox.setCurrentIndex(cindex)
             with contextlib.suppress(KeyError):
                 self.licenseComboBox.setCurrentText(
                     self.project.getProjectData(dataKey="LICENSE")
@@ -166,7 +209,7 @@
             self.versionEdit.setText("0.1")
             self.vcsLabel.hide()
             self.vcsInfoButton.hide()
-            if not self.project.vcsSoftwareAvailable():
+            if self.__remoteProject or not self.project.vcsSoftwareAvailable():
                 self.vcsCheckBox.hide()
 
         self.__origProgrammingLanguage = self.languageComboBox.currentText()
@@ -252,10 +295,17 @@
         @param txt name of the project directory
         @type str
         """
-        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
-            bool(txt)
-            and FileSystemUtilities.fromNativeSeparators(txt) not in self.__initPaths
-        )
+        if self.__remoteProject:
+            self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+                FileSystemUtilities.isRemoteFileName(txt)
+                and txt not in self.__initPaths
+            )
+        else:
+            self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+                bool(txt)
+                and FileSystemUtilities.fromNativeSeparators(txt)
+                not in self.__initPaths
+            )
 
     @pyqtSlot(str)
     def on_srcDirPicker_pathSelected(self, srcDir):
@@ -268,7 +318,13 @@
         if srcDir:
             ppath = self.dirPicker.text()
             if ppath:
-                ppath = QDir(ppath).absolutePath() + QDir.separator()
+                if self.__remoteProject:
+                    ppath = (
+                        FileSystemUtilities.remoteFileName(ppath)
+                        + self.__remotefsInterface.separator()
+                    )
+                else:
+                    ppath = os.path.abspath(ppath) + os.sep
                 srcDir = srcDir.replace(ppath, "")
             self.srcDirPicker.setText(srcDir)
 
@@ -280,7 +336,10 @@
         """
         ppath = self.dirPicker.text()
         if not ppath:
-            ppath = QDir.currentPath()
+            if self.__remoteProject:
+                ppath = self.__remotefsInterface.getcwd()
+            else:
+                ppath = QDir.currentPath()
         self.srcDirPicker.setDefaultDirectory(ppath)
 
     @pyqtSlot()
@@ -307,7 +366,7 @@
 
         if self.transPropertiesDlg is None:
             self.transPropertiesDlg = TranslationPropertiesDialog(
-                self.project, self.newProject, self
+                self.project, self.newProject, self, isRemote=self.__remoteProject
             )
         else:
             self.transPropertiesDlg.initFilters()
@@ -341,7 +400,13 @@
         if script:
             ppath = self.dirPicker.text()
             if ppath:
-                ppath = QDir(ppath).absolutePath() + QDir.separator()
+                if self.__remoteProject:
+                    ppath = (
+                        FileSystemUtilities.remoteFileName(ppath)
+                        + self.__remotefsInterface.separator()
+                    )
+                else:
+                    ppath = os.path.abspath(ppath) + os.sep
                 script = script.replace(ppath, "")
             self.mainscriptPicker.setText(script)
 
@@ -353,7 +418,10 @@
         """
         ppath = self.dirPicker.text()
         if not ppath:
-            ppath = QDir.currentPath()
+            if self.__remoteProject:
+                ppath = self.__remotefsInterface.getcwd()
+            else:
+                ppath = QDir.currentPath()
         self.mainscriptPicker.setDefaultDirectory(ppath)
 
     @pyqtSlot()
@@ -388,7 +456,10 @@
         @return data of the project directory edit
         @rtype str
         """
-        return os.path.abspath(self.dirPicker.text())
+        if self.__remoteProject:
+            return FileSystemUtilities.remoteFileName(self.dirPicker.text())
+        else:
+            return os.path.abspath(self.dirPicker.text())
 
     @pyqtSlot()
     def __initFileTypesDict(self, force=False):
@@ -445,18 +516,35 @@
 
         self.__setMainScriptPickerFilters()
 
+        self.__setMainScriptPickerFilters()
+
     def storeData(self):
         """
         Public method to store the entered/modified data.
         """
-        self.project.ppath = os.path.abspath(self.dirPicker.text())
-        fn = self.nameEdit.text()
-        if fn:
-            self.project.name = fn
-            fn = f"{fn}.epj"
-            self.project.pfile = os.path.join(self.project.ppath, fn)
-        else:
-            self.project.pfile = ""
+        if self.newProject:
+            if self.__remoteProject:
+                self.project.ppath = FileSystemUtilities.remoteFileName(
+                    self.__remotefsInterface.abspath(self.dirPicker.text())
+                )
+                fn = self.nameEdit.text()
+                if fn:
+                    self.project.name = fn
+                    fn = f"{fn}.epj"
+                    self.project.pfile = self.__remotefsInterface.join(
+                        self.project.ppath, fn
+                    )
+                else:
+                    self.project.pfile = ""
+            else:
+                self.project.ppath = os.path.abspath(self.dirPicker.text())
+                fn = self.nameEdit.text()
+                if fn:
+                    self.project.name = fn
+                    fn = f"{fn}.epj"
+                    self.project.pfile = os.path.join(self.project.ppath, fn)
+                else:
+                    self.project.pfile = ""
         self.project.setProjectData(self.versionEdit.text(), dataKey="VERSION")
         srcDir = self.srcDirPicker.text()
         if srcDir:
@@ -468,7 +556,10 @@
         if fn:
             fn = self.project.getRelativePath(fn)
             self.project.setProjectData(fn, dataKey="MAINSCRIPT")
-            self.project.translationsRoot = os.path.splitext(fn)[0]
+            if self.__remoteProject:
+                self.project.translationsRoot = self.__remotefsInterface.splitext(fn)[0]
+            else:
+                self.project.translationsRoot = os.path.splitext(fn)[0]
         else:
             self.project.setProjectData("", dataKey="MAINSCRIPT")
             self.project.translationsRoot = ""
--- a/src/eric7/Project/QuickFindFileDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/QuickFindFileDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -21,6 +21,8 @@
     QWidget,
 )
 
+from eric7.EricWidgets.EricApplication import ericApp
+
 from .Ui_QuickFindFile import Ui_QuickFindFile
 
 
@@ -61,7 +63,11 @@
         self.stopButton = self.buttonBox.addButton(
             self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole
         )
+
         self.project = project
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
 
     def eventFilter(self, source, event):
         """
@@ -126,7 +132,11 @@
         if itm is not None:
             filePath = itm.text(1)
             fileName = itm.text(0)
-            fullPath = os.path.join(self.project.ppath, filePath, fileName)
+            fullPath = (
+                self.__remotefsInterface.join(self.project.ppath, filePath, fileName)
+                if self.__isRemote
+                else os.path.join(self.project.ppath, filePath, fileName)
+            )
 
             if fullPath.endswith(".ui"):
                 self.designerFile.emit(fullPath)
@@ -192,7 +202,14 @@
         ordered = []
         for _, in_order, name in possible:
             try:
-                age = os.stat(os.path.join(self.project.ppath, name)).st_mtime
+                age = (
+                    self.__remotefsInterface.stat(
+                        self.__remotefsInterface.join(self.project.ppath, name),
+                        ["st_mtime"],
+                    )["st_mtime"]
+                    if self.__isRemote
+                    else os.stat(os.path.join(self.project.ppath, name)).st_mtime
+                )
             except OSError:
                 # skipping, because it doesn't appear to exist...
                 continue
@@ -223,9 +240,12 @@
 
         for _in_order, _age, name in ordered:
             found = True
-            QTreeWidgetItem(
-                self.fileList, [os.path.basename(name), os.path.dirname(name)]
+            head, tail = (
+                self.__remotefsInterface.split(name)
+                if self.__isRemote
+                else os.path.split(name)
             )
+            QTreeWidgetItem(self.fileList, [tail, head])
         QApplication.processEvents()
 
         del locations
@@ -283,10 +303,15 @@
             current is not None
         )
 
-    def show(self):
+    def show(self, isRemote):
         """
-        Public method to enable/disable the project checkbox.
+        Public method to perform actions before showing the dialog.
+
+        @param isRemote flag indicating a remote project
+        @type bool
         """
+        self.__isRemote = isRemote
+
         self.fileNameEdit.selectAll()
         self.fileNameEdit.setFocus()
 
--- a/src/eric7/Project/TranslationPropertiesDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/TranslationPropertiesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -13,6 +13,7 @@
 from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem
 
 from eric7.EricWidgets import EricFileDialog
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricCompleters import EricFileCompleter
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
 from eric7.SystemUtilities import FileSystemUtilities
@@ -25,7 +26,7 @@
     Class implementing the Translations Properties dialog.
     """
 
-    def __init__(self, project, new, parent):
+    def __init__(self, project, new, parent, isRemote=False):
         """
         Constructor
 
@@ -35,14 +36,21 @@
         @type bool
         @param parent parent widget of this dialog
         @type QWidget
+        @param isRemote flag indicating a remote project (defaults to False)
+        @type bool (optional)
         """
         super().__init__(parent)
         self.setupUi(self)
 
         self.transPatternPicker.setMode(EricPathPickerModes.SAVE_FILE_MODE)
         self.transPatternPicker.setDefaultDirectory(project.ppath)
+        self.transPatternPicker.setRemote(isRemote)
+
         self.transBinPathPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.transBinPathPicker.setDefaultDirectory(project.ppath)
+        self.transBinPathPicker.setRemote(isRemote)
+
+        self.__isRemote = isRemote
 
         self.project = project
         self.parent = parent
@@ -149,13 +157,21 @@
         """
         Private slot to add the shown exception to the listwidget.
         """
+        separator = (
+            ericApp()
+            .getObject("EricServer")
+            .getServiceInterface("FileSystem")
+            .separator()
+            if self.__isRemote
+            else os.sep
+        )
         texcept = self.exceptionEdit.text()
         texcept = (
-            texcept.replace(self.parent.getPPath() + os.sep, "")
+            texcept.replace(self.parent.getPPath() + separator, "")
             if self.project.ppath == ""
             else self.project.getRelativePath(texcept)
         )
-        if texcept.endswith(os.sep):
+        if texcept.endswith(separator):
             texcept = texcept[:-1]
         if texcept:
             QListWidgetItem(texcept, self.exceptionsList)
--- a/src/eric7/Project/Ui_DebuggerPropertiesDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/Ui_DebuggerPropertiesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,18 +16,18 @@
         DebuggerPropertiesDialog.setSizeGripEnabled(True)
         self.verticalLayout = QtWidgets.QVBoxLayout(DebuggerPropertiesDialog)
         self.verticalLayout.setObjectName("verticalLayout")
-        self.groupBox = QtWidgets.QGroupBox(parent=DebuggerPropertiesDialog)
-        self.groupBox.setObjectName("groupBox")
-        self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox)
+        self.debugClientGroup = QtWidgets.QGroupBox(parent=DebuggerPropertiesDialog)
+        self.debugClientGroup.setObjectName("debugClientGroup")
+        self.horizontalLayout = QtWidgets.QHBoxLayout(self.debugClientGroup)
         self.horizontalLayout.setObjectName("horizontalLayout")
-        self.debugClientPicker = EricComboPathPicker(parent=self.groupBox)
+        self.debugClientPicker = EricComboPathPicker(parent=self.debugClientGroup)
         self.debugClientPicker.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
         self.debugClientPicker.setObjectName("debugClientPicker")
         self.horizontalLayout.addWidget(self.debugClientPicker)
-        self.debugClientClearHistoryButton = QtWidgets.QToolButton(parent=self.groupBox)
+        self.debugClientClearHistoryButton = QtWidgets.QToolButton(parent=self.debugClientGroup)
         self.debugClientClearHistoryButton.setObjectName("debugClientClearHistoryButton")
         self.horizontalLayout.addWidget(self.debugClientClearHistoryButton)
-        self.verticalLayout.addWidget(self.groupBox)
+        self.verticalLayout.addWidget(self.debugClientGroup)
         self.venvGroupBox = QtWidgets.QGroupBox(parent=DebuggerPropertiesDialog)
         self.venvGroupBox.setObjectName("venvGroupBox")
         self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.venvGroupBox)
@@ -145,7 +145,7 @@
     def retranslateUi(self, DebuggerPropertiesDialog):
         _translate = QtCore.QCoreApplication.translate
         DebuggerPropertiesDialog.setWindowTitle(_translate("DebuggerPropertiesDialog", "Debugger Properties"))
-        self.groupBox.setTitle(_translate("DebuggerPropertiesDialog", "Debug Client"))
+        self.debugClientGroup.setTitle(_translate("DebuggerPropertiesDialog", "Debug Client"))
         self.debugClientPicker.setToolTip(_translate("DebuggerPropertiesDialog", "Enter the path of the Debug Client to be used.  Leave empty to use the default."))
         self.debugClientClearHistoryButton.setToolTip(_translate("DebuggerPropertiesDialog", "Press to clear the history of entered debug clients"))
         self.venvGroupBox.setTitle(_translate("DebuggerPropertiesDialog", "Virtual Environment"))
--- a/src/eric7/Project/UserProjectFile.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Project/UserProjectFile.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,6 +16,8 @@
 from eric7 import Preferences
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 Project = typing.TypeVar("Project")
 
@@ -47,6 +49,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         userProjectDict = {
             "header": {
                 "comment": "eric user project file for project {0}".format(
@@ -62,13 +68,18 @@
 
         try:
             jsonString = json.dumps(userProjectDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote User Project Properties")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save User Project Properties")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save User Project Properties"),
+                    title,
                     self.tr(
                         "<p>The user specific project properties file"
                         " <b>{0}</b> could not be written.</p>"
@@ -89,14 +100,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote User Project Properties")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read User Project Properties")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             userProjectDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read User Project Properties"),
+                title,
                 self.tr(
                     "<p>The user specific project properties file <b>{0}</b>"
                     " could not be read.</p><p>Reason: {1}</p>"
--- a/src/eric7/QScintilla/Editor.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/QScintilla/Editor.py	Fri Jun 07 13:58:16 2024 +0200
@@ -68,6 +68,7 @@
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.Globals import recentNameBreakpointConditions
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
 from eric7.UI import PythonDisViewer
 from eric7.Utilities import MouseUtilities
@@ -265,6 +266,10 @@
         self.project = ericApp().getObject("Project")
         self.setFileName(fn)
 
+        self.__remotefsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         # clear some variables
         self.lastHighlight = None  # remember the last highlighted line
         self.lastErrorMarker = None  # remember the last error line
@@ -445,11 +450,20 @@
                 if not Utilities.MimeTypes.isTextFile(self.fileName):
                     raise OSError()
 
-                if (
-                    FileSystemUtilities.isPlainFileName(self.fileName)
-                    and pathlib.Path(self.fileName).exists()
-                ):
+                if FileSystemUtilities.isRemoteFileName(self.fileName):
+                    fileIsRemote = True
+                    fileExists = self.__remotefsInterface.exists(self.fileName)
+                    fileSizeKB = (
+                        self.__remotefsInterface.stat(self.fileName, ["st_size"])[
+                            "st_size"
+                        ]
+                        // 1024
+                    )
+                else:
+                    fileIsRemote = False
+                    fileExists = pathlib.Path(self.fileName).exists()
                     fileSizeKB = pathlib.Path(self.fileName).stat().st_size // 1024
+                if fileExists:
                     if fileSizeKB > Preferences.getEditor("RejectFilesize"):
                         EricMessageBox.warning(
                             None,
@@ -479,7 +493,7 @@
                         if not res:
                             raise OSError()
 
-                    self.readFile(self.fileName, True)
+                    self.readFile(self.fileName, createIt=True, isRemote=fileIsRemote)
 
                 self.__bindLexer(self.fileName)
                 self.__bindCompleter(self.fileName)
@@ -1054,6 +1068,11 @@
             self.tr("Save As..."),
             self.__contextSaveAs,
         )
+        self.menuActs["SaveAsRemote"] = self.menu.addAction(
+            EricPixmapCache.getIcon("fileSaveAsRemote"),
+            self.tr("Save as (Remote)..."),
+            self.__contextSaveAsRemote,
+        )
         self.menu.addAction(
             EricPixmapCache.getIcon("fileSaveCopy"),
             self.tr("Save Copy..."),
@@ -1181,7 +1200,9 @@
         """
         menu = QMenu(self.tr("Show"))
 
-        menu.addAction(self.tr("Code metrics..."), self.__showCodeMetrics)
+        self.codeMetricsAct = menu.addAction(
+            self.tr("Code metrics..."), self.__showCodeMetrics
+        )
         self.coverageMenuAct = menu.addAction(
             self.tr("Code coverage..."), self.__showCodeCoverage
         )
@@ -2161,8 +2182,8 @@
         @param m modification status
         @type bool
         """
-        if not m and bool(self.fileName) and pathlib.Path(self.fileName).exists():
-            self.lastModified = pathlib.Path(self.fileName).stat().st_mtime
+        if not m:
+            self.recordModificationTime()
         self.modificationStatusChanged.emit(m, self)
         self.undoAvailable.emit(self.isUndoAvailable())
         self.redoAvailable.emit(self.isRedoAvailable())
@@ -3458,11 +3479,7 @@
                 self,
                 self.tr("File Modified"),
                 self.tr("<p>The file <b>{0}</b> has unsaved changes.</p>").format(fn),
-                (
-                    self.saveFile
-                    if not FileSystemUtilities.isRemoteFileName(self.fileName)
-                    else None
-                ),
+                self.saveFile,
             )
             if res:
                 self.vm.setEditorName(self, self.fileName)
@@ -3490,7 +3507,7 @@
                     break
                     # Couldn't find the unmodified state
 
-    def readFile(self, fn, createIt=False, encoding="", noempty=False):
+    def readFile(self, fn, createIt=False, encoding="", noempty=False, isRemote=False):
         """
         Public method to read the text from a file.
 
@@ -3504,26 +3521,46 @@
         @type str (optional)
         @param noempty flag indicating to not set an empty text (defaults to False)
         @type bool (optional)
-        """
-        self.__loadEditorConfig(fileName=fn)
+        @param isRemote flag indicating a remote file (defaults to False)
+        @type bool (optional)
+        """
+        if FileSystemUtilities.isPlainFileName(fn):
+            self.__loadEditorConfig(fileName=fn)
 
         try:
             with EricOverrideCursor():
-                if createIt and not os.path.exists(fn):
-                    with open(fn, "w"):
-                        pass
-                if encoding == "":
-                    encoding = self.__getEditorConfig("DefaultEncoding", nodefault=True)
-                if encoding:
-                    txt, self.encoding = Utilities.readEncodedFileWithEncoding(
-                        fn, encoding
-                    )
+                if FileSystemUtilities.isRemoteFileName(fn) or isRemote:
+                    title = self.tr("Open Remote File")
+                    if encoding:
+                        (
+                            txt,
+                            self.encoding,
+                        ) = self.__remotefsInterface.readEncodedFileWithEncoding(
+                            fn, encoding, create=True
+                        )
+                    else:
+                        txt, self.encoding = self.__remotefsInterface.readEncodedFile(
+                            fn, create=True
+                        )
                 else:
-                    txt, self.encoding = Utilities.readEncodedFile(fn)
+                    title = self.tr("Open File")
+                    if createIt and not os.path.exists(fn):
+                        with open(fn, "w"):
+                            pass
+                    if encoding == "":
+                        encoding = self.__getEditorConfig(
+                            "DefaultEncoding", nodefault=True
+                        )
+                    if encoding:
+                        txt, self.encoding = Utilities.readEncodedFileWithEncoding(
+                            fn, encoding
+                        )
+                    else:
+                        txt, self.encoding = Utilities.readEncodedFile(fn)
         except (OSError, UnicodeDecodeError) as why:
             EricMessageBox.critical(
                 self.vm,
-                self.tr("Open File"),
+                title,
                 self.tr(
                     "<p>The file <b>{0}</b> could not be opened.</p>"
                     "<p>Reason: {1}</p>"
@@ -3553,7 +3590,7 @@
 
             self.extractTasks()
 
-            self.lastModified = pathlib.Path(fn).stat().st_mtime
+            self.recordModificationTime(filename=fn)
 
     @pyqtSlot()
     def __convertTabs(self):
@@ -3605,7 +3642,11 @@
         @return flag indicating success
         @rtype bool
         """
-        config = self.__loadEditorConfigObject(fn)
+        config = (
+            None
+            if self.fileName and fn == self.fileName
+            else self.__loadEditorConfigObject(fn)
+        )
 
         eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config)
         if eol is not None:
@@ -3627,7 +3668,7 @@
 
         # create a backup file, if the option is set
         createBackup = backup and Preferences.getEditor("CreateBackupFile")
-        if createBackup:
+        if createBackup and FileSystemUtilities.isPlainFileName(fn):
             if os.path.islink(fn):
                 fn = os.path.realpath(fn)
             bfn = "{0}~".format(fn)
@@ -3647,28 +3688,41 @@
             editorConfigEncoding = self.__getEditorConfig(
                 "DefaultEncoding", nodefault=True, config=config
             )
-            self.encoding = Utilities.writeEncodedFile(
-                fn, txt, self.encoding, forcedEncoding=editorConfigEncoding
-            )
-            if createBackup and perms_valid:
-                os.chmod(fn, permissions)
+            if FileSystemUtilities.isPlainFileName(fn):
+                title = self.tr("Save File")
+                self.encoding = Utilities.writeEncodedFile(
+                    fn, txt, self.encoding, forcedEncoding=editorConfigEncoding
+                )
+                if createBackup and perms_valid:
+                    os.chmod(fn, permissions)
+            else:
+                title = self.tr("Save Remote File")
+                self.encoding = self.__remotefsInterface.writeEncodedFile(
+                    fn,
+                    txt,
+                    self.encoding,
+                    forcedEncoding=editorConfigEncoding,
+                    createBackup=createBackup,
+                )
             return True
         except (OSError, UnicodeError, Utilities.CodingError) as why:
             EricMessageBox.critical(
                 self,
-                self.tr("Save File"),
+                title,
                 self.tr(
                     "<p>The file <b>{0}</b> could not be saved.<br/>Reason: {1}</p>"
                 ).format(fn, str(why)),
             )
             return False
 
-    def __getSaveFileName(self, path=None):
+    def __getSaveFileName(self, path=None, remote=False):
         """
         Private method to get the name of the file to be saved.
 
-        @param path directory to save the file in
-        @type str
+        @param path directory to save the file in (defaults to None)
+        @type str (optional)
+        @param remote flag indicating to save as a remote file (defaults to False)
+        @type bool (optional)
         @return file name
         @rtype str
         """
@@ -3682,7 +3736,12 @@
         if not path and self.fileName:
             path = os.path.dirname(self.fileName)
         if not path:
-            path = Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()
+            if remote:
+                path = ""
+            else:
+                path = (
+                    Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()
+                )
 
         if self.fileName:
             filterPattern = "(*{0})".format(os.path.splitext(self.fileName)[1])
@@ -3694,14 +3753,26 @@
                 defaultFilter = Preferences.getEditor("DefaultSaveFilter")
         else:
             defaultFilter = Preferences.getEditor("DefaultSaveFilter")
-        fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
-            self,
-            self.tr("Save File"),
-            path,
-            Lexers.getSaveFileFiltersList(True, True),
-            defaultFilter,
-            EricFileDialog.DontConfirmOverwrite,
-        )
+
+        if remote or FileSystemUtilities.isRemoteFileName(path):
+            title = self.tr("Save Remote File")
+            fn, selectedFilter = EricServerFileDialog.getSaveFileNameAndFilter(
+                self,
+                title,
+                path,
+                Lexers.getSaveFileFiltersList(True, True),
+                defaultFilter,
+            )
+        else:
+            title = self.tr("Save File")
+            fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+                self,
+                title,
+                path,
+                Lexers.getSaveFileFiltersList(True, True),
+                defaultFilter,
+                EricFileDialog.DontConfirmOverwrite,
+            )
 
         if fn:
             if fn.endswith("."):
@@ -3712,10 +3783,13 @@
                 ex = selectedFilter.split("(*")[1].split(")")[0]
                 if ex:
                     fpath = fpath.with_suffix(ex)
-            if fpath.exists():
+            if (
+                FileSystemUtilities.isRemoteFileName(str(fpath))
+                and self.__remotefsInterface.exists(str(fpath))
+            ) or (FileSystemUtilities.isPlainFileName(str(fpath)) and fpath.exists()):
                 res = EricMessageBox.yesNo(
                     self,
-                    self.tr("Save File"),
+                    title,
                     self.tr(
                         "<p>The file <b>{0}</b> already exists. Overwrite it?</p>"
                     ).format(fpath),
@@ -3748,21 +3822,21 @@
 
         return res
 
-    def saveFile(self, saveas=False, path=None):
+    def saveFile(self, saveas=False, path=None, remote=False):
         """
         Public method to save the text to a file.
 
-        @param saveas flag indicating a 'save as' action
-        @type bool
-        @param path directory to save the file in
-        @type str
+        @param saveas flag indicating a 'save as' action (defaults to False)
+        @type bool (optional)
+        @param path directory to save the file in (defaults to None)
+        @type str (optional)
+        @param remote flag indicating to save as a remote file (defaults to False)
+        @type bool (optional)
         @return flag indicating success
         @rtype bool
         """
-        if not saveas and (
-            not self.isModified() or FileSystemUtilities.isRemoteFileName(self.fileName)
-        ):
-            # do nothing if text wasn't changed or is a remote file
+        if not saveas and not self.isModified():
+            # do nothing if text was not changed
             return False
 
         if FileSystemUtilities.isDeviceFileName(self.fileName):
@@ -3772,7 +3846,7 @@
         if saveas or self.fileName == "":
             saveas = True
 
-            fn = self.__getSaveFileName(path)
+            fn = self.__getSaveFileName(path=path, remote=remote)
             if not fn:
                 return False
 
@@ -3791,13 +3865,13 @@
         else:
             fn = self.fileName
 
-        self.__loadEditorConfig(fn)
         self.editorAboutToBeSaved.emit(self.fileName)
         if self.__autosaveTimer.isActive():
             self.__autosaveTimer.stop()
         if self.writeFile(fn):
             if saveas:
                 self.__clearBreakpoints(self.fileName)
+                self.__loadEditorConfig(fileName=fn)
             self.setFileName(fn)
             self.setModified(False)
             self.setReadOnly(False)
@@ -3819,7 +3893,7 @@
 
                 self.setLanguage(self.fileName)
 
-            self.lastModified = pathlib.Path(fn).stat().st_mtime
+            self.recordModificationTime()
             if newName is not None:
                 self.vm.addToRecentList(newName)
             self.editorSaved.emit(self.fileName)
@@ -3829,21 +3903,21 @@
             self.__checkEncoding()
             return True
         else:
-            self.lastModified = (
-                pathlib.Path(fn).stat().st_mtime if pathlib.Path(fn).exists() else 0
-            )
+            self.recordModificationTime(filename=fn)
             return False
 
-    def saveFileAs(self, path=None):
+    def saveFileAs(self, path=None, remote=False):
         """
         Public method to save a file with a new name.
 
-        @param path directory to save the file in
-        @type str
+        @param path directory to save the file in (defaults to None)
+        @type str (optional)
+        @param remote flag indicating to save as a remote file (defaults to False)
+        @type bool (optional)
         @return tuple containing a success indicator and the name of the saved file
         @rtype tuple of (bool, str)
         """
-        return self.saveFile(True, path)
+        return self.saveFile(True, path=path, remote=remote)
 
     def __saveDeviceFile(self, saveas=False):
         """
@@ -3904,7 +3978,7 @@
         if self.lexer_ is None:
             self.setLanguage(self.fileName)
 
-        self.lastModified = pathlib.Path(fn).stat().st_mtime
+        self.recordModificationTime()
         self.vm.setEditorName(self, self.fileName)
         self.__updateReadOnly(True)
 
@@ -6335,6 +6409,9 @@
         )
         self.menuActs["Reload"].setEnabled(bool(self.fileName))
         self.menuActs["Save"].setEnabled(self.isModified())
+        self.menuActs["SaveAsRemote"].setEnabled(
+            ericApp().getObject("EricServer").isServerConnected()
+        )
         self.menuActs["Undo"].setEnabled(self.isUndoAvailable())
         self.menuActs["Redo"].setEnabled(self.isRedoAvailable())
         self.menuActs["Revert"].setEnabled(self.isModified())
@@ -6352,6 +6429,9 @@
                 self.menuActs["Diagrams"].setEnabled(True)
             else:
                 self.menuActs["Diagrams"].setEnabled(False)
+            self.menuActs["Formatting"].setEnabled(
+                not FileSystemUtilities.isRemoteFileName(self.fileName)
+            )
         if not self.miniMenu:
             if self.lexer_ is not None:
                 self.menuActs["Comment"].setEnabled(self.lexer_.canBlockComment())
@@ -6613,6 +6693,15 @@
             self.vm.setEditorName(self, self.fileName)
 
     @pyqtSlot()
+    def __contextSaveAsRemote(self):
+        """
+        Private slot handling the save as (remote) context menu entry.
+        """
+        ok = self.saveFileAs(remote=True)
+        if ok:
+            self.vm.setEditorName(self, self.fileName)
+
+    @pyqtSlot()
     def __contextSaveCopy(self):
         """
         Private slot handling the save copy context menu entry.
@@ -6968,9 +7057,26 @@
         self.__coverageFile = fn
 
         if fn:
-            cover = Coverage(data_file=fn)
-            cover.load()
-            missing = cover.analysis2(self.fileName)[3]
+            if FileSystemUtilities.isRemoteFileName(fn):
+                coverageInterface = (
+                    ericApp().getObject("EricServer").getServiceInterface("Coverage")
+                )
+                ok, error = coverageInterface.loadCoverageData(fn)
+                if not ok and not silent:
+                    EricMessageBox.critical(
+                        self,
+                        self.tr("Load Coverage Data"),
+                        self.tr(
+                            "<p>The coverage data could not be loaded from file"
+                            " <b>{0}</b>.</p><p>Reason: {1}</p>"
+                        ).format(self.cfn, error),
+                    )
+                    return
+                missing = coverageInterface.analyzeFile(self.fileName)[3]
+            else:
+                cover = Coverage(data_file=fn)
+                cover.load()
+                missing = cover.analysis2(self.fileName)[3]
             if missing:
                 for line in missing:
                     handle = self.markerAdd(line - 1, self.notcovered)
@@ -8034,9 +8140,19 @@
 
         with contextlib.suppress(AttributeError):
             self.setCaretWidth(self.caretWidth)
-        self.__updateReadOnly(False)
+        if not self.dbs.isDebugging:
+            self.__updateReadOnly(False)
         self.setCursorFlashTime(QApplication.cursorFlashTime())
 
+        if (
+            self.fileName
+            and FileSystemUtilities.isRemoteFileName(self.fileName)
+            and not self.inReopenPrompt
+        ):
+            self.inReopenPrompt = True
+            self.checkRereadFile()
+            self.inReopenPrompt = False
+
         super().focusInEvent(event)
 
     def focusOutEvent(self, event):
@@ -8046,7 +8162,11 @@
         @param event the event object
         @type QFocusEvent
         """
-        if Preferences.getEditor("AutosaveOnFocusLost") and self.__shouldAutosave():
+        if (
+            Preferences.getEditor("AutosaveOnFocusLost")
+            and self.__shouldAutosave()
+            and not self.inReopenPrompt
+        ):
             self.saveFile()
 
         self.vm.editorActGrp.setEnabled(False)
@@ -8219,9 +8339,7 @@
                 signal if there was an attribute change.
         @type bool
         """
-        if self.fileName == "" or not FileSystemUtilities.isPlainFileName(
-            self.fileName
-        ):
+        if self.fileName == "" or FileSystemUtilities.isDeviceFileName(self.fileName):
             return
 
         readOnly = self.checkReadOnly()
@@ -8243,9 +8361,18 @@
         @rtype bool
         """
         return (
-            FileSystemUtilities.isPlainFileName(self.fileName)
-            and not os.access(self.fileName, os.W_OK)
-        ) or self.isReadOnly()
+            (
+                FileSystemUtilities.isPlainFileName(self.fileName)
+                and not os.access(self.fileName, os.W_OK)
+            )
+            or (
+                FileSystemUtilities.isRemoteFileName(self.fileName)
+                and not self.__remotefsInterface.access(
+                    FileSystemUtilities.plainFileName(self.fileName), "write"
+                )
+            )
+            or self.isReadOnly()
+        )
 
     @pyqtSlot()
     def checkRereadFile(self):
@@ -8253,13 +8380,9 @@
         Public slot to check, if the file needs to be re-read, and refresh it if
         needed.
         """
-        if (
-            self.fileName
-            and pathlib.Path(self.fileName).exists()
-            and pathlib.Path(self.fileName).stat().st_mtime != self.lastModified
-        ):
+        if self.checkModificationTime():
             if Preferences.getEditor("AutoReopen") and not self.isModified():
-                self.refresh()
+                self.__refresh()
             else:
                 msg = self.tr(
                     """<p>The file <b>{0}</b> has been changed while it"""
@@ -8280,10 +8403,10 @@
                     yesDefault=yesDefault,
                 )
                 if res:
-                    self.refresh()
+                    self.__refresh()
                 else:
                     # do not prompt for this change again...
-                    self.lastModified = pathlib.Path(self.fileName).stat().st_mtime
+                    self.recordModificationTime()
 
     def getModificationTime(self):
         """
@@ -8295,17 +8418,73 @@
         return self.lastModified
 
     @pyqtSlot()
-    def recordModificationTime(self):
+    def recordModificationTime(self, filename=""):
         """
         Public slot to record the modification time of our file.
-        """
-        if self.fileName and pathlib.Path(self.fileName).exists():
-            self.lastModified = pathlib.Path(self.fileName).stat().st_mtime
-
-    @pyqtSlot()
-    def refresh(self):
-        """
-        Public slot to refresh the editor contents.
+
+        @param filename name of the file to record the modification tome for
+            (defaults to "")
+        @type str (optional)
+        """
+        if not filename:
+            filename = self.fileName
+
+        if filename:
+            if FileSystemUtilities.isRemoteFileName(filename):
+                filename = FileSystemUtilities.plainFileName(filename)
+                if self.__remotefsInterface.exists(filename):
+                    mtime = self.__remotefsInterface.stat(
+                        FileSystemUtilities.plainFileName(filename), ["st_mtime"]
+                    )["st_mtime"]
+                    self.lastModified = mtime if mtime is not None else 0
+                else:
+                    self.lastModified = 0
+            elif pathlib.Path(filename).exists():
+                self.lastModified = pathlib.Path(filename).stat().st_mtime
+            else:
+                self.lastModified = 0
+
+        else:
+            self.lastModified = 0
+
+    def checkModificationTime(self, filename=""):
+        """
+        Public method to check, if the modification time of the file is different
+        from the recorded one.
+
+        @param filename name of the file to check against (defaults to "")
+        @type str (optional)
+        @return flag indicating that the file modification time is different. For
+            non-existent files a 'False' value will be reported.
+        @rtype bool
+        """
+        if not filename:
+            filename = self.fileName
+
+        if filename:
+            if (
+                FileSystemUtilities.isRemoteFileName(filename)
+                and not self.dbs.isDebugging
+            ):
+                plainFilename = FileSystemUtilities.plainFileName(filename)
+                if self.__remotefsInterface.exists(plainFilename):
+                    mtime = self.__remotefsInterface.stat(plainFilename, ["st_mtime"])[
+                        "st_mtime"
+                    ]
+                    return mtime != self.lastModified
+
+            elif (
+                FileSystemUtilities.isPlainFileName(filename)
+                and pathlib.Path(filename).exists()
+            ):
+                return pathlib.Path(filename).stat().st_mtime != self.lastModified
+
+        return False
+
+    @pyqtSlot()
+    def __refresh(self):
+        """
+        Private slot to refresh the editor contents.
         """
         # save cursor position
         cline, cindex = self.getCursorPosition()
@@ -8325,11 +8504,6 @@
             self.markerDeleteHandle(handle)
         self.breaks.clear()
 
-        if not os.path.exists(self.fileName):
-            # close the file, if it was deleted in the background
-            self.close()
-            return
-
         # reread the file
         try:
             self.readFile(self.fileName, noempty=True)
@@ -8626,11 +8800,18 @@
         if not self.checkDirty():
             return
 
-        package = (
-            os.path.isdir(self.fileName)
-            and self.fileName
-            or os.path.dirname(self.fileName)
-        )
+        if FileSystemUtilities.isRemoteFileName(self.fileName):  # noqa: Y108
+            package = (
+                self.fileName
+                if self.__remotefsInterface.isdir(self.fileName)
+                else self.__remotefsInterface.dirname(self.fileName)
+            )
+        else:
+            package = (
+                self.fileName
+                if os.path.isdir(self.fileName)
+                else os.path.dirname(self.fileName)
+            )
         res = EricMessageBox.yesNo(
             self,
             self.tr("Package Diagram"),
@@ -9752,7 +9933,7 @@
         """
         editorConfig = {}
 
-        if fileName and FileSystemUtilities.isPlainFileName(self.fileName):
+        if fileName and FileSystemUtilities.isPlainFileName(fileName):
             try:
                 editorConfig = editorconfig.get_properties(fileName)
             except editorconfig.EditorConfigError:
@@ -9790,16 +9971,6 @@
         if config is None:
             config = self.__editorConfig
 
-        if not config:
-            if nodefault:
-                return None
-            else:
-                value = self.__getOverrideValue(option)
-                if value is None:
-                    # no override
-                    value = Preferences.getEditor(option)
-                return value
-
         try:
             if option == "EOLMode":
                 value = config["end_of_line"]
--- a/src/eric7/QScintilla/Shell.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/QScintilla/Shell.py	Fri Jun 07 13:58:16 2024 +0200
@@ -34,6 +34,7 @@
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricSimpleHelpDialog import EricSimpleHelpDialog
+from eric7.SystemUtilities import FileSystemUtilities
 from eric7.UI.SearchWidget import SearchWidget
 
 from . import Lexers
@@ -395,10 +396,16 @@
         self.lmenu.clear()
         venvManager = ericApp().getObject("VirtualEnvManager")
         for venvName in sorted(venvManager.getVirtualenvNames()):
-            self.lmenu.addAction(venvName)
+            act = self.lmenu.addAction(venvName)
+            act.setData(venvName)
         if self.__project and self.__project.isOpen():
             self.lmenu.addSeparator()
-            self.lmenu.addAction(self.tr("Project"))
+            act = self.lmenu.addAction(self.tr("Project"))
+            act.setData("<<project>>")
+        if ericApp().getObject("EricServer").isServerConnected():
+            self.lmenu.addSeparator()
+            act = self.lmenu.addAction(self.tr("eric-ide Server"))
+            act.setData("<<eric-server>>")
 
     def __resizeLinenoMargin(self):
         """
@@ -2175,13 +2182,15 @@
         @param action context menu action that was triggered
         @type QAction
         """
-        venvName = action.text()
-        if venvName == self.tr("Project"):
+        venvName = action.data()
+        if venvName == "<<project>>":
             if self.__project and self.__project.isOpen():
                 self.__currentWorkingDirectory = self.__project.getProjectPath()
             self.dbs.startClient(
                 False, forProject=True, workingDir=self.__currentWorkingDirectory
             )
+        elif venvName == "<<eric-server>>":
+            self.dbs.startClient(False, startRemote=True)
         else:
             self.dbs.startClient(False, venvName=venvName)
         self.__getBanner()
@@ -2642,8 +2651,12 @@
         Private slot to start the shell for the opened project.
         """
         if Preferences.getProject("RestartShellForProject"):
+            ppath = self.__project.getProjectPath()
             self.dbs.startClient(
-                False, forProject=True, workingDir=self.__project.getProjectPath()
+                False,
+                forProject=True,
+                workingDir=ppath,
+                startRemote=FileSystemUtilities.isRemoteFileName(ppath),
             )
             self.__currentWorkingDirectory = self.__project.getProjectPath()
             self.__getBanner()
@@ -2674,6 +2687,27 @@
             linenumber = int(match.group(2))
             self.vm.openSourceFile(filename, lineno=linenumber)
 
+    #################################################################
+    ## eric-ide Server Support
+    #################################################################
+
+    @pyqtSlot(bool)
+    def remoteConnectionChanged(self, connected):
+        """
+        Public slot handling a change of the connection state to an eric-ide server.
+
+        @param connected flag indicating the connection state
+        @type bool
+        """
+        if connected:
+            if Preferences.getEricServer("AutostartShell"):
+                self.dbs.startClient(False, startRemote=True)
+        else:
+            if self.__currentVenv == self.dbs.getEricServerEnvironmentString():
+                # start default backend
+                self.dbs.startClient(False)
+                self.__getBanner()
+
 
 #
 # eflag: noqa = M601
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/EricRequestCategory.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an enum for the various service categories.
+"""
+
+import enum
+
+
+class EricRequestCategory(enum.IntEnum):
+    """
+    Class defining the service categories of the eric remote server.
+    """
+
+    FileSystem = 0
+    Project = 1
+    Debugger = 2
+    Coverage = 3
+
+    Echo = 253
+    Server = 254
+    Error = 255  # only sent by the server to report an issue
+
+    # user/plugins may define own categories starting with this value
+    UserCategory = 1024
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/EricServer.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,562 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the eric remote server.
+"""
+
+import io
+import json
+import selectors
+import socket
+import struct
+import sys
+import traceback
+import types
+import zlib
+
+from eric7.__version__ import Version
+
+from .EricRequestCategory import EricRequestCategory
+from .EricServerCoverageRequestHandler import EricServerCoverageRequestHandler
+from .EricServerDebuggerRequestHandler import EricServerDebuggerRequestHandler
+from .EricServerFileSystemRequestHandler import EricServerFileSystemRequestHandler
+
+
+class EricServer:
+    """
+    Class implementing the eric remote server.
+    """
+
+    def __init__(self, port=42024, useIPv6=False):
+        """
+        Constructor
+
+        @param port port to listen on (defaults to 42024)
+        @type int (optional)
+        @param useIPv6 flag indicating to use IPv6 protocol (defaults to False)
+        @type bool (optional)
+        """
+        self.__requestCategoryHandlerRegistry = {}
+        # Dictionary containing the defined and registered request category
+        # handlers. The key is the request category and the value is the respective
+        # handler method. This method must have the signature:
+        #     handler(request:str, params:dict, reqestUuid:str) -> None
+        self.__registerInternalHandlers()
+
+        self.__connection = None
+
+        self.__selector = selectors.DefaultSelector()
+
+        # create and register the 'Debugger' request handler
+        self.__debuggerRequestHandler = EricServerDebuggerRequestHandler(self)
+        self.registerRequestHandler(
+            EricRequestCategory.Debugger,
+            self.__debuggerRequestHandler.handleRequest,
+        )
+
+        # create and register the 'File System' request handler
+        self.__fileSystemRequestHandler = EricServerFileSystemRequestHandler(self)
+        self.registerRequestHandler(
+            EricRequestCategory.FileSystem,
+            self.__fileSystemRequestHandler.handleRequest,
+        )
+
+        # create and register the 'Coverage' request handler
+        self.__coverageRequestHandler = EricServerCoverageRequestHandler(self)
+        self.registerRequestHandler(
+            EricRequestCategory.Coverage,
+            self.__coverageRequestHandler.handleRequest,
+        )
+
+        # TODO: implement an 'EditorConfig' handler (?)
+
+        self.__address = ("", port)
+        self.__useIPv6 = useIPv6
+
+    def getSelector(self):
+        """
+        Public method to get a reference to the selector object.
+
+        @return reference to the selector object
+        @rtype selectors.BaseSelector
+        """
+        return self.__selector
+
+    #######################################################################
+    ## Methods for receiving requests and sending the results.
+    #######################################################################
+
+    def sendJson(self, category, reply, params, reqestUuid=""):
+        """
+        Public method to send a single refactoring command to the server.
+
+        @param category service category
+        @type EricRequestCategory
+        @param reply reply name to be sent
+        @type str
+        @param params dictionary of named parameters for the request
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+            (defaults to "", i.e. no UUID received)
+        @type str
+        """
+        if self.__connection is not None:
+            commandDict = {
+                "jsonrpc": "2.0",
+                "category": category,
+                "reply": reply,
+                "params": params,
+                "uuid": reqestUuid,
+            }
+            self.sendJsonCommand(commandDict, self.__connection)
+
+    def sendJsonCommand(self, jsonCommand, sock):
+        """
+        Public method to send a JSON encoded command/response via a given socket.
+
+        @param jsonCommand dictionary containing the command data or a JSON encoded
+            command string
+        @type dict or str
+        @param sock reference to the socket to send the data to
+        @type socket.socket
+        @return flag indicating a successful transmission
+        @rtype bool
+        """
+        if isinstance(jsonCommand, dict):
+            jsonCommand = json.dumps(jsonCommand)
+        # - print("Eric Server Send:", jsonCommand)  # for debugging
+
+        data = jsonCommand.encode("utf8", "backslashreplace")
+        header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+        try:
+            sock.sendall(header)
+            sock.sendall(data)
+            return True
+        except BrokenPipeError:
+            return False
+
+    def __receiveBytes(self, length, sock):
+        """
+        Private method to receive the given length of bytes.
+
+        @param length bytes to receive
+        @type int
+        @param sock reference to the socket to receive the data from
+        @type socket.socket
+        @return received bytes or None if connection closed
+        @rtype bytes
+        """
+        data = bytearray()
+        while sock is not None and len(data) < length:
+            try:
+                newData = sock.recv(length - len(data))
+                if not newData:
+                    return None
+
+                data += newData
+            except OSError as err:
+                if err.errno != 11:
+                    data = None  # in case some data was received already
+                    break
+        return data
+
+    def receiveJsonCommand(self, sock):
+        """
+        Public method to receive a JSON encoded command and data.
+
+        @param sock reference to the socket to receive the data from
+        @type socket.socket
+        @return dictionary containing the JSON command data or None to signal
+            an issue while receiving data
+        @rtype dict
+        """
+        if self.isSocketClosed(sock):
+            return None
+
+        header = self.__receiveBytes(struct.calcsize(b"!II"), sock)
+        if not header:
+            return {}
+
+        length, datahash = struct.unpack(b"!II", header)
+
+        length = int(length)
+        data = self.__receiveBytes(length, sock)
+        if data is None:
+            return None
+
+        if not data or zlib.adler32(data) & 0xFFFFFFFF != datahash:
+            self.sendJson(
+                category=EricRequestCategory.Error,
+                reply="EricServerChecksumException",
+                params={
+                    "ExceptionType": "ProtocolChecksumError",
+                    "ExceptionValue": "The checksum of the data does not match.",
+                    "ProtocolData": data.decode("utf8", "backslashreplace"),
+                },
+            )
+            return {}
+
+        jsonStr = data.decode("utf8", "backslashreplace")
+        print("Eric Server Receive:", jsonStr)  # for debugging  # noqa: M801
+        try:
+            return json.loads(jsonStr.strip())
+        except (TypeError, ValueError) as err:
+            self.sendJson(
+                category=EricRequestCategory.Error,
+                reply="EricServerException",
+                params={
+                    "ExceptionType": "ProtocolError",
+                    "ExceptionValue": str(err),
+                    "ProtocolData": jsonStr.strip(),
+                },
+            )
+            return {}
+
+    def __receiveJson(self):
+        """
+        Private method to receive a JSON encoded command and data from the
+        server.
+
+        @return tuple containing the received service category, the command,
+            a dictionary containing the associated data and the UUID of the
+            request
+        @rtype tuple of (int, str, dict, str)
+        """
+        requestDict = self.receiveJsonCommand(self.__connection)
+
+        if not requestDict:
+            return EricRequestCategory.Error, None, None, None
+
+        category = requestDict["category"]
+        request = requestDict["request"]
+        params = requestDict["params"]
+        reqestUuid = requestDict["uuid"]
+
+        return category, request, params, reqestUuid
+
+    def isSocketClosed(self, sock):
+        """
+        Public method to check, if a given socket is closed.
+
+        @param sock reference to the socket to be checked
+        @type socket.socket
+        @return flag indicating a closed state
+        @rtype bool
+        """
+        try:
+            # this will try to read bytes without removing them from buffer (peek only)
+            data = sock.recv(16, socket.MSG_PEEK)
+            if len(data) == 0:
+                return True
+        except BlockingIOError:
+            return False  # socket is open and reading from it would block
+        except ConnectionError:
+            return True  # socket was closed for some other reason
+        except Exception:
+            return False
+        return False
+
+    #######################################################################
+    ## Methods for the server main loop.
+    #######################################################################
+
+    def __initializeIdeSocket(self):
+        """
+        Private method to initialize and register the eric-ide server socket.
+        """
+        if socket.has_dualstack_ipv6() and self.__useIPv6:
+            self.__socket = socket.create_server(
+                self.__address, family=socket.AF_INET6, backlog=0, dualstack_ipv6=True
+            )
+        else:
+            self.__socket = socket.create_server(
+                self.__address, family=socket.AF_INET, backlog=0
+            )
+
+        self.__socket.listen(0)
+        self.__socket.setblocking(False)
+        address = self.__socket.getsockname()
+        print(  # noqa: M801
+            f"Listening for 'eric-ide' connections on {address[0]}, port {address[1]}"
+        )
+        data = types.SimpleNamespace(
+            name="server", acceptHandler=self.__acceptIdeConnection
+        )
+        self.__selector.register(self.__socket, selectors.EVENT_READ, data=data)
+
+    def __unregisterIdeSocket(self):
+        """
+        Private method to unregister the eric-ide server socket because only one
+        connection is allowed.
+        """
+        self.__selector.unregister(self.__socket)
+        self.__socket.shutdown(socket.SHUT_RDWR)
+        self.__socket.close()
+        self.__socket = None
+
+    def __shutdown(self):
+        """
+        Private method to shut down the server.
+        """
+        self.__closeIdeConnection(shutdown=True)
+
+        print("Stop listening for 'eric-ide' connections.")  # noqa: M801
+        if self.__socket is not None:
+            self.__socket.shutdown(socket.SHUT_RDWR)
+            self.__socket.close()
+
+        self.__selector.close()
+
+    def __acceptIdeConnection(self, sock):
+        """
+        Private method to accept the connection on the listening IDE server socket.
+
+        @param sock reference to the listening socket
+        @type socket.socket
+        """
+        connection, address = sock.accept()  # Should be ready to read
+        if self.__connection is None:
+            print(f"'eric-ide' connection from {address[0]}, port {address[1]}")
+            # noqa: M801
+            self.__connection = connection
+            data = types.SimpleNamespace(
+                name="eric-ide", address=address, handler=self.__serviceIdeConnection
+            )
+            events = selectors.EVENT_READ
+            self.__selector.register(self.__connection, events, data=data)
+
+            self.__unregisterIdeSocket()
+        else:
+            print(  # noqa: M801
+                f"'eric-ide' connection from {address[0]}, port {address[1]} rejected"
+            )
+            connection.close()
+
+    def __closeIdeConnection(self, shutdown=False):
+        """
+        Private method to close the connection to an eric-ide.
+
+        @param shutdown flag indicating a shutdown process
+        @type bool
+        """
+        if self.__connection is not None:
+            self.__selector.unregister(self.__connection)
+            try:
+                address = self.__connection.getpeername()
+                print(  # noqa: M801
+                    f"Closing 'eric-ide' connection to {address[0]}, port {address[1]}."
+                )
+                self.__connection.shutdown(socket.SHUT_RDWR)
+                self.__connection.close()
+            except OSError:
+                print("'eric-ide' connection gone.")  # noqa: M801
+            self.__connection = None
+
+            self.__debuggerRequestHandler.shutdownClients()
+
+        if not shutdown:
+            self.__initializeIdeSocket()
+
+    def __serviceIdeConnection(self, key):
+        """
+        Private method to service the eric-ide connection.
+
+        @param key reference to the SelectorKey object associated with the connection
+            to be serviced
+        @type selectors.SelectorKey
+        """
+        if key.data.name == "eric-ide":
+            category, request, params, reqestUuid = self.__receiveJson()
+            if category == EricRequestCategory.Error or request is None:
+                self.__closeIdeConnection()
+                return
+
+            if category == EricRequestCategory.Server and request.lower() == "shutdown":
+                self.__shouldStop = True
+                return
+
+            self.__handleRequest(category, request, params, reqestUuid)
+
+    def run(self):
+        """
+        Public method implementing the remote server main loop.
+
+        Exiting the inner loop, that receives and dispatches the requests, will
+        cause the server to stop and exit. The main loop handles these requests.
+        <ul>
+        <li>exit - exit the handler loop and wait for the next connection</li>
+        <li>shutdown - exit the handler loop and perform a clean shutdown</li>
+        </ul>
+
+        @return flag indicating a clean shutdown
+        @rtype bool
+        """
+        cleanExit = True
+        self.__shouldStop = False
+
+        # initialize the eric-ide server socket and listen for new connections
+        self.__initializeIdeSocket()
+
+        # initialize the debug client server socket
+        self.__debuggerRequestHandler.initServerSocket()
+
+        while True:
+            try:
+                events = self.__selector.select(timeout=None)
+                for key, _mask in events:
+                    if key.data.name == "server":
+                        # it is an event for a server socket
+                        key.data.acceptHandler(key.fileobj)
+                    else:
+                        key.data.handler(key)
+
+            except KeyboardInterrupt:
+                # intercept user pressing Ctrl+C
+                self.__shouldStop = True
+
+            except Exception:
+                exctype, excval, exctb = sys.exc_info()
+                tbinfofile = io.StringIO()
+                traceback.print_tb(exctb, None, tbinfofile)
+                tbinfofile.seek(0)
+                tbinfo = tbinfofile.read()
+
+                print("Stopping due to an exception.\nDetails:")  # noqa: M801
+                print(f"{str(exctype)} / {str(excval)} / {tbinfo}")  # noqa: M801
+
+                self.__shouldStop = True
+                cleanExit = False
+
+            if self.__shouldStop:
+                # exit the outer loop and shut down the server
+                self.__shutdown()
+                break
+
+        return cleanExit
+
+    #######################################################################
+    ## Methods for registering and unregistering handlers.
+    #######################################################################
+
+    def registerRequestHandler(self, requestCategory, handler):
+        """
+        Public method to register a request handler method for the given request
+        category.
+
+        @param requestCategory request category to be registered
+        @type EricRequestCategory or int (>= EricRequestCategory.UserCategory)
+        @param handler reference to the handler method. This handler must accept
+            the parameters 'request', 'params', and 'requestUuid'
+        @type function(request:str, params:dict, requestUuid:str)
+        @exception ValueError raised to signal a request category collision
+        """
+        if requestCategory in self.__requestCategoryHandlerRegistry:
+            raise ValueError(f"Request category '{requestCategory} already registered.")
+
+        self.__requestCategoryHandlerRegistry[requestCategory] = handler
+
+    def unregisterRequestHandler(self, requestCategory, ignoreError=False):
+        """
+        Public method to unregister a handler for the given request category.
+
+        Note: This method will raise a KeyError exception in case the request
+        category has not been registered and ignoreError is False (the default).
+
+        @param requestCategory request category to be unregistered
+        @type EricRequestCategory or int (>= EricRequestCategory.UserCategory)
+        @param ignoreError flag indicating to ignore errors (defaults to False)
+        @type bool (optional)
+        """
+        try:
+            del self.__requestCategoryHandlerRegistry[requestCategory]
+        except KeyError:
+            if not ignoreError:
+                raise
+
+    def __registerInternalHandlers(self):
+        """
+        Private method to register request handler categories of this class.
+        """
+        self.registerRequestHandler(EricRequestCategory.Echo, self.__handleEchoRequest)
+        self.registerRequestHandler(
+            EricRequestCategory.Server, self.__handleServerRequest
+        )
+        self.registerRequestHandler(EricRequestCategory.Error, None)
+        # Register a None handler to indicate we are not expecting a request of the
+        # 'Error' category.
+
+    #######################################################################
+    ## Request handler methods.
+    #######################################################################
+
+    def __handleRequest(self, category, request, params, reqestUuid):
+        """
+        Private method handling or dispatching the received requests.
+
+        @param category category of the request
+        @type EricRequestCategory
+        @param request request name
+        @type str
+        @param params request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+        @type str
+        """
+        try:
+            handler = self.__requestCategoryHandlerRegistry[category]
+            handler(request=request, params=params, reqestUuid=reqestUuid)
+        except KeyError:
+            self.sendJson(
+                category=EricRequestCategory.Error,
+                reply="UnsupportedServiceCategory",
+                params={"Category": category},
+            )
+
+    def __handleEchoRequest(self, request, params, reqestUuid):  # noqa: U100
+        """
+        Private method to handle an 'Echo' request.
+
+        @param request request name
+        @type str
+        @param params request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+            (defaults to "", i.e. no UUID received)
+        @type str
+        """
+        self.sendJson(
+            category=EricRequestCategory.Echo,
+            reply="Echo",
+            params=params,
+            reqestUuid=reqestUuid,
+        )
+
+    def __handleServerRequest(self, request, params, reqestUuid):  # noqa: U100
+        """
+        Private method to handle a 'Server' request.
+
+        @param request request name
+        @type str
+        @param params request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+            (defaults to "", i.e. no UUID received)
+        @type str
+        """
+        # 'Exit' and 'Shutdown' are handled in the 'run()' method.
+
+        if request.lower() == "versions":
+            self.sendJson(
+                category=EricRequestCategory.Server,
+                reply="Versions",
+                params={
+                    "python": sys.version.split()[0],
+                    "py_bitsize": "64-Bit" if sys.maxsize > 2**32 else "32-Bit",
+                    "version": Version,
+                    "hostname": socket.gethostname(),
+                },
+                reqestUuid=reqestUuid,
+            )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/EricServerCoverageRequestHandler.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the code coverage request handler of the eric-ide server.
+"""
+
+from coverage import Coverage
+from coverage.misc import CoverageException
+
+from eric7.SystemUtilities import FileSystemUtilities
+
+from .EricRequestCategory import EricRequestCategory
+
+
+class EricServerCoverageRequestHandler:
+    """
+    Class implementing the code coverage request handler of the eric-ide server.
+    """
+
+    def __init__(self, server):
+        """
+        Constructor
+
+        @param server reference to the eric-ide server object
+        @type EricServer
+        """
+        self.__server = server
+
+        self.__requestMethodMapping = {
+            "LoadData": self.__loadCoverageData,
+            "AnalyzeFile": self.__analyzeFile,
+            "AnalyzeFiles": self.__analyzeFiles,
+            "AnalyzeDirectory": self.__analyzeDirectory,
+        }
+
+        self.__cover = None
+
+    def handleRequest(self, request, params, reqestUuid):
+        """
+        Public method handling the received file system requests.
+
+        @param request request name
+        @type str
+        @param params dictionary containing the request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+        @type str
+        """
+        try:
+            result = self.__requestMethodMapping[request](params)
+            self.__server.sendJson(
+                category=EricRequestCategory.Coverage,
+                reply=request,
+                params=result,
+                reqestUuid=reqestUuid,
+            )
+
+        except KeyError:
+            self.__server.sendJson(
+                category=EricRequestCategory.Coverage,
+                reply=request,
+                params={"Error": f"Request type '{request}' is not supported."},
+            )
+
+    def __loadCoverageData(self, params):
+        """
+        Private method to load the data collected by a code coverage run.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        if self.__cover is not None:
+            del self.__cover
+            self.__cover = None
+
+        try:
+            self.__cover = Coverage(data_file=params["data_file"])
+            self.__cover.load()
+            if params["exclude"]:
+                self.__cover.exclude(params["exclude"])
+            return {"ok": True}
+        except CoverageException as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __analyzeFile(self, params):
+        """
+        Private method to analyze a single file.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        if self.__cover is None:
+            return {
+                "ok": False,
+                "error": "Coverage data has to be loaded first.",
+            }
+
+        try:
+            return {
+                "ok": True,
+                "result": self.__cover.analysis2(params["filename"]),
+            }
+        except CoverageException as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __analyzeFiles(self, params):
+        """
+        Private method to analyze a list of files.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        if self.__cover is None:
+            return {
+                "ok": False,
+                "error": "Coverage data has to be loaded first.",
+            }
+
+        try:
+            return {
+                "ok": True,
+                "results": [self.__cover.analysis2(f) for f in params["filenames"]],
+            }
+        except CoverageException as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __analyzeDirectory(self, params):
+        """
+        Private method to analyze files of a directory tree.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        if self.__cover is None:
+            return {
+                "ok": False,
+                "error": "Coverage data has to be loaded first.",
+            }
+
+        files = FileSystemUtilities.direntries(params["directory"], True, "*.py", False)
+
+        try:
+            return {
+                "ok": True,
+                "results": [self.__cover.analysis2(f) for f in files],
+            }
+        except CoverageException as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/EricServerDebuggerRequestHandler.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the debugger request handler of the eric-ide server.
+"""
+
+import json
+import os
+import selectors
+import socket
+import subprocess
+import sys
+import types
+
+from .EricRequestCategory import EricRequestCategory
+
+
+class EricServerDebuggerRequestHandler:
+    """
+    Class implementing the debugger request handler of the eric-ide server.
+    """
+
+    def __init__(self, server):
+        """
+        Constructor
+
+        @param server reference to the eric-ide server object
+        @type EricServer
+        """
+        self.__server = server
+
+        self.__requestMethodMapping = {
+            "StartClient": self.__startClient,
+            "StopClient": self.__stopClient,
+            "DebugClientCommand": self.__relayDebugClientCommand,
+        }
+
+        self.__mainClientId = None
+        self.__client = None
+        self.__inStartClient = False
+        self.__pendingConnections = []
+        self.__connections = {}
+
+        address = ("127.0.0.1", 0)
+        self.__socket = socket.create_server(address, family=socket.AF_INET)
+
+        self.__originalPathString = os.getenv("PATH")
+
+    def initServerSocket(self):
+        """
+        Public method to initialize the server socket listening for debug client
+        connections.
+        """
+        # listen on the debug server socket
+        self.__socket.listen()
+        self.__socket.setblocking(False)
+        address = self.__socket.getsockname()
+        print(  # noqa: M801
+            f"Listening for 'Debug Client' connections on"
+            f" {address[0]}, port {address[1]}"
+        )
+        data = types.SimpleNamespace(
+            name="server", acceptHandler=self.__acceptDbgClientConnection
+        )
+        self.__server.getSelector().register(
+            self.__socket, selectors.EVENT_READ, data=data
+        )
+
+    def handleRequest(self, request, params, reqestUuid):
+        """
+        Public method handling the received debugger requests.
+
+        @param request request name
+        @type str
+        @param params dictionary containing the request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+        @type str
+        """
+        try:
+            result = self.__requestMethodMapping[request](params)
+            if result:
+                self.__server.sendJson(
+                    category=EricRequestCategory.Debugger,
+                    reply=request,
+                    params=result,
+                    reqestUuid=reqestUuid,
+                )
+
+        except KeyError:
+            self.__server.sendJson(
+                category=EricRequestCategory.Debugger,
+                reply="DebuggerRequestError",
+                params={"Error": f"Request type '{request}' is not supported."},
+            )
+
+    #######################################################################
+    ## DebugServer like methods.
+    #######################################################################
+
+    def __acceptDbgClientConnection(self, sock):
+        """
+        Private method to accept the connection on the listening debug server socket.
+
+        @param sock reference to the listening socket
+        @type socket.socket
+        """
+        connection, address = sock.accept()  # Should be ready to read
+        print(f"'Debug Client' connection from {address[0]}, port {address[1]}")
+        # noqa: M801
+        connection.setblocking(False)
+        self.__pendingConnections.append(connection)
+
+        data = types.SimpleNamespace(
+            name="debug_client",
+            address=address,
+            handler=self.__serviceDbgClientConnection,
+        )
+        self.__server.getSelector().register(
+            connection, selectors.EVENT_READ, data=data
+        )
+
+    def __serviceDbgClientConnection(self, key):
+        """
+        Private method to service the debug client connection.
+
+        @param key reference to the SelectorKey object associated with the connection
+            to be serviced
+        @type selectors.SelectorKey
+        """
+        sock = key.fileobj
+        data = self.__server.receiveJsonCommand(sock)
+
+        if data is None:
+            # socket was closed by debug client
+            self.__clientSocketDisconnected(sock)
+        elif data:
+            method = data["method"]
+
+            # 1. process debug client messages before relaying
+            if method == "DebuggerId" and sock in self.__pendingConnections:
+                debuggerId = data["params"]["debuggerId"]
+                self.__connections[debuggerId] = sock
+                self.__pendingConnections.remove(sock)
+                if self.__mainClientId is None:
+                    self.__mainClientId = debuggerId
+
+            elif method == "ResponseBanner":
+                # add an indicator for the eric-ide server
+                data["params"]["platform"] += " (eric-ide Server)"
+
+            # 2. pass on the data to the eric-ide
+            jsonStr = json.dumps(data)
+            # - print("Client Response:", jsonStr)
+            self.__server.sendJson(
+                category=EricRequestCategory.Debugger,
+                reply="DebugClientResponse",
+                params={"response": jsonStr},
+            )
+
+            # 3. process debug client messages after relaying
+            if method == "ResponseExit":
+                for sock in list(self.__connections.values()):
+                    if not self.__server.isSocketClosed(sock):
+                        self.__clientSocketDisconnected(sock)
+
+    def __clientSocketDisconnected(self, sock):
+        """
+        Private method handling a socket disconnecting.
+
+        @param sock reference to the disconnected socket
+        @type socket.socket
+        """
+        self.__server.getSelector().unregister(sock)
+
+        address = sock.getpeername()
+        print(  # noqa: M801
+            f"'Debug Client' connection from {address[0]}, port {address[1]} closed."
+        )
+
+        for debuggerId in list(self.__connections):
+            if self.__connections[debuggerId] is sock:
+                del self.__connections[debuggerId]
+                self.__server.sendJson(
+                    category=EricRequestCategory.Debugger,
+                    reply="DebugClientDisconnected",
+                    params={"debugger_id": debuggerId},
+                )
+
+                if debuggerId == self.__mainClientId:
+                    self.__mainClientId = None
+
+                break
+        else:
+            if sock in self.__pendingConnections:
+                self.__pendingConnections.remove(sock)
+
+        sock.close()
+
+    def __mainClientExited(self):
+        """
+        Private method to handle exiting of the main debug client.
+        """
+        self.__server.sendJson(
+            category=EricRequestCategory.Debugger,
+            reply="MainClientExited",
+            params={"debugger_id": self.__mainClientId if self.__mainClientId else ""},
+        )
+
+    def shutdownClients(self):
+        """
+        Public method to shut down all connected clients.
+        """
+        if not self.__client:
+            # no client started yet
+            return
+
+        while self.__pendingConnections:
+            sock = self.__pendingConnections.pop()
+            commandDict = self.__prepareClientCommand("RequestShutdown", {})
+            self.__server.sendJsonCommand(commandDict, sock)
+            self.__shutdownSocket("", sock)
+
+        while self.__connections:
+            debuggerId, sock = self.__connections.popitem()
+            commandDict = self.__prepareClientCommand("RequestShutdown", {})
+            self.__server.sendJsonCommand(commandDict, sock)
+            self.__shutdownSocket(debuggerId, sock)
+
+        # reinitialize
+        self.__mainClientId = None
+        self.__client = None
+
+    def __shutdownSocket(self, debuggerId, sock):
+        """
+        Private method to shut down a socket.
+
+        @param debuggerId ID of the debugger the socket belongs to
+        @type str
+        @param sock reference to the socket
+        @type socket.socket
+        """
+        self.__server.getSelector().unregister(sock)
+        sock.shutdown(socket.SHUT_RDWR)
+        sock.close()
+
+        if debuggerId:
+            self.__server.sendJson(
+                category=EricRequestCategory.Debugger,
+                reply="DebugClientDisconnected",
+                params={"debugger_id": debuggerId},
+            )
+
+    def __prepareClientCommand(self, command, params):
+        """
+        Private method to prepare a command dictionary for the debug client.
+
+        @param command command to be sent
+        @type str
+        @param params dictionary containing the command parameters
+        @type dict
+        @return completed command dictionary to be sent to the debug client
+        @rtype dict
+        """
+        return {
+            "jsonrpc": "2.0",
+            "method": command,
+            "params": params,
+        }
+
+    #######################################################################
+    ## Individual request handler methods.
+    #######################################################################
+
+    def __startClient(self, params):
+        """
+        Private method to start a debug client process.
+
+        @param params dictionary containing the request data
+        @type dict
+        """
+        self.__inStartClient = True
+
+        # start a debug client
+        debugClient = os.path.abspath(
+            os.path.join(
+                os.path.dirname(__file__),
+                "..",
+                "DebugClients",
+                "Python",
+                "DebugClient.py",
+            )
+        )
+        ipaddr, port = self.__socket.getsockname()
+        args = [sys.executable, debugClient]
+        args.extend(params["arguments"])
+        args.extend([str(port), "True", ipaddr])
+
+        workingDir = params["working_dir"] if params["working_dir"] else None
+
+        clientEnv = os.environ.copy()
+        if self.__originalPathString:
+            clientEnv["PATH"] = self.__originalPathString
+
+        self.__client = subprocess.Popen(
+            args,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            cwd=workingDir,
+            env=clientEnv,
+        )
+
+    def __stopClient(self, params):  # noqa: U100
+        """
+        Private method to stop the current debug client process.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        self.shutdownClients()
+
+        return {"ok": True}
+
+    def __relayDebugClientCommand(self, params):
+        """
+        Private method to relay a debug client command to the client.
+
+        @param params dictionary containing the request data
+        @type dict
+        """
+        debuggerId = params["debugger_id"]
+        jsonStr = params["command"]
+
+        if not debuggerId and self.__mainClientId and "RequestBanner" in jsonStr:
+            # modify the target for the 'RequestBanner' request
+            debuggerId = self.__mainClientId
+
+        if debuggerId == "<<all>>":
+            # broadcast to all connected debug clients
+            for sock in self.__connections.values():
+                self.__server.sendJsonCommand(jsonStr, sock)
+        else:
+            try:  # noqa: Y105
+                sock = self.__connections[debuggerId]
+                self.__server.sendJsonCommand(jsonStr, sock)
+            except KeyError:
+                pass
+                # - print(f"Command for unknown debugger ID '{debuggerId}' received.")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/EricServerFileSystemRequestHandler.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,483 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the file system request handler of the eric-ide server.
+"""
+
+import base64
+import contextlib
+import os
+import shutil
+import stat
+import time
+
+from eric7.SystemUtilities import FileSystemUtilities
+
+from .EricRequestCategory import EricRequestCategory
+
+
+class EricServerFileSystemRequestHandler:
+    """
+    Class implementing the file system request handler of the eric-ide server.
+    """
+
+    def __init__(self, server):
+        """
+        Constructor
+
+        @param server reference to the eric-ide server object
+        @type EricServer
+        """
+        self.__server = server
+
+        self.__requestMethodMapping = {
+            "GetPathSep": self.__getPathSeparator,
+            "Chdir": self.__chdir,
+            "Getcwd": self.__getcwd,
+            "Listdir": self.__listdir,
+            "Mkdir": self.__mkdir,
+            "MakeDirs": self.__makedirs,
+            "Rmdir": self.__rmdir,
+            "Replace": self.__replace,
+            "Remove": self.__remove,
+            "Stat": self.__stat,
+            "Exists": self.__exists,
+            "Access": self.__access,
+            "ReadFile": self.__readFile,
+            "WriteFile": self.__writeFile,
+            "DirEntries": self.__dirEntries,
+            "ExpandUser": self.__expanduser,
+            "ShutilCopy": self.__shutilCopy,
+            "ShutilRmtree": self.__shutilRmtree,
+        }
+
+    def handleRequest(self, request, params, reqestUuid):
+        """
+        Public method handling the received file system requests.
+
+        @param request request name
+        @type str
+        @param params dictionary containing the request parameters
+        @type dict
+        @param reqestUuid UUID of the associated request as sent by the eric IDE
+        @type str
+        """
+        try:
+            result = self.__requestMethodMapping[request](params)
+            self.__server.sendJson(
+                category=EricRequestCategory.FileSystem,
+                reply=request,
+                params=result,
+                reqestUuid=reqestUuid,
+            )
+
+        except KeyError:
+            self.__server.sendJson(
+                category=EricRequestCategory.FileSystem,
+                reply=request,
+                params={
+                    "ok": False,
+                    "error": f"Request type '{request}' is not supported.",
+                    "info": list(self.__requestMethodMapping.keys()),
+                },
+                reqestUuid=reqestUuid,
+            )
+
+    def __getPathSeparator(self, params):  # noqa: U100
+        """
+        Private method to report the path separator.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        return {"separator": os.sep}
+
+    def __chdir(self, params):
+        """
+        Private method to change the current working directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.chdir(params["directory"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __getcwd(self, params):  # noqa: U100
+        """
+        Private method to report the current working directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        return {"directory": os.getcwd()}
+
+    def __listdir(self, params):
+        """
+        Private method to report a directory listing.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        directory = params["directory"]
+        if not directory:
+            directory = os.getcwd()
+
+        try:
+            listing = self.__scanDirectory(directory, params["recursive"])
+
+            return {
+                "ok": True,
+                "directory": directory,
+                "listing": listing,
+                "separator": os.sep,
+            }
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __scanDirectory(self, directory, recursive, withHidden=False):
+        """
+        Private method to scan a given directory.
+
+        @param directory path of the directory to be scanned
+        @type str
+        @param recursive flag indicating a recursive scan
+        @type bool
+        @param withHidden flag indicating to list hidden files and directories
+            as well (defaults to False)
+        @type bool (optional)
+        @return list of file and directory entries
+        @rtype list of dict
+        """
+        listing = []
+        for dirEntry in os.scandir(directory):
+            filestat = dirEntry.stat()
+            if withHidden or not dirEntry.name.startswith("."):
+                entry = {
+                    "name": dirEntry.name,
+                    "path": dirEntry.path,
+                    "is_dir": dirEntry.is_dir(),
+                    "is_file": dirEntry.is_file(),
+                    "is_link": dirEntry.is_symlink(),
+                    "mode": filestat.st_mode,
+                    "mode_str": stat.filemode(filestat.st_mode),
+                    "size": filestat.st_size,
+                    "mtime": filestat.st_mtime,
+                    "mtime_str": time.strftime(
+                        "%Y-%m-%d %H:%M:%S", time.localtime(filestat.st_mtime)
+                    ),
+                }
+                listing.append(entry)
+
+                if entry["is_dir"] and recursive:
+                    listing += self.__scanDirectory(dirEntry.path, recursive)
+
+        return listing
+
+    def __stat(self, params):
+        """
+        Private method to get the status of a file.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            result = os.stat(params["filename"])
+            resultDict = {st: getattr(result, st) for st in params["st_names"]}
+            return {"ok": True, "result": resultDict}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __exists(self, params):
+        """
+        Private method to check if a file or directory of the given name exists.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        return {"exists": os.path.exists(params["name"])}
+
+    def __access(self, params):
+        """
+        Private method to test, if the eric-ide server has the given access rights
+        to a file or directory..
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        mode = os.F_OK
+        for modeStr in params["modes"]:
+            if modeStr == "read":
+                mode |= os.R_OK
+            elif modeStr == "write":
+                mode |= os.W_OK
+            elif modeStr in ("execute", "exec"):
+                mode |= os.X_OK
+
+        return {"ok": os.access(params["name"], mode)}
+
+    def __mkdir(self, params):
+        """
+        Private method to create a new directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.mkdir(params["directory"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __makedirs(self, params):
+        """
+        Private method to create a new directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.makedirs(params["directory"], exist_ok=params["exist_ok"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __rmdir(self, params):
+        """
+        Private method to delete a directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.rmdir(params["directory"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __replace(self, params):
+        """
+        Private method to replace (rename) a file or directory.
+
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.replace(params["old_name"], params["new_name"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __remove(self, params):
+        """
+        Private method to delete a file.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            os.remove(params["filename"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __readFile(self, params):
+        """
+        Private method to read the contents of a file.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        filename = params["filename"]
+
+        if params["create"] and not os.path.exists(filename):
+            with open(filename, "wb"):
+                pass
+
+        newline = None if params["newline"] == "<<none>>" else params["newline"]
+        try:
+            with open(filename, "rb", newline=newline) as f:
+                data = f.read()
+            return {
+                "ok": True,
+                "filedata": str(base64.b85encode(data), encoding="ascii"),
+            }
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __writeFile(self, params):
+        """
+        Private method to write data into a file.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        filename = params["filename"]
+        data = base64.b85decode(bytes(params["filedata"], encoding="ascii"))
+
+        # 1. create backup file if asked for
+        if params["with_backup"]:
+            if os.path.islink(filename):
+                filename = os.path.realpath(filename)
+            backupFilename = "{0}~".format(filename)
+            try:
+                permissions = os.stat(filename).st_mode
+                perms_valid = True
+            except OSError:
+                # if there was an error, ignore it
+                perms_valid = False
+            with contextlib.suppress(OSError):
+                os.remove(backupFilename)
+            with contextlib.suppress(OSError):
+                os.rename(filename, backupFilename)
+
+        # 2. write the data to the file and reset the permissions
+        newline = None if params["newline"] == "<<none>>" else params["newline"]
+        try:
+            with open(filename, "wb", newline=newline) as f:
+                f.write(data)
+            if params["with_backup"] and perms_valid:
+                os.chmod(filename, permissions)
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __dirEntries(self, params):
+        """
+        Private method to get a list of all files and directories of a given directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        directory = params["directory"]
+        result = FileSystemUtilities.direntries(
+            directory,
+            filesonly=params["files_only"],
+            pattern=params["pattern"],
+            followsymlinks=params["follow_symlinks"],
+            ignore=params["ignore"],
+            recursive=params["recursive"],
+            dirsonly=params["dirs_only"],
+        )
+        return {
+            "ok": True,
+            "result": result,
+        }
+
+    def __expanduser(self, params):
+        """
+        Private method to replace an initial component of ~ or ~user replaced.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        return {
+            "ok": True,
+            "name": os.path.expanduser(params["name"]),
+        }
+
+    def __shutilCopy(self, params):
+        """
+        Private method to copy a source file to a destination file or directory.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            return {
+                "ok": True,
+                "dst": shutil.copy(params["src_name"], params["dst_name"]),
+            }
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
+
+    def __shutilRmtree(self, params):
+        """
+        Private method to delete an entire directory tree.
+
+        @param params dictionary containing the request data
+        @type dict
+        @return dictionary containing the reply data
+        @rtype dict
+        """
+        try:
+            shutil.rmtree(params["name"], params["ignore_errors"])
+            return {"ok": True}
+        except OSError as err:
+            return {
+                "ok": False,
+                "error": str(err),
+            }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServer/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the components of the eric-ide remote server.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerConnectionDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+
+"""
+Module implementing a dialog to enter the parameters for a connection to an eric-ide
+server.
+"""
+
+import ipaddress
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
+
+from eric7 import Preferences
+
+from .Ui_EricServerConnectionDialog import Ui_EricServerConnectionDialog
+
+
+class EricServerConnectionDialog(QDialog, Ui_EricServerConnectionDialog):
+    """
+    Class implementing a dialog to enter the parameters for a connection to an eric-ide
+    server.
+    """
+
+    def __init__(self, profileNames=None, parent=None):
+        """
+        Constructor
+
+        @param profileNames list of defined connection profile names (defaults to None)
+        @type list of str (optional)
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.timeoutSpinBox.setToolTip(
+            self.tr(
+                "Enter the timeout for the connection attempt (default: {0} s."
+            ).format(Preferences.getEricServer("ConnectionTimeout"))
+        )
+
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+
+        if profileNames is None:
+            self.nameLabel.setVisible(False)
+            self.nameEdit.setVisible(False)
+            self.nameEdit.setEnabled(False)
+
+        self.__profileNames = profileNames[:] if bool(profileNames) else []
+        self.__originalName = ""
+
+        self.nameEdit.textChanged.connect(self.__updateOK)
+        self.hostnameEdit.textChanged.connect(self.__updateOK)
+
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+
+    @pyqtSlot()
+    def __updateOK(self):
+        """
+        Private slot to update the enabled state of the OK button.
+        """
+        hostname = self.hostnameEdit.text()
+
+        if hostname and hostname[0] in "0123456789" and ":" not in hostname:
+            # possibly an IPv4 address
+            try:
+                ipaddress.IPv4Address(hostname)
+                valid = True
+            except ipaddress.AddressValueError:
+                # leading zeros are not allowed
+                valid = False
+        elif ":" in hostname:
+            # possibly an IPv6 address
+            try:
+                ipaddress.IPv6Address(hostname)
+                valid = True
+            except ipaddress.AddressValueError:
+                # leading zeros are not allowed
+                valid = False
+        elif ":" not in hostname:
+            valid = bool(hostname)
+        else:
+            valid = False
+
+        if self.nameEdit.isEnabled():
+            # connection profile mode
+            name = self.nameEdit.text()
+            valid &= name == self.__originalName or name not in self.__profileNames
+
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(valid)
+
+    def getData(self):
+        """
+        Public method to get the entered data.
+
+        @return tuple containing the entered host name or IP address, the port number
+            and the timeout (in seconds)
+        @rtype tuple of (str, int, int)
+        """
+        port = self.portSpinBox.value()
+        if port == self.portSpinBox.minimum():
+            port = None
+
+        timeout = self.timeoutSpinBox.value()
+        if timeout == self.timeoutSpinBox.minimum():
+            timeout = None
+
+        return self.hostnameEdit.text(), port, timeout
+
+    def getProfileData(self):
+        """
+        Public method to get the entered data for connection profile mode.
+
+        @return tuple containing the profile name, host name or IP address,
+            the port number and the timeout (in seconds)
+        @rtype tuple of (str, str, int, int)
+        """
+        port = self.portSpinBox.value()
+        if port == self.portSpinBox.minimum():
+            port = 0
+
+        timeout = self.timeoutSpinBox.value()
+        if timeout == self.timeoutSpinBox.minimum():
+            timeout = 0
+
+        return self.nameEdit.text(), self.hostnameEdit.text(), port, timeout
+
+    def setProfileData(self, name, hostname, port, timeout):
+        """
+        Public method to set the connection profile data to be edited.
+
+        @param name profile name
+        @type str
+        @param hostname host name or IP address
+        @type str
+        @param port port number
+        @type int
+        @param timeout timeout value in seconds
+        @type int
+        """
+        # adjust some values
+        if not bool(port):
+            port = self.portSpinBox.minimum()
+        if not bool(timeout):
+            timeout = self.timeoutSpinBox.minimum()
+
+        self.__originalName = name
+
+        self.nameEdit.setText(name)
+        self.hostnameEdit.setText(hostname)
+        self.portSpinBox.setValue(port)
+        self.timeoutSpinBox.setValue(timeout)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerConnectionDialog.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EricServerConnectionDialog</class>
+ <widget class="QDialog" name="EricServerConnectionDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>169</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>eric-ide Server Connection</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="nameLabel">
+     <property name="text">
+      <string>Name:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1" colspan="2">
+    <widget class="QLineEdit" name="nameEdit">
+     <property name="toolTip">
+      <string>Enter the name for the eric-ide server connection profile.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Hostname:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <widget class="QLineEdit" name="hostnameEdit">
+     <property name="toolTip">
+      <string>Enter the hostname or IP address of the eric-ide server to connect to.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Port:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QSpinBox" name="portSpinBox">
+     <property name="toolTip">
+      <string>Enter the port number the eric-ide server listens on (default: 42024).</string>
+     </property>
+     <property name="wrapping">
+      <bool>true</bool>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+     <property name="specialValueText">
+      <string>default</string>
+     </property>
+     <property name="accelerated">
+      <bool>true</bool>
+     </property>
+     <property name="showGroupSeparator" stdset="0">
+      <bool>true</bool>
+     </property>
+     <property name="minimum">
+      <number>1024</number>
+     </property>
+     <property name="maximum">
+      <number>65535</number>
+     </property>
+     <property name="value">
+      <number>1024</number>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>240</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Timeout:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QSpinBox" name="timeoutSpinBox">
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+     <property name="specialValueText">
+      <string>default</string>
+     </property>
+     <property name="suffix">
+      <string> s</string>
+     </property>
+     <property name="maximum">
+      <number>60</number>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="3">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>nameEdit</tabstop>
+  <tabstop>hostnameEdit</tabstop>
+  <tabstop>portSpinBox</tabstop>
+  <tabstop>timeoutSpinBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>EricServerConnectionDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>EricServerConnectionDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerCoverageInterface.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the code coverage interface to the eric-ide server.
+"""
+
+import contextlib
+
+from PyQt6.QtCore import QEventLoop, QObject
+
+from eric7.RemoteServer.EricRequestCategory import EricRequestCategory
+from eric7.SystemUtilities import FileSystemUtilities
+
+
+class EricServerCoverageError(Exception):
+    """
+    Class defining a substitute exception for coverage errors of the server.
+    """
+
+    pass
+
+
+class EricServerCoverageInterface(QObject):
+    """
+    Class implementing the code coverage interface to the eric-ide server.
+    """
+
+    def __init__(self, serverInterface):
+        """
+        Constructor
+
+        @param serverInterface reference to the eric-ide server interface
+        @type EricServerInterface
+        """
+        super().__init__(parent=serverInterface)
+
+        self.__serverInterface = serverInterface
+
+    def loadCoverageData(self, dataFile, excludePattern=""):
+        """
+        Public method to tell the server to load the coverage data for a later analysis.
+
+        @param dataFile name of the data file to be loaded
+        @type str
+        @param excludePattern regular expression determining files to be excluded
+            (defaults to "")
+        @type str (optional)
+        @return tuple containing a success flag and an error message
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "LoadData":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Coverage,
+                request="LoadData",
+                params={
+                    "data_file": FileSystemUtilities.plainFileName(dataFile),
+                    "exclude": excludePattern,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            return ok, error
+
+        else:
+            return False, "Not connected to an 'eric-ide' server."
+
+    def analyzeFile(self, filename):
+        """
+        Public method to analyze the code coverage of one file.
+
+        @param filename name of the file to be analyzed
+        @type str
+        @return list containing coverage result as reported by Coverage.analysis2()
+        @rtype list of [str, list of int, list of int, list of int, str]
+        @exception EricServerCoverageError raised to indicate a coverage exception
+        @exception OSError raised to indicate that server is not connected
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        result = None
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, result
+
+            if reply == "AnalyzeFile":
+                ok = params["ok"]
+                if ok:
+                    result = params["result"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise OSError("Not connected to an 'eric-ide' server.")
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Coverage,
+                request="AnalyzeFile",
+                params={"filename": FileSystemUtilities.plainFileName(filename)},
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise EricServerCoverageError(error)
+
+            return result
+
+    def analyzeFiles(self, filenames):
+        """
+        Public method to analyze the code coverage of a list of files.
+
+        @param filenames list of file names to be analyzed
+        @type str
+        @return lists containing coverage results as reported by Coverage.analysis2()
+        @rtype list of [list of [str, list of int, list of int, list of int, str]]
+        @exception EricServerCoverageError raised to indicate a coverage exception
+        @exception OSError raised to indicate that server is not connected
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        result = None
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, result
+
+            if reply == "AnalyzeFiles":
+                ok = params["ok"]
+                if ok:
+                    result = params["results"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise OSError("Not connected to an 'eric-ide' server.")
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Coverage,
+                request="AnalyzeFiles",
+                params={
+                    "filenames": [
+                        FileSystemUtilities.plainFileName(f) for f in filenames
+                    ]
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise EricServerCoverageError(error)
+
+            return result
+
+    def analyzeDirectory(self, directory):
+        """
+        Public method to analyze the code coverage of a directory.
+
+        @param directory directory name to be analyzed
+        @type str
+        @return lists containing coverage results as reported by Coverage.analysis2()
+        @rtype list of [list of [str, list of int, list of int, list of int, str]]
+        @exception EricServerCoverageError raised to indicate a coverage exception
+        @exception OSError raised to indicate that server is not connected
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        result = None
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, result
+
+            if reply == "AnalyzeDirectory":
+                ok = params["ok"]
+                if ok:
+                    result = params["results"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise OSError("Not connected to an 'eric-ide' server.")
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Coverage,
+                request="AnalyzeDirectory",
+                params={"directory": FileSystemUtilities.plainFileName(directory)},
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise EricServerCoverageError(error)
+
+            return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerDebuggerInterface.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the file system interface to the eric-ide server.
+"""
+
+from PyQt6.QtCore import QEventLoop, QObject, pyqtSignal, pyqtSlot
+
+from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.RemoteServer.EricRequestCategory import EricRequestCategory
+from eric7.SystemUtilities import FileSystemUtilities
+
+
+class EricServerDebuggerInterface(QObject):
+    """
+    Class implementing the file system interface to the eric-ide server.
+
+    @signal debugClientResponse(response:str) emitted to relay a response of
+        the remote debug client
+    @signal debugClientDisconnected(debuggerId:str) emitted when a remote debug
+        client did disconnect from the eric-ide server
+    @signal lastClientExited() emitted to indicate that the last debug client of
+        the eric-ide server exited
+    """
+
+    debugClientResponse = pyqtSignal(str)
+    debugClientDisconnected = pyqtSignal(str)
+    lastClientExited = pyqtSignal()
+
+    def __init__(self, serverInterface):
+        """
+        Constructor
+
+        @param serverInterface reference to the eric-ide server interface
+        @type EricServerInterface
+        """
+        super().__init__(parent=serverInterface)
+
+        self.__serverInterface = serverInterface
+        self.__clientStarted = False
+
+        self.__replyMethodMapping = {
+            "DebuggerRequestError": self.__handleDbgRequestError,
+            "DebugClientResponse": self.__handleDbgClientResponse,
+            "DebugClientDisconnected": self.__handleDbgClientDisconnected,
+            "LastDebugClientExited": self.__handleLastDbgClientExited,
+            "MainClientExited": self.__handleMainClientExited,
+        }
+
+        # connect some signals
+        self.__serverInterface.remoteDebuggerReply.connect(self.__handleDebuggerReply)
+
+    def sendClientCommand(self, debuggerId, jsonCommand):
+        """
+        Public method to rely a debug client command via the eric-ide server.
+
+        @param debuggerId id of the debug client to send the command to
+        @type str
+        @param jsonCommand JSON encoded command dictionary to be relayed
+        @type str
+        """
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Debugger,
+                request="DebugClientCommand",
+                params={"debugger_id": debuggerId, "command": jsonCommand},
+            )
+
+    @pyqtSlot(str, dict)
+    def __handleDebuggerReply(self, reply, params):
+        """
+        Private slot to handle a debugger reply from the eric-ide server.
+
+        @param reply name of the server reply
+        @type str
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        if self.__serverInterface.isServerConnected():
+            try:
+                self.__replyMethodMapping[reply](params)
+            except KeyError:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Unknown Server Reply"),
+                    self.tr(
+                        "<p>The eric-ide server debugger interface sent the unknown"
+                        " reply <b>{0}</b>.</p>"
+                    ).format(reply),
+                )
+
+    #######################################################################
+    ## Methods for handling of debug client replies.
+    #######################################################################
+
+    def __handleDbgRequestError(self, params):
+        """
+        Private method to handle an error reported by the debugger interface of
+        the eric-ide server.
+
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        EricMessageBox.warning(
+            None,
+            self.tr("Debug Client Command"),
+            self.tr(
+                "<p>The IDE received an error message.</p><p>Error: {0}</p>"
+            ).format(params["Error"]),
+        )
+
+    def __handleDbgClientResponse(self, params):
+        """
+        Private method to handle a response from a debug client connected to the
+        eric-ide server.
+
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        self.debugClientResponse.emit(params["response"])
+
+    def __handleDbgClientDisconnected(self, params):
+        """
+        Private method to handle a debug client disconnect report of the
+        eric-ide server.
+
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        self.debugClientDisconnected.emit(params["debugger_id"])
+
+    def __handleLastDbgClientExited(self, params):  # noqa: U100
+        """
+        Private method to handle a report of the eric-ide server, that the last
+        debug client has disconnected.
+
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        self.__clientStarted = False
+        self.lastClientExited.emit()
+
+    def __handleMainClientExited(self, params):  # noqa: U100
+        """
+        Private method to handle the main client exiting.
+
+        @param params dictionary containing the reply data
+        @type dict
+        """
+        self.__clientStarted = False
+        ericApp().getObject("DebugServer").signalMainClientExit()
+
+    #######################################################################
+    ## Methods for sending debug server commands to the eric-ide server.
+    #######################################################################
+
+    def startClient(self, interpreter, originalPathString, args, workingDir=""):
+        """
+        Public method to send a command to start a debug client.
+
+        @param interpreter path of the remote interpreter to be used
+        @type str
+        @param originalPathString original PATH environment variable
+        @type str
+        @param args list of command line parameters for the debug client
+        @type list of str
+        @param workingDir directory to start the debugger client in (defaults to "")
+        @type str (optional)
+        """
+        self.__serverInterface.sendJson(
+            category=EricRequestCategory.Debugger,
+            request="StartClient",
+            params={
+                "interpreter": FileSystemUtilities.plainFileName(interpreter),
+                "path": originalPathString,
+                "arguments": args,
+                "working_dir": FileSystemUtilities.plainFileName(workingDir),
+            },
+        )
+        self.__clientStarted = True
+
+    def stopClient(self):
+        """
+        Public method to stop the debug client synchronously.
+        """
+        if self.__serverInterface.isServerConnected() and self.__clientStarted:
+            loop = QEventLoop()
+
+            def callback(reply, params):  # noqa: U100
+                """
+                Function to handle the server reply
+
+                @param reply name of the server reply
+                @type str
+                @param params dictionary containing the reply data
+                @type dict
+                """
+                if reply == "StopClient":
+                    loop.quit()
+
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.Debugger,
+                request="StopClient",
+                params={},
+                callback=callback,
+            )
+
+            loop.exec()
+            self.__clientStarted = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerFileDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,1037 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a file dialog showing the file system of the eric-ide server.
+"""
+
+import enum
+import fnmatch
+import re
+
+from PyQt6.QtCore import QLocale, QPoint, Qt, pyqtSlot
+from PyQt6.QtWidgets import (
+    QAbstractItemView,
+    QCompleter,
+    QDialog,
+    QInputDialog,
+    QLineEdit,
+    QMenu,
+    QTreeWidgetItem,
+)
+
+from eric7.EricGui import EricPixmapCache
+from eric7.EricGui.EricFileIconProvider import EricFileIconProvider
+from eric7.EricWidgets import EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.Globals import dataString
+from eric7.SystemUtilities import FileSystemUtilities
+
+from .Ui_EricServerFileDialog import Ui_EricServerFileDialog
+
+
+class AcceptMode(enum.Enum):
+    """
+    Class defining the dialog accept modes.
+    """
+
+    AcceptOpen = 0
+    AcceptSave = 1
+
+
+class FileMode(enum.Enum):
+    """
+    Class defining what the user may select in the file dialog.
+    """
+
+    AnyFile = 0
+    ExistingFile = 1
+    Directory = 2
+    ExistingFiles = 3
+
+
+class EricServerFileDialog(QDialog, Ui_EricServerFileDialog):
+    """
+    Class implementing a file dialog showing the file system of the eric-ide server.
+    """
+
+    IsDirectoryRole = Qt.ItemDataRole.UserRole
+
+    def __init__(self, parent=None, caption="", directory="", filter=""):  # noqa: M132
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        @param caption dialog title (defaults to "")
+        @type str (optional)
+        @param directory initial directory (defaults to "")
+        @type str (optional)
+        @param filter Qt file filter string (defaults to "")
+        @type str (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        # finish UI setup
+        self.backButton.setIcon(EricPixmapCache.getIcon("1leftarrow"))
+        self.forwardButton.setIcon(EricPixmapCache.getIcon("1rightarrow"))
+        self.upButton.setIcon(EricPixmapCache.getIcon("1uparrow"))
+        self.newDirButton.setIcon(EricPixmapCache.getIcon("dirNew"))
+        self.reloadButton.setIcon(EricPixmapCache.getIcon("reload"))
+        self.cancelButton.setIcon(EricPixmapCache.getIcon("dialog-cancel"))
+
+        self.setWindowTitle(caption)
+
+        self.__iconProvider = EricFileIconProvider()
+
+        self.__nameCompleter = QCompleter()
+        self.__nameCompleter.setModel(self.listing.model())
+        self.__nameCompleter.setCompletionColumn(0)
+        self.__nameCompleter.activated.connect(self.__nameCompleterActivated)
+        self.nameEdit.setCompleter(self.__nameCompleter)
+
+        self.__contextMenu = QMenu(self)
+
+        self.__fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
+        # set some default values
+        self.__fileMode = FileMode.ExistingFile
+        self.__dirsOnly = False
+        self.__acceptMode = AcceptMode.AcceptOpen
+        self.__showHidden = False
+        self.__sep = "/"
+        self.__filters = []
+
+        self.__history = []
+        self.__currentHistoryIndex = -1  # empty history
+        self.__updateHistoryButtons()
+
+        self.__filenameCache = []
+        self.__directoryCache = []
+        self.__selectedDirectory = None
+
+        if filter:
+            self.setNameFilters(filter.split(";;"))
+        else:
+            self.setNameFilters([self.tr("All Files (*)")])
+
+        self.reloadButton.clicked.connect(self.__reload)
+        self.cancelButton.clicked.connect(self.reject)
+
+        self.treeCombo.currentTextChanged.connect(self.setDirectory)
+
+        self.setDirectory(directory)
+
+    def acceptMode(self):
+        """
+        Public method to get the accept mode of the dialog.
+
+        @return accept mode
+        @rtype AcceptMode
+        """
+        return self.__acceptMode
+
+    def setAcceptMode(self, mode):
+        """
+        Public method to set the accept mode of the dialog.
+
+        @param mode accept mode
+        @type AcceptMode
+        """
+        self.__acceptMode = mode
+
+        self.__updateOkButton()
+
+    def fileMode(self):
+        """
+        Public method to get the current file mode of the dialog.
+
+        @return file mode
+        @rtype FileMode
+        """
+        return self.__fileMode
+
+    def setFileMode(self, mode):
+        """
+        Public method to set the file mode of the dialog.
+
+        @param mode file mode
+        @type FileMode
+        """
+        self.__fileMode = mode
+
+        self.listing.clearSelection()
+        if mode == FileMode.ExistingFiles:
+            self.listing.setSelectionMode(
+                QAbstractItemView.SelectionMode.ExtendedSelection
+            )
+        else:
+            self.listing.setSelectionMode(
+                QAbstractItemView.SelectionMode.SingleSelection
+            )
+
+        if mode == FileMode.Directory:
+            self.setNameFilters([self.tr("Directories")])
+
+        self.__updateOkButton()
+
+    def setNameFilters(self, filters):
+        """
+        Public method to set the list of file/directory name filters.
+
+        @param filters list of filter expressions
+            ("filter_name (pattern1 ... patternN)")
+        @type list of str
+        """
+        self.__filters = []
+        for f in filters:
+            if " (" in f and ")" in f:
+                self.__filters.append(f.split(" (", 1)[1].split(")", 1)[0].split())
+            elif f:
+                self.__filters.append(f)
+
+        self.filterCombo.clear()
+        self.filterCombo.addItems([f for f in filters if f])
+
+    def setNameFilter(self, filter):  # noqa: M132
+        """
+        Public method to set the current name filter.
+
+        @param filter filter text to make current
+        @type str
+        """
+        self.filterCombo.setCurrentText(filter)
+
+    def setDirectoriesOnly(self, dirsOnly):
+        """
+        Public method to set a flag to just show directories.
+
+        @param dirsOnly flag indicating to just show directories
+        @type bool
+        """
+        self.__dirsOnly = dirsOnly
+
+        filters = self.__filters[self.filterCombo.currentIndex()]
+        self.__filterList(filters)
+
+    def __addToHistory(self, entry):
+        """
+        Private method to add a directory to the history list.
+
+        @param entry name of the directory to be added
+        @type str
+        """
+        try:
+            # is in the history already?
+            index = self.__history.index(entry)
+            self.__currentHistoryIndex = index
+        except ValueError:
+            # new entry
+            self.__history.append(entry)
+            self.__currentHistoryIndex = len(self.__history) - 1
+
+        self.__updateHistoryButtons()
+
+    @pyqtSlot()
+    def __updateHistoryButtons(self):
+        """
+        Private method to update the enabled state of the back and forward buttons.
+        """
+        if not self.__history:
+            self.backButton.setEnabled(False)
+            self.forwardButton.setEnabled(False)
+        else:
+            self.backButton.setEnabled(self.__currentHistoryIndex > 0)
+            self.forwardButton.setEnabled(
+                self.__currentHistoryIndex < len(self.__history) - 1
+            )
+
+    @pyqtSlot()
+    def on_backButton_clicked(self):
+        """
+        Private slot to move back in history of visited directories.
+        """
+        self.setDirectory(self.__history[self.__currentHistoryIndex - 1])
+
+    @pyqtSlot()
+    def on_forwardButton_clicked(self):
+        """
+        Private slot to move forward in history of visited directories.
+        """
+        self.setDirectory(self.__history[self.__currentHistoryIndex + 1])
+
+    @pyqtSlot()
+    def __updateUpButton(self):
+        """
+        Private slot to update the enabled state of the 'Up' button.
+        """
+        self.upButton.setEnabled(
+            self.treeCombo.currentIndex() < self.treeCombo.count() - 1
+        )
+
+    @pyqtSlot()
+    def on_upButton_clicked(self):
+        """
+        Private slot to move up one level in the hierarchy.
+        """
+        self.treeCombo.setCurrentIndex(self.treeCombo.currentIndex() + 1)
+
+    @pyqtSlot()
+    def on_newDirButton_clicked(self):
+        """
+        Private slot to create a new directory.
+        """
+        newDir, ok = QInputDialog.getText(
+            self,
+            self.tr("New Directory"),
+            self.tr("Enter the name for the new directory:"),
+            QLineEdit.EchoMode.Normal,
+        )
+        if ok and newDir:
+            if newDir in self.__directoryCache or newDir in self.__filenameCache:
+                EricMessageBox.warning(
+                    self,
+                    self.tr("New Directory"),
+                    self.tr(
+                        "<p>A file or directory with the name <b>{0}</b> exists"
+                        " already. Aborting...</p>"
+                    ).format(newDir),
+                )
+                return
+
+            ok, error = self.__fsInterface.mkdir(self.__getFullPath(newDir))
+            if ok:
+                # refresh
+                self.__reload()
+            else:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("New Directory"),
+                    self.tr(
+                        "<p>The directory <b>{0}</b> could not be created.</p>"
+                        "<p>Reason: {1}</p>"
+                    ).format(
+                        self.__getFullPath(newDir),
+                        error if error else self.tr("Unknown"),
+                    ),
+                )
+
+    @pyqtSlot()
+    def __reload(self):
+        """
+        Private slot to reload the directory listing.
+        """
+        self.setDirectory(self.treeCombo.currentText())
+
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_listing_itemActivated(self, item, column):
+        """
+        Private slot to handle the activation of an item in the list.
+
+        @param item reference to the activated item
+        @type QTreeWidgetItem
+        @param column column number (unused)
+        @type int
+        """
+        if item.data(0, EricServerFileDialog.IsDirectoryRole):
+            self.setDirectory(self.__getFullPath(item.text(0)))
+        else:
+            self.accept()
+
+    @pyqtSlot()
+    def on_listing_itemSelectionChanged(self):
+        """
+        Private slot to handle the selection of listed items.
+        """
+        for itm in self.listing.selectedItems():
+            if itm.data(0, EricServerFileDialog.IsDirectoryRole):
+                self.__selectedDirectory = itm.text(0)
+                break
+        else:
+            self.__selectedDirectory = None
+
+        selectedNames = []
+        selectedItems = self.listing.selectedItems()
+        for itm in selectedItems:
+            isDir = itm.data(0, EricServerFileDialog.IsDirectoryRole)
+            if self.__fileMode == FileMode.Directory and isDir:
+                selectedNames.append(itm.text(0))
+            elif not isDir:
+                selectedNames.append(itm.text(0))
+
+        blocked = self.nameEdit.blockSignals(True)
+        if len(selectedNames) == 1:
+            self.nameEdit.setText(selectedNames[0])
+        elif len(selectedNames) > 1:
+            self.nameEdit.setText('"{0}"'.format('" "'.join(selectedNames)))
+        self.nameEdit.blockSignals(blocked)
+
+        self.__updateOkButton()
+
+    @pyqtSlot()
+    def __nameCompleterActivated(self):
+        """
+        Private slot handling the activation of the completer.
+        """
+        if self.okButton.isEnabled():
+            self.okButton.animateClick()
+
+    @pyqtSlot(str)
+    def on_nameEdit_textChanged(self, name):
+        """
+        Private slot handling the editing of a file or directory name.
+
+        @param name current text of the name edit
+        @type str
+        """
+        self.listing.clearSelection()
+        items = self.listing.findItems(name, Qt.MatchFlag.MatchExactly)
+        for itm in items:
+            itm.setSelected(True)
+
+        self.__updateOkButton()
+
+    def __getNames(self):
+        """
+        Private method to get the selected names list.
+
+        @return list containing the selected names
+        @rtype list of str
+        """
+        namesStr = self.nameEdit.text()
+        if namesStr.startswith('"'):
+            namesStr = namesStr[1:]
+        if namesStr.endswith('"'):
+            namesStr = namesStr[:-1]
+        names = re.split(r'"\s+"', namesStr)
+        return names
+
+    def __getFullPath(self, name):
+        """
+        Private method to get the full path for a given file or directory name.
+
+        @param name name of the file or directory
+        @type str
+        @return full path of the file or directory
+        @rtype str
+        """
+        return "{0}{1}{2}".format(self.treeCombo.currentText(), self.__sep, name)
+
+    @pyqtSlot()
+    def __updateOkButton(self):
+        """
+        Private slot to set the 'OK' button state, icon and label.
+        """
+        # 1. adjust icon and label
+        if (
+            self.__acceptMode == AcceptMode.AcceptOpen
+            or self.__selectedDirectory is not None
+        ):
+            self.okButton.setIcon(EricPixmapCache.getIcon("dialog-ok"))
+            if self.__fileMode != FileMode.Directory:
+                self.okButton.setText(self.tr("Open"))
+            else:
+                self.okButton.setText(self.tr("Choose"))
+        else:
+            self.okButton.setIcon(EricPixmapCache.getIcon("fileSave"))
+            self.okButton.setText(self.tr("Save"))
+
+        # 2. adjust enabled state
+        if self.__selectedDirectory and self.__fileMode != FileMode.Directory:
+            self.okButton.setEnabled(True)
+        elif self.__fileMode == FileMode.AnyFile:
+            self.okButton.setEnabled(bool(self.nameEdit.text()))
+        elif self.__fileMode == FileMode.ExistingFile:
+            self.okButton.setEnabled(self.nameEdit.text() in self.__filenameCache)
+        elif self.__fileMode == FileMode.ExistingFiles:
+            names = self.__getNames()
+            self.okButton.setEnabled(all(n in self.__filenameCache for n in names))
+        elif self.__fileMode == FileMode.Directory:
+            self.okButton.setEnabled(True)
+        else:
+            self.okButton.setEnabled(False)
+
+    @pyqtSlot()
+    def on_okButton_clicked(self):
+        """
+        Private slot handling the press of the OK button.
+        """
+        if self.__selectedDirectory and self.__fileMode != FileMode.Directory:
+            self.setDirectory(self.__getFullPath(self.__selectedDirectory))
+        else:
+            self.accept()
+
+    @pyqtSlot(int)
+    def on_filterCombo_currentIndexChanged(self, index):
+        """
+        Private slot handling the selection of a new file filter..
+
+        @param index index of the selected entry
+        @type int
+        """
+        filters = self.__filters[index]
+        self.__filterList(filters)
+
+    @pyqtSlot(str)
+    def setDirectory(self, directory):
+        """
+        Public slot to set the current directory and populate the tree list.
+
+        @param directory directory to be set as current. An empty directory sets the
+            server's current directory.
+        @type str
+        """
+        self.__filenameCache.clear()
+        self.__directoryCache.clear()
+
+        if self.__fsInterface.isfile(directory):
+            directory, basename = self.__fsInterface.split(directory)
+        else:
+            basename = ""
+
+        try:
+            directory, sep, dirListing = self.__fsInterface.listdir(directory)
+
+            self.__sep = sep
+
+            # 1. populate the directory tree combo box
+            self.treeCombo.blockSignals(True)
+            self.treeCombo.clear()
+            if len(directory) > 1 and directory.endswith(sep):
+                directory = directory[:-1]
+            if len(directory) > 2 and directory[1] == ":":
+                # starts with a Windows drive letter
+                directory = directory[2:]
+            if sep:
+                directoryParts = directory.split(sep)
+                while directoryParts:
+                    if directoryParts[-1]:
+                        self.treeCombo.addItem(sep.join(directoryParts))
+                    directoryParts.pop()
+                self.treeCombo.addItem(sep)
+            self.treeCombo.blockSignals(False)
+
+            # 2. populate the directory listing
+            self.listing.clear()
+            for dirEntry in sorted(
+                dirListing,
+                key=lambda d: (
+                    " " + d["name"].lower() if d["is_dir"] else d["name"].lower()
+                ),
+            ):
+                if dirEntry["is_dir"]:
+                    type_ = self.tr("Directory")
+                    iconName = "dirClosed"
+                    sizeStr = ""
+                    self.__directoryCache.append(dirEntry["name"])
+                else:
+                    type_ = self.tr("File")
+                    iconName = self.__iconProvider.fileIconName(dirEntry["name"])
+                    sizeStr = dataString(dirEntry["size"], QLocale.system())
+                    self.__filenameCache.append(dirEntry["name"])
+                itm = QTreeWidgetItem(
+                    self.listing,
+                    [dirEntry["name"], sizeStr, type_, dirEntry["mtime_str"]],
+                )
+                itm.setIcon(0, EricPixmapCache.getIcon(iconName))
+                itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight)
+                itm.setTextAlignment(2, Qt.AlignmentFlag.AlignHCenter)
+                itm.setData(0, EricServerFileDialog.IsDirectoryRole, dirEntry["is_dir"])
+
+            currentFilterIndex = self.filterCombo.currentIndex()
+            filters = (
+                [] if currentFilterIndex == -1 else self.__filters[currentFilterIndex]
+            )
+            self.__filterList(filters)
+
+            # 3. add the directory to the history
+            self.__addToHistory(directory)
+
+        except OSError as err:
+            EricMessageBox.critical(
+                self,
+                self.tr("Remote Directory Listung"),
+                self.tr(
+                    "<p>The directory <b>{0}</b> could not be listed due to an error"
+                    " reported by the eric-ide server.</p><p>Reason: {1}</p>"
+                ).format(directory, str(err)),
+            )
+
+        # 4. update some dependent states
+        if basename:
+            self.nameEdit.setText(basename)
+        else:
+            self.nameEdit.clear()
+        self.__updateUpButton()
+
+    @pyqtSlot(QPoint)
+    def on_listing_customContextMenuRequested(self, pos):
+        """
+        Private slot to show a context menu.
+
+        @param pos mouse pointer position to show the menu at
+        @type QPoint
+        """
+        self.__contextMenu.clear()
+
+        itm = self.listing.itemAt(pos)
+        if itm is not None:
+            self.__contextMenu.addAction(
+                self.tr("Rename"), lambda: self.__renameItem(itm)
+            )
+            self.__contextMenu.addAction(
+                self.tr("Delete"), lambda: self.__deleteItem(itm)
+            )
+            self.__contextMenu.addSeparator()
+        act = self.__contextMenu.addAction(self.tr("Show Hidden Files"))
+        act.setCheckable(True)
+        act.setChecked(self.__showHidden)
+        act.toggled.connect(self.__showHiddenToggled)
+        self.__contextMenu.addAction(
+            self.tr("New Directory"), self.on_newDirButton_clicked
+        )
+
+        self.__contextMenu.popup(self.listing.mapToGlobal(pos))
+
+    @pyqtSlot(QTreeWidgetItem)
+    def __renameItem(self, item):
+        """
+        Private slot to rename the given file/directory item.
+
+        @param item reference to the item to be renamed
+        @type QTreeWidgetItem
+        """
+        title = (
+            self.tr("Rename Directory")
+            if item.data(0, EricServerFileDialog.IsDirectoryRole)
+            else self.tr("Rename File")
+        )
+
+        newName, ok = QInputDialog.getText(
+            self,
+            title,
+            self.tr("<p>Enter the new name <b>{0}</b>:</p>").format(item.text(0)),
+            QLineEdit.EchoMode.Normal,
+            item.text(0),
+        )
+        if ok and newName:
+            if newName in self.__directoryCache or newName in self.__filenameCache:
+                EricMessageBox.warning(
+                    self,
+                    title,
+                    self.tr(
+                        "<p>A file or directory with the name <b>{0}</b> exists"
+                        " already. Aborting...</p>"
+                    ).format(newName),
+                )
+                return
+
+            ok, error = self.__fsInterface.replace(
+                self.__getFullPath(item.text(0)), self.__getFullPath(newName)
+            )
+            if ok:
+                # refresh
+                self.__reload()
+            else:
+                EricMessageBox.critical(
+                    self,
+                    title,
+                    self.tr(
+                        "<p>The renaming operation failed.</p><p>Reason: {0}</p>"
+                    ).format(error if error else self.tr("Unknown")),
+                )
+
+    @pyqtSlot(QTreeWidgetItem)
+    def __deleteItem(self, item):
+        """
+        Private slot to delete the given file/directory  item.
+
+        @param item reference to the item to be deleted
+        @type QTreeWidgetItem
+        """
+        isDir = item.data(0, EricServerFileDialog.IsDirectoryRole)
+        if isDir:
+            title = self.tr("Delete Directory")
+            itemType = self.tr("directory")
+        else:
+            title = self.tr("Delete File")
+            itemType = self.tr("file")
+
+        yes = EricMessageBox.yesNo(
+            self,
+            title,
+            self.tr("Shall the selected {0} really be deleted?").format(itemType),
+        )
+        if yes:
+            ok, error = (
+                self.__fsInterface.rmdir(self.__getFullPath(item.text(0)))
+                if isDir
+                else self.__fsInterface.remove(self.__getFullPath(item.text(0)))
+            )
+            if ok:
+                # refresh
+                self.__reload()
+            else:
+                EricMessageBox.critical(
+                    self,
+                    title,
+                    self.tr(
+                        "<p>The deletion operation failed.</p><p>Reason: {0}</p>"
+                    ).format(error if error else self.tr("Unknown")),
+                )
+
+    @pyqtSlot(bool)
+    def __showHiddenToggled(self, on):
+        """
+        Private slot to handle toggling the display of hidden files/directories.
+
+        @param on flag indicating to show hidden files and directories
+        @type bool
+        """
+        self.__showHidden = on
+        filters = self.__filters[self.filterCombo.currentIndex()]
+        self.__filterList(filters)
+
+    def selectedFiles(self):
+        """
+        Public method to get the selected files or the current viewport path.
+
+        @return selected files or current viewport path
+        @rtype str
+        """
+        if self.__fileMode == FileMode.Directory and not self.nameEdit.text():
+            return [self.treeCombo.currentText()]
+        else:
+            return [self.__getFullPath(n) for n in self.__getNames()]
+
+    def selectedNameFilter(self):
+        """
+        Public method to get the selected name filter.
+
+        @return selected name filter
+        @rtype str
+        """
+        return self.filterCombo.currentText()
+
+    def __isHidden(self, name):
+        """
+        Private method to check, if the given name is indicating a hidden file or
+        directory.
+
+        @param name name of the file or directory
+        @type str
+        @return flag indicating a hidden file or directory
+        @rtype bool
+        """
+        return name.startswith(".") or name.endswith("~")
+
+    def __filterList(self, filters):
+        """
+        Private method to filter the files and directories list based on the given
+        filters and whether hidden files/directories should be shown.
+
+        @param filters list of filter patterns (only applied to files
+        @type list of str
+        """
+        self.listing.clearSelection()
+        for row in range(self.listing.topLevelItemCount()):
+            itm = self.listing.topLevelItem(row)
+            name = itm.text(0)
+            if self.__dirsOnly and not itm.data(
+                0, EricServerFileDialog.IsDirectoryRole
+            ):
+                itm.setHidden(True)
+            elif not self.__showHidden and self.__isHidden(name):
+                # applies to files and directories
+                itm.setHidden(True)
+            elif not itm.data(0, EricServerFileDialog.IsDirectoryRole):
+                # it is a file item, apply the filter
+                itm.setHidden(not any(fnmatch.fnmatch(name, pat) for pat in filters))
+            else:
+                itm.setHidden(False)
+
+        # resize the columns
+        for column in range(4):
+            self.listing.resizeColumnToContents(column)
+
+
+###########################################################################
+## Module functions mimicing the interface of EricFileDialog/QFileDialog
+###########################################################################
+
+
+def getOpenFileName(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get the name of a file for opening it.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return name of file to be opened
+    @rtype str
+    """
+    return getOpenFileNameAndFilter(
+        parent, caption, directory, filterStr, initialFilter, withRemote
+    )[0]
+
+
+def getOpenFileNameAndFilter(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get the name of a file for opening it and the selected
+    file name filter.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return tuple containing the list of file names to be opened and the
+        selected file name filter
+    @rtype tuple of (list of str, str)
+    """
+    dlg = EricServerFileDialog(
+        parent=parent, caption=caption, directory=directory, filter=filterStr
+    )
+    dlg.setFileMode(FileMode.ExistingFile)
+    dlg.setNameFilter(initialFilter)
+    if dlg.exec() == QDialog.DialogCode.Accepted:
+        if withRemote:
+            fileName = FileSystemUtilities.remoteFileName(dlg.selectedFiles()[0])
+        else:
+            fileName = dlg.selectedFiles()[0]
+        selectedFilter = dlg.selectedNameFilter()
+    else:
+        fileName = ""
+        selectedFilter = ""
+
+    return fileName, selectedFilter
+
+
+def getOpenFileNames(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get a list of names of files for opening.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return list of file names to be opened
+    @rtype list of str
+    """
+    return getOpenFileNamesAndFilter(
+        parent, caption, directory, filterStr, initialFilter, withRemote
+    )[0]
+
+
+def getOpenFileNamesAndFilter(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get a list of names of files for opening and the
+    selected file name filter.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return tuple containing the list of file names to be opened and the
+        selected file name filter
+    @rtype tuple of (list of str, str)
+    """
+    dlg = EricServerFileDialog(
+        parent=parent, caption=caption, directory=directory, filter=filterStr
+    )
+    dlg.setFileMode(FileMode.ExistingFiles)
+    dlg.setNameFilter(initialFilter)
+    if dlg.exec() == QDialog.DialogCode.Accepted:
+        if withRemote:
+            filesList = [
+                FileSystemUtilities.remoteFileName(f) for f in dlg.selectedFiles()
+            ]
+        else:
+            filesList = dlg.selectedFiles()
+        selectedFilter = dlg.selectedNameFilter()
+    else:
+        filesList = []
+        selectedFilter = ""
+
+    return filesList, selectedFilter
+
+
+def getSaveFileName(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get the name of a file for saving.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return name of file to be saved
+    @rtype str
+    """
+    return getSaveFileNameAndFilter(
+        parent, caption, directory, filterStr, initialFilter, withRemote
+    )[0]
+
+
+def getSaveFileNameAndFilter(
+    parent=None,
+    caption="",
+    directory="",
+    filterStr="",
+    initialFilter="",
+    withRemote=True,
+):
+    """
+    Module function to get the name of a file for saving and the selected file name
+    filter.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param filterStr filter string for the dialog (defaults to "")
+    @type str (optional)
+    @param initialFilter initial filter for the dialog (defaults to "")
+    @type str (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return name of file to be saved and selected filte
+    @rtype tuple of (str, str)
+    """
+    dlg = EricServerFileDialog(
+        parent=parent, caption=caption, directory=directory, filter=filterStr
+    )
+    dlg.setFileMode(FileMode.AnyFile)
+    dlg.setNameFilter(initialFilter)
+    if dlg.exec() == QDialog.DialogCode.Accepted:
+        if withRemote:
+            fileName = FileSystemUtilities.remoteFileName(dlg.selectedFiles()[0])
+        else:
+            fileName = dlg.selectedFiles()[0]
+        selectedFilter = dlg.selectedNameFilter()
+    else:
+        fileName = ""
+        selectedFilter = ""
+
+    return fileName, selectedFilter
+
+
+def getExistingDirectory(
+    parent=None, caption="", directory="", dirsOnly=True, withRemote=True
+):
+    """
+    Module function to get the name of a directory.
+
+    @param parent parent widget of the dialog (defaults to None)
+    @type QWidget (optional)
+    @param caption window title of the dialog (defaults to "")
+    @type str (optional)
+    @param directory working directory of the dialog (defaults to "")
+    @type str (optional)
+    @param dirsOnly flag indicating to just show directories (defaults to True)
+    @type bool (optional)
+    @param withRemote flag indicating to create the file names with the remote
+        indicator (defaults to True)
+    @type bool (optional)
+    @return name of selected directory
+    @rtype str
+    """
+    dlg = EricServerFileDialog(parent=parent, caption=caption, directory=directory)
+    dlg.setFileMode(FileMode.Directory)
+    dlg.setDirectoriesOnly(dirsOnly)
+    if dlg.exec() == QDialog.DialogCode.Accepted:
+        if withRemote:
+            dirName = FileSystemUtilities.remoteFileName(dlg.selectedFiles()[0])
+        else:
+            dirName = dlg.selectedFiles()[0]
+    else:
+        dirName = ""
+
+    return dirName
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerFileDialog.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EricServerFileDialog</class>
+ <widget class="QDialog" name="EricServerFileDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Open Files</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Look in:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="treeCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="backButton">
+       <property name="toolTip">
+        <string>Press to move back in history.</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="forwardButton">
+       <property name="toolTip">
+        <string>Press to move forward in history.</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="upButton">
+       <property name="toolTip">
+        <string>Press to move up one level.</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="newDirButton">
+       <property name="toolTip">
+        <string>Press to create a new directory.</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="reloadButton">
+       <property name="toolTip">
+        <string>Press to reload the directory listing.</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="listing">
+     <property name="contextMenuPolicy">
+      <enum>Qt::CustomContextMenu</enum>
+     </property>
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>false</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Size</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Type</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Date Modified</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="nameLabel">
+       <property name="text">
+        <string>File Name:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="nameEdit">
+       <property name="clearButtonEnabled">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="2">
+      <widget class="QPushButton" name="okButton">
+       <property name="text">
+        <string>Open</string>
+       </property>
+       <property name="default">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="filterLabel">
+       <property name="text">
+        <string>Files of Type:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QComboBox" name="filterCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="2">
+      <widget class="QPushButton" name="cancelButton">
+       <property name="text">
+        <string>Cancel</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>nameEdit</tabstop>
+  <tabstop>filterCombo</tabstop>
+  <tabstop>okButton</tabstop>
+  <tabstop>cancelButton</tabstop>
+  <tabstop>treeCombo</tabstop>
+  <tabstop>backButton</tabstop>
+  <tabstop>forwardButton</tabstop>
+  <tabstop>upButton</tabstop>
+  <tabstop>newDirButton</tabstop>
+  <tabstop>reloadButton</tabstop>
+  <tabstop>listing</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerFileSystemInterface.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,1426 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the file system interface to the eric-ide server.
+"""
+
+import base64
+import contextlib
+import os
+import re
+import stat
+
+from PyQt6.QtCore import QByteArray, QEventLoop, QObject, pyqtSlot
+
+from eric7 import Utilities
+from eric7.RemoteServer.EricRequestCategory import EricRequestCategory
+from eric7.SystemUtilities import FileSystemUtilities
+
+_RemoteFsCache = {}
+# dictionary containing cached remote file system data keyed by remote path
+
+
+class EricServerNotConnectedError(OSError):
+    """
+    Class defining a special OSError indicating a missing server connection.
+    """
+
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__("Not connected to an 'eric-ide' server.")
+
+
+class EricServerFileSystemInterface(QObject):
+    """
+    Class implementing the file system interface to the eric-ide server.
+    """
+
+    _MagicCheck = re.compile("([*?[])")
+
+    NotConnectedMessage = "Not connected to an 'eric-ide' server."
+
+    def __init__(self, serverInterface):
+        """
+        Constructor
+
+        @param serverInterface reference to the eric-ide server interface
+        @type EricServerInterface
+        """
+        super().__init__(parent=serverInterface)
+
+        self.__serverInterface = serverInterface
+        self.__serverInterface.connectionStateChanged.connect(
+            self.__connectionStateChanged
+        )
+
+        self.__serverPathSep = self.__getPathSep()
+
+    def __hasMagic(self, pathname):
+        """
+        Private method to check, if a given path contains glob style magic characters.
+
+        Note: This was taken from 'glob.glob'.
+
+        @param pathname path name to be checked
+        @type str
+        @return flag indicating the presence of magic characters
+        @rtype bool
+        """
+        match = self._MagicCheck.search(pathname)
+        return match is not None
+
+    @pyqtSlot(bool)
+    def __connectionStateChanged(self, connected):
+        """
+        Private slot handling a change of the server connection state.
+
+        @param connected flag indicating a connected state
+        @type bool
+        """
+        if connected and not bool(self.__serverPathSep):
+            self.__serverPathSep = self.__getPathSep()
+
+    def __getPathSep(self):
+        """
+        Private method to get the path separator of the connected server.
+
+        @return path separator character of the server
+        @rtype str
+        """
+        loop = QEventLoop()
+        sep = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal sep
+
+            if reply == "GetPathSep":
+                sep = params["separator"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="GetPathSep",
+                params={},
+                callback=callback,
+            )
+
+            loop.exec()
+
+        return sep
+
+    def getcwd(self):
+        """
+        Public method to get the current working directory of the eric-ide server.
+
+        @return current working directory of the eric-ide server
+        @rtype str
+        """
+        loop = QEventLoop()
+        cwd = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal cwd
+
+            if reply == "Getcwd":
+                cwd = params["directory"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Getcwd",
+                params={},
+                callback=callback,
+            )
+
+            loop.exec()
+
+        return FileSystemUtilities.remoteFileName(cwd)
+
+    def chdir(self, directory):
+        """
+        Public method to change the current working directory of the eric-ide server.
+
+        @param directory absolute path of the working directory to change to
+        @type str
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "Chdir":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Chdir",
+                params={"directory": FileSystemUtilities.plainFileName(directory)},
+                callback=callback,
+            )
+
+            loop.exec()
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def listdir(self, directory="", recursive=False):
+        """
+        Public method to get a directory listing.
+
+        @param directory directory to be listed. An empty directory means to list
+            the eric-ide server current directory. (defaults to "")
+        @type str (optional)
+        @param recursive flag indicating a recursive listing (defaults to False)
+        @type bool (optional)
+        @return tuple containing the listed directory, the path separator and the
+            directory listing. Each directory listing entry contains a dictionary
+            with the relevant data.
+        @rtype tuple of (str, str, dict)
+        @exception OSError raised in case the server reported an issue
+        """
+        if directory is None:
+            # sanitize the directory in case it is None
+            directory = ""
+
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        listedDirectory = ""
+        separator = ""
+        listing = []
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal listedDirectory, listing, separator, ok, error
+
+            if reply == "Listdir":
+                ok = params["ok"]
+                if ok:
+                    listedDirectory = params["directory"]
+                    listing = params["listing"]
+                    separator = params["separator"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Listdir",
+                params={
+                    "directory": FileSystemUtilities.plainFileName(directory),
+                    "recursive": recursive,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+            for entry in listing:
+                entry["path"] = FileSystemUtilities.remoteFileName(entry["path"])
+
+        return listedDirectory, separator, listing
+
+    def direntries(
+        self,
+        directory,
+        filesonly=False,
+        pattern=None,
+        followsymlinks=True,
+        ignore=None,
+        recursive=True,
+        dirsonly=False,
+    ):
+        """
+        Public method to get a list of all files and directories of a given directory.
+
+        @param directory root of the tree to check
+        @type str
+        @param filesonly flag indicating that only files are wanted (defaults to False)
+        @type bool (optional)
+        @param pattern a filename pattern or list of filename patterns to check
+            against (defaults to None)
+        @type str or list of str (optional)
+        @param followsymlinks flag indicating whether symbolic links should be
+            followed (defaults to True)
+        @type bool (optional)
+        @param ignore list of entries to be ignored (defaults to None)
+        @type list of str (optional)
+        @param recursive flag indicating a recursive search (defaults to True)
+        @type bool (optional)
+        @param dirsonly flag indicating to return only directories. When True it has
+            precedence over the 'filesonly' parameter (defaults to False)
+        @type bool
+        @return list of all files and directories in the tree rooted at path.
+            The names are expanded to start with the given directory name.
+        @rtype list of str
+        @exception OSError raised in case the server reported an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        result = []
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal result, ok, error
+
+            if reply == "DirEntries":
+                ok = params["ok"]
+                if ok:
+                    result = params["result"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="DirEntries",
+                params={
+                    "directory": FileSystemUtilities.plainFileName(directory),
+                    "files_only": filesonly,
+                    "pattern": [] if pattern is None else pattern,
+                    "follow_symlinks": followsymlinks,
+                    "ignore": [] if ignore is None else ignore,
+                    "recursive": recursive,
+                    "dirs_only": dirsonly,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+        return [FileSystemUtilities.remoteFileName(r) for r in result]
+
+    def glob(self, pathname, recursive=False, includeHidden=False):
+        """
+        Public method to get a list of of all files matching a given pattern
+        like 'glob.glob()'.
+
+        @param pathname path name pattern with simple shell-style wildcards
+        @type str
+        @param recursive flag indicating a recursive list (defaults to False)
+        @type bool (optional)
+        @param includeHidden flag indicating to include hidden files (defaults to False)
+        @type bool (optional)
+        @return list of all files matching the pattern
+        @rtype list of str
+        """
+        result = []
+
+        pathname = FileSystemUtilities.plainFileName(pathname)
+        dirname, basename = os.path.split(pathname)
+        if dirname and not self.__hasMagic(dirname):
+            with contextlib.suppress(OSError):
+                entries = self.direntries(
+                    dirname, pattern=basename, recursive=recursive, filesonly=True
+                )
+                result = (
+                    [FileSystemUtilities.remoteFileName(e) for e in entries]
+                    if includeHidden
+                    else [
+                        FileSystemUtilities.remoteFileName(e)
+                        for e in entries
+                        if not e.startswith(".")
+                    ]
+                )
+
+        return result
+
+    def stat(self, filename, stNames):
+        """
+        Public method to get the status of a file.
+
+        @param filename name of the file
+        @type str
+        @param stNames list of 'stat_result' members to retrieve
+        @type list of str
+        @return dictionary containing the requested status data
+        @rtype dict
+        @exception OSError raised in case the server reported an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        stResult = {}
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, stResult
+
+            if reply == "Stat":
+                ok = params["ok"]
+                if ok:
+                    stResult = params["result"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Stat",
+                params={
+                    "filename": FileSystemUtilities.plainFileName(filename),
+                    "st_names": stNames,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+        return stResult
+
+    def isdir(self, name):
+        """
+        Public method to check, if the given name is a directory.
+
+        @param name name to be checked
+        @type str
+        @return flag indicating a directory
+        @rtype bool
+        """
+        try:
+            return stat.S_ISDIR(_RemoteFsCache[name]["mode"])
+        except KeyError:
+            with contextlib.suppress(KeyError, OSError):
+                result = self.stat(name, ["st_mode"])
+                return stat.S_ISDIR(result["st_mode"])
+
+        return False
+
+    def isfile(self, name):
+        """
+        Public method to check, if the given name is a regular file.
+
+        @param name name to be checked
+        @type str
+        @return flag indicating a regular file
+        @rtype bool
+        """
+        try:
+            return stat.S_ISREG(_RemoteFsCache[name]["mode"])
+        except KeyError:
+            with contextlib.suppress(KeyError, OSError):
+                result = self.stat(name, ["st_mode"])
+                return stat.S_ISREG(result["st_mode"])
+
+        return False
+
+    def exists(self, name):
+        """
+        Public method the existence of a file or directory.
+
+        @param name name of the file or directory
+        @type str
+        @return flag indicating the file existence
+        @rtype bool
+        """
+        loop = QEventLoop()
+        nameExists = False
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal nameExists
+
+            if reply == "Exists":
+                nameExists = params["exists"]
+                loop.quit()
+
+        if name in _RemoteFsCache:
+            return True
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Exists",
+                params={"name": FileSystemUtilities.plainFileName(name)},
+                callback=callback,
+            )
+
+            loop.exec()
+
+        return nameExists
+
+    def access(self, name, modes):
+        """
+        Public method to test the given access rights to a file or directory.
+
+        The modes to check for are 'read', 'write' or 'execute' or any combination.
+
+        @param name name of the file or directory
+        @type str
+        @param modes list of modes to check for
+        @type str or list of str
+        @return flag indicating the user has the asked for permissions
+        @rtype bool
+        @exception ValueError raised for an illegal modes list
+        """
+        if not modes:
+            raise ValueError(
+                "At least one of 'read', 'write' or 'execute' must be specified."
+            )
+
+        if isinstance(modes, str):
+            # convert to a list with one element
+            modes = [modes]
+
+        loop = QEventLoop()
+        accessOK = False
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal accessOK
+
+            if reply == "Access":
+                accessOK = params["ok"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Access",
+                params={
+                    "name": FileSystemUtilities.plainFileName(name),
+                    "modes": modes,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+
+        return accessOK
+
+    def mkdir(self, directory):
+        """
+        Public method to create a new directory on the eric-ide server.
+
+        @param directory absolute path of the new directory
+        @type str
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "Mkdir":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Mkdir",
+                params={"directory": FileSystemUtilities.plainFileName(directory)},
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                self.populateFsCache(directory)
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def makedirs(self, directory, exist_ok=False):
+        """
+        Public method to create a new directory on the eric-ide serverincluding all
+        intermediate-level directories.
+
+        @param directory absolute path of the new directory
+        @type str
+        @param exist_ok flag indicating that the existence of the directory is
+            acceptable (defaults to False)
+        @type bool (optional)
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "MakeDirs":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="MakeDirs",
+                params={
+                    "directory": FileSystemUtilities.plainFileName(directory),
+                    "exist_ok": exist_ok,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                self.populateFsCache(directory)
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def rmdir(self, directory):
+        """
+        Public method to delete a directory on the eric-ide server.
+
+        @param directory absolute path of the directory
+        @type str
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "Rmdir":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Rmdir",
+                params={"directory": FileSystemUtilities.plainFileName(directory)},
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                self.removeFromFsCache(directory)
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def replace(self, oldName, newName):
+        """
+        Public method to rename a file or directory.
+
+        @param oldName current name of the file or directory
+        @type str
+        @param newName new name for the file or directory
+        @type str
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "Replace":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Replace",
+                params={
+                    "old_name": FileSystemUtilities.plainFileName(oldName),
+                    "new_name": FileSystemUtilities.plainFileName(newName),
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                with contextlib.suppress(KeyError):
+                    entry = _RemoteFsCache.pop(oldName)
+                    entry["path"] = newName
+                    entry["name"] = self.basename(newName)
+                    _RemoteFsCache[newName] = entry
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def remove(self, filename):
+        """
+        Public method to delete a file on the eric-ide server.
+
+        @param filename absolute path of the file
+        @type str
+        @return tuple containing an OK flag and an error string in case of an issue
+        @rtype tuple of (bool, str)
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "Remove":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="Remove",
+                params={"filename": FileSystemUtilities.plainFileName(filename)},
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                with contextlib.suppress(KeyError):
+                    del _RemoteFsCache[filename]
+            return ok, error
+
+        else:
+            return False, EricServerFileSystemInterface.NotConnectedMessage
+
+    def expanduser(self, name):
+        """
+        Public method to expand an initial '~' or '~user' component.
+
+        @param name path name to be expanded
+        @type str
+        @return expanded path name
+        @rtype str
+        """
+        loop = QEventLoop()
+        ok = False
+        expandedName = name
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, expandedName
+
+            if reply == "ExpandUser":
+                ok = params["ok"]
+                expandedName = params["name"]
+                loop.quit()
+
+        if self.__serverInterface.isServerConnected():
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ExpandUser",
+                params={"name": FileSystemUtilities.plainFileName(name)},
+                callback=callback,
+            )
+
+            loop.exec()
+        if FileSystemUtilities.isRemoteFileName(name):
+            return FileSystemUtilities.remoteFileName(expandedName)
+        else:
+            return expandedName
+
+    #######################################################################
+    ## Methods for splitting or joining remote path names.
+    ##
+    ## These are simplified variants of the os.path functions. If the
+    ## 'eric-ide' server is not connected, the os.path functions are used.
+    #######################################################################
+
+    def separator(self):
+        """
+        Public method to return the server side path separator string.
+
+        @return path separator
+        @rtype str
+        """
+        return self.__serverPathSep
+
+    def isabs(self, p):
+        """
+        Public method to chack a path for being an absolute path.
+
+        @param p path to be checked
+        @type str
+        @return flag indicating an absolute path
+        @rtype bool
+        """
+        if self.__serverPathSep == "\\":
+            s = FileSystemUtilities.plainFileName(p)[:3].replace("/", "\\")
+            return s.startswith("\\)") or s.startswith(":\\", 1)
+        else:
+            return FileSystemUtilities.plainFileName(p).startswith("/")
+
+    def abspath(self, p):
+        """
+        Public method to convert the given path to an absolute path.
+
+        @param p path to be converted
+        @type str
+        @return absolute path
+        @rtype str
+        """
+        p = FileSystemUtilities.plainFileName(p)
+        if not self.isabs(p):
+            p = self.join(self.getcwd(), p)
+        return FileSystemUtilities.remoteFileName(p)
+
+    def join(self, a, *p):
+        """
+        Public method to join two or more path name components using the path separator
+        of the server side.
+
+        @param a first path component
+        @type str
+        @param *p list of additional path components
+        @type list of str
+        @return joined path name
+        @rtype str
+        """
+        path = a
+        for b in p:
+            if b.startswith(self.__serverPathSep):
+                path = b
+            elif not path or path.endswith(self.__serverPathSep):
+                path += b
+            else:
+                path += self.__serverPathSep + b
+        return path
+
+    def split(self, p):
+        """
+        Public method to split a path name.
+
+        @param p path name to be split
+        @type str
+        @return tuple containing head and tail, where tail is everything after the last
+            path separator.
+        @rtype tuple of (str, str)
+        """
+        normp = (
+            p.replace("/", "\\")  # remote is a Windows system
+            if self.__serverPathSep == "\\"
+            else p.replace("\\", "/")  # remote is a Posix system
+        )
+
+        i = normp.rfind(self.__serverPathSep) + 1
+        head, tail = normp[:i], normp[i:]
+        if head and head != self.__serverPathSep * len(head):
+            head = head.rstrip(self.__serverPathSep)
+        return head, tail
+
+    def splitext(self, p):
+        """
+        Public method to split a path name into a root part and an extension.
+
+        @param p path name to be split
+        @type str
+        @return tuple containing the root part and the extension
+        @rtype tuple of (str, str)
+        """
+        return os.path.splitext(p)
+
+    def splitdrive(self, p):
+        """
+        Public method to split a path into drive and path.
+
+        @param p path name to be split
+        @type str
+        @return tuple containing the drive letter (incl. colon) and the path
+        @rtype tuple of (str, str)
+        """
+        plainp = FileSystemUtilities.plainFileName(p)
+
+        if self.__serverPathSep == "\\":
+            # remote is a Windows system
+            normp = plainp.replace("/", "\\")
+            if normp[1:2] == ":":
+                return normp[:2], normp[2:]
+            else:
+                return "", normp
+        else:
+            # remote is a Posix system
+            normp = plainp.replace("\\", "/")
+            return "", normp
+
+    def dirname(self, p):
+        """
+        Public method to extract the directory component of a path name.
+
+        @param p path name
+        @type str
+        @return directory component
+        @rtype str
+        """
+        return self.split(p)[0]
+
+    def basename(self, p):
+        """
+        Public method to extract the final component of a path name.
+
+        @param p path name
+        @type str
+        @return final component
+        @rtype str
+        """
+        return self.split(p)[1]
+
+    def toNativeSeparators(self, p):
+        """
+        Public method to convert a path to use server native separator characters.
+
+        @param p path name to be converted
+        @type str
+        @return path name with converted separator characters
+        @rtype str
+        """
+        if self.__serverPathSep == "/":
+            return p.replace("\\", "/")
+        else:
+            return p.replace("/", "\\")
+
+    def fromNativeSeparators(self, p):
+        """
+        Public method to convert a path using server native separator characters to
+        use "/" separator characters.
+
+        @param p path name to be converted
+        @type str
+        @return path name with converted separator characters
+        @rtype str
+        """
+        return p.replace(self.__serverPathSep, "/")
+
+    #######################################################################
+    ## Methods for reading and writing files
+    #######################################################################
+
+    def readFile(self, filename, create=False, newline=None):
+        """
+        Public method to read a file from the eric-ide server.
+
+        @param filename name of the file to read
+        @type str
+        @param create flag indicating to create an empty file, if it does not exist
+            (defaults to False)
+        @type bool (optional)
+        @param newline determines how to parse newline characters from the stream
+            (defaults to None)
+        @type str (optional)
+        @return bytes data read from the eric-ide server
+        @rtype bytes
+        @exception EricServerNotConnectedError raised to indicate a missing server
+            connection
+        @exception OSError raised in case the server reported an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        bText = b""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, bText
+
+            if reply == "ReadFile":
+                ok = params["ok"]
+                if ok:
+                    bText = base64.b85decode(
+                        bytes(params["filedata"], encoding="ascii")
+                    )
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise EricServerNotConnectedError()
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ReadFile",
+                params={
+                    "filename": FileSystemUtilities.plainFileName(filename),
+                    "create": create,
+                    "newline": "<<none>>" if newline is None else newline,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+            return bText
+
+    def writeFile(self, filename, data, withBackup=False, newline=None):
+        """
+        Public method to write the data to a file on the eric-ide server.
+
+        @param filename name of the file to write
+        @type str
+        @param data data to be written
+        @type bytes or QByteArray
+        @param withBackup flag indicating to create a backup file first
+            (defaults to False)
+        @type bool (optional)
+        @param newline determines how to parse newline characters from the stream
+            (defaults to None)
+        @type str (optional)
+        @exception EricServerNotConnectedError raised to indicate a missing server
+            connection
+        @exception OSError raised in case the server reported an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "WriteFile":
+                ok = params["ok"]
+                with contextlib.suppress(KeyError):
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise EricServerNotConnectedError
+
+        else:
+            if isinstance(data, QByteArray):
+                data = bytes(data)
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="WriteFile",
+                params={
+                    "filename": FileSystemUtilities.plainFileName(filename),
+                    "filedata": str(base64.b85encode(data), encoding="ascii"),
+                    "with_backup": withBackup,
+                    "newline": "<<none>>" if newline is None else newline,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+    def readEncodedFile(self, filename, create=False):
+        """
+        Public method to read a file and decode its contents into proper text.
+
+        @param filename name of the file to read
+        @type str
+        @param create flag indicating to create an empty file, if it does not exist
+            (defaults to False)
+        @type bool (optional)
+        @return tuple of decoded text and encoding
+        @rtype tuple of (str, str)
+        """
+        data = self.readFile(filename, create=create)
+        return Utilities.decode(data)
+
+    def readEncodedFileWithEncoding(self, filename, encoding, create=False):
+        """
+        Public method to read a file and decode its contents into proper text.
+
+        @param filename name of the file to read
+        @type str
+        @param encoding encoding to be used to read the file
+        @type str
+        @param create flag indicating to create an empty file, if it does not exist
+            (defaults to False)
+        @type bool (optional)
+        @return tuple of decoded text and encoding
+        @rtype tuple of (str, str)
+        """
+        data = self.readFile(filename, create=create)
+        return Utilities.decodeWithEncoding(data, encoding)
+
+    def writeEncodedFile(
+        self, filename, text, origEncoding, forcedEncoding="", withBackup=False
+    ):
+        """
+        Public method to write a file with properly encoded text.
+
+        @param filename name of the file to read
+        @type str
+        @param text text to be written
+        @type str
+        @param origEncoding type of the original encoding
+        @type str
+        @param forcedEncoding encoding to be used for writing, if no coding
+            line is present (defaults to "")
+        @type str (optional)
+        @param withBackup flag indicating to create a backup file first
+            (defaults to False)
+        @type bool (optional)
+        @return encoding used for writing the file
+        @rtype str
+        """
+        data, encoding = Utilities.encode(
+            text, origEncoding, forcedEncoding=forcedEncoding
+        )
+        self.writeFile(filename, data, withBackup=withBackup)
+
+        return encoding
+
+    #######################################################################
+    ## Methods implementing some 'shutil' like functionality.
+    #######################################################################
+
+    def shutilCopy(self, srcName, dstName):
+        """
+        Public method to copy a source file to a given destination file or directory.
+
+        @param srcName name of the source file
+        @type str
+        @param dstName name of the destination file or directory
+        @type str
+        @return name of the destination file
+        @rtype str
+        @exception EricServerNotConnectedError raised to indicate a missing server
+            connection
+        @exception OSError raised to indicate an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+        dst = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error, dst
+
+            if reply == "ShutilCopy":
+                ok = params["ok"]
+                if ok:
+                    dst = params["dst"]
+                else:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise EricServerNotConnectedError
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ShutilCopy",
+                params={
+                    "src_name": FileSystemUtilities.plainFileName(srcName),
+                    "dst_name": FileSystemUtilities.plainFileName(dstName),
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if not ok:
+                raise OSError(error)
+
+            return dst
+
+    def shutilRmtree(self, pathname, ignore_errors=False):
+        """
+        Public method to delete an entire directory tree.
+
+        @param pathname name of the directory to be deleted
+        @type str
+        @param ignore_errors flag indicating to ignore error resulting from failed
+            removals (defaults to False)
+        @type bool (optional)
+        @exception EricServerNotConnectedError raised to indicate a missing server
+            connection
+        @exception OSError raised to indicate an issue
+        """
+        loop = QEventLoop()
+        ok = False
+        error = ""
+
+        def callback(reply, params):
+            """
+            Function to handle the server reply
+
+            @param reply name of the server reply
+            @type str
+            @param params dictionary containing the reply data
+            @type dict
+            """
+            nonlocal ok, error
+
+            if reply == "ShutilRmtree":
+                ok = params["ok"]
+                if not ok:
+                    error = params["error"]
+                loop.quit()
+
+        if not self.__serverInterface.isServerConnected():
+            raise EricServerNotConnectedError
+
+        else:
+            self.__serverInterface.sendJson(
+                category=EricRequestCategory.FileSystem,
+                request="ShutilRmtree",
+                params={
+                    "name": FileSystemUtilities.plainFileName(pathname),
+                    "ignore_errors": ignore_errors,
+                },
+                callback=callback,
+            )
+
+            loop.exec()
+            if ok:
+                self.removeFromFsCache(pathname)
+
+            if not ok:
+                raise OSError(error)
+
+    #######################################################################
+    ## Utility methods.
+    #######################################################################
+
+    def compactPath(self, longPath, width, measure=len):
+        """
+        Public method to return a compacted path fitting inside the given width.
+
+        @param longPath path to be compacted
+        @type str
+        @param width width for the compacted path
+        @type int
+        @param measure reference to a function used to measure the length of the
+            string (defaults to len)
+        @type function (optional)
+        @return compacted path
+        @rtype str
+        """
+        if measure(longPath) <= width:
+            return longPath
+
+        ellipsis = "..."
+
+        head, tail = self.split(longPath)
+        mid = len(head) // 2
+        head1 = head[:mid]
+        head2 = head[mid:]
+        while head1:
+            # head1 is same size as head2 or one shorter
+            cpath = self.join(f"{head1}{ellipsis}{head2}", tail)
+            if measure(cpath) <= width:
+                return cpath
+            head1 = head1[:-1]
+            head2 = head2[1:]
+        cpath = self.join(ellipsis, tail)
+        if measure(cpath) <= width:
+            return cpath
+        remoteMarker = FileSystemUtilities.remoteFileName("")
+        if width <= len(remoteMarker):
+            return f"{remoteMarker}{ellipsis}{tail}"
+        while tail:
+            cpath = f"{remoteMarker}{ellipsis}{tail}"
+            if measure(cpath) <= width:
+                return cpath
+            tail = tail[1:]
+        return ""
+
+    #######################################################################
+    ## Remote file system cache methods.
+    #######################################################################
+
+    def populateFsCache(self, directory):
+        """
+        Public method to populate the remote file system cache for a given directory.
+
+        @param directory remote directory to be cached
+        @type str
+        @exception ValueError raised to indicate an empty directory
+        """
+        if not directory:
+            raise ValueError("The directory to be cached must not be empty.")
+
+        try:
+            listing = self.listdir(directory=directory, recursive=True)[2]
+            for entry in listing:
+                _RemoteFsCache[FileSystemUtilities.remoteFileName(entry["path"])] = (
+                    entry
+                )
+            print(f"Remote Cache Size: {len(_RemoteFsCache)} entries")
+        except OSError as err:
+            print("Error in 'populateFsCache()':", str(err))  # noqa: M801
+
+    def removeFromFsCache(self, directory):
+        """
+        Public method to remove a given directory from the remote file system cache.
+
+        @param directory remote directory to be removed
+        @type str
+        """
+        for entryPath in list(_RemoteFsCache.keys()):
+            if entryPath.startswith(directory):
+                del _RemoteFsCache[entryPath]
+        print(f"Remote Cache Size: {len(_RemoteFsCache)} entries")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerInterface.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,773 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the interface to the eric remote server.
+"""
+
+import collections
+import json
+import struct
+import uuid
+import zlib
+
+from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import QAction, QKeySequence
+from PyQt6.QtNetwork import QAbstractSocket, QTcpSocket
+from PyQt6.QtWidgets import QDialog, QMenu, QToolBar, QToolButton
+
+from eric7 import Preferences, Utilities
+from eric7.EricGui import EricPixmapCache
+from eric7.EricGui.EricAction import EricAction
+from eric7.EricWidgets import EricMessageBox
+from eric7.RemoteServer.EricRequestCategory import EricRequestCategory
+
+
+class EricServerInterface(QObject):
+    """
+    Class implementing the interface to the eric remote server.
+
+    @signal showMenu(name:str, menu:QMenu) emitted when a menu is about to be shown.
+        The name of the menu and a reference to the menu are given.
+
+    @signal connectionStateChanged(state:bool) emitted to indicate a change of the
+        connection state
+    @signal aboutToDisconnect() emitted just befor the remote server is disconnected
+
+    @signal remoteReply(category:int, request:str, params:dict) emitted to deliver the
+        reply of an unknown category
+    @signal remoteCoverageReply(request:str, params:dict) emitted to deliver the reply
+        of a remote server code coverage request
+    @signal remoteDebuggerReply(request:str, params:dict) emitted to deliver the reply
+        of a remote server debugger request
+    @signal remoteEchoReply(request:str, params:dict) emitted to deliver the reply of
+        a remote server echo request
+    @signal remoteFileSystemReply(request:str, params:dict) emitted to deliver the
+        reply of a remote server file system request
+    @signal remoteProjectReply(request:str, params:dict) emitted to deliver the reply
+        of a remote server project related request
+    @signal remoteServerReply(request:str, params:dict) emitted to deliver the reply
+        of a remote server control request
+    """
+
+    showMenu = pyqtSignal(str, QMenu)
+
+    aboutToDisconnect = pyqtSignal()
+    connectionStateChanged = pyqtSignal(bool)
+
+    remoteReply = pyqtSignal(int, str, dict)
+
+    remoteCoverageReply = pyqtSignal(str, dict)
+    remoteDebuggerReply = pyqtSignal(str, dict)
+    remoteEchoReply = pyqtSignal(str, dict)
+    remoteFileSystemReply = pyqtSignal(str, dict)
+    remoteProjectReply = pyqtSignal(str, dict)
+    remoteServerReply = pyqtSignal(str, dict)
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent object (defaults to None)
+        @type QObject (optional)
+        """
+        super().__init__(parent=parent)
+
+        self.__ui = parent
+
+        self.__categorySignalMapping = {
+            EricRequestCategory.Coverage: self.remoteCoverageReply,
+            EricRequestCategory.Debugger: self.remoteDebuggerReply,
+            EricRequestCategory.Echo: self.remoteEchoReply,
+            EricRequestCategory.FileSystem: self.remoteFileSystemReply,
+            EricRequestCategory.Project: self.remoteProjectReply,
+            EricRequestCategory.Server: self.remoteServerReply,
+        }
+        self.__serviceInterfaces = {}
+        # no specific service interfaces have been created yet
+
+        self.__connection = None
+        self.__callbacks = {}  # callback references indexed by UUID
+        self.__messageQueue = collections.deque()
+        self.__connected = False
+
+        self.connectionStateChanged.connect(self.__connectionStateChanged)
+
+    def getServiceInterface(self, name):
+        """
+        Public method to get a references to a specific service interface by
+        service name.
+
+        @param name service name
+        @type str
+        @return reference to the service interface
+        @rtype QObject
+        @exception ValueError raised to indicate an unsupported server interface
+            was requested
+        """
+        lname = name.lower()
+        try:
+            return self.__serviceInterfaces[lname]
+        except KeyError:
+            if lname not in ("coverage", "debugger", "filesystem"):
+                raise ValueError(f"no such service supported ({name})")
+            else:
+                # instantiate the service interface
+                if lname == "filesystem":
+                    from .EricServerFileSystemInterface import (  # noqa: I101
+                        EricServerFileSystemInterface,
+                    )
+
+                    self.__serviceInterfaces[lname] = EricServerFileSystemInterface(
+                        self
+                    )
+                elif lname == "debugger":
+                    from .EricServerDebuggerInterface import (  # noqa: I101
+                        EricServerDebuggerInterface,
+                    )
+
+                    self.__serviceInterfaces[lname] = EricServerDebuggerInterface(self)
+                elif lname == "coverage":
+                    from .EricServerCoverageInterface import (  # noqa: I101
+                        EricServerCoverageInterface,
+                    )
+
+                    self.__serviceInterfaces[lname] = EricServerCoverageInterface(self)
+
+            return self.__serviceInterfaces[lname]
+
+    #######################################################################
+    ## Methods for handling the server connection.
+    #######################################################################
+
+    def connectToServer(self, host, port=None, timeout=None):
+        """
+        Public method to connect to the given host and port.
+
+        @param host host name or IP address of the eric remote server
+        @type str
+        @param port port number to connect to (defaults to None)
+        @type int (optional)
+        @param timeout timeout im seconds for the connection attempt
+            (defaults to None)
+        @type int (optional)
+        @return flag indicating success
+        @rtype bool
+        """
+        if not bool(port):  # None or 0
+            # use default port
+            port = 42024
+
+        if not bool(timeout):  # None or 0
+            # use configured default timeout
+            timeout = Preferences.getEricServer("ConnectionTimeout")
+        timeout *= 1000  # convert to milliseconds
+
+        if self.__connection is not None:
+            self.disconnectFromServer()
+
+        self.__connection = QTcpSocket(self)
+        self.__connection.connectToHost(host, port)
+        if not self.__connection.waitForConnected(timeout):
+            EricMessageBox.critical(
+                None,
+                self.tr("Connect to eric-ide Server"),
+                self.tr(
+                    "<p>The connection to the eric-ide server {0}:{1} could not be"
+                    " established.</p><p>Reason: {2}</p>"
+                ).format(
+                    host if ":" not in host else f"[{host}]",
+                    port,
+                    self.__connection.errorString(),
+                ),
+            )
+
+            self.__connection = None
+            return False
+
+        self.__connection.readyRead.connect(self.__receiveJson)
+        self.__connection.disconnected.connect(self.__handleDisconnect)
+
+        self.connectionStateChanged.emit(True)
+
+        return True
+
+    @pyqtSlot()
+    def disconnectFromServer(self):
+        """
+        Public method to disconnect from the eric remote server.
+        """
+        if self.__connection is not None and self.__connection.isValid():
+            # signal we are abouzt to disconnect
+            self.aboutToDisconnect.emit()
+
+            # disconnect from the eric-ide server
+            self.__connection.disconnectFromHost()
+            if self.__connection is not None:
+                # may have disconnected already
+                self.__connection.waitForDisconnected(
+                    Preferences.getEricServer("ConnectionTimeout") * 1000
+                )
+
+                self.connectionStateChanged.emit(False)
+                self.__connection = None
+                self.__callbacks.clear()
+
+    def isServerConnected(self):
+        """
+        Public method to check, if a connection to an eric-ide server has been
+        established.
+
+        @return flag indicating the interface connection state
+        @rtype bool
+        """
+        return (
+            self.__connection is not None
+            and self.__connection.state() == QAbstractSocket.SocketState.ConnectedState
+        )
+
+    @pyqtSlot()
+    def __handleDisconnect(self):
+        """
+        Private slot handling a disconnect of the client.
+        """
+        if self.__connection is not None:
+            self.__connection.close()
+
+        self.connectionStateChanged.emit(False)
+        self.__connection = None
+        self.__callbacks.clear()
+
+    def getHost(self):
+        """
+        Public method to get the connected host as "host name:port".
+
+        @return connected host as "host name:port" or an empty string, if there is no
+            valid connection
+        @rtype str
+        """
+        if self.isServerConnected():
+            return f"{self.__connection.peerName()}:{self.__connection.peerPort()}"
+        else:
+            return ""
+
+    #######################################################################
+    ## Methods for sending requests and receiving the replies.
+    #######################################################################
+
+    @pyqtSlot()
+    def __receiveJson(self):
+        """
+        Private slot handling received data from the eric remote server.
+        """
+        while self.__connection and self.__connection.bytesAvailable():
+            header = self.__connection.read(struct.calcsize(b"!II"))
+            length, datahash = struct.unpack(b"!II", header)
+
+            data = bytearray()
+            while len(data) < length:
+                maxSize = length - len(data)
+                if self.__connection.bytesAvailable() < maxSize:
+                    self.__connection.waitForReadyRead(50)
+                if not self.__connection:
+                    # connection to server is gone uncontrolled
+                    break
+                newData = self.__connection.read(maxSize)
+                if newData:
+                    data += newData
+
+            if zlib.adler32(data) & 0xFFFFFFFF != datahash:
+                # corrupted data -> discard and continue
+                continue
+
+            jsonString = data.decode("utf-8", "backslashreplace")
+
+            # - print("Remote Server Interface Receive: {0}".format(jsonString))
+            # - this is for debugging only
+
+            try:
+                serverDataDict = json.loads(jsonString.strip())
+            except (TypeError, ValueError) as err:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("JSON Protocol Error"),
+                    self.tr(
+                        """<p>The response received from the remote server"""
+                        """ could not be decoded. Please report"""
+                        """ this issue with the received data to the"""
+                        """ eric bugs email address.</p>"""
+                        """<p>Error: {0}</p>"""
+                        """<p>Data:<br/>{1}</p>"""
+                    ).format(str(err), Utilities.html_encode(jsonString.strip())),
+                    EricMessageBox.Ok,
+                )
+                return
+
+            reqUuid = serverDataDict["uuid"]
+            if reqUuid:
+                # It is a response to a synchronous request -> handle the call back
+                # immediately.
+                self.__callbacks[reqUuid](
+                    serverDataDict["reply"], serverDataDict["params"]
+                )
+                del self.__callbacks[reqUuid]
+            else:
+                self.__messageQueue.append(serverDataDict)
+
+        while self.__messageQueue:
+            serverDataDict = self.__messageQueue.popleft()  # get the first message
+            try:
+                self.__categorySignalMapping[serverDataDict["category"]].emit(
+                    serverDataDict["reply"], serverDataDict["params"]
+                )
+            except KeyError:
+                if serverDataDict["category"] == EricRequestCategory.Error:
+                    # handle server errors in here
+                    self.__handleServerError(
+                        serverDataDict["reply"], serverDataDict["params"]
+                    )
+                else:
+                    self.remoteReply.emit(
+                        serverDataDict["category"],
+                        serverDataDict["reply"],
+                        serverDataDict["params"],
+                    )
+
+    def sendJson(self, category, request, params, callback=None, flush=False):
+        """
+        Public method to send a single command to a client.
+
+        @param category service category
+        @type EricRequestCategory
+        @param request request name to be sent
+        @type str
+        @param params dictionary of named parameters for the request
+        @type dict
+        @param callback callback function for the reply from the eric remote server
+            (defaults to None)
+        @type function (optional)
+        @param flush flag indicating to flush the data to the socket
+            (defaults to False)
+        @type bool (optional)
+        """
+        if callback:
+            reqUuid = str(uuid.uuid4())
+            self.__callbacks[reqUuid] = callback
+        else:
+            reqUuid = ""
+
+        serviceDict = {
+            "jsonrpc": "2.0",
+            "category": category,
+            "request": request,
+            "params": params,
+            "uuid": reqUuid,
+        }
+        jsonString = json.dumps(serviceDict) + "\n"
+
+        # - print("Remote Server Interface Send: {0}".format(jsonString))
+        # - this is for debugging only
+
+        if self.__connection is not None:
+            data = jsonString.encode("utf8", "backslashreplace")
+            header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+            self.__connection.write(header)
+            self.__connection.write(data)
+            if flush:
+                self.__connection.flush()
+
+    def shutdownServer(self):
+        """
+        Public method shutdown the currebtly connected eric-ide remote server.
+        """
+        if self.__connection:
+            self.sendJson(
+                category=EricRequestCategory.Server,
+                request="Shutdown",
+                params={},
+            )
+
+    @pyqtSlot()
+    def serverVersions(self):
+        """
+        Public slot to request the eric-ide version of the server.
+        """
+        if self.__connection:
+            self.sendJson(
+                category=EricRequestCategory.Server,
+                request="Versions",
+                params={},
+                callback=self.__handleServerVersionReply,
+            )
+
+    #######################################################################
+    ## Callback methods
+    #######################################################################
+
+    def __handleServerVersionReply(self, reply, params):
+        """
+        Private method to handle the reply of a 'Version' request.
+
+        @param reply name of the eric-ide server reply
+        @type str
+        @param params dictionary containing the reply data
+        @type dict
+        @exception ValueError raised in case of an unsupported reply
+        """
+        if reply != "Versions":
+            raise ValueError(f"unsupported reply received ({reply})")
+
+        else:
+            hostname = params["hostname"]
+            versionText = self.tr("<h2>{0}Version Numbers</h2><table>").format(
+                self.tr("{0} - ").format(hostname) if hostname else ""
+            )
+
+            # Python version
+            versionText += (
+                """<tr><td><b>Python</b></td><td>{0}, {1}</td></tr>"""
+            ).format(params["python"], params["py_bitsize"])
+
+            # eric7 version
+            versionText += (
+                """<tr><td><b>eric7_server</b></td><td>{0}</td></tr>"""
+            ).format(params["version"])
+
+            versionText += self.tr("""</table>""")
+
+            EricMessageBox.about(
+                None,
+                self.tr("eric-ide Server Versions"),
+                versionText,
+            )
+
+    #######################################################################
+    ## Reply handler methods
+    #######################################################################
+
+    def __handleServerError(self, reply, params):
+        """
+        Private method handling server error replies.
+
+        @param reply name of the error reply
+        @type str
+        @param params dictionary containing the specific reply data
+        @type dict
+        """
+        if reply == "ClientChecksumException":
+            self.__ui.appendToStderr(
+                self.tr(
+                    "eric-ide Server Checksum Error\nError: {0}\nData:\n{1}\n"
+                ).format(params["ExceptionValue"], params["ProtocolData"])
+            )
+
+        elif reply == "ClientException":
+            self.__ui.appendToStderr(
+                self.tr("eric-ide Server Data Error\nError: {0}\nData:\n{1}\n").format(
+                    params["ExceptionValue"], params["ProtocolData"]
+                )
+            )
+
+        elif reply == "UnsupportedServiceCategory":
+            self.__ui.appendToStderr(
+                self.tr(
+                    "eric-ide Server Unsupported Category\n"
+                    "Error: The server received the unsupported request category '{0}'."
+                ).format(params["Category"])
+            )
+
+    #######################################################################
+    ## User interface related methods
+    #######################################################################
+
+    def initActions(self):
+        """
+        Public slot to initialize the eric-ide server actions.
+        """
+        self.actions = []
+
+        self.connectServerAct = EricAction(
+            self.tr("Connect"),
+            EricPixmapCache.getIcon("linkConnect"),
+            self.tr("Connect..."),
+            QKeySequence(self.tr("Meta+Shift+C")),
+            0,
+            self,
+            "remote_server_connect",
+        )
+        self.connectServerAct.setStatusTip(
+            self.tr("Show a dialog to connect to an 'eric-ide' server")
+        )
+        self.connectServerAct.setWhatsThis(
+            self.tr(
+                """<b>Connect...</b>"""
+                """<p>This opens a dialog to enter the connection parameters to"""
+                """ connect to a remote 'eric-ide' server.</p>"""
+            )
+        )
+        self.connectServerAct.triggered.connect(self.__connectToServer)
+        self.actions.append(self.connectServerAct)
+
+        self.disconnectServerAct = EricAction(
+            self.tr("Disconnect"),
+            EricPixmapCache.getIcon("linkDisconnect"),
+            self.tr("Disconnect"),
+            QKeySequence(self.tr("Meta+Shift+D")),
+            0,
+            self,
+            "remote_server_disconnect",
+        )
+        self.disconnectServerAct.setStatusTip(
+            self.tr("Disconnect from the currently connected 'eric-ide' server")
+        )
+        self.disconnectServerAct.setWhatsThis(
+            self.tr(
+                """<b>Disconnect</b>"""
+                """<p>This disconnects from the currently connected 'eric-ide'"""
+                """ server.</p>"""
+            )
+        )
+        self.disconnectServerAct.triggered.connect(self.disconnectFromServer)
+        self.actions.append(self.disconnectServerAct)
+
+        self.stopServerAct = EricAction(
+            self.tr("Stop Server"),
+            EricPixmapCache.getIcon("stopScript"),
+            self.tr("Stop Server"),
+            QKeySequence(self.tr("Meta+Shift+S")),
+            0,
+            self,
+            "remote_server_shutdown",
+        )
+        self.stopServerAct.setStatusTip(
+            self.tr("Stop the currently connected 'eric-ide' server")
+        )
+        self.stopServerAct.setWhatsThis(
+            self.tr(
+                """<b>Stop Server</b>"""
+                """<p>This stops the currently connected 'eric-ide server.</p>"""
+            )
+        )
+        self.stopServerAct.triggered.connect(self.__shutdownServer)
+        self.actions.append(self.stopServerAct)
+
+        self.serverVersionsAct = EricAction(
+            self.tr("Show Server Versions"),
+            EricPixmapCache.getIcon("helpAbout"),
+            self.tr("Show Server Versions"),
+            0,
+            0,
+            self,
+            "remote_server_versions",
+        )
+        self.serverVersionsAct.setStatusTip(
+            self.tr("Show the eric-ide server versions")
+        )
+        self.serverVersionsAct.setWhatsThis(
+            self.tr(
+                """<b>Show Server Versions</b>"""
+                """<p>This opens a dialog to show the eric-ide server versions.</p>"""
+            )
+        )
+        self.serverVersionsAct.triggered.connect(self.serverVersions)
+        self.actions.append(self.serverVersionsAct)
+
+        self.disconnectServerAct.setEnabled(False)
+        self.stopServerAct.setEnabled(False)
+        self.serverVersionsAct.setEnabled(False)
+
+    def initMenu(self):
+        """
+        Public slot to initialize the eric-ide server menu.
+
+        @return the menu generated
+        @rtype QMenu
+        """
+        self.__serverProfilesMenu = QMenu(self.tr("Connect to"))
+        self.__serverProfilesMenu.aboutToShow.connect(self.__showServerProfilesMenu)
+        self.__serverProfilesMenu.triggered.connect(self.__serverProfileTriggered)
+
+        menu = QMenu(self.tr("eric-ide Server"), self.__ui)
+        menu.setTearOffEnabled(True)
+        menu.aboutToShow.connect(self.__showEricServerMenu)
+        menu.addAction(self.connectServerAct)
+        menu.addMenu(self.__serverProfilesMenu)
+        # TODO: add a 'Recent Connections' submenu
+        menu.addSeparator()
+        menu.addAction(self.disconnectServerAct)
+        menu.addSeparator()
+        menu.addAction(self.stopServerAct)
+        menu.addSeparator()
+        menu.addAction(self.serverVersionsAct)
+
+        self.__menus = {
+            "Main": menu,
+            ##"Recent": self.recentMenu,
+        }
+
+        return menu
+
+    def initToolbar(self, toolbarManager):
+        """
+        Public slot to initialize the eric-ide server toolbar.
+
+        @param toolbarManager reference to a toolbar manager object
+        @type EricToolBarManager
+        @return the toolbar generated
+        @rtype QToolBar
+        """
+        self.__connectButton = QToolButton()
+        self.__connectButton.setIcon(self.connectServerAct.icon())
+        self.__connectButton.setToolTip(self.connectServerAct.toolTip())
+        self.__connectButton.setWhatsThis(self.connectServerAct.whatsThis())
+        self.__connectButton.setPopupMode(
+            QToolButton.ToolButtonPopupMode.MenuButtonPopup
+        )
+        self.__connectButton.setMenu(self.__serverProfilesMenu)
+        self.connectServerAct.enabledChanged.connect(self.__connectButton.setEnabled)
+        self.__connectButton.clicked.connect(self.connectServerAct.triggered)
+
+        tb = QToolBar(self.tr("eric-ide Server"), self.__ui)
+        tb.setObjectName("EricServerToolbar")
+        tb.setToolTip(self.tr("eric-ide Server"))
+
+        act = tb.addWidget(self.__connectButton)
+        act.setText(self.connectServerAct.iconText())
+        act.setIcon(self.connectServerAct.icon())
+        tb.addAction(self.disconnectServerAct)
+        tb.addSeparator()
+        tb.addAction(self.stopServerAct)
+        tb.addSeparator()
+        tb.addAction(self.serverVersionsAct)
+
+        toolbarManager.addToolBar(tb, tb.windowTitle())
+
+        return tb
+
+    @pyqtSlot()
+    def __showEricServerMenu(self):
+        """
+        Private slot to display the server menu.
+        """
+        connected = self.isServerConnected()
+        self.connectServerAct.setEnabled(not connected)
+        self.disconnectServerAct.setEnabled(connected)
+        self.stopServerAct.setEnabled(connected)
+        self.serverVersionsAct.setEnabled(connected)
+
+        self.showMenu.emit("Main", self.__menus["Main"])
+
+    @pyqtSlot()
+    def __showServerProfilesMenu(self):
+        """
+        Private slot to prepare the eric server profiles menu.
+        """
+        profiles = Preferences.getEricServer("ConnectionProfiles")
+
+        self.__serverProfilesMenu.clear()
+
+        if not self.isServerConnected():
+            for profile in sorted(profiles):
+                act = self.__serverProfilesMenu.addAction(profile)
+                act.setData(profiles[profile])
+            self.__serverProfilesMenu.addSeparator()
+
+        self.__serverProfilesMenu.addAction(
+            self.tr("Manage Server Connections"), self.__manageServerProfiles
+        )
+
+    @pyqtSlot(bool)
+    def __connectionStateChanged(self, connected):
+        """
+        Private slot to handle the connection state change.
+
+        @param connected flag indicating the connection state
+        @type bool
+        """
+        if connected != self.__connected:  # prevent executing it twice in succession
+            self.__connected = connected
+
+            self.connectServerAct.setEnabled(not connected)
+            self.disconnectServerAct.setEnabled(connected)
+            self.stopServerAct.setEnabled(connected)
+            self.serverVersionsAct.setEnabled(connected)
+
+            if connected:
+                peerName = self.__connection.peerName()
+                EricMessageBox.information(
+                    None,
+                    self.tr("Connect to eric-ide Server"),
+                    self.tr(
+                        "<p>The eric-ide server at <b>{0}:{1}</b> was connected"
+                        " successfully.</p>"
+                    ).format(
+                        f"[{peerName}]" if ":" in peerName else peerName,
+                        self.__connection.peerPort(),
+                    ),
+                )
+            else:
+                EricMessageBox.information(
+                    None,
+                    self.tr("Disonnect from eric-ide Server"),
+                    self.tr("""The eric-ide server was disconnected."""),
+                )
+
+    @pyqtSlot()
+    def __connectToServer(self):
+        """
+        Private slot to connect to a remote eric-ide server.
+        """
+        from .EricServerConnectionDialog import EricServerConnectionDialog
+
+        dlg = EricServerConnectionDialog(parent=self.__ui)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            hostname, port, timeout = dlg.getData()
+            self.connectToServer(hostname, port=port, timeout=timeout)
+
+    @pyqtSlot()
+    def __shutdownServer(self):
+        """
+        Private slot to shut down the currently connected eric-ide server.
+        """
+        ok = EricMessageBox.yesNo(
+            None,
+            self.tr("Stop Server"),
+            self.tr(
+                "Do you really want to stop the currently connected eric-ide server?"
+                " No further connections will be possible without restarting the"
+                " server."
+            ),
+        )
+        if ok:
+            self.shutdownServer()
+
+    @pyqtSlot(QAction)
+    def __serverProfileTriggered(self, act):
+        """
+        Private slot to handle the selection of a remote server connection.
+
+        @param act reference to the triggered profile action
+        @type QAction
+        """
+        data = act.data()
+        if data is not None:
+            # handle the connection
+            hostname, port, timeout = data
+            self.connectToServer(hostname, port=port, timeout=timeout)
+
+    @pyqtSlot()
+    def __manageServerProfiles(self):
+        """
+        Private slot to show a dialog to manage the eric-ide server connection
+        profiles.
+        """
+        from .EricServerProfilesDialog import EricServerProfilesDialog
+
+        dlg = EricServerProfilesDialog(
+            Preferences.getEricServer("ConnectionProfiles"), self.__ui
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            profiles = dlg.getConnectionProfiles()
+            Preferences.setEricServer("ConnectionProfiles", profiles)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerProfilesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to manage server connection profiles.
+"""
+
+import copy
+
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import QDialog, QListWidgetItem
+
+from eric7.EricWidgets import EricMessageBox
+
+from .EricServerConnectionDialog import EricServerConnectionDialog
+from .Ui_EricServerProfilesDialog import Ui_EricServerProfilesDialog
+
+
+class EricServerProfilesDialog(QDialog, Ui_EricServerProfilesDialog):
+    """
+    Class implementing a dialog to manage server connection profiles.
+    """
+
+    def __init__(self, profiles, parent=None):
+        """
+        Constructor
+
+        @param profiles dictionary containing the server connection profiles
+        @type dict
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.__profiles = copy.deepcopy(profiles)
+        self.__populateProfilesList()
+
+        self.on_connectionsList_itemSelectionChanged()
+
+    def __populateProfilesList(self):
+        """
+        Private method to (re-) populate the list of server connection profiles.
+        """
+        self.connectionsList.clear()
+
+        for profile in self.__profiles:
+            itm = QListWidgetItem(profile, self.connectionsList)
+            itm.setData(Qt.ItemDataRole.UserRole, self.__profiles[profile])
+
+    def __getProfilesList(self):
+        """
+        Private method to get the list of defined profile names.
+
+        @return list of defined profile names
+        @rtype list of str
+        """
+        profileNames = []
+        for row in range(self.connectionsList.count()):
+            itm = self.connectionsList.item(row)
+            profileNames.append(itm.text())
+
+        return profileNames
+
+    @pyqtSlot()
+    def on_connectionsList_itemSelectionChanged(self):
+        """
+        Private slot to handle a change of selected items.
+        """
+        selectedItems = self.connectionsList.selectedItems()
+        self.editButton.setEnabled(len(selectedItems) == 1)
+        self.removeButton.setEnabled(len(selectedItems) > 0)
+
+    @pyqtSlot()
+    def on_addButton_clicked(self):
+        """
+        Private slot add a new connection profile.
+        """
+        dlg = EricServerConnectionDialog(
+            profileNames=self.__getProfilesList(), parent=self
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            profileData = dlg.getProfileData()
+            itm = QListWidgetItem(profileData[0], self.connectionsList)
+            itm.setData(Qt.ItemDataRole.UserRole, profileData[1:])
+
+    @pyqtSlot()
+    def on_editButton_clicked(self):
+        """
+        Private slot to edit the selected entry.
+        """
+        selectedItems = self.connectionsList.selectedItems()
+        if selectedItems:
+            itm = selectedItems[0]
+            dlg = EricServerConnectionDialog(
+                profileNames=self.__getProfilesList(), parent=self
+            )
+            data = itm.data(Qt.ItemDataRole.UserRole)
+            dlg.setProfileData(itm.text(), *data)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                profileData = dlg.getProfileData()
+                itm.setText(profileData[0])
+                itm.setData(Qt.ItemDataRole.UserRole, profileData[1:])
+
+    @pyqtSlot()
+    def on_removeButton_clicked(self):
+        """
+        Private slot to remove the selected connection profiles.
+        """
+        yes = EricMessageBox.yesNo(
+            self,
+            self.tr("Remove Selected Entries"),
+            self.tr("Do you really want to remove the selected entries from the list?"),
+        )
+        if yes:
+            for itm in self.connectionsList.selectedItems()[:]:
+                self.connectionsList.takeItem(self.connectionsList.row(itm))
+                del itm
+
+    @pyqtSlot()
+    def on_resetButton_clicked(self):
+        """
+        Private slot to reset all changes performed.
+        """
+        yes = EricMessageBox.yesNo(
+            self,
+            self.tr("Reset Changes"),
+            self.tr(
+                "Do you really want to reset all changes performed up to this point?"
+            ),
+        )
+        if yes:
+            self.__populateProfilesList()
+
+    def getConnectionProfiles(self):
+        """
+        Public method to get the configured connection profiles.
+
+        @return dictionary containing the configured connection profiles
+        @rtype dict
+        """
+        profiles = {}
+
+        for row in range(self.connectionsList.count()):
+            itm = self.connectionsList.item(row)
+            profiles[itm.text()] = itm.data(Qt.ItemDataRole.UserRole)
+
+        return profiles
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/EricServerProfilesDialog.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EricServerProfilesDialog</class>
+ <widget class="QDialog" name="EricServerProfilesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>441</width>
+    <height>281</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Manage Server Connections</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QListWidget" name="connectionsList">
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::ExtendedSelection</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QPushButton" name="addButton">
+       <property name="toolTip">
+        <string>Press to open a dialog to add a new server connection.</string>
+       </property>
+       <property name="text">
+        <string>Add...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="editButton">
+       <property name="toolTip">
+        <string>Press to open a dialog to edit the selected server connection.</string>
+       </property>
+       <property name="text">
+        <string>Edit...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="toolTip">
+        <string>Press to remove the selected server connections.</string>
+       </property>
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="resetButton">
+       <property name="toolTip">
+        <string>Press to reset all changes performed.</string>
+       </property>
+       <property name="text">
+        <string>Reset</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>EricServerProfilesDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>EricServerProfilesDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/Ui_EricServerConnectionDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,82 @@
+# Form implementation generated from reading ui file 'src/eric7/RemoteServerInterface/EricServerConnectionDialog.ui'
+#
+# Created by: PyQt6 UI code generator 6.7.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_EricServerConnectionDialog(object):
+    def setupUi(self, EricServerConnectionDialog):
+        EricServerConnectionDialog.setObjectName("EricServerConnectionDialog")
+        EricServerConnectionDialog.resize(400, 169)
+        EricServerConnectionDialog.setSizeGripEnabled(True)
+        self.gridLayout = QtWidgets.QGridLayout(EricServerConnectionDialog)
+        self.gridLayout.setObjectName("gridLayout")
+        self.nameLabel = QtWidgets.QLabel(parent=EricServerConnectionDialog)
+        self.nameLabel.setObjectName("nameLabel")
+        self.gridLayout.addWidget(self.nameLabel, 0, 0, 1, 1)
+        self.nameEdit = QtWidgets.QLineEdit(parent=EricServerConnectionDialog)
+        self.nameEdit.setClearButtonEnabled(True)
+        self.nameEdit.setObjectName("nameEdit")
+        self.gridLayout.addWidget(self.nameEdit, 0, 1, 1, 2)
+        self.label = QtWidgets.QLabel(parent=EricServerConnectionDialog)
+        self.label.setObjectName("label")
+        self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
+        self.hostnameEdit = QtWidgets.QLineEdit(parent=EricServerConnectionDialog)
+        self.hostnameEdit.setClearButtonEnabled(True)
+        self.hostnameEdit.setObjectName("hostnameEdit")
+        self.gridLayout.addWidget(self.hostnameEdit, 1, 1, 1, 2)
+        self.label_2 = QtWidgets.QLabel(parent=EricServerConnectionDialog)
+        self.label_2.setObjectName("label_2")
+        self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
+        self.portSpinBox = QtWidgets.QSpinBox(parent=EricServerConnectionDialog)
+        self.portSpinBox.setWrapping(True)
+        self.portSpinBox.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
+        self.portSpinBox.setAccelerated(True)
+        self.portSpinBox.setProperty("showGroupSeparator", True)
+        self.portSpinBox.setMinimum(1024)
+        self.portSpinBox.setMaximum(65535)
+        self.portSpinBox.setProperty("value", 1024)
+        self.portSpinBox.setObjectName("portSpinBox")
+        self.gridLayout.addWidget(self.portSpinBox, 2, 1, 1, 1)
+        spacerItem = QtWidgets.QSpacerItem(240, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+        self.gridLayout.addItem(spacerItem, 2, 2, 1, 1)
+        self.label_3 = QtWidgets.QLabel(parent=EricServerConnectionDialog)
+        self.label_3.setObjectName("label_3")
+        self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
+        self.timeoutSpinBox = QtWidgets.QSpinBox(parent=EricServerConnectionDialog)
+        self.timeoutSpinBox.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
+        self.timeoutSpinBox.setMaximum(60)
+        self.timeoutSpinBox.setObjectName("timeoutSpinBox")
+        self.gridLayout.addWidget(self.timeoutSpinBox, 3, 1, 1, 1)
+        self.buttonBox = QtWidgets.QDialogButtonBox(parent=EricServerConnectionDialog)
+        self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
+        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
+        self.buttonBox.setObjectName("buttonBox")
+        self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 3)
+
+        self.retranslateUi(EricServerConnectionDialog)
+        self.buttonBox.accepted.connect(EricServerConnectionDialog.accept) # type: ignore
+        self.buttonBox.rejected.connect(EricServerConnectionDialog.reject) # type: ignore
+        QtCore.QMetaObject.connectSlotsByName(EricServerConnectionDialog)
+        EricServerConnectionDialog.setTabOrder(self.nameEdit, self.hostnameEdit)
+        EricServerConnectionDialog.setTabOrder(self.hostnameEdit, self.portSpinBox)
+        EricServerConnectionDialog.setTabOrder(self.portSpinBox, self.timeoutSpinBox)
+
+    def retranslateUi(self, EricServerConnectionDialog):
+        _translate = QtCore.QCoreApplication.translate
+        EricServerConnectionDialog.setWindowTitle(_translate("EricServerConnectionDialog", "eric-ide Server Connection"))
+        self.nameLabel.setText(_translate("EricServerConnectionDialog", "Name:"))
+        self.nameEdit.setToolTip(_translate("EricServerConnectionDialog", "Enter the name for the eric-ide server connection profile."))
+        self.label.setText(_translate("EricServerConnectionDialog", "Hostname:"))
+        self.hostnameEdit.setToolTip(_translate("EricServerConnectionDialog", "Enter the hostname or IP address of the eric-ide server to connect to."))
+        self.label_2.setText(_translate("EricServerConnectionDialog", "Port:"))
+        self.portSpinBox.setToolTip(_translate("EricServerConnectionDialog", "Enter the port number the eric-ide server listens on (default: 42024)."))
+        self.portSpinBox.setSpecialValueText(_translate("EricServerConnectionDialog", "default"))
+        self.label_3.setText(_translate("EricServerConnectionDialog", "Timeout:"))
+        self.timeoutSpinBox.setSpecialValueText(_translate("EricServerConnectionDialog", "default"))
+        self.timeoutSpinBox.setSuffix(_translate("EricServerConnectionDialog", " s"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/Ui_EricServerFileDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,115 @@
+# Form implementation generated from reading ui file 'src/eric7/RemoteServerInterface/EricServerFileDialog.ui'
+#
+# Created by: PyQt6 UI code generator 6.7.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_EricServerFileDialog(object):
+    def setupUi(self, EricServerFileDialog):
+        EricServerFileDialog.setObjectName("EricServerFileDialog")
+        EricServerFileDialog.resize(600, 450)
+        EricServerFileDialog.setSizeGripEnabled(True)
+        self.verticalLayout = QtWidgets.QVBoxLayout(EricServerFileDialog)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.horizontalLayout = QtWidgets.QHBoxLayout()
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.label = QtWidgets.QLabel(parent=EricServerFileDialog)
+        self.label.setObjectName("label")
+        self.horizontalLayout.addWidget(self.label)
+        self.treeCombo = QtWidgets.QComboBox(parent=EricServerFileDialog)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.treeCombo.sizePolicy().hasHeightForWidth())
+        self.treeCombo.setSizePolicy(sizePolicy)
+        self.treeCombo.setObjectName("treeCombo")
+        self.horizontalLayout.addWidget(self.treeCombo)
+        self.backButton = QtWidgets.QToolButton(parent=EricServerFileDialog)
+        self.backButton.setObjectName("backButton")
+        self.horizontalLayout.addWidget(self.backButton)
+        self.forwardButton = QtWidgets.QToolButton(parent=EricServerFileDialog)
+        self.forwardButton.setObjectName("forwardButton")
+        self.horizontalLayout.addWidget(self.forwardButton)
+        self.upButton = QtWidgets.QToolButton(parent=EricServerFileDialog)
+        self.upButton.setObjectName("upButton")
+        self.horizontalLayout.addWidget(self.upButton)
+        self.newDirButton = QtWidgets.QToolButton(parent=EricServerFileDialog)
+        self.newDirButton.setObjectName("newDirButton")
+        self.horizontalLayout.addWidget(self.newDirButton)
+        self.reloadButton = QtWidgets.QToolButton(parent=EricServerFileDialog)
+        self.reloadButton.setObjectName("reloadButton")
+        self.horizontalLayout.addWidget(self.reloadButton)
+        self.verticalLayout.addLayout(self.horizontalLayout)
+        self.listing = QtWidgets.QTreeWidget(parent=EricServerFileDialog)
+        self.listing.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
+        self.listing.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
+        self.listing.setAlternatingRowColors(True)
+        self.listing.setRootIsDecorated(False)
+        self.listing.setItemsExpandable(False)
+        self.listing.setObjectName("listing")
+        self.verticalLayout.addWidget(self.listing)
+        self.gridLayout = QtWidgets.QGridLayout()
+        self.gridLayout.setObjectName("gridLayout")
+        self.nameLabel = QtWidgets.QLabel(parent=EricServerFileDialog)
+        self.nameLabel.setObjectName("nameLabel")
+        self.gridLayout.addWidget(self.nameLabel, 0, 0, 1, 1)
+        self.nameEdit = QtWidgets.QLineEdit(parent=EricServerFileDialog)
+        self.nameEdit.setClearButtonEnabled(True)
+        self.nameEdit.setObjectName("nameEdit")
+        self.gridLayout.addWidget(self.nameEdit, 0, 1, 1, 1)
+        self.okButton = QtWidgets.QPushButton(parent=EricServerFileDialog)
+        self.okButton.setDefault(True)
+        self.okButton.setObjectName("okButton")
+        self.gridLayout.addWidget(self.okButton, 0, 2, 1, 1)
+        self.filterLabel = QtWidgets.QLabel(parent=EricServerFileDialog)
+        self.filterLabel.setObjectName("filterLabel")
+        self.gridLayout.addWidget(self.filterLabel, 1, 0, 1, 1)
+        self.filterCombo = QtWidgets.QComboBox(parent=EricServerFileDialog)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.filterCombo.sizePolicy().hasHeightForWidth())
+        self.filterCombo.setSizePolicy(sizePolicy)
+        self.filterCombo.setObjectName("filterCombo")
+        self.gridLayout.addWidget(self.filterCombo, 1, 1, 1, 1)
+        self.cancelButton = QtWidgets.QPushButton(parent=EricServerFileDialog)
+        self.cancelButton.setObjectName("cancelButton")
+        self.gridLayout.addWidget(self.cancelButton, 1, 2, 1, 1)
+        self.verticalLayout.addLayout(self.gridLayout)
+
+        self.retranslateUi(EricServerFileDialog)
+        QtCore.QMetaObject.connectSlotsByName(EricServerFileDialog)
+        EricServerFileDialog.setTabOrder(self.nameEdit, self.filterCombo)
+        EricServerFileDialog.setTabOrder(self.filterCombo, self.okButton)
+        EricServerFileDialog.setTabOrder(self.okButton, self.cancelButton)
+        EricServerFileDialog.setTabOrder(self.cancelButton, self.treeCombo)
+        EricServerFileDialog.setTabOrder(self.treeCombo, self.backButton)
+        EricServerFileDialog.setTabOrder(self.backButton, self.forwardButton)
+        EricServerFileDialog.setTabOrder(self.forwardButton, self.upButton)
+        EricServerFileDialog.setTabOrder(self.upButton, self.newDirButton)
+        EricServerFileDialog.setTabOrder(self.newDirButton, self.reloadButton)
+        EricServerFileDialog.setTabOrder(self.reloadButton, self.listing)
+
+    def retranslateUi(self, EricServerFileDialog):
+        _translate = QtCore.QCoreApplication.translate
+        EricServerFileDialog.setWindowTitle(_translate("EricServerFileDialog", "Open Files"))
+        self.label.setText(_translate("EricServerFileDialog", "Look in:"))
+        self.backButton.setToolTip(_translate("EricServerFileDialog", "Press to move back in history."))
+        self.forwardButton.setToolTip(_translate("EricServerFileDialog", "Press to move forward in history."))
+        self.upButton.setToolTip(_translate("EricServerFileDialog", "Press to move up one level."))
+        self.newDirButton.setToolTip(_translate("EricServerFileDialog", "Press to create a new directory."))
+        self.reloadButton.setToolTip(_translate("EricServerFileDialog", "Press to reload the directory listing."))
+        self.listing.setSortingEnabled(False)
+        self.listing.headerItem().setText(0, _translate("EricServerFileDialog", "Name"))
+        self.listing.headerItem().setText(1, _translate("EricServerFileDialog", "Size"))
+        self.listing.headerItem().setText(2, _translate("EricServerFileDialog", "Type"))
+        self.listing.headerItem().setText(3, _translate("EricServerFileDialog", "Date Modified"))
+        self.nameLabel.setText(_translate("EricServerFileDialog", "File Name:"))
+        self.okButton.setText(_translate("EricServerFileDialog", "Open"))
+        self.filterLabel.setText(_translate("EricServerFileDialog", "Files of Type:"))
+        self.cancelButton.setText(_translate("EricServerFileDialog", "Cancel"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/Ui_EricServerProfilesDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,64 @@
+# Form implementation generated from reading ui file 'src/eric7/RemoteServerInterface/EricServerProfilesDialog.ui'
+#
+# Created by: PyQt6 UI code generator 6.7.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_EricServerProfilesDialog(object):
+    def setupUi(self, EricServerProfilesDialog):
+        EricServerProfilesDialog.setObjectName("EricServerProfilesDialog")
+        EricServerProfilesDialog.resize(441, 281)
+        EricServerProfilesDialog.setSizeGripEnabled(True)
+        self.gridLayout = QtWidgets.QGridLayout(EricServerProfilesDialog)
+        self.gridLayout.setObjectName("gridLayout")
+        self.connectionsList = QtWidgets.QListWidget(parent=EricServerProfilesDialog)
+        self.connectionsList.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
+        self.connectionsList.setAlternatingRowColors(True)
+        self.connectionsList.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
+        self.connectionsList.setObjectName("connectionsList")
+        self.gridLayout.addWidget(self.connectionsList, 0, 0, 1, 1)
+        self.verticalLayout = QtWidgets.QVBoxLayout()
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.addButton = QtWidgets.QPushButton(parent=EricServerProfilesDialog)
+        self.addButton.setObjectName("addButton")
+        self.verticalLayout.addWidget(self.addButton)
+        self.editButton = QtWidgets.QPushButton(parent=EricServerProfilesDialog)
+        self.editButton.setObjectName("editButton")
+        self.verticalLayout.addWidget(self.editButton)
+        self.removeButton = QtWidgets.QPushButton(parent=EricServerProfilesDialog)
+        self.removeButton.setObjectName("removeButton")
+        self.verticalLayout.addWidget(self.removeButton)
+        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
+        self.verticalLayout.addItem(spacerItem)
+        self.resetButton = QtWidgets.QPushButton(parent=EricServerProfilesDialog)
+        self.resetButton.setObjectName("resetButton")
+        self.verticalLayout.addWidget(self.resetButton)
+        self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
+        self.buttonBox = QtWidgets.QDialogButtonBox(parent=EricServerProfilesDialog)
+        self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
+        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
+        self.buttonBox.setObjectName("buttonBox")
+        self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 2)
+
+        self.retranslateUi(EricServerProfilesDialog)
+        self.buttonBox.accepted.connect(EricServerProfilesDialog.accept) # type: ignore
+        self.buttonBox.rejected.connect(EricServerProfilesDialog.reject) # type: ignore
+        QtCore.QMetaObject.connectSlotsByName(EricServerProfilesDialog)
+
+    def retranslateUi(self, EricServerProfilesDialog):
+        _translate = QtCore.QCoreApplication.translate
+        EricServerProfilesDialog.setWindowTitle(_translate("EricServerProfilesDialog", "Manage Server Connections"))
+        self.connectionsList.setSortingEnabled(True)
+        self.addButton.setToolTip(_translate("EricServerProfilesDialog", "Press to open a dialog to add a new server connection."))
+        self.addButton.setText(_translate("EricServerProfilesDialog", "Add..."))
+        self.editButton.setToolTip(_translate("EricServerProfilesDialog", "Press to open a dialog to edit the selected server connection."))
+        self.editButton.setText(_translate("EricServerProfilesDialog", "Edit..."))
+        self.removeButton.setToolTip(_translate("EricServerProfilesDialog", "Press to remove the selected server connections."))
+        self.removeButton.setText(_translate("EricServerProfilesDialog", "Remove"))
+        self.resetButton.setToolTip(_translate("EricServerProfilesDialog", "Press to reset all changes performed."))
+        self.resetButton.setText(_translate("EricServerProfilesDialog", "Reset"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/RemoteServerInterface/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the components of the eric-ide remote server interface.
+"""
--- a/src/eric7/Sessions/SessionFile.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Sessions/SessionFile.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,6 +16,7 @@
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 
 class SessionFile(QObject):
@@ -46,6 +47,10 @@
         @rtype bool
         """
         # get references to objects we need
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         project = ericApp().getObject("Project")
         projectBrowser = ericApp().getObject("ProjectBrowser")
         multiProject = ericApp().getObject("MultiProject")
@@ -223,13 +228,18 @@
 
         try:
             jsonString = json.dumps(sessionDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Session")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Session")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Session"),
+                    title,
                     self.tr(
                         "<p>The session file <b>{0}</b> could not be"
                         " written.</p><p>Reason: {1}</p>"
@@ -248,14 +258,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Session")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Session")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             sessionDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Session"),
+                title,
                 self.tr(
                     "<p>The session file <b>{0}</b> could not be read.</p>"
                     "<p>Reason: {1}</p>"
--- a/src/eric7/SystemUtilities/FileSystemUtilities.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/SystemUtilities/FileSystemUtilities.py	Fri Jun 07 13:58:16 2024 +0200
@@ -359,14 +359,18 @@
     if f1 is None or f2 is None:
         return False
 
-    if followSymlinks:
-        if normcaseabspath(os.path.realpath(f1)) == normcaseabspath(
-            os.path.realpath(f2)
-        ):
-            return True
+    if isPlainFileName(f1) and isPlainFileName(f2):
+        if followSymlinks:
+            if normcaseabspath(os.path.realpath(f1)) == normcaseabspath(
+                os.path.realpath(f2)
+            ):
+                return True
+        else:
+            if normcaseabspath(f1) == normcaseabspath(f2):
+                return True
+
     else:
-        if normcaseabspath(f1) == normcaseabspath(f2):
-            return True
+        return f1 == f2
 
     return False
 
@@ -389,14 +393,20 @@
     if f1 is None or f2 is None:
         return False
 
-    if followSymlinks:
-        if normcaseabspath(os.path.dirname(os.path.realpath(f1))) == normcaseabspath(
-            os.path.dirname(os.path.realpath(f2))
-        ):
-            return True
+    if isPlainFileName(f1) and isPlainFileName(f2):
+        if followSymlinks:
+            if normcaseabspath(
+                os.path.dirname(os.path.realpath(f1))
+            ) == normcaseabspath(os.path.dirname(os.path.realpath(f2))):
+                return True
+        else:
+            if normcaseabspath(os.path.dirname(f1)) == normcaseabspath(
+                os.path.dirname(f2)
+            ):
+                return True
+
     else:
-        if normcaseabspath(os.path.dirname(f1)) == normcaseabspath(os.path.dirname(f2)):
-            return True
+        return os.path.dirname(f1) == os.path.dirname(f2)
 
     return False
 
@@ -492,30 +502,37 @@
     followsymlinks=True,
     checkStop=None,
     ignore=None,
+    recursive=True,
+    dirsonly=False,
 ):
     """
     Function returning a list of all files and directories.
 
     @param path root of the tree to check
     @type str
-    @param filesonly flag indicating that only files are wanted
-    @type bool
+    @param filesonly flag indicating that only files are wanted (defaults to False)
+    @type bool (optional)
     @param pattern a filename pattern or list of filename patterns to check
-        against
-    @type str or list of str
+        against (defaults to None)
+    @type str or list of str (optional)
     @param followsymlinks flag indicating whether symbolic links
-        should be followed
+        should be followed (defaults to True)
+    @type bool (optional)
+    @param checkStop function to be called to check for a stop (defaults to None)
+    @type function (optional)
+    @param ignore list of entries to be ignored (defaults to None)
+    @type list of str (optional)
+    @param recursive flag indicating a recursive search (defaults to True)
+    @type bool (optional)
+    @param dirsonly flag indicating to return only directories. When True it has
+        precedence over the 'filesonly' parameter ((defaults to False)
     @type bool
-    @param checkStop function to be called to check for a stop
-    @type function
-    @param ignore list of entries to be ignored
-    @type list of str
     @return list of all files and directories in the tree rooted
         at path. The names are expanded to start with path.
     @rtype list of str
     """
     patterns = pattern if isinstance(pattern, list) else [pattern]
-    files = [] if filesonly else [path]
+    files = [] if (filesonly and not dirsonly) else [path]
     ignoreList = [
         ".svn",
         ".hg",
@@ -547,18 +564,21 @@
                 continue
 
             if dirEntry.is_dir():
-                if dirEntry.path in ignoreList:
-                    continue
-                if dirEntry.is_symlink() and not followsymlinks:
+                if dirEntry.path in ignoreList or (
+                    dirEntry.is_symlink() and not followsymlinks
+                ):
                     continue
-                files += direntries(
-                    dirEntry.path,
-                    filesonly=filesonly,
-                    pattern=pattern,
-                    followsymlinks=followsymlinks,
-                    checkStop=checkStop,
-                    ignore=ignore,
-                )
+                if recursive:
+                    files += direntries(
+                        dirEntry.path,
+                        filesonly=filesonly,
+                        pattern=pattern,
+                        followsymlinks=followsymlinks,
+                        checkStop=checkStop,
+                        ignore=ignore,
+                    )
+                elif dirsonly:
+                    files.append(dirEntry.path)
             else:
                 files.append(dirEntry.path)
     return files
@@ -774,7 +794,11 @@
     @return device file name
     @rtype str
     """
-    return f"{_DeviceFileMarker}{fileName}"
+    if fileName.startswith(_DeviceFileMarker):
+        # it is already a device file name
+        return fileName
+    else:
+        return f"{_DeviceFileMarker}{fileName}"
 
 
 def isDeviceFileName(fileName):
@@ -798,7 +822,11 @@
     @return remote file name
     @rtype str
     """
-    return f"{_RemoteFileMarker}{fileName}"
+    if fileName.startswith(_RemoteFileMarker):
+        # it is already a remote file name
+        return fileName
+    else:
+        return f"{_RemoteFileMarker}{fileName}"
 
 
 def isRemoteFileName(fileName):
--- a/src/eric7/Tasks/TasksFile.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Tasks/TasksFile.py	Fri Jun 07 13:58:16 2024 +0200
@@ -16,6 +16,7 @@
 from eric7.EricGui.EricOverrideCursor import EricOverridenCursor
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 from .Task import TaskPriority, TaskType
 
@@ -46,6 +47,10 @@
         @return flag indicating a successful write
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         # prepare the tasks data dictionary
         # step 0: header
         tasksDict = {}
@@ -85,13 +90,18 @@
 
         try:
             jsonString = json.dumps(tasksDict, indent=2) + "\n"
-            with open(filename, "w") as f:
-                f.write(jsonString)
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Save Remote Tasks")
+                fsInterface.writeFile(filename, jsonString.encode("utf-8"))
+            else:
+                title = self.tr("Save Tasks")
+                with open(filename, "w") as f:
+                    f.write(jsonString)
         except (OSError, TypeError) as err:
             with EricOverridenCursor():
                 EricMessageBox.critical(
                     None,
-                    self.tr("Save Tasks"),
+                    title,
                     self.tr(
                         "<p>The tasks file <b>{0}</b> could not be"
                         " written.</p><p>Reason: {1}</p>"
@@ -110,14 +120,23 @@
         @return flag indicating a successful read
         @rtype bool
         """
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+
         try:
-            with open(filename, "r") as f:
-                jsonString = f.read()
+            if FileSystemUtilities.isRemoteFileName(filename):
+                title = self.tr("Read Remote Tasks")
+                jsonString = fsInterface.readFile(filename).decode("utf-8")
+            else:
+                title = self.tr("Read Tasks")
+                with open(filename, "r") as f:
+                    jsonString = f.read()
             tasksDict = json.loads(jsonString)
         except (OSError, json.JSONDecodeError) as err:
             EricMessageBox.critical(
                 None,
-                self.tr("Read Tasks"),
+                title,
                 self.tr(
                     "<p>The tasks file <b>{0}</b> could not be read.</p>"
                     "<p>Reason: {1}</p>"
--- a/src/eric7/UI/Browser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/UI/Browser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -35,6 +35,7 @@
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.Project.ProjectBrowserModel import ProjectBrowserSimpleDirectoryItem
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.SystemUtilities import FileSystemUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 from eric7.Utilities import MimeTypes
@@ -110,10 +111,12 @@
     pdfFile = pyqtSignal(str)
     testFile = pyqtSignal(str)
 
-    def __init__(self, parent=None):
+    def __init__(self, serverInterface, parent=None):
         """
         Constructor
 
+        @param serverInterface reference to the 'eric-ide' server interface object
+        @type EricServerInterface
         @param parent parent widget
         @type QWidget
         """
@@ -122,7 +125,10 @@
         self.setWindowTitle(QCoreApplication.translate("Browser", "File-Browser"))
         self.setWindowIcon(EricPixmapCache.getIcon("eric"))
 
-        self.__model = BrowserModel()
+        self.__ericServerInterface = serverInterface
+        self.__remotefsInterface = serverInterface.getServiceInterface("FileSystem")
+
+        self.__model = BrowserModel(fsInterface=self.__remotefsInterface)
         self.__sortModel = BrowserSortFilterProxyModel()
         self.__sortModel.setSourceModel(self.__model)
         self.setModel(self.__sortModel)
@@ -342,15 +348,19 @@
         # create the directory menu
         self.dirMenu = QMenu(self)
         self.dirMenu.addAction(
-            QCoreApplication.translate("Browser", "New toplevel directory..."),
-            self.__newToplevelDir,
+            QCoreApplication.translate("Browser", "New Top Level Directory..."),
+            self.__newTopLevelDir,
+        )
+        self.__dmRemoteTopLevelAct = self.dirMenu.addAction(
+            QCoreApplication.translate("Browser", "New Remote Top Level Directory..."),
+            self.__newRemoteTopLevelDir,
         )
         self.addAsTopLevelAct = self.dirMenu.addAction(
-            QCoreApplication.translate("Browser", "Add as toplevel directory"),
+            QCoreApplication.translate("Browser", "Add as top level directory"),
             self.__addAsToplevelDir,
         )
         self.removeFromToplevelAct = self.dirMenu.addAction(
-            QCoreApplication.translate("Browser", "Remove from toplevel"),
+            QCoreApplication.translate("Browser", "Remove from top level"),
             self.__removeToplevel,
         )
         self.dirMenu.addSeparator()
@@ -391,8 +401,12 @@
 
         self.attributeMenu = QMenu(self)
         self.attributeMenu.addAction(
-            QCoreApplication.translate("Browser", "New toplevel directory..."),
-            self.__newToplevelDir,
+            QCoreApplication.translate("Browser", "New Top Level Directory..."),
+            self.__newTopLevelDir,
+        )
+        self.__amRemoteTopLevelAct = self.attributeMenu.addAction(
+            QCoreApplication.translate("Browser", "New Remote Top Level Directory..."),
+            self.__newRemoteTopLevelDir,
         )
         self.attributeMenu.addSeparator()
         self.attributeMenu.addMenu(self.gotoMenu)
@@ -400,8 +414,12 @@
         # create the background menu
         self.backMenu = QMenu(self)
         self.backMenu.addAction(
-            QCoreApplication.translate("Browser", "New toplevel directory..."),
-            self.__newToplevelDir,
+            QCoreApplication.translate("Browser", "New Top Level Directory..."),
+            self.__newTopLevelDir,
+        )
+        self.__bmRemoteTopLevelAct = self.backMenu.addAction(
+            QCoreApplication.translate("Browser", "New Remote Top Level Directory..."),
+            self.__newRemoteTopLevelDir,
         )
         self.backMenu.addSeparator()
         self.backMenu.addAction(self.showHiddenFilesAct)
@@ -435,7 +453,7 @@
 
     def _contextMenuRequested(self, coord):
         """
-        Protected slot to show the context menu of the listview.
+        Protected slot to show the context menu of the list view.
 
         @param coord the position of the mouse pointer
         @type QPoint
@@ -479,6 +497,9 @@
                     self.openInPdfViewerAct.setVisible(False)
                     self.menu.popup(coord)
                 elif isinstance(itm, BrowserClassAttributeItem):
+                    self.__amRemoteTopLevelAct.setEnabled(
+                        self.__ericServerInterface.isServerConnected()
+                    )
                     self.attributeMenu.popup(coord)
                 elif isinstance(itm, BrowserDirectoryItem):
                     if not index.parent().isValid():
@@ -487,10 +508,19 @@
                     else:
                         self.removeFromToplevelAct.setEnabled(False)
                         self.addAsTopLevelAct.setEnabled(True)
+                    self.__dmRemoteTopLevelAct.setEnabled(
+                        self.__ericServerInterface.isServerConnected()
+                    )
                     self.dirMenu.popup(coord)
                 else:
+                    self.__bmRemoteTopLevelAct.setEnabled(
+                        self.__ericServerInterface.isServerConnected()
+                    )
                     self.backMenu.popup(coord)
             else:
+                self.__bmRemoteTopLevelAct.setEnabled(
+                    self.__ericServerInterface.isServerConnected()
+                )
                 self.backMenu.popup(self.mapToGlobal(coord))
 
     def _showGotoMenu(self):
@@ -759,6 +789,7 @@
         # remember the current state
         Preferences.setUI("BrowsersListHiddenFiles", checked)
 
+    @pyqtSlot()
     def handleTesting(self):
         """
         Public slot to handle the testing popup menu entry.
@@ -773,13 +804,14 @@
         if pyfn is not None:
             self.testFile.emit(pyfn)
 
-    def __newToplevelDir(self):
+    @pyqtSlot()
+    def __newTopLevelDir(self):
         """
-        Private slot to handle the New toplevel directory popup menu entry.
+        Private slot to handle the New Top Level Directory popup menu entry.
         """
         dname = EricFileDialog.getExistingDirectory(
             None,
-            QCoreApplication.translate("Browser", "New toplevel directory"),
+            QCoreApplication.translate("Browser", "New Top Level Directory"),
             "",
             EricFileDialog.ShowDirsOnly,
         )
@@ -787,22 +819,39 @@
             dname = os.path.abspath(FileSystemUtilities.toNativeSeparators(dname))
             self.__model.addTopLevelDir(dname)
 
+    @pyqtSlot()
+    def __newRemoteTopLevelDir(self):
+        """
+        Private slot to handle the New Remote Top Level Directory popup menu entry.
+        """
+        dname = EricServerFileDialog.getExistingDirectory(
+            None,
+            QCoreApplication.translate("Browser", "New Remote Top Level Directory"),
+            "",
+            dirsOnly=True,
+        )
+        if dname:
+            self.__model.addTopLevelDir(dname)
+
+    @pyqtSlot()
     def __removeToplevel(self):
         """
-        Private slot to handle the Remove from toplevel popup menu entry.
+        Private slot to handle the Remove from top level popup menu entry.
         """
         index = self.currentIndex()
         sindex = self.model().mapToSource(index)
         self.__model.removeToplevelDir(sindex)
 
+    @pyqtSlot()
     def __addAsToplevelDir(self):
         """
-        Private slot to handle the Add as toplevel directory popup menu entry.
+        Private slot to handle the Add as top level directory popup menu entry.
         """
         index = self.currentIndex()
         dname = self.model().item(index).dirName()
         self.__model.addTopLevelDir(dname)
 
+    @pyqtSlot()
     def __refreshDirectory(self):
         """
         Private slot to refresh a directory entry.
@@ -811,6 +860,7 @@
         refreshDir = self.model().item(index).dirName()
         self.__model.directoryChanged(refreshDir)
 
+    @pyqtSlot()
     def __findInDirectory(self):
         """
         Private slot to handle the Find in directory popup menu entry.
@@ -820,6 +870,7 @@
 
         ericApp().getObject("UserInterface").showFindFilesWidget(searchDir=searchDir)
 
+    @pyqtSlot()
     def __replaceInDirectory(self):
         """
         Private slot to handle the Find&Replace in directory popup menu entry.
@@ -829,6 +880,7 @@
 
         ericApp().getObject("UserInterface").showReplaceFilesWidget(searchDir=searchDir)
 
+    @pyqtSlot(str)
     def handleProgramChange(self, fn):
         """
         Public slot to handle the programChange signal.
@@ -838,6 +890,7 @@
         """
         self.__model.programChange(os.path.dirname(fn))
 
+    @pyqtSlot(str)
     def handleInterpreterChanged(self, interpreter):
         """
         Public slot to handle a change of the debug client's interpreter.
--- a/src/eric7/UI/BrowserModel.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/UI/BrowserModel.py	Fri Jun 07 13:58:16 2024 +0200
@@ -62,14 +62,17 @@
     Class implementing the browser model.
     """
 
-    def __init__(self, parent=None, nopopulate=False):
+    def __init__(self, parent=None, nopopulate=False, fsInterface=None):
         """
         Constructor
 
-        @param parent reference to parent object
-        @type QObject
-        @param nopopulate flag indicating to not populate the model
-        @type bool
+        @param parent reference to parent object (defaults to None)
+        @type QObject (optional)
+        @param nopopulate flag indicating to not populate the model (defaults to False)
+        @type bool (optional)
+        @param fsInterface reference to the 'eric-ide' server interface object
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
         super().__init__(parent)
 
@@ -78,6 +81,8 @@
         self.__sysPathInterpreter = ""
         self.__sysPathItem = None
 
+        self.__remotefsInterface = fsInterface
+
         if not nopopulate:
             self.watchedDirItems = {}
             self.watchedFileItems = {}
@@ -329,8 +334,8 @@
             dirName = itm.dirName()
             if (
                 dirName != ""
-                and not dirName.startswith("//")
-                and not dirName.startswith("\\\\")
+                and not FileSystemUtilities.isRemoteFileName(dirName)
+                and not dirName.startswith(("//", "\\\\"))
             ):
                 EricFileSystemWatcher.instance().addPath(dirName)
                 if dirName in self.watchedDirItems:
@@ -429,7 +434,9 @@
                 )
 
         for d in self.toplevelDirs:
-            itm = BrowserDirectoryItem(self.rootItem, d)
+            itm = BrowserDirectoryItem(
+                self.rootItem, d, fsInterface=self.__remotefsInterface
+            )
             self._addItem(itm, self.rootItem)
 
     def interpreterChanged(self, interpreter):
@@ -489,7 +496,9 @@
             self.endRemoveRows()
             self.progDir = None
 
-        itm = BrowserDirectoryItem(self.rootItem, dirname)
+        itm = BrowserDirectoryItem(
+            self.rootItem, dirname, fsInterface=self.__remotefsInterface
+        )
         self.addItem(itm)
         self.progDir = itm
 
@@ -501,7 +510,9 @@
         @type str
         """
         if dirname not in self.toplevelDirs:
-            itm = BrowserDirectoryItem(self.rootItem, dirname)
+            itm = BrowserDirectoryItem(
+                self.rootItem, dirname, fsInterface=self.__remotefsInterface
+            )
             self.addItem(itm)
             self.toplevelDirs.append(itm.dirName())
 
@@ -595,39 +606,85 @@
         """
         self._addWatchedItem(parentItem)
 
-        qdir = QDir(parentItem.dirName())
+        dirName = parentItem.dirName()
+        if FileSystemUtilities.isPlainFileName(dirName):
+            qdir = QDir(dirName)
 
-        dirFilter = (
-            QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot | QDir.Filter.Hidden
-        )
-        entryInfoList = qdir.entryInfoList(dirFilter)
-        if len(entryInfoList) > 0:
-            if repopulate:
-                self.beginInsertRows(
-                    self.createIndex(parentItem.row(), 0, parentItem),
-                    0,
-                    len(entryInfoList) - 1,
-                )
-            for f in entryInfoList:
-                if f.isDir():
-                    node = BrowserDirectoryItem(
-                        parentItem,
-                        FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
-                        False,
+            dirFilter = (
+                QDir.Filter.AllEntries | QDir.Filter.NoDotAndDotDot | QDir.Filter.Hidden
+            )
+            entryInfoList = qdir.entryInfoList(dirFilter)
+            if len(entryInfoList) > 0:
+                if repopulate:
+                    self.beginInsertRows(
+                        self.createIndex(parentItem.row(), 0, parentItem),
+                        0,
+                        len(entryInfoList) - 1,
                     )
-                else:
-                    fileFilters = Preferences.getUI("BrowsersFileFilters").split(";")
-                    if fileFilters:
-                        fn = f.fileName()
-                        if any(fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters):
-                            continue
-                    node = BrowserFileItem(
-                        parentItem,
-                        FileSystemUtilities.toNativeSeparators(f.absoluteFilePath()),
+                for f in entryInfoList:
+                    if f.isDir():
+                        node = BrowserDirectoryItem(
+                            parentItem,
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
+                            False,
+                        )
+                    else:
+                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(
+                            ";"
+                        )
+                        if fileFilters:
+                            fn = f.fileName()
+                            if any(
+                                fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters
+                            ):
+                                continue
+                        node = BrowserFileItem(
+                            parentItem,
+                            FileSystemUtilities.toNativeSeparators(
+                                f.absoluteFilePath()
+                            ),
+                        )
+                    self._addItem(node, parentItem)
+                if repopulate:
+                    self.endInsertRows()
+
+        elif FileSystemUtilities.isRemoteFileName(dirName):
+            entriesList = self.__remotefsInterface.listdir(dirName)[2]
+            if len(entriesList) > 0:
+                if repopulate:
+                    self.beginInsertRows(
+                        self.createIndex(parentItem.row(), 0, parentItem),
+                        0,
+                        len(entryInfoList) - 1,
                     )
-                self._addItem(node, parentItem)
-            if repopulate:
-                self.endInsertRows()
+                for entry in entriesList:
+                    if entry["is_dir"]:
+                        node = BrowserDirectoryItem(
+                            parentItem,
+                            entry["path"],
+                            False,
+                            fsInterface=self.__remotefsInterface,
+                        )
+                    else:
+                        fileFilters = Preferences.getUI("BrowsersFileFilters").split(
+                            ";"
+                        )
+                        if fileFilters:
+                            fn = entry["name"]
+                            if any(
+                                fnmatch.fnmatch(fn, ff.strip()) for ff in fileFilters
+                            ):
+                                continue
+                        node = BrowserFileItem(
+                            parentItem,
+                            entry["path"],
+                            fsInterface=self.__remotefsInterface,
+                        )
+                    self._addItem(node, parentItem)
+                if repopulate:
+                    self.endInsertRows()
 
     def populateSysPathItem(self, parentItem, repopulate=False):
         """
@@ -1161,7 +1218,7 @@
     Class implementing the data structure for browser simple directory items.
     """
 
-    def __init__(self, parent, text, path=""):
+    def __init__(self, parent, text, path="", fsInterface=None):
         """
         Constructor
 
@@ -1171,16 +1228,29 @@
         @type str
         @param path path of the directory
         @type str
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
         BrowserItem.__init__(self, parent, text)
 
+        self.__fsInterface = fsInterface
+
         self.type_ = BrowserItemType.SimpleDirectory
 
         self._dirName = path
-        if not os.path.isdir(self._dirName):
-            self._dirName = os.path.dirname(self._dirName)
+        if FileSystemUtilities.isRemoteFileName(self._dirName):
+            if not self.__fsInterface.isdir(self._dirName):
+                self._dirName = self.__fsInterface.dirname(self._dirName)
+        else:
+            if not os.path.isdir(self._dirName):
+                self._dirName = os.path.dirname(self._dirName)
 
-        if os.path.lexists(self._dirName) and os.path.islink(self._dirName):
+        if (
+            FileSystemUtilities.isPlainFileName(self._dirName)
+            and os.path.lexists(self._dirName)
+            and os.path.islink(self._dirName)
+        ):
             self.symlink = True
             self.icon = EricPixmapCache.getSymlinkIcon("dirClosed")
         else:
@@ -1195,8 +1265,12 @@
         @param full flag indicating full path name should be displayed (unused)
         @type bool
         """
-        self._dirName = os.path.abspath(dinfo)
-        self.itemData[0] = os.path.basename(self._dirName)
+        if FileSystemUtilities.isRemoteFileName(dinfo):
+            self._dirName = dinfo
+            self.itemData[0] = self.__fsInterface.basename(self._dirName)
+        else:
+            self._dirName = os.path.abspath(dinfo)
+            self.itemData[0] = os.path.basename(self._dirName)
 
     def dirName(self):
         """
@@ -1242,7 +1316,7 @@
     Class implementing the data structure for browser directory items.
     """
 
-    def __init__(self, parent, dinfo, full=True):
+    def __init__(self, parent, dinfo, full=True, fsInterface=None):
         """
         Constructor
 
@@ -1250,21 +1324,33 @@
         @type BrowserItem
         @param dinfo dinfo is the string for the directory
         @type str
-        @param full flag indicating full pathname should be displayed
-        @type bool
+        @param full flag indicating full pathname should be displayed (defaults to True)
+        @type bool (optional)
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        self._dirName = os.path.abspath(dinfo)
-        dn = self._dirName if full else os.path.basename(self._dirName)
+        self.__fsInterface = fsInterface
+
+        if FileSystemUtilities.isRemoteFileName(dinfo):
+            self._dirName = dinfo
+            dn = self._dirName if full else self.__fsInterface.basename(self._dirName)
+        else:
+            self._dirName = os.path.abspath(dinfo)
+            dn = self._dirName if full else os.path.basename(self._dirName)
         BrowserItem.__init__(self, parent, dn)
 
         self.type_ = BrowserItemType.Directory
         if (
-            not FileSystemUtilities.isDrive(self._dirName)
+            FileSystemUtilities.isPlainFileName(self._dirName)
+            and not FileSystemUtilities.isDrive(self._dirName)
             and os.path.lexists(self._dirName)
             and os.path.islink(self._dirName)
         ):
             self.symlink = True
             self.icon = EricPixmapCache.getSymlinkIcon("dirClosed")
+        elif FileSystemUtilities.isRemoteFileName(self._dirName):
+            self.icon = EricPixmapCache.getIcon("open-remote")
         else:
             self.icon = EricPixmapCache.getIcon("dirClosed")
         self._populated = False
@@ -1279,8 +1365,12 @@
         @param full flag indicating full pathname should be displayed
         @type bool
         """
-        self._dirName = os.path.abspath(dinfo)
-        dn = self._dirName if full else os.path.basename(self._dirName)
+        if FileSystemUtilities.isRemoteFileName(dinfo):
+            self._dirName = dinfo
+            dn = self._dirName if full else self.__fsInterface.basename(self._dirName)
+        else:
+            self._dirName = os.path.abspath(dinfo)
+            dn = self._dirName if full else os.path.basename(self._dirName)
         self.itemData[0] = dn
 
     def dirName(self):
@@ -1356,7 +1446,7 @@
     Class implementing the data structure for browser file items.
     """
 
-    def __init__(self, parent, finfo, full=True, sourceLanguage=""):
+    def __init__(self, parent, finfo, full=True, sourceLanguage="", fsInterface=None):
         """
         Constructor
 
@@ -1364,17 +1454,29 @@
         @type BrowserItem
         @param finfo the string for the file
         @type str
-        @param full flag indicating full pathname should be displayed
-        @type bool
-        @param sourceLanguage source code language of the project
-        @type str
+        @param full flag indicating full pathname should be displayed (defaults to True)
+        @type bool (optional)
+        @param sourceLanguage source code language of the project (defaults to "")
+        @type str (optional)
+        @param fsInterface reference to the 'eric-ide' server file system interface
+            (defaults to None)
+        @type EricServerFileSystemInterface (optional)
         """
-        BrowserItem.__init__(self, parent, os.path.basename(finfo))
+        self.__fsInterface = fsInterface
 
+        if FileSystemUtilities.isRemoteFileName(finfo):
+            dirname, basename = self.__fsInterface.split(finfo)
+            self.fileext = self.__fsInterface.splitext(finfo)[1].lower()
+            self._filename = finfo
+        else:
+            dirname, basename = os.path.split(finfo)
+            self.fileext = os.path.splitext(finfo)[1].lower()
+            self._filename = os.path.abspath(finfo)
+
+        BrowserItem.__init__(self, parent, basename)
+
+        self._dirName = dirname
         self.type_ = BrowserItemType.File
-        self.fileext = os.path.splitext(finfo)[1].lower()
-        self._filename = os.path.abspath(finfo)
-        self._dirName = os.path.dirname(finfo)
         self.sourceLanguage = sourceLanguage
 
         self._moduleName = ""
@@ -1384,17 +1486,17 @@
             pixName = "filePython"
             self._populated = False
             self._lazyPopulation = True
-            self._moduleName = os.path.basename(finfo)
+            self._moduleName = basename
         elif self.isCythonFile():
             pixName = "lexerCython"
             self._populated = False
             self._lazyPopulation = True
-            self._moduleName = os.path.basename(finfo)
+            self._moduleName = basename
         elif self.isRubyFile():
             pixName = "fileRuby"
             self._populated = False
             self._lazyPopulation = True
-            self._moduleName = os.path.basename(finfo)
+            self._moduleName = basename
         elif self.isDesignerFile():
             pixName = "fileDesigner"
         elif self.isLinguistFile():
@@ -1418,18 +1520,22 @@
             pixName = "fileJavascript"
             self._populated = False
             self._lazyPopulation = True
-            self._moduleName = os.path.basename(finfo)
+            self._moduleName = basename
         elif self.isEricGraphicsFile():
             pixName = "fileUML"
         elif self.isParsableFile():
             pixName = ClassBrowsers.getIcon(self._filename)
             self._populated = False
             self._lazyPopulation = True
-            self._moduleName = os.path.basename(finfo)
+            self._moduleName = basename
         else:
             pixName = "fileMisc"
 
-        if os.path.lexists(self._filename) and os.path.islink(self._filename):
+        if (
+            FileSystemUtilities.isPlainFileName(self._filename)
+            and os.path.lexists(self._filename)
+            and os.path.islink(self._filename)
+        ):
             self.symlink = True
             self.icon = EricPixmapCache.getSymlinkIcon(pixName)
         else:
@@ -1444,12 +1550,25 @@
         @param full flag indicating full path name should be displayed (unused)
         @type bool
         """
-        self._filename = os.path.abspath(finfo)
-        self.itemData[0] = os.path.basename(finfo)
-        self.fileext = os.path.splitext(finfo)[1].lower()
-        if self.isPython3File() or self.isRubyFile() or self.isParsableFile():
-            self._dirName = os.path.dirname(finfo)
-            self._moduleName = os.path.basename(finfo)
+        if FileSystemUtilities.isRemoteFileName(finfo):
+            dirname, basename = self.__fsInterface.split(finfo)
+            self.fileext = self.__fsInterface.splitext(finfo)[1].lower()
+            self._filename = finfo
+        else:
+            dirname, basename = os.path.split(finfo)
+            self.fileext = os.path.splitext(finfo)[1].lower()
+            self._filename = os.path.abspath(finfo)
+
+        self.itemData[0] = basename
+        if (
+            self.isPython3File()
+            or self.isCythonFile()
+            or self.isRubyFile()
+            or self.isJavaScriptFile()
+            or self.isParsableFile()
+        ):
+            self._dirName = dirname
+            self._moduleName = basename
 
     def fileName(self):
         """
@@ -1663,8 +1782,18 @@
             return order == Qt.SortOrder.DescendingOrder
 
         if issubclass(other.__class__, BrowserFileItem):
-            sinit = os.path.basename(self._filename).startswith("__init__.py")
-            oinit = os.path.basename(other.fileName()).startswith("__init__.py")
+            if FileSystemUtilities.isRemoteFileName(self._filename):
+                basename = self.__fsInterface.basename(self._filename)
+            else:
+                basename = os.path.basename(self._filename)
+            sinit = basename.startswith("__init__.py")
+
+            if FileSystemUtilities.isRemoteFileName(other.fileName()):
+                basename = self.__fsInterface.basename(other.fileName())
+            else:
+                basename = os.path.basename(other.fileName())
+            oinit = basename.startswith("__init__.py")
+
             if sinit and not oinit:
                 return order == Qt.SortOrder.AscendingOrder
             if not sinit and oinit:
--- a/src/eric7/UI/UserInterface.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/UI/UserInterface.py	Fri Jun 07 13:58:16 2024 +0200
@@ -87,6 +87,7 @@
 from eric7.Preferences import Shortcuts
 from eric7.Project.Project import Project
 from eric7.QScintilla.SpellChecker import SpellChecker
+from eric7.RemoteServerInterface.EricServerInterface import EricServerInterface
 from eric7.Sessions.SessionFile import SessionFile
 from eric7.SystemUtilities import (
     DesktopUtilities,
@@ -314,14 +315,22 @@
 
         splash.showMessage(self.tr("Initializing Basic Services..."))
 
+        # create the remote server interface
+        logging.debug("Creating 'eric-ide' Server Interface...")
+        self.__ericServerInterface = EricServerInterface(self)
+        # register it early because it is needed very soon
+        ericApp().registerObject("EricServer", self.__ericServerInterface)
+
         # Generate the conda interface
         logging.debug("Creating Conda Interface...")
         self.condaInterface = Conda(self)
+        # register it early because it is needed very soon
         ericApp().registerObject("Conda", self.condaInterface)
 
         # Generate the pip interface
         logging.debug("Creating Pip Interface...")
         self.pipInterface = Pip(self)
+        # register it early because it is needed very soon
         ericApp().registerObject("Pip", self.pipInterface)
 
         # Generate the virtual environment manager
@@ -332,7 +341,7 @@
 
         # Generate an empty project object and multi project object
         logging.debug("Creating Project Manager...")
-        self.project = Project(self)
+        self.project = Project(self, remoteServer=self.__ericServerInterface)
         ericApp().registerObject("Project", self.project)
 
         logging.debug("Creating Multi-Project Manager...")
@@ -599,6 +608,21 @@
                 self.viewmanager.closeDeviceEditors
             )
 
+        self.__ericServerInterface.connectionStateChanged.connect(
+            self.project.remoteConnectionChanged
+        )
+        self.__ericServerInterface.connectionStateChanged.connect(
+            self.viewmanager.remoteConnectionChanged
+        )
+        self.__ericServerInterface.connectionStateChanged.connect(
+            self.shell.remoteConnectionChanged
+        )
+
+        self.__ericServerInterface.aboutToDisconnect.connect(self.project.closeProject)
+        self.__ericServerInterface.aboutToDisconnect.connect(
+            self.viewmanager.closeRemoteEditors
+        )
+
         # create the toolbar manager object
         self.toolbarManager = EricToolBarManager(self, self)
         self.toolbarManager.setMainWindow(self)
@@ -804,7 +828,7 @@
         # Create the view manager depending on the configuration setting
         logging.debug("Creating Viewmanager...")
         self.viewmanager = ViewManager.factory(
-            self, self.__debugServer, self.pluginManager
+            self, self.__debugServer, self.__ericServerInterface, self.pluginManager
         )
 
         # Create previewer
@@ -861,7 +885,7 @@
             logging.debug("Creating File Browser...")
             from .Browser import Browser  # noqa: I101
 
-            self.browser = Browser()
+            self.browser = Browser(self.__ericServerInterface)
         else:
             logging.debug("File Browser disabled")
             self.browser = None
@@ -3604,6 +3628,9 @@
         # initialize multi project actions
         self.multiProject.initActions()
 
+        # initialize eric-ide server actions
+        self.__ericServerInterface.initActions()
+
     def __initQtDocActions(self):
         """
         Private slot to initialize the action to show the Qt documentation.
@@ -3813,6 +3840,12 @@
             mb.setNativeMenuBar(False)
 
         ##############################################################
+        ## Remote Server menu
+        ##############################################################
+
+        self.__menus["server"] = self.__ericServerInterface.initMenu()
+
+        ##############################################################
         ## File menu
         ##############################################################
 
@@ -3827,6 +3860,9 @@
         act = self.__menus["file"].actions()[0]
         sep = self.__menus["file"].insertSeparator(act)
         self.__menus["file"].insertAction(sep, self.newWindowAct)
+        self.__menus["file"].insertSeparator(sep)
+        self.__menus["file"].insertMenu(sep, self.__menus["server"])
+        self.__menus["file"].insertSeparator(sep)
         self.__menus["file"].aboutToShow.connect(self.__showFileMenu)
 
         ##############################################################
@@ -4125,6 +4161,7 @@
         helptb = QToolBar(self.tr("Help"), self)
         profilestb = QToolBar(self.tr("Profiles"), self)
         pluginstb = QToolBar(self.tr("Plugins"), self)
+        servertb = self.__ericServerInterface.initToolbar(self.toolbarManager)
 
         toolstb.setObjectName("ToolsToolbar")
         testingtb.setObjectName("UnittestToolbar")
@@ -4228,6 +4265,7 @@
 
         # add the various toolbars
         self.addToolBar(filetb)
+        self.addToolBar(servertb)
         self.addToolBar(edittb)
         self.addToolBar(searchtb)
         self.addToolBar(viewtb)
@@ -4278,6 +4316,7 @@
         ]
         self.__toolbars["spelling"] = [spellingtb.windowTitle(), spellingtb, ""]
         self.__toolbars["vcs"] = [vcstb.windowTitle(), vcstb, "vcs"]
+        self.__toolbars["server"] = [servertb.windowTitle(), servertb, ""]
 
     def __initDebugToolbarsLayout(self):
         """
@@ -8656,3 +8695,18 @@
         elif self.__layoutType == "Sidebars":
             self.__activateLeftRightSidebarWidget(self.__virtualenvManagerWidget)
         self.__virtualenvManagerWidget.setFocus(Qt.FocusReason.ActiveWindowFocusReason)
+
+    ############################################################
+    ## Interface to the eric-ide server interface
+    ############################################################
+
+    def isEricServerConnected(self):
+        """
+        Public method to check, if a connection to an eric-ide server has been
+        established.
+
+        @return flag indicating the interface connection state
+        @rtype bool
+        """
+        # simply delegated to the eric-ide server interface object
+        return self.__ericServerInterface.isServerConnected()
--- a/src/eric7/Utilities/ClassBrowsers/__init__.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Utilities/ClassBrowsers/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -108,7 +108,7 @@
     return None
 
 
-def readmodule(module, path=None, isPyFile=False):
+def readmodule(module, searchPath=None, isPyFile=False):
     """
     Function to read a source file and return a dictionary of classes, functions,
     modules, etc. .
@@ -118,7 +118,7 @@
 
     @param module name of the source file
     @type str
-    @param path list of paths the file should be searched in
+    @param searchPath list of paths the file should be searched in
     @type list of str
     @param isPyFile flag indicating a Python file
     @type bool
@@ -126,13 +126,13 @@
     @rtype dict
     """
     ext = os.path.splitext(module)[1].lower()
-    path = [] if path is None else path[:]
+    searchPath = [] if searchPath is None else searchPath[:]
 
     if not isPyFile:
         for classBrowserName in ClassBrowserRegistry:
             if ext in ClassBrowserRegistry[classBrowserName]["Extensions"]:
                 return ClassBrowserRegistry[classBrowserName]["ReadModule"](
-                    module, path
+                    module, searchPath
                 )
 
     if ext in __extensions["Ruby"]:
@@ -145,7 +145,7 @@
 
     classBrowserModule = getClassBrowserModule(moduleType)
     dictionary = (
-        classBrowserModule.readmodule_ex(module, path, isTypeFile=isPyFile)
+        classBrowserModule.readmodule_ex(module, searchPath, isTypeFile=isPyFile)
         if classBrowserModule
         else {}
     )
@@ -244,6 +244,34 @@
     raise ImportError
 
 
+def determineSourceType(name, isPyFile=False):
+    """
+    Function to determine the type of a source file given its name.
+
+    @param name file name or module name
+    @type str
+    @param isPyFile flag indicating a Python file (defaults to False)
+    @type bool (optional)
+    @return source file type
+    @rtype int
+    """
+    ext = os.path.splitext(name)[1].lower()
+
+    if ext in __extensions["Ruby"]:
+        sourceType = RB_SOURCE
+    elif ext == ".ptl":
+        sourceType = PTL_SOURCE
+    elif (
+        name.lower().endswith(tuple(Preferences.getPython("Python3Extensions")))
+        or isPyFile
+    ):
+        sourceType = PY_SOURCE
+    else:
+        sourceType = UNKNOWN_SOURCE
+
+    return sourceType
+
+
 def getIcon(filename):
     """
     Function to get an icon name for the given file (only for class browsers provided
--- a/src/eric7/Utilities/ClassBrowsers/pyclbr.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Utilities/ClassBrowsers/pyclbr.py	Fri Jun 07 13:58:16 2024 +0200
@@ -20,6 +20,8 @@
 from PyQt6.QtCore import QRegularExpression
 
 from eric7 import Utilities
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 from eric7.Utilities import ClassBrowsers
 
 from . import ClbrBaseClasses
@@ -407,7 +409,7 @@
                 self.importedNames[name].append(lineno)
 
 
-def readmodule_ex(module, path=None, isTypeFile=False):
+def readmodule_ex(module, searchPath=None, isTypeFile=False):
     """
     Read a module file and return a dictionary of classes.
 
@@ -417,29 +419,38 @@
 
     @param module name of the module file
     @type str
-    @param path path the module should be searched in
+    @param searchPath path the module should be searched in
     @type list of str
     @param isTypeFile flag indicating a file of this type
     @type bool
     @return the resulting dictionary
     @rtype dict
     """
-    # search the path for the module
-    path = [] if path is None else path[:]
-    f = None
-    if f is None:
-        fullpath = path[:] + sys.path[:]
-        f, file, (_suff, _mode, type) = ClassBrowsers.find_module(
+    fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+
+    if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]):
+        sourceType = ClassBrowsers.determineSourceType(module, isTypeFile)
+        file = fsInterface.join(searchPath[0], module)
+    else:
+        # search the path for the module
+        searchPath = [] if searchPath is None else searchPath[:]
+        fullpath = searchPath[:] + sys.path[:]
+        f, file, (_suff, mode, sourceType) = ClassBrowsers.find_module(
             module, fullpath, isTypeFile
         )
-    if f:
-        f.close()
-    if type not in SUPPORTED_TYPES:
+        if f:
+            f.close()
+
+    if sourceType not in SUPPORTED_TYPES:
         # not Python source, can't do anything with this module
         return {}
 
     try:
-        src = Utilities.readEncodedFile(file)[0]
+        src = (
+            fsInterface.readEncodedFile(file)[0]
+            if FileSystemUtilities.isRemoteFileName(file)
+            else Utilities.readEncodedFile(file)[0]
+        )
     except (OSError, UnicodeError):
         # can't do anything with this module
         return {}
--- a/src/eric7/Utilities/ClassBrowsers/rbclbr.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Utilities/ClassBrowsers/rbclbr.py	Fri Jun 07 13:58:16 2024 +0200
@@ -17,6 +17,8 @@
 from PyQt6.QtCore import QRegularExpression
 
 from eric7 import Utilities
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 from eric7.Utilities import ClassBrowsers
 
 from . import ClbrBaseClasses
@@ -300,31 +302,43 @@
         self.setPrivate()
 
 
-def readmodule_ex(module, path=None, isTypeFile=False):  # noqa: U100
+def readmodule_ex(module, searchPath=None, isTypeFile=False):  # noqa: U100
     """
     Read a Ruby file and return a dictionary of classes, functions and modules.
 
     @param module name of the Ruby file
     @type str
-    @param path path the file should be searched in
+    @param searchPath path the file should be searched in
     @type list of str
     @param isTypeFile flag indicating a file of this type (unused)
     @type bool
     @return the resulting dictionary
     @rtype dict
     """
-    # search the path for the file
-    f = None
-    fullpath = [] if path is None else path[:]
-    f, file, (_suff, _mode, type) = ClassBrowsers.find_module(module, fullpath)
-    if f:
-        f.close()
-    if type not in SUPPORTED_TYPES:
+    fsInterface = ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+
+    if searchPath and FileSystemUtilities.isRemoteFileName(searchPath[0]):
+        sourceType = ClassBrowsers.determineSourceType(module)
+        file = fsInterface.join(searchPath[0], module)
+    else:
+        # search the path for the module
+        fullpath = [] if searchPath is None else searchPath[:]
+        f, file, (_suff, _mode, sourceType) = ClassBrowsers.find_module(
+            module, fullpath
+        )
+        if f:
+            f.close()
+
+    if sourceType not in SUPPORTED_TYPES:
         # not Ruby source, can't do anything with this module
         return {}
 
     try:
-        src = Utilities.readEncodedFile(file)[0]
+        src = (
+            fsInterface.readEncodedFile(file)[0]
+            if FileSystemUtilities.isRemoteFileName(file)
+            else Utilities.readEncodedFile(file)[0]
+        )
     except (OSError, UnicodeError):
         # can't do anything with this module
         return {}
--- a/src/eric7/Utilities/ModuleParser.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Utilities/ModuleParser.py	Fri Jun 07 13:58:16 2024 +0200
@@ -26,6 +26,8 @@
 from PyQt6.QtCore import QRegularExpression
 
 from eric7 import Utilities
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.SystemUtilities import FileSystemUtilities
 
 __all__ = [
     "Attribute",
@@ -1677,42 +1679,50 @@
     @return reference to a Module object containing the parsed
         module information
     @rtype Module
+    @exception ImportError raised to indicate an unsupported source code type
     """
     global _modules
 
     _extensions = (
         [".py", ".pyw", ".pyi", ".ptl", ".rb"] if extensions is None else extensions[:]
     )
-    with contextlib.suppress(ValueError):
-        _extensions.remove(".py")
+    modname = module
+    isRemoteFileName = FileSystemUtilities.isRemoteFileName(module)
 
-    modname = module
+    if isRemoteFileName:
+        fsInterface = (
+            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
+        )
+        module, extension = fsInterface.splitext(fsInterface.basename(module))
+    else:
+        with contextlib.suppress(ValueError):
+            _extensions.remove(".py")
 
-    if os.path.exists(module):
-        path = [os.path.dirname(module)]
-        if module.lower().endswith(".py"):
-            module = module[:-3]
-        if (
-            os.path.exists(os.path.join(path[0], "__init__.py"))
-            or os.path.exists(os.path.join(path[0], "__init__.pyi"))
-            or os.path.exists(os.path.join(path[0], "__init__.rb"))
-            or inpackage
-        ):
-            if basename:
-                module = module.replace(basename, "")
-            if os.path.isabs(module):
-                modname = os.path.splitdrive(module)[1][len(os.sep) :]
+        if os.path.exists(module):
+            path = [os.path.dirname(module)]
+            if module.lower().endswith(".py"):
+                module = module[:-3]
+            if (
+                os.path.exists(os.path.join(path[0], "__init__.py"))
+                or os.path.exists(os.path.join(path[0], "__init__.pyi"))
+                or os.path.exists(os.path.join(path[0], "__init__.rb"))
+                or inpackage
+            ):
+                if basename:
+                    module = module.replace(basename, "")
+                if os.path.isabs(module):
+                    modname = os.path.splitdrive(module)[1][len(os.sep) :]
+                else:
+                    modname = module
+                modname = modname.replace(os.sep, ".")
+                inpackage = True
             else:
-                modname = module
-            modname = modname.replace(os.sep, ".")
-            inpackage = True
-        else:
-            modname = os.path.basename(module)
-        for ext in _extensions:
-            if modname.lower().endswith(ext):
-                modname = modname[: -len(ext)]
-                break
-        module = os.path.basename(module)
+                modname = os.path.basename(module)
+            for ext in _extensions:
+                if modname.lower().endswith(ext):
+                    modname = modname[: -len(ext)]
+                    break
+            module = os.path.basename(module)
 
     if caching and modname in _modules:
         # we've seen this module before...
@@ -1725,19 +1735,44 @@
             _modules[modname] = mod
         return mod
 
-    # search the path for the module
-    path = [] if path is None else path[:]
-    f = None
-    if inpackage:
-        try:
-            f, file, (_suff, _mode, moduleType) = find_module(module, path, _extensions)
-        except ImportError:
-            f = None
-    if f is None:
-        fullpath = path[:] + sys.path[:]
-        f, file, (_suff, _mode, moduleType) = find_module(module, fullpath, _extensions)
-    if f:
-        f.close()
+    if isRemoteFileName:
+        if not fsInterface.exists(modname):
+            raise ImportError
+
+        if extension == ".ptl":
+            moduleType = PTL_SOURCE
+        elif extension == ".rb":
+            moduleType = RB_SOURCE
+        elif extension in _extensions:  # noqa: Y106
+            moduleType = PY_SOURCE
+        else:
+            raise ImportError
+
+        file = modname
+
+        modname = FileSystemUtilities.plainFileName(modname)
+        if modname.startswith(("/", "\\")):
+            modname = modname[1:]
+        modname = os.path.splitext(modname)[0].replace("/", ".").replace("\\", ".")
+    else:
+        # search the path for the module
+        path = [] if path is None else path[:]
+        f = None
+        if inpackage:
+            try:
+                f, file, (_suff, _mode, moduleType) = find_module(
+                    module, path, _extensions
+                )
+            except ImportError:
+                f = None
+        if f is None:
+            fullpath = path[:] + sys.path[:]
+            f, file, (_suff, _mode, moduleType) = find_module(
+                module, fullpath, _extensions
+            )
+        if f:
+            f.close()
+
     if moduleType not in SUPPORTED_TYPES:
         # not supported source, can't do anything with this module
         _modules[modname] = Module(modname, None, None)
@@ -1745,7 +1780,14 @@
 
     mod = Module(modname, file, moduleType)
     with contextlib.suppress(UnicodeError, OSError):
-        src = Utilities.readEncodedFile(file)[0]
+        src = (
+            ericApp()
+            .getObject("EricServer")
+            .getServiceInterface("FileSystem")
+            .readEncodedFile(file)[0]
+            if isRemoteFileName
+            else Utilities.readEncodedFile(file)[0]
+        )
         mod.scan(src)
     if caching:
         _modules[modname] = mod
--- a/src/eric7/Utilities/__init__.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/Utilities/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -36,7 +36,7 @@
 from eric7 import Preferences
 from eric7.__version__ import Version
 from eric7.EricWidgets.EricApplication import ericApp
-from eric7.SystemUtilities import DesktopUtilities, OSUtilities
+from eric7.SystemUtilities import DesktopUtilities, FileSystemUtilities, OSUtilities
 from eric7.UI.Info import Program
 
 
@@ -339,6 +339,32 @@
     return str(text, "utf-8", "ignore"), "utf-8-ignore"
 
 
+def decodeWithEncoding(text, encoding):
+    """
+    Function to decode some byte text into a string.
+
+    @param text byte text to decode
+    @type bytes
+    @param encoding encoding to be used to read the file
+    @type str
+    @return tuple of decoded text and encoding
+    @rtype tuple of (str, str)
+    """
+    if encoding:
+        with contextlib.suppress(UnicodeError, LookupError):
+            return str(text, encoding), "{0}-selected".format(encoding)
+
+        # Try default encoding
+        with contextlib.suppress(UnicodeError, LookupError):
+            codec = Preferences.getEditor("DefaultEncoding")
+            return str(text, codec), "{0}-default".format(codec)
+
+        # Assume UTF-8 loosing information
+        return str(text, "utf-8", "ignore"), "utf-8-ignore"
+    else:
+        return decode(text)
+
+
 def readEncodedFileWithEncoding(filename, encoding):
     """
     Function to read a file and decode its contents into proper text.
@@ -352,19 +378,7 @@
     """
     with open(filename, "rb") as f:
         text = f.read()
-    if encoding:
-        with contextlib.suppress(UnicodeError, LookupError):
-            return str(text, encoding), "{0}-selected".format(encoding)
-
-        # Try default encoding
-        with contextlib.suppress(UnicodeError, LookupError):
-            codec = Preferences.getEditor("DefaultEncoding")
-            return str(text, codec), "{0}-default".format(codec)
-
-        # Assume UTF-8 loosing information
-        return str(text, "utf-8", "ignore"), "utf-8-ignore"
-    else:
-        return decode(text)
+    return decodeWithEncoding(text, encoding)
 
 
 def writeEncodedFile(filename, text, origEncoding, forcedEncoding=""):
@@ -890,6 +904,16 @@
     basename = os.path.splitext(fn)[0]
     filename = "{0}.coverage".format(basename)
     if mustExist:
+        if FileSystemUtilities.isRemoteFileName(fn):
+            ericServer = ericApp().getObject("EricServer")
+            if ericServer.isServerConnected() and ericServer.getServiceInterface(
+                "FileSystem"
+            ).exists(filename):
+                return filename
+            else:
+                return ""
+
+        # It is a local file.
         if os.path.isfile(filename):
             return filename
         else:
@@ -930,12 +954,22 @@
     basename = os.path.splitext(fn)[0]
     filename = "{0}.profile".format(basename)
     if mustExist:
+        if FileSystemUtilities.isRemoteFileName(fn):
+            ericServer = ericApp().getObject("EricServer")
+            if ericServer.isServerConnected() and ericServer.getServiceInterface(
+                "FileSystem"
+            ).exists(filename):
+                return filename
+            else:
+                return ""
+
+        # It is a local file.
         if os.path.isfile(filename):
             return filename
         else:
             return ""
-    else:
-        return filename
+
+    return filename
 
 
 def parseOptionString(s):
--- a/src/eric7/ViewManager/ViewManager.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/ViewManager/ViewManager.py	Fri Jun 07 13:58:16 2024 +0200
@@ -39,6 +39,7 @@
 from eric7.QScintilla.SpellChecker import SpellChecker
 from eric7.QScintilla.SpellingDictionaryEditDialog import SpellingDictionaryEditDialog
 from eric7.QScintilla.ZoomDialog import ZoomDialog
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities
 
 
@@ -151,7 +152,7 @@
 
         self.__watchedFilePaths = []
 
-    def setReferences(self, ui, dbs):
+    def setReferences(self, ui, dbs, remoteServerInterface):
         """
         Public method to set some references needed later on.
 
@@ -159,11 +160,16 @@
         @type UserInterface
         @param dbs reference to the debug server object
         @type DebugServer
+        @param remoteServerInterface reference to the 'eric-ide' server interface
+        @type EricServerInterface
         """
         from eric7.QScintilla.SearchReplaceWidget import SearchReplaceSlidingWidget
 
         self.ui = ui
         self.dbs = dbs
+        self.__remotefsInterface = remoteServerInterface.getServiceInterface(
+            "FileSystem"
+        )
 
         self.__searchReplaceWidget = SearchReplaceSlidingWidget(self, ui)
 
@@ -653,6 +659,30 @@
         self.openAct.triggered.connect(self.__openFiles)
         self.fileActions.append(self.openAct)
 
+        self.openRemoteAct = EricAction(
+            QCoreApplication.translate("ViewManager", "Open (Remote)"),
+            EricPixmapCache.getIcon("open-remote"),
+            QCoreApplication.translate("ViewManager", "Open (Remote)..."),
+            0,
+            0,
+            self,
+            "vm_file_open_remote",
+        )
+        self.openRemoteAct.setStatusTip(
+            QCoreApplication.translate("ViewManager", "Open a remote file")
+        )
+        self.openRemoteAct.setWhatsThis(
+            QCoreApplication.translate(
+                "ViewManager",
+                """<b>Open a remote file</b>"""
+                """<p>You will be asked for the name of a remote file to be opened"""
+                """ in an editor window.</p>""",
+            )
+        )
+        self.openRemoteAct.triggered.connect(self.__openRemoteFiles)
+        self.openRemoteAct.setEnabled(False)
+        self.fileActions.append(self.openRemoteAct)
+
         self.reloadAct = EricAction(
             QCoreApplication.translate("ViewManager", "Reload"),
             EricPixmapCache.getIcon("reload"),
@@ -771,13 +801,40 @@
             QCoreApplication.translate(
                 "ViewManager",
                 """<b>Save File as</b>"""
-                """<p>Save the contents of current editor window to a new file."""
+                """<p>Save the contents of the current editor window to a new file."""
                 """ The file can be entered in a file selection dialog.</p>""",
             )
         )
         self.saveAsAct.triggered.connect(self.saveAsCurrentEditor)
         self.fileActions.append(self.saveAsAct)
 
+        self.saveAsRemoteAct = EricAction(
+            QCoreApplication.translate("ViewManager", "Save as (Remote)"),
+            EricPixmapCache.getIcon("fileSaveAsRemote"),
+            QCoreApplication.translate("ViewManager", "Save as (Remote)..."),
+            0,
+            0,
+            self.saveActGrp,
+            "vm_file_save_as_remote",
+        )
+        self.saveAsRemoteAct.setStatusTip(
+            QCoreApplication.translate(
+                "ViewManager",
+                "Save the current file to a new one on an eric-ide server",
+            )
+        )
+        self.saveAsRemoteAct.setWhatsThis(
+            QCoreApplication.translate(
+                "ViewManager",
+                """<b>Save File as (Remote)</b>"""
+                """<p>Save the contents of the current editor window to a new file"""
+                """ on the connected eric-ide server. The file can be entered in a"""
+                """ file selection dialog.</p>""",
+            )
+        )
+        self.saveAsRemoteAct.triggered.connect(self.saveAsRemoteCurrentEditor)
+        self.fileActions.append(self.saveAsRemoteAct)
+
         self.saveCopyAct = EricAction(
             QCoreApplication.translate("ViewManager", "Save Copy"),
             EricPixmapCache.getIcon("fileSaveCopy"),
@@ -922,6 +979,7 @@
 
         menu.addAction(self.newAct)
         menu.addAction(self.openAct)
+        menu.addAction(self.openRemoteAct)
         menu.addAction(self.reloadAct)
         self.menuRecentAct = menu.addMenu(self.recentMenu)
         menu.addMenu(self.bookmarkedMenu)
@@ -933,6 +991,7 @@
         menu.addSeparator()
         menu.addAction(self.saveAct)
         menu.addAction(self.saveAsAct)
+        menu.addAction(self.saveAsRemoteAct)
         menu.addAction(self.saveCopyAct)
         menu.addAction(self.saveAllAct)
         self.exportersMenuAct = menu.addMenu(self.exportersMenu)
@@ -965,11 +1024,13 @@
 
         tb.addAction(self.newAct)
         tb.addAction(self.openAct)
+        tb.addAction(self.openRemoteAct)
         tb.addAction(self.reloadAct)
         tb.addAction(self.closeAct)
         tb.addSeparator()
         tb.addAction(self.saveAct)
         tb.addAction(self.saveAsAct)
+        tb.addAction(self.saveAsRemoteAct)
         tb.addAction(self.saveCopyAct)
         tb.addAction(self.saveAllAct)
 
@@ -5417,20 +5478,17 @@
     ## Methods and slots that deal with file and window handling
     ##################################################################
 
+    @pyqtSlot()
     def __openFiles(self):
         """
         Private slot to open some files.
         """
-        # set the cwd of the dialog based on the following search criteria:
-        #     1: Directory of currently active editor
-        #     2: Directory of currently active project
-        #     3: CWD
         from eric7.QScintilla import Lexers
 
         fileFilter = self._getOpenFileFilter()
         progs = EricFileDialog.getOpenFileNamesAndFilter(
             self.ui,
-            QCoreApplication.translate("ViewManager", "Open files"),
+            QCoreApplication.translate("ViewManager", "Open Files"),
             self._getOpenStartDir(),
             Lexers.getOpenFileFiltersList(True, True),
             fileFilter,
@@ -5438,6 +5496,33 @@
         for prog in progs:
             self.openFiles(prog)
 
+    @pyqtSlot()
+    def __openRemoteFiles(self):
+        """
+        Private slot to open some files.
+        """
+        from eric7.QScintilla import Lexers
+
+        if self.ui.isEricServerConnected():
+            fileFilter = self._getOpenFileFilter()
+            progs = EricServerFileDialog.getOpenFileNames(
+                self.ui,
+                QCoreApplication.translate("ViewManager", "Open Remote Files"),
+                self._getOpenStartDir(forRemote=True),
+                Lexers.getOpenFileFiltersList(True, True),
+                fileFilter,
+            )
+            for prog in progs:
+                self.openFiles(prog)
+        else:
+            EricMessageBox.critical(
+                self.ui,
+                self.tr("Open Remote Files"),
+                self.tr(
+                    "You must be connected to a remote eric-ide server. Aborting..."
+                ),
+            )
+
     def openFiles(self, prog):
         """
         Public slot to open some files.
@@ -5445,7 +5530,8 @@
         @param prog name of file to be opened
         @type str
         """
-        prog = os.path.abspath(prog)
+        if FileSystemUtilities.isPlainFileName(prog):
+            prog = os.path.abspath(prog)
         # Open up the new files.
         self.openSourceFile(prog)
 
@@ -5659,6 +5745,26 @@
             if FileSystemUtilities.isDeviceFileName(editor.getFileName()):
                 self.closeEditor(editor, ignoreDirty=True)
 
+    @pyqtSlot()
+    def closeRemoteEditors(self):
+        """
+        Public slot to close all editors related to a connected eric-ide server.
+        """
+        for editor in self.editors[:]:
+            if FileSystemUtilities.isRemoteFileName(editor.getFileName()):
+                self.closeEditor(editor, ignoreDirty=True)
+
+    @pyqtSlot(bool)
+    def remoteConnectionChanged(self, connected):
+        """
+        Public slot handling a change of the connection state to an eric-ide server.
+
+        @param connected flag indicating the connection state
+        @type bool
+        """
+        self.openRemoteAct.setEnabled(connected)
+        self.saveAsRemoteAct.setEnabled(self.saveActGrp.isEnabled() and connected)
+
     def exit(self):
         """
         Public method to handle the debugged program terminating.
@@ -6065,9 +6171,15 @@
         filenames = []
         for editor in self.editors:
             fn = editor.getFileName()
-            if fn is not None and fn not in filenames and os.path.exists(fn):
+            if fn is not None and fn not in filenames:
                 # only return names of existing files
-                filenames.append(fn)
+                exists = (
+                    True
+                    if FileSystemUtilities.isRemoteFileName(fn)
+                    else os.path.exists(fn)
+                )
+                if exists:
+                    filenames.append(fn)
 
         return filenames
 
@@ -6280,6 +6392,29 @@
         aw = self.activeWindow()
         self.saveAsEditorEd(aw)
 
+    @pyqtSlot(Editor)
+    def saveAsRemoteEditorEd(self, ed):
+        """
+        Public slot to save the contents of an editor to a new file on a
+        connected eric-ide server.
+
+        @param ed editor to be saved
+        @type Editor
+        """
+        if ed:
+            ok = ed.saveFileAs(remote=True)
+            if ok:
+                self.setEditorName(ed, ed.getFileName())
+
+    @pyqtSlot()
+    def saveAsRemoteCurrentEditor(self):
+        """
+        Public slot to save the contents of the current editor to a new file on a
+        connected eric-ide server.
+        """
+        aw = self.activeWindow()
+        self.saveAsRemoteEditorEd(aw)
+
     def saveCopyEditorEd(self, ed):
         """
         Public slot to save the contents of an editor to a new copy of
@@ -7668,6 +7803,7 @@
         """
         self.closeActGrp.setEnabled(True)
         self.saveActGrp.setEnabled(True)
+        self.saveAsRemoteAct.setEnabled(self.ui.isEricServerConnected())
         self.exportersMenuAct.setEnabled(True)
         self.printAct.setEnabled(True)
         if self.printPreviewAct:
@@ -7701,10 +7837,7 @@
         """
         if editor is not None:
             self.reloadAct.setEnabled(bool(editor.getFileName()))
-            self.saveAct.setEnabled(
-                editor.isModified()
-                and not FileSystemUtilities.isRemoteFileName(editor.getFileName())
-            )
+            self.saveAct.setEnabled(editor.isModified())
             self.revertAct.setEnabled(editor.isModified())
 
             self.undoAct.setEnabled(editor.isUndoAvailable())
@@ -8143,7 +8276,7 @@
     ## Below are protected utility methods
     ##################################################################
 
-    def _getOpenStartDir(self):
+    def _getOpenStartDir(self, forRemote=False):
         """
         Protected method to return the starting directory for a file open
         dialog.
@@ -8152,22 +8285,42 @@
         using the following search order, until a match is found:<br />
             1: Directory of currently active editor<br />
             2: Directory of currently active Project<br />
-            3: CWD
-
+            3: Directory defined as the workspace (only for local access)<br />
+            4: CWD
+
+        @param forRemote flag indicating to get the start directory for a remote
+            operation (defaults to False)
+        @type bool (optional)
         @return name of directory to start
         @rtype str
         """
         # if we have an active source, return its path
-        if self.activeWindow() is not None and self.activeWindow().getFileName():
-            return os.path.dirname(self.activeWindow().getFileName())
+        if self.activeWindow() is not None:
+            fn = self.activeWindow().getFileName()
+            if forRemote and FileSystemUtilities.isRemoteFileName(fn):
+                return (
+                    ericApp()
+                    .getObject("EricServer")
+                    .getServiceInterface("FileSystem")
+                    .dirname(fn)
+                )
+            if not forRemote and FileSystemUtilities.isPlainFileName(fn):
+                return os.path.dirname(fn)
 
         # check, if there is an active project and return its path
-        elif ericApp().getObject("Project").isOpen():
-            return ericApp().getObject("Project").ppath
-
-        else:
+        if ericApp().getObject("Project").isOpen():
+            ppath = ericApp().getObject("Project").ppath
+            if (forRemote and FileSystemUtilities.isRemoteFileName(ppath)) or (
+                not forRemote and FileSystemUtilities.isPlainFileName(ppath)
+            ):
+                return ppath
+
+        if not forRemote:
             return Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()
 
+        # return empty string
+        return ""
+
     def _getOpenFileFilter(self):
         """
         Protected method to return the active filename filter for a file open
--- a/src/eric7/ViewManager/__init__.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/ViewManager/__init__.py	Fri Jun 07 13:58:16 2024 +0200
@@ -23,7 +23,7 @@
 ######################################################################
 
 
-def factory(ui, dbs, pluginManager):
+def factory(ui, dbs, remoteServerInterface, pluginManager):
     """
     Modul factory function to generate the right viewmanager type.
 
@@ -34,6 +34,8 @@
     @type UserInterface
     @param dbs reference to the debug server object
     @type DebugServer
+    @param remoteServerInterface reference to the 'eric-ide' server interface
+    @type EricServerInterface
     @param pluginManager reference to the plugin manager object
     @type PluginManager
     @return the instantiated viewmanager
@@ -48,5 +50,5 @@
         if vm is None:
             raise RuntimeError(f"Could not create a viemanager object.\nError: {err}")
         Preferences.setViewManager("tabview")
-    vm.setReferences(ui, dbs)
+    vm.setReferences(ui, dbs, remoteServerInterface)
     return vm
--- a/src/eric7/VirtualEnv/Ui_VirtualenvAddEditDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/VirtualEnv/Ui_VirtualenvAddEditDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -12,7 +12,7 @@
 class Ui_VirtualenvAddEditDialog(object):
     def setupUi(self, VirtualenvAddEditDialog):
         VirtualenvAddEditDialog.setObjectName("VirtualenvAddEditDialog")
-        VirtualenvAddEditDialog.resize(700, 300)
+        VirtualenvAddEditDialog.resize(700, 400)
         VirtualenvAddEditDialog.setSizeGripEnabled(True)
         self.gridLayout = QtWidgets.QGridLayout(VirtualenvAddEditDialog)
         self.gridLayout.setObjectName("gridLayout")
@@ -23,9 +23,33 @@
         self.nameEdit.setClearButtonEnabled(True)
         self.nameEdit.setObjectName("nameEdit")
         self.gridLayout.addWidget(self.nameEdit, 0, 1, 1, 1)
+        self.groupBox = QtWidgets.QGroupBox(parent=VirtualenvAddEditDialog)
+        self.groupBox.setObjectName("groupBox")
+        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+        self.standardRadioButton = QtWidgets.QRadioButton(parent=self.groupBox)
+        self.standardRadioButton.setChecked(True)
+        self.standardRadioButton.setObjectName("standardRadioButton")
+        self.horizontalLayout_2.addWidget(self.standardRadioButton)
+        self.anacondaRadioButton = QtWidgets.QRadioButton(parent=self.groupBox)
+        self.anacondaRadioButton.setObjectName("anacondaRadioButton")
+        self.horizontalLayout_2.addWidget(self.anacondaRadioButton)
+        self.remoteRadioButton = QtWidgets.QRadioButton(parent=self.groupBox)
+        self.remoteRadioButton.setObjectName("remoteRadioButton")
+        self.horizontalLayout_2.addWidget(self.remoteRadioButton)
+        self.serverRadioButton = QtWidgets.QRadioButton(parent=self.groupBox)
+        self.serverRadioButton.setObjectName("serverRadioButton")
+        self.horizontalLayout_2.addWidget(self.serverRadioButton)
+        self.verticalLayout.addLayout(self.horizontalLayout_2)
+        self.globalCheckBox = QtWidgets.QCheckBox(parent=self.groupBox)
+        self.globalCheckBox.setObjectName("globalCheckBox")
+        self.verticalLayout.addWidget(self.globalCheckBox)
+        self.gridLayout.addWidget(self.groupBox, 1, 0, 1, 2)
         self.label_2 = QtWidgets.QLabel(parent=VirtualenvAddEditDialog)
         self.label_2.setObjectName("label_2")
-        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
+        self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
         self.targetDirectoryPicker = EricPathPicker(parent=VirtualenvAddEditDialog)
         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
         sizePolicy.setHorizontalStretch(0)
@@ -34,10 +58,10 @@
         self.targetDirectoryPicker.setSizePolicy(sizePolicy)
         self.targetDirectoryPicker.setFocusPolicy(QtCore.Qt.FocusPolicy.WheelFocus)
         self.targetDirectoryPicker.setObjectName("targetDirectoryPicker")
-        self.gridLayout.addWidget(self.targetDirectoryPicker, 1, 1, 1, 1)
+        self.gridLayout.addWidget(self.targetDirectoryPicker, 2, 1, 1, 1)
         self.label_3 = QtWidgets.QLabel(parent=VirtualenvAddEditDialog)
         self.label_3.setObjectName("label_3")
-        self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
+        self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
         self.pythonExecPicker = EricPathPicker(parent=VirtualenvAddEditDialog)
         sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
         sizePolicy.setHorizontalStretch(0)
@@ -46,21 +70,7 @@
         self.pythonExecPicker.setSizePolicy(sizePolicy)
         self.pythonExecPicker.setFocusPolicy(QtCore.Qt.FocusPolicy.WheelFocus)
         self.pythonExecPicker.setObjectName("pythonExecPicker")
-        self.gridLayout.addWidget(self.pythonExecPicker, 2, 1, 1, 1)
-        self.horizontalLayout = QtWidgets.QHBoxLayout()
-        self.horizontalLayout.setObjectName("horizontalLayout")
-        self.globalCheckBox = QtWidgets.QCheckBox(parent=VirtualenvAddEditDialog)
-        self.globalCheckBox.setObjectName("globalCheckBox")
-        self.horizontalLayout.addWidget(self.globalCheckBox)
-        self.anacondaCheckBox = QtWidgets.QCheckBox(parent=VirtualenvAddEditDialog)
-        self.anacondaCheckBox.setObjectName("anacondaCheckBox")
-        self.horizontalLayout.addWidget(self.anacondaCheckBox)
-        self.remoteCheckBox = QtWidgets.QCheckBox(parent=VirtualenvAddEditDialog)
-        self.remoteCheckBox.setObjectName("remoteCheckBox")
-        self.horizontalLayout.addWidget(self.remoteCheckBox)
-        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
-        self.horizontalLayout.addItem(spacerItem)
-        self.gridLayout.addLayout(self.horizontalLayout, 3, 0, 1, 2)
+        self.gridLayout.addWidget(self.pythonExecPicker, 3, 1, 1, 1)
         self.label_5 = QtWidgets.QLabel(parent=VirtualenvAddEditDialog)
         self.label_5.setObjectName("label_5")
         self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
@@ -75,39 +85,56 @@
         self.descriptionEdit = QtWidgets.QPlainTextEdit(parent=VirtualenvAddEditDialog)
         self.descriptionEdit.setObjectName("descriptionEdit")
         self.gridLayout.addWidget(self.descriptionEdit, 5, 1, 1, 1)
+        self.label_6 = QtWidgets.QLabel(parent=VirtualenvAddEditDialog)
+        self.label_6.setObjectName("label_6")
+        self.gridLayout.addWidget(self.label_6, 6, 0, 1, 1)
+        self.serverLineEdit = QtWidgets.QLineEdit(parent=VirtualenvAddEditDialog)
+        self.serverLineEdit.setReadOnly(True)
+        self.serverLineEdit.setObjectName("serverLineEdit")
+        self.gridLayout.addWidget(self.serverLineEdit, 6, 1, 1, 1)
         self.buttonBox = QtWidgets.QDialogButtonBox(parent=VirtualenvAddEditDialog)
         self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
         self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
         self.buttonBox.setObjectName("buttonBox")
-        self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2)
+        self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 2)
 
         self.retranslateUi(VirtualenvAddEditDialog)
         self.buttonBox.accepted.connect(VirtualenvAddEditDialog.accept) # type: ignore
         self.buttonBox.rejected.connect(VirtualenvAddEditDialog.reject) # type: ignore
         QtCore.QMetaObject.connectSlotsByName(VirtualenvAddEditDialog)
-        VirtualenvAddEditDialog.setTabOrder(self.nameEdit, self.targetDirectoryPicker)
+        VirtualenvAddEditDialog.setTabOrder(self.nameEdit, self.standardRadioButton)
+        VirtualenvAddEditDialog.setTabOrder(self.standardRadioButton, self.anacondaRadioButton)
+        VirtualenvAddEditDialog.setTabOrder(self.anacondaRadioButton, self.remoteRadioButton)
+        VirtualenvAddEditDialog.setTabOrder(self.remoteRadioButton, self.serverRadioButton)
+        VirtualenvAddEditDialog.setTabOrder(self.serverRadioButton, self.globalCheckBox)
+        VirtualenvAddEditDialog.setTabOrder(self.globalCheckBox, self.targetDirectoryPicker)
         VirtualenvAddEditDialog.setTabOrder(self.targetDirectoryPicker, self.pythonExecPicker)
-        VirtualenvAddEditDialog.setTabOrder(self.pythonExecPicker, self.globalCheckBox)
-        VirtualenvAddEditDialog.setTabOrder(self.globalCheckBox, self.anacondaCheckBox)
-        VirtualenvAddEditDialog.setTabOrder(self.anacondaCheckBox, self.remoteCheckBox)
-        VirtualenvAddEditDialog.setTabOrder(self.remoteCheckBox, self.execPathEdit)
+        VirtualenvAddEditDialog.setTabOrder(self.pythonExecPicker, self.execPathEdit)
+        VirtualenvAddEditDialog.setTabOrder(self.execPathEdit, self.descriptionEdit)
 
     def retranslateUi(self, VirtualenvAddEditDialog):
         _translate = QtCore.QCoreApplication.translate
         self.label.setText(_translate("VirtualenvAddEditDialog", "Logical Name:"))
         self.nameEdit.setToolTip(_translate("VirtualenvAddEditDialog", "Enter a unique name for the virtual environment"))
-        self.label_2.setText(_translate("VirtualenvAddEditDialog", "Directory:"))
-        self.targetDirectoryPicker.setToolTip(_translate("VirtualenvAddEditDialog", "Enter the directory of the virtual environment"))
-        self.label_3.setText(_translate("VirtualenvAddEditDialog", "Python Interpreter:"))
-        self.pythonExecPicker.setToolTip(_translate("VirtualenvAddEditDialog", "Enter the Python interpreter of the virtual environment"))
+        self.groupBox.setTitle(_translate("VirtualenvAddEditDialog", "Environment Type"))
+        self.standardRadioButton.setToolTip(_translate("VirtualenvAddEditDialog", "Select to indicate a standard environment"))
+        self.standardRadioButton.setText(_translate("VirtualenvAddEditDialog", "Standard"))
+        self.anacondaRadioButton.setToolTip(_translate("VirtualenvAddEditDialog", "Select to indicate an Anaconda environment"))
+        self.anacondaRadioButton.setText(_translate("VirtualenvAddEditDialog", "Anaconda"))
+        self.remoteRadioButton.setToolTip(_translate("VirtualenvAddEditDialog", "Select to indicate a remotely (ssh) accessed environment"))
+        self.remoteRadioButton.setText(_translate("VirtualenvAddEditDialog", "Remote"))
+        self.serverRadioButton.setToolTip(_translate("VirtualenvAddEditDialog", "Select to indicate an eric IDE server environment."))
+        self.serverRadioButton.setText(_translate("VirtualenvAddEditDialog", "eric IDE Server"))
         self.globalCheckBox.setToolTip(_translate("VirtualenvAddEditDialog", "Select,if this is a global environment (i.e. no virtual environment directory to be given)"))
         self.globalCheckBox.setWhatsThis(_translate("VirtualenvAddEditDialog", "<b>Global Environment</b>\n"
 "<p>Setting this indicates, that the environment is defined globally, i.e. not user specific. Usually such environments cannot be deleted by a standard user. The respective button of the Virtual Environment Manager dialog will be disabled for these entries.</p>"))
         self.globalCheckBox.setText(_translate("VirtualenvAddEditDialog", "Global Environment"))
-        self.anacondaCheckBox.setToolTip(_translate("VirtualenvAddEditDialog", "Select, if this is a Conda environment"))
-        self.anacondaCheckBox.setText(_translate("VirtualenvAddEditDialog", "Conda Environment"))
-        self.remoteCheckBox.setToolTip(_translate("VirtualenvAddEditDialog", "Select, if this is a remotely accessed environment"))
-        self.remoteCheckBox.setText(_translate("VirtualenvAddEditDialog", "Remote Environment"))
+        self.label_2.setText(_translate("VirtualenvAddEditDialog", "Directory:"))
+        self.targetDirectoryPicker.setToolTip(_translate("VirtualenvAddEditDialog", "Enter the directory of the virtual environment"))
+        self.label_3.setText(_translate("VirtualenvAddEditDialog", "Python Interpreter:"))
+        self.pythonExecPicker.setToolTip(_translate("VirtualenvAddEditDialog", "Enter the Python interpreter of the virtual environment"))
         self.label_5.setText(_translate("VirtualenvAddEditDialog", "PATH Prefix:"))
         self.label_4.setText(_translate("VirtualenvAddEditDialog", "Description:"))
+        self.label_6.setText(_translate("VirtualenvAddEditDialog", "Server:"))
+        self.serverLineEdit.setToolTip(_translate("VirtualenvAddEditDialog", "Shows the host name of the server this entry belongs to (eric IDE Server environment only)"))
 from eric7.EricWidgets.EricPathPicker import EricPathPicker
--- a/src/eric7/VirtualEnv/VirtualenvAddEditDialog.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/VirtualEnv/VirtualenvAddEditDialog.py	Fri Jun 07 13:58:16 2024 +0200
@@ -12,8 +12,9 @@
 from PyQt6.QtCore import Qt, pyqtSlot
 from PyQt6.QtWidgets import QDialog, QDialogButtonBox
 
+from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
-from eric7.SystemUtilities import OSUtilities, PythonUtilities
+from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
 
 from .Ui_VirtualenvAddEditDialog import Ui_VirtualenvAddEditDialog
 from .VirtualenvMeta import VirtualenvMetaData
@@ -50,6 +51,8 @@
         self.__venvName = "" if metadata is None else metadata.name
         self.__manager = manager
         self.__editMode = bool(self.__venvName)
+        self.__serverInterface = ericApp().getObject("EricServer")
+        self.__fsInterface = self.__serverInterface.getServiceInterface("FileSystem")
 
         if self.__editMode:
             self.setWindowTitle(self.tr("Edit Virtual Environment"))
@@ -60,15 +63,29 @@
         if not self.__envBaseDir:
             self.__envBaseDir = OSUtilities.getHomeDir()
 
+        self.serverRadioButton.setEnabled(self.__serverInterface.isServerConnected())
+
         self.targetDirectoryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
         self.targetDirectoryPicker.setWindowTitle(
             self.tr("Virtualenv Target Directory")
         )
-        self.targetDirectoryPicker.setDefaultDirectory(self.__envBaseDir)
+        if self.__serverInterface.isServerConnected():
+            self.targetDirectoryPicker.setRemote(
+                metadata.is_eric_server if metadata else False
+            )
+        if metadata is None or (not metadata.is_eric_server and not metadata.is_remote):
+            self.targetDirectoryPicker.setDefaultDirectory(self.__envBaseDir)
 
         self.pythonExecPicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
         self.pythonExecPicker.setWindowTitle(self.tr("Python Interpreter"))
-        self.pythonExecPicker.setDefaultDirectory(PythonUtilities.getPythonExecutable())
+        if self.__serverInterface.isServerConnected():
+            self.pythonExecPicker.setRemote(
+                metadata.is_eric_server if metadata else False
+            )
+        if metadata is None or (not metadata.is_eric_server and not metadata.is_remote):
+            self.pythonExecPicker.setDefaultDirectory(
+                PythonUtilities.getPythonExecutable()
+            )
 
         self.execPathEdit.setToolTip(
             self.tr(
@@ -81,27 +98,40 @@
         if metadata:
             if metadata.path:
                 self.targetDirectoryPicker.setText(
-                    metadata.path, toNative=not metadata.is_remote
+                    metadata.path,
+                    toNative=not metadata.is_remote and not metadata.is_eric_server,
                 )
             else:
                 self.targetDirectoryPicker.setText(
-                    self.__envBaseDir, toNative=not metadata.is_remote
+                    self.__envBaseDir,
+                    toNative=not metadata.is_remote and not metadata.is_eric_server,
                 )
-            if not metadata.interpreter and metadata.path and not metadata.is_remote:
+            if (
+                not metadata.interpreter
+                and metadata.path
+                and not metadata.is_remote
+                and not metadata.is_eric_server
+            ):
                 py = self.__detectPythonInterpreter(metadata.path)
                 self.pythonExecPicker.setText(py)
             else:
                 self.pythonExecPicker.setText(
-                    metadata.interpreter, toNative=not metadata.is_remote
+                    metadata.interpreter,
+                    toNative=not metadata.is_remote and not metadata.is_eric_server,
                 )
         else:
             self.targetDirectoryPicker.setText(self.__envBaseDir, toNative=True)
 
         self.globalCheckBox.setChecked(metadata.is_global if metadata else False)
-        self.anacondaCheckBox.setChecked(metadata.is_conda if metadata else False)
-        self.remoteCheckBox.setChecked(metadata.is_remote if metadata else False)
+        self.standardRadioButton.setChecked(True)
+        self.anacondaRadioButton.setChecked(metadata.is_conda if metadata else False)
+        self.remoteRadioButton.setChecked(metadata.is_remote if metadata else False)
+        self.serverRadioButton.setChecked(
+            metadata.is_eric_server if metadata else False
+        )
         self.execPathEdit.setText(metadata.exec_path if metadata else "")
         self.descriptionEdit.setPlainText(metadata.description if metadata else "")
+        self.serverLineEdit.setText(metadata.eric_server if metadata else "")
 
         self.__updateOk()
 
@@ -127,18 +157,34 @@
         )
 
         if not self.globalCheckBox.isChecked():
-            enable &= self.remoteCheckBox.isChecked() or (
+            enable &= self.remoteRadioButton.isChecked() or (
                 bool(self.targetDirectoryPicker.text())
                 and self.targetDirectoryPicker.text() != self.__envBaseDir
-                and os.path.exists(self.targetDirectoryPicker.text())
+                and (
+                    (
+                        self.serverRadioButton.isChecked()
+                        and self.__fsInterface.exists(self.targetDirectoryPicker.text())
+                    )
+                    or (
+                        not self.serverRadioButton.isChecked()
+                        and os.path.exists(self.targetDirectoryPicker.text())
+                    )
+                )
             )
 
-        enable = (
-            enable
-            and bool(self.pythonExecPicker.text())
+        enable &= self.remoteRadioButton.isChecked() or (
+            bool(self.pythonExecPicker.text())
             and (
-                self.remoteCheckBox.isChecked()
-                or os.access(self.pythonExecPicker.text(), os.X_OK)
+                (
+                    self.serverRadioButton.isChecked()
+                    and self.__fsInterface.access(
+                        self.pythonExecPicker.text(), "execute"
+                    )
+                )
+                or (
+                    not self.serverRadioButton.isChecked()
+                    and os.access(self.pythonExecPicker.text(), os.X_OK)
+                )
             )
         )
 
@@ -220,21 +266,38 @@
         self.__updateOk()
 
     @pyqtSlot(bool)
-    def on_remoteCheckBox_toggled(self, checked):
+    def on_remoteRadioButton_toggled(self, checked):
         """
-        Private slot handling a change of the remote check box state.
+        Private slot handling a change of the remote radio button state.
 
-        @param checked state of the check box
+        @param checked state of the radio button
         @type bool
         """
         self.__updateOk()
 
     @pyqtSlot(bool)
-    def on_anacondaCheckBox_clicked(self, checked):
+    def on_serverRadioButton_toggled(self, checked):
+        """
+        Private slot handling a change of the eric IDE server radio button state.
+
+        @param checked state of the radio button
+        @type bool
         """
-        Private slot handling a user click on this check box.
+        if self.__serverInterface.isServerConnected():
+            self.targetDirectoryPicker.setRemote(checked)
+            self.pythonExecPicker.setRemote(checked)
+            self.serverLineEdit.setText(
+                self.__serverInterface.getHost() if checked else ""
+            )
 
-        @param checked state of the check box
+        self.__updateOk()
+
+    @pyqtSlot(bool)
+    def on_anacondaRadioButton_clicked(self, checked):
+        """
+        Private slot handling a user click on this radio button.
+
+        @param checked state of the radio button
         @type bool
         """
         if checked and not bool(self.execPathEdit.text()):
@@ -263,14 +326,34 @@
         @return metadata for the virtual environment
         @rtype VirtualenvMetaData
         """
-        nativePaths = not self.remoteCheckBox.isChecked()
+        nativePaths = (
+            not self.remoteRadioButton.isChecked()
+            and not self.serverRadioButton.isChecked()
+        )
+        is_eric_server = self.serverRadioButton.isChecked()
+        envPath = (
+            FileSystemUtilities.remoteFileName(self.targetDirectoryPicker.text())
+            if is_eric_server
+            else FileSystemUtilities.plainFileName(
+                self.targetDirectoryPicker.text(toNative=nativePaths)
+            )
+        )
+        interpreter = (
+            FileSystemUtilities.remoteFileName(self.pythonExecPicker.text())
+            if is_eric_server
+            else FileSystemUtilities.plainFileName(
+                self.pythonExecPicker.text(toNative=nativePaths)
+            )
+        )
         return VirtualenvMetaData(
             name=self.nameEdit.text(),
-            path=self.targetDirectoryPicker.text(toNative=nativePaths),
-            interpreter=self.pythonExecPicker.text(toNative=nativePaths),
+            path=envPath,
+            interpreter=interpreter,
             is_global=self.globalCheckBox.isChecked(),
-            is_conda=self.anacondaCheckBox.isChecked(),
-            is_remote=self.remoteCheckBox.isChecked(),
+            is_conda=self.anacondaRadioButton.isChecked(),
+            is_remote=self.remoteRadioButton.isChecked(),
             exec_path=self.execPathEdit.text(),
             description=self.descriptionEdit.toPlainText(),
+            is_eric_server=is_eric_server,
+            eric_server=self.serverLineEdit.text(),
         )
--- a/src/eric7/VirtualEnv/VirtualenvAddEditDialog.ui	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/VirtualEnv/VirtualenvAddEditDialog.ui	Fri Jun 07 13:58:16 2024 +0200
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>700</width>
-    <height>300</height>
+    <height>400</height>
    </rect>
   </property>
   <property name="sizeGripEnabled">
@@ -31,14 +31,84 @@
      </property>
     </widget>
    </item>
-   <item row="1" column="0">
+   <item row="1" column="0" colspan="2">
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Environment Type</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QRadioButton" name="standardRadioButton">
+          <property name="toolTip">
+           <string>Select to indicate a standard environment</string>
+          </property>
+          <property name="text">
+           <string>Standard</string>
+          </property>
+          <property name="checked">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QRadioButton" name="anacondaRadioButton">
+          <property name="toolTip">
+           <string>Select to indicate an Anaconda environment</string>
+          </property>
+          <property name="text">
+           <string>Anaconda</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QRadioButton" name="remoteRadioButton">
+          <property name="toolTip">
+           <string>Select to indicate a remotely (ssh) accessed environment</string>
+          </property>
+          <property name="text">
+           <string>Remote</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QRadioButton" name="serverRadioButton">
+          <property name="toolTip">
+           <string>Select to indicate an eric IDE server environment.</string>
+          </property>
+          <property name="text">
+           <string>eric IDE Server</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <widget class="QCheckBox" name="globalCheckBox">
+        <property name="toolTip">
+         <string>Select,if this is a global environment (i.e. no virtual environment directory to be given)</string>
+        </property>
+        <property name="whatsThis">
+         <string>&lt;b&gt;Global Environment&lt;/b&gt;
+&lt;p&gt;Setting this indicates, that the environment is defined globally, i.e. not user specific. Usually such environments cannot be deleted by a standard user. The respective button of the Virtual Environment Manager dialog will be disabled for these entries.&lt;/p&gt;</string>
+        </property>
+        <property name="text">
+         <string>Global Environment</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="2" column="0">
     <widget class="QLabel" name="label_2">
      <property name="text">
       <string>Directory:</string>
      </property>
     </widget>
    </item>
-   <item row="1" column="1">
+   <item row="2" column="1">
     <widget class="EricPathPicker" name="targetDirectoryPicker" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@@ -54,14 +124,14 @@
      </property>
     </widget>
    </item>
-   <item row="2" column="0">
+   <item row="3" column="0">
     <widget class="QLabel" name="label_3">
      <property name="text">
       <string>Python Interpreter:</string>
      </property>
     </widget>
    </item>
-   <item row="2" column="1">
+   <item row="3" column="1">
     <widget class="EricPathPicker" name="pythonExecPicker" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@@ -77,57 +147,6 @@
      </property>
     </widget>
    </item>
-   <item row="3" column="0" colspan="2">
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <item>
-      <widget class="QCheckBox" name="globalCheckBox">
-       <property name="toolTip">
-        <string>Select,if this is a global environment (i.e. no virtual environment directory to be given)</string>
-       </property>
-       <property name="whatsThis">
-        <string>&lt;b&gt;Global Environment&lt;/b&gt;
-&lt;p&gt;Setting this indicates, that the environment is defined globally, i.e. not user specific. Usually such environments cannot be deleted by a standard user. The respective button of the Virtual Environment Manager dialog will be disabled for these entries.&lt;/p&gt;</string>
-       </property>
-       <property name="text">
-        <string>Global Environment</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QCheckBox" name="anacondaCheckBox">
-       <property name="toolTip">
-        <string>Select, if this is a Conda environment</string>
-       </property>
-       <property name="text">
-        <string>Conda Environment</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QCheckBox" name="remoteCheckBox">
-       <property name="toolTip">
-        <string>Select, if this is a remotely accessed environment</string>
-       </property>
-       <property name="text">
-        <string>Remote Environment</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-    </layout>
-   </item>
    <item row="4" column="0">
     <widget class="QLabel" name="label_5">
      <property name="text">
@@ -155,7 +174,24 @@
    <item row="5" column="1">
     <widget class="QPlainTextEdit" name="descriptionEdit"/>
    </item>
-   <item row="6" column="0" colspan="2">
+   <item row="6" column="0">
+    <widget class="QLabel" name="label_6">
+     <property name="text">
+      <string>Server:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="1">
+    <widget class="QLineEdit" name="serverLineEdit">
+     <property name="toolTip">
+      <string>Shows the host name of the server this entry belongs to (eric IDE Server environment only)</string>
+     </property>
+     <property name="readOnly">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="7" column="0" colspan="2">
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -177,12 +213,15 @@
  </customwidgets>
  <tabstops>
   <tabstop>nameEdit</tabstop>
+  <tabstop>standardRadioButton</tabstop>
+  <tabstop>anacondaRadioButton</tabstop>
+  <tabstop>remoteRadioButton</tabstop>
+  <tabstop>serverRadioButton</tabstop>
+  <tabstop>globalCheckBox</tabstop>
   <tabstop>targetDirectoryPicker</tabstop>
   <tabstop>pythonExecPicker</tabstop>
-  <tabstop>globalCheckBox</tabstop>
-  <tabstop>anacondaCheckBox</tabstop>
-  <tabstop>remoteCheckBox</tabstop>
   <tabstop>execPathEdit</tabstop>
+  <tabstop>descriptionEdit</tabstop>
  </tabstops>
  <resources/>
  <connections>
--- a/src/eric7/VirtualEnv/VirtualenvManager.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/VirtualEnv/VirtualenvManager.py	Fri Jun 07 13:58:16 2024 +0200
@@ -84,6 +84,7 @@
         #   is_global:      a flag indicating a global environment
         #   is_conda:       a flag indicating an Anaconda environment
         #   is_remote:      a flag indicating a remotely accessed environment
+        #   is_eric_server  a flag indicating an eric IDE server environment
         #   exec_path:      a string to be prefixed to the PATH environment
         #                   setting
         #   description     a description of the environment
@@ -153,18 +154,32 @@
 
     def __cleanEnvironments(self):
         """
-        Private method to delete all non-existent local environments.
+        Private method to delete all non-existent local or eric IDE server environments.
         """
         removed = False
 
         for venvName in list(self.__virtualEnvironments):
             venvItem = self.__virtualEnvironments[venvName]
             if not venvItem.is_remote:
-                # It is a local environment; check it is still valid.
                 venvPath = venvItem.path
-                if venvPath and not os.path.exists(venvPath):
-                    del self.__virtualEnvironments[venvName]
-                    removed = True
+                if venvPath:
+                    if venvItem.is_eric_server:
+                        # It is an eric IDE server environment; check it is still valid.
+                        ericServer = ericApp().getObject("EricServer")
+                        if (
+                            ericServer.isServerConnected()
+                            and ericServer.getHost() == venvItem.eric_server
+                            and not ericServer.getServiceInterface("FileSystem").exists(
+                                venvPath
+                            )
+                        ):
+                            del self.__virtualEnvironments[venvName]
+                            removed = True
+                    else:
+                        # It is a local environment; check it is still valid.
+                        if not os.path.exists(venvPath):
+                            del self.__virtualEnvironments[venvName]
+                            removed = True
         if removed:
             self.__saveSettings()
             self.virtualEnvironmentRemoved.emit()
@@ -441,6 +456,7 @@
             ok &= bool(self.__virtualEnvironments[venvName].path)
             ok &= not self.__virtualEnvironments[venvName].is_global
             ok &= not self.__virtualEnvironments[venvName].is_remote
+            ok &= not self.__virtualEnvironments[venvName].is_eric_server
             ok &= os.access(self.__virtualEnvironments[venvName].path, os.W_OK)
 
         return ok
@@ -633,9 +649,9 @@
         @return flag indicating a global environment
         @rtype bool
         """
-        if venvName in self.__virtualEnvironments:
+        try:
             return self.__virtualEnvironments[venvName].is_global
-        else:
+        except KeyError:
             return False
 
     def isCondaEnvironment(self, venvName):
@@ -648,9 +664,9 @@
         @return flag indicating an Anaconda environment
         @rtype bool
         """
-        if venvName in self.__virtualEnvironments:
+        try:
             return self.__virtualEnvironments[venvName].is_conda
-        else:
+        except KeyError:
             return False
 
     def isRemoteEnvironment(self, venvName):
@@ -663,9 +679,24 @@
         @return flag indicating a remotely accessed environment
         @rtype bool
         """
-        if venvName in self.__virtualEnvironments:
+        try:
             return self.__virtualEnvironments[venvName].is_remote
-        else:
+        except KeyError:
+            return False
+
+    def isEricServerEnvironment(self, venvName):
+        """
+        Public method to test, if a given environment is an environment accessed
+        through an eric IDE server.
+
+        @param venvName logical name of the virtual environment
+        @type str
+        @return flag indicating a remotely accessed environment
+        @rtype bool
+        """
+        try:
+            return self.__virtualEnvironments[venvName].is_eric_server
+        except KeyError:
             return False
 
     def getVirtualenvExecPath(self, venvName):
@@ -677,9 +708,9 @@
         @return search path prefix
         @rtype str
         """
-        if venvName in self.__virtualEnvironments:
+        try:
             return self.__virtualEnvironments[venvName].exec_path
-        else:
+        except KeyError:
             return ""
 
     def setVirtualEnvironmentsBaseDir(self, baseDir):
--- a/src/eric7/VirtualEnv/VirtualenvMeta.py	Fri Jun 07 13:51:43 2024 +0200
+++ b/src/eric7/VirtualEnv/VirtualenvMeta.py	Fri Jun 07 13:58:16 2024 +0200
@@ -24,6 +24,8 @@
     is_remote: bool = False  # flag indicating a remotely accessed environment
     exec_path: str = ""  # string to be prefixed to the PATH environment setting
     description: str = ""  # description of the environment
+    is_eric_server: bool = False  # flag indicating an eric IDE server environment
+    eric_server: str = ""  # server name the environment belongs to
 
     def as_dict(self):
         """
@@ -53,4 +55,6 @@
             is_remote=data.get("is_remote", False),
             exec_path=data.get("exec_path", ""),
             description=data.get("description", ""),
+            is_eric_server=data.get("is_eric_server", False),
+            eric_server=data.get("eric_server", ""),
         )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/eric7_server.py	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+eric-ide Server.
+
+This is the main Python script of the eric-ide server. This is a server to perform
+remote development (e.g. code hosted on another computer or through a docker
+container).
+"""
+
+import argparse
+import socket
+import sys
+
+from eric7.RemoteServer.EricServer import EricServer
+from eric7.__version__ import Version
+
+
+def createArgumentParser():
+    """
+    Function to create an argument parser.
+
+    @return created argument parser object
+    @rtype argparse.ArgumentParser
+    """
+    parser = argparse.ArgumentParser(
+        description=(
+            "Start the eric-ide server component. This will listen for connections"
+            " from the eric IDE in order to perform remote development."
+        ),
+        epilog="Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>.",
+    )
+
+    parser.add_argument(
+        "-p",
+        "--port",
+        type=int,
+        default=42024,
+        help="Listen on the given port for connections from an eric IDE.",
+    )
+    parser.add_argument(
+        "-6",
+        "--with-ipv6",
+        action="store_true",
+        help="Listen on IPv6 interfaces as well if the system supports the creation"
+        " of TCP sockets which can handle both IPv4 and IPv6. {0}".format(
+            "This system supports this feature."
+            if socket.has_dualstack_ipv6()
+            else "This system does not support this feature. The option will be"
+            " ignored."
+        ),
+    )
+    parser.add_argument(
+        "-V",
+        "--version",
+        action="version",
+        version="%(prog)s {0}".format(Version),
+        help="Show version information and exit.",
+    )
+
+    return parser
+
+
+def main():
+    """
+    Main entry point into the application.
+    """
+    global supportedExtensions
+
+    parser = createArgumentParser()
+    args = parser.parse_args()
+
+    server = EricServer(port=args.port, useIPv6=args.with_ipv6)
+    ok = server.run()
+
+    sys.exit(0 if ok else 1)
+
+
+if __name__ == "__main__":
+    main()
+
+#
+# eflag: noqa = M801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/dialog-cancel.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dialog-cancel.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.471276"
+     inkscape:cy="11.160662"
+     inkscape:window-width="2580"
+     inkscape:window-height="1255"
+     inkscape:window-x="692"
+     inkscape:window-y="13"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 11,1 C 8.4655025,1 6.16085,1.9458675 4.3984375,3.5 4.2406387,3.63915 4.0784,3.780975 3.9296875,3.9296875 L 3.5,4.3984375 C 1.9458675,6.16085 1,8.4655025 1,11 c 0,5.52285 4.47715,10 10,10 2.534497,0 4.83915,-0.945867 6.601563,-2.5 l 0.46875,-0.429687 C 18.219025,17.9216 18.36085,17.759361 18.5,17.601563 20.054133,15.83915 21,13.534497 21,11 21,5.47715 16.52285,1 11,1 m 0,1.25 c 4.832487,0 8.75,3.9175125 8.75,8.75 0,2.192075 -0.816519,4.167835 -2.148437,5.703125 L 5.296875,4.3984375 C 6.8321637,3.0665187 8.807925,2.25 11,2.25 M 4.3984375,5.296875 16.703125,17.601563 C 15.167835,18.933481 13.192075,19.75 11,19.75 6.1675125,19.75 2.25,15.832487 2.25,11 2.25,8.807925 3.0665187,6.8321637 4.3984375,5.296875"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/dialog-ok.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dialog-ok.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.449854"
+     inkscape:cy="11.26777"
+     inkscape:window-width="2580"
+     inkscape:window-height="1271"
+     inkscape:window-x="861"
+     inkscape:window-y="110"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.291"
+     d="M 20.116087,3.0000134 7.0676375,17.088646 1.8839,11.491668 1,12.446045 l 5.1837375,5.596951 -0.0025,0.0026 0.8839125,0.954364 0.0025,-0.0026 0.0025,0.0026 0.8838875,-0.954364 -0.0025,-0.0026 L 21,3.9543498 20.1161,3 Z"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/dirNew.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dirNew.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.492697"
+     inkscape:cy="11.278481"
+     inkscape:window-width="2580"
+     inkscape:window-height="1326"
+     inkscape:window-x="0"
+     inkscape:window-y="55"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 2.2499998 20.999996 H 2.2499998 13.499998 v -1.25 H 2.2499998 V 9.7499984 h 3.7499993 v -0.012208 l 0.00977,0.012208 2.4999995,-2.4999996 H 19.749998 v 6.2499992 h 1.25 V 3.4999995 H 12.259764 L 9.7597646,1 9.7500021,1.0122125 V 1 H 2.2500035 1.0000037 M 17.250001,14.749997 v 2.5 h -2.5 v 1.25 h 2.5 v 2.499999 H 18.5 V 18.499997 H 21 v -1.25 h -2.5 v -2.5 h -1.249999"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/fileSaveAsRemote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg8"
+   sodipodi:docname="fileSaveAsRemote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview10"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="12.028238"
+     inkscape:cy="11.546251"
+     inkscape:window-width="2580"
+     inkscape:window-height="1321"
+     inkscape:window-x="860"
+     inkscape:window-y="59"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <path
+     class="ColorScheme-Text"
+     d="M 2,1.0001509 V 15.994085 h 9.000109 v -0.93707 H 6.0000082 V 9.4348532 H 11.000109 V 8.4977845 H 5.0000061 V 15.057015 H 3.000002 V 1.9388863 H 6.0000082 V 6.6240635 H 14.000114 V 1.9388863 h 0.292971 l 2.707054,2.5365439 V 7.561049 h 1.000002 V 4.0947694 h -0.0077 l 0.0077,-0.00915 L 14.707092,1 14.699256,1.00915 V 1 H 14.000034 Z M 7.0001004,1.9389695 H 10.900567 V 5.6870781 H 7.0001004 Z M 18.000033,8.4982011 17.003901,9.4297698 h -0.0038 L 12,14.113115 l 0.0077,0.0092 -0.0039,0.940653 H 11.9999 V 16 h 2.000071 l 0.0059,-0.0092 h 0.01364 l -0.0059,-0.0092 0.98632,-0.924234 4.00001,-3.744443 -0.705086,-0.664339 -4.990291,4.672345 -0.593755,-0.554534 4.99227,-4.674178 0.591802,0.556359 0.705087,0.658847 L 20,10.37034 Z"
+     color="#eff0f1"
+     fill="currentColor"
+     id="path6"
+     style="stroke-width:0.866025" />
+  <path
+     style="color:#eff0f1;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999983,17 v 1.333333 H 1 v 1.333334 H 8.4999983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.499997 V 17 H 8.4999983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2499999,-0.594666 -1.2499999,-1.333333 0,-0.738667 0.5574999,-1.333333 1.2499999,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/open-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="open-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.621227"
+     inkscape:cy="11.332035"
+     inkscape:window-width="2580"
+     inkscape:window-height="1337"
+     inkscape:window-x="861"
+     inkscape:window-y="44"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 2.25 16 H 2.2499998 V 9.75 H 5.9999991 V 9.74023 L 6.0097653,9.75 8.5097648,7.25 H 19.749998 V 16 h 1.25 V 3.5 H 12.259764 L 9.7597646,1 9.7500046,1.00976 V 1 H 2.2500035 1.0000037 M 8.5000023,17.25 V 18.5 H 1.0000037 v 1.25 H 8.5000023 V 21 H 13.500001 V 19.75 H 21 V 18.5 H 13.500001 V 17.25 H 8.5000023 m 2.4999997,0.625 c 0.6925,0 1.25,0.5575 1.25,1.25 0,0.6925 -0.5575,1.25 -1.25,1.25 -0.6925,0 -1.2499999,-0.5575 -1.2499999,-1.25 0,-0.6925 0.5574999,-1.25 1.2499999,-1.25"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/preferences-eric-server.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   height="22"
+   width="22"
+   version="1.1"
+   id="svg1718"
+   sodipodi:docname="preferences-eric-server.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs1722" />
+  <sodipodi:namedview
+     id="namedview1720"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="32.09375"
+     inkscape:cx="16.623174"
+     inkscape:cy="16.327167"
+     inkscape:window-width="2580"
+     inkscape:window-height="1381"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1718" />
+  <linearGradient
+     id="b"
+     gradientTransform="matrix(0.7,0,0,0.7,-0.7,-1.1)"
+     gradientUnits="userSpaceOnUse"
+     x2="0"
+     y1="44"
+     y2="4">
+    <stop
+       offset="0"
+       stop-color="#1d1e1e"
+       id="stop1680" />
+    <stop
+       offset="1"
+       stop-color="#44484c"
+       id="stop1682" />
+  </linearGradient>
+  <linearGradient
+     id="c"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-350.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="a"
+     gradientUnits="userSpaceOnUse"
+     x2="0"
+     y1="507.79999"
+     y2="506.79999">
+    <stop
+       offset="0"
+       stop-color="#3da103"
+       id="stop1686" />
+    <stop
+       offset="1"
+       stop-color="#7ddf07"
+       id="stop1688" />
+  </linearGradient>
+  <linearGradient
+     id="d"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-339.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="e"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-328.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="f"
+     gradientUnits="userSpaceOnUse"
+     x1="5"
+     x2="18"
+     y1="12"
+     y2="25">
+    <stop
+       offset="0"
+       stop-color="#292c2f"
+       id="stop1693"
+       style="stop-color:#c0c0c0;stop-opacity:1;" />
+    <stop
+       offset="1"
+       stop-opacity="0"
+       id="stop1695" />
+  </linearGradient>
+  <g
+     id="g2294"
+     transform="matrix(0.72413793,0,0,0.71428571,-1.1724138,-0.42857143)">
+    <path
+       d="M 3,7 H 29 V 24 H 3 Z"
+       fill="#111213"
+       id="path1698" />
+    <path
+       d="M 3,2 V 30 H 29 V 2 Z m 1,6 h 24 v 4 H 4 Z m 0,11 h 24 v 4 H 4 Z"
+       fill="url(#b)"
+       id="path1700"
+       style="fill:#ececec;stroke-width:0.7" />
+    <path
+       d="M 5,4 H 7 V 5 H 5 Z"
+       fill="url(#c)"
+       id="path1702"
+       style="fill:url(#c);stroke-width:0.7" />
+    <path
+       d="m 5,15 h 2 v 1 H 5 Z"
+       fill="url(#d)"
+       id="path1704"
+       style="fill:url(#d);stroke-width:0.7" />
+    <path
+       d="m 5,26 h 2 v 1 H 5 Z"
+       fill="url(#e)"
+       id="path1706"
+       style="fill:url(#e);stroke-width:0.7" />
+    <path
+       d="m 3,29 h 26 v 1 H 3 Z"
+       opacity="0.2"
+       id="path1710" />
+    <path
+       d="m 4,12 7,7 h 17 v 4 H 15 l 7,7 h 7 V 13 l -1,-1 z"
+       fill="url(#f)"
+       fill-rule="evenodd"
+       opacity="0.4"
+       id="path1712"
+       style="fill:url(#f)" />
+    <rect
+       fill="#eff0f1"
+       height="16"
+       rx="2"
+       width="16"
+       x="16"
+       y="14"
+       id="rect1714" />
+    <path
+       d="m 19,16 v 2 h -1 v 2 h 1 v 4 h -1 v 2 h 1 v 2 H 30 V 16 Z m 1,1 h 1 v 10 h -1 z m 2,0 h 7 v 10 h -7 z"
+       fill="#232629"
+       id="path1716" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/projectOpen-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg10"
+   sodipodi:docname="projectOpen-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview12"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="12.028238"
+     inkscape:cy="11.546251"
+     inkscape:window-width="2580"
+     inkscape:window-height="1285"
+     inkscape:window-x="861"
+     inkscape:window-y="96"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg10" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g4755"
+     transform="matrix(0.75,0,0,0.75,2.75,0.25)">
+    <path
+       d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+       color="#eff0f1"
+       fill="currentColor"
+       id="path6" />
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z"
+       color="#eff0f1"
+       fill="currentColor"
+       id="path8" />
+  </g>
+  <path
+     style="color:#eff0f1;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999998,17 v 1.333333 H 1 v 1.333334 H 8.4999998 V 21 H 13.499999 V 19.666667 H 21 V 18.333333 H 13.499999 V 17 H 8.4999998 M 11,17.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2500002,-0.594666 -1.2500002,-1.333333 0,-0.738667 0.5575002,-1.333333 1.2500002,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/projectSaveAs-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg10"
+   sodipodi:docname="projectSaveAs-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview12"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="12.135346"
+     inkscape:cy="11.567673"
+     inkscape:window-width="2580"
+     inkscape:window-height="1281"
+     inkscape:window-x="861"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg10" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g2432"
+     transform="matrix(0.74999663,0,0,0.74999663,2.7500034,0.25000337)">
+    <g
+       id="g2427">
+      <path
+         d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+         color="#eff0f1"
+         fill="currentColor"
+         id="path6" />
+    </g>
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12.5 v 8.4969 h 4.5 V 20.46594 H 14 v -3.186 h 2.5 v -0.531 h -3 v 3.717 h -1 v -7.4339 H 14 v 2.655 h 4 v -2.655 h 0.14648 l 1.3535,1.4374 v 1.7486 h 0.5 v -1.9643 h -0.0039 l 0.0039,-0.0051 -1.6465,-1.7486 -0.0039,0.0051 v -0.0051 h -0.34961 z m 2.5,0.53203 h 1.9502 v 2.124 H 14.5 Z m 5.5,3.717 -0.49805,0.52788 h -0.0019 l -2.5,2.6539 0.0039,0.0051 -0.0019,0.53307 h -0.0019 v 0.531 h 1 l 0.0029,-0.0051 h 0.0068 l -0.0029,-0.0051 0.49316,-0.52374 2,-2.1219 -0.35254,-0.37647 -2.4951,2.6477 -0.29688,-0.31424 2.4961,-2.6488 0.2959,0.31528 0.35254,0.37336 0.49996,-0.531 z"
+       color="#eff0f1"
+       fill="currentColor"
+       id="path8" />
+  </g>
+  <path
+     style="color:#eff0f1;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999998,17 v 1.333333 H 1 v 1.333334 H 8.4999998 V 21 H 13.499999 V 19.666667 H 21 V 18.333333 H 13.499999 V 17 H 8.4999998 M 11,17.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2500002,-0.594666 -1.2500002,-1.333333 0,-0.738667 0.5575002,-1.333333 1.2500002,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/dialog-cancel.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dialog-cancel.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.53554"
+     inkscape:cy="11.332035"
+     inkscape:window-width="2580"
+     inkscape:window-height="1327"
+     inkscape:window-x="861"
+     inkscape:window-y="54"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 11,1 C 8.4655025,1 6.16085,1.9458675 4.3984375,3.5 4.2406388,3.63915 4.0784,3.780975 3.9296875,3.9296875 L 3.5,4.3984375 C 1.9458675,6.16085 1,8.4655025 1,11 c 0,5.52285 4.47715,10 10,10 2.534497,0 4.83915,-0.945867 6.601562,-2.5 l 0.46875,-0.429688 C 18.219025,17.9216 18.36085,17.759361 18.5,17.601562 20.054133,15.83915 21,13.534497 21,11 21,5.47715 16.52285,1 11,1 m 0,1.25 c 4.832487,0 8.75,3.9175125 8.75,8.75 0,2.192075 -0.816519,4.167835 -2.148438,5.703125 L 5.296875,4.3984375 C 6.8321638,3.0665187 8.807925,2.25 11,2.25 M 4.3984375,5.296875 16.703125,17.601562 C 15.167835,18.933481 13.192075,19.75 11,19.75 6.1675125,19.75 2.25,15.832487 2.25,11 2.25,8.807925 3.0665187,6.8321638 4.3984375,5.296875"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/dialog-ok.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dialog-ok.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.514119"
+     inkscape:cy="11.26777"
+     inkscape:window-width="2580"
+     inkscape:window-height="1335"
+     inkscape:window-x="861"
+     inkscape:window-y="46"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="M 20.116087,3.0000133 7.0676375,17.088623 1.8839,11.491654 1,12.446029 l 5.1837375,5.596943 -0.0025,0.0027 L 7.06515,19 7.06765,18.997333 7.07015,19 7.9540375,18.045638 7.9515375,18.042972 21,3.9543483 20.1161,3 Z"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/dirNew.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="dirNew.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.514119"
+     inkscape:cy="11.246349"
+     inkscape:window-width="2580"
+     inkscape:window-height="1351"
+     inkscape:window-x="0"
+     inkscape:window-y="30"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 2.2499998 20.999996 H 2.2499998 13.499998 v -1.25 H 2.2499998 V 9.7499984 h 3.7499993 v -0.012208 l 0.00977,0.012208 2.4999995,-2.4999996 H 19.749998 v 6.2499992 h 1.25 V 3.4999995 H 12.259764 L 9.7597646,1 9.7500021,1.0122125 V 1 H 2.2500035 1.0000037 M 17.250001,14.749997 v 2.5 h -2.5 v 1.25 h 2.5 v 2.499999 H 18.5 V 18.499997 H 21 v -1.25 h -2.5 v -2.5 h -1.249999"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/fileSaveAsRemote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg8"
+   sodipodi:docname="fileSaveAsRemote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview10"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.53554"
+     inkscape:cy="11.246349"
+     inkscape:window-width="2580"
+     inkscape:window-height="1381"
+     inkscape:window-x="861"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg8" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <path
+     class="ColorScheme-Text"
+     d="M 2,1.0001508 V 15.994083 h 9.000144 V 15.057015 H 6.000024 V 9.4348525 h 5.00012 V 8.4977834 H 5.000018 V 15.057015 H 3.000006 V 1.9388862 h 3.000018 v 4.6851769 h 8.000138 V 1.9388862 h 0.292973 L 17.000198,4.47543 v 3.0856185 h 1.000006 V 4.0947693 h -0.0078 l 0.0078,-0.00915 L 14.707141,1 14.699341,1.00915 V 1 h -0.69923 z m 5.00012,0.9388187 h 3.900482 V 5.6870777 H 7.00012 Z M 18.000096,8.4982001 17.00396,9.4297694 h -0.0039 l -5.00012,4.6833436 0.0078,0.0092 -0.0039,0.940652 h -0.0039 V 16 h 2.000012 l 0.0059,-0.0092 h 0.01364 l -0.0059,-0.0092 0.986326,-0.924235 4.000024,-3.744443 -0.705089,-0.664339 -4.99031,4.672344 -0.593758,-0.554533 4.99229,-4.674177 0.591805,0.556358 0.705089,0.658848 L 20,10.370438 Z"
+     color="#eff0f1"
+     fill="#232629"
+     id="path6"
+     style="stroke-width:0.866025" />
+  <path
+     style="color:#232629;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999983,17 v 1.333333 H 1 v 1.333334 H 8.4999983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.499997 V 17 H 8.4999983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.2499999,-0.594666 -1.2499999,-1.333333 0,-0.738667 0.5574999,-1.333333 1.2499999,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/open-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="open-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.514119"
+     inkscape:cy="11.26777"
+     inkscape:window-width="2580"
+     inkscape:window-height="1333"
+     inkscape:window-x="861"
+     inkscape:window-y="48"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 2.25 16 H 2.2499998 V 9.75 H 5.9999991 V 9.74023 L 6.0097653,9.75 8.5097648,7.25 H 19.749998 V 16 h 1.25 V 3.5 H 12.259764 L 9.7597646,1 9.7500046,1.00976 V 1 H 2.2500035 1.0000037 M 8.5000023,17.25 V 18.5 H 1.0000037 v 1.25 H 8.5000023 V 21 H 13.500001 V 19.75 H 21 V 18.5 H 13.500001 V 17.25 H 8.5000023 m 2.4999997,0.625 c 0.6925,0 1.25,0.5575 1.25,1.25 0,0.6925 -0.5575,1.25 -1.25,1.25 -0.6925,0 -1.2499999,-0.5575 -1.2499999,-1.25 0,-0.6925 0.5574999,-1.25 1.2499999,-1.25"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/preferences-eric-server.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   height="22"
+   width="22"
+   version="1.1"
+   id="svg3827"
+   sodipodi:docname="preferences-eric-server.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs3831" />
+  <sodipodi:namedview
+     id="namedview3829"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="32.09375"
+     inkscape:cx="16.747809"
+     inkscape:cy="16.389484"
+     inkscape:window-width="2580"
+     inkscape:window-height="1321"
+     inkscape:window-x="0"
+     inkscape:window-y="60"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg3827" />
+  <linearGradient
+     id="b"
+     gradientTransform="matrix(0.7,0,0,0.7,-0.7,-1.1)"
+     gradientUnits="userSpaceOnUse"
+     x2="0"
+     y1="44"
+     y2="4">
+    <stop
+       offset="0"
+       stop-color="#1d1e1e"
+       id="stop3789" />
+    <stop
+       offset="1"
+       stop-color="#44484c"
+       id="stop3791" />
+  </linearGradient>
+  <linearGradient
+     id="c"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-350.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="a"
+     gradientUnits="userSpaceOnUse"
+     x2="0"
+     y1="507.79999"
+     y2="506.79999">
+    <stop
+       offset="0"
+       stop-color="#3da103"
+       id="stop3795" />
+    <stop
+       offset="1"
+       stop-color="#7ddf07"
+       id="stop3797" />
+  </linearGradient>
+  <linearGradient
+     id="d"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-339.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="e"
+     gradientTransform="matrix(0.7,0,0,0.7,-270.499,-328.76)"
+     x2="0"
+     xlink:href="#a"
+     y1="507.79999"
+     y2="506.79999" />
+  <linearGradient
+     id="f"
+     gradientUnits="userSpaceOnUse"
+     x1="5"
+     x2="18"
+     y1="12"
+     y2="25">
+    <stop
+       offset="0"
+       stop-color="#292c2f"
+       id="stop3802" />
+    <stop
+       offset="1"
+       stop-opacity="0"
+       id="stop3804" />
+  </linearGradient>
+  <g
+     id="g5049"
+     transform="matrix(0.72413793,0,0,0.71428571,-1.1724138,-0.42857143)">
+    <path
+       d="M 3,7 H 29 V 24 H 3 Z"
+       fill="#111213"
+       id="path3807" />
+    <g
+       stroke-width="0.7"
+       id="g3817">
+      <path
+         d="M 3,2 V 30 H 29 V 2 Z m 1,6 h 24 v 4 H 4 Z m 0,11 h 24 v 4 H 4 Z"
+         fill="url(#b)"
+         id="path3809"
+         style="fill:url(#b)" />
+      <path
+         d="M 5,4 H 7 V 5 H 5 Z"
+         fill="url(#c)"
+         id="path3811"
+         style="fill:url(#c)" />
+      <path
+         d="m 5,15 h 2 v 1 H 5 Z"
+         fill="url(#d)"
+         id="path3813"
+         style="fill:url(#d)" />
+      <path
+         d="m 5,26 h 2 v 1 H 5 Z"
+         fill="url(#e)"
+         id="path3815"
+         style="fill:url(#e)" />
+    </g>
+    <path
+       d="m 3,29 h 26 v 1 H 3 Z"
+       opacity="0.2"
+       id="path3819" />
+    <path
+       d="m 4,12 7,7 h 17 v 4 H 15 l 7,7 h 7 V 13 l -1,-1 z"
+       fill="url(#f)"
+       fill-rule="evenodd"
+       opacity="0.4"
+       id="path3821"
+       style="fill:url(#f)" />
+    <rect
+       fill="#eff0f1"
+       height="16"
+       rx="2"
+       width="16"
+       x="16"
+       y="14"
+       id="rect3823" />
+    <path
+       d="m 19,16 v 2 h -1 v 2 h 1 v 4 h -1 v 2 h 1 v 2 H 30 V 16 Z m 1,1 h 1 v 10 h -1 z m 2,0 h 7 v 10 h -7 z"
+       fill="#232629"
+       id="path3825" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/projectOpen-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg1062"
+   sodipodi:docname="projectOpen-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1064"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.289192"
+     inkscape:cy="11.803311"
+     inkscape:window-width="1857"
+     inkscape:window-height="1337"
+     inkscape:window-x="62"
+     inkscape:window-y="17"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1062" />
+  <defs
+     id="defs1056">
+    <style
+       type="text/css"
+       id="style1054">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g9300"
+     transform="matrix(0.75,0,0,0.75,2.75,0.25)">
+    <path
+       d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path1058" />
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12 v 9 h 9 V 13.5 H 17.2559 L 15.7559,12 15.75,12.0059 V 12 h -3 z m 0.75,0.75 h 2.6938 l 0.75146,0.75 h -0.44531 v 0.0059 l -0.0058,-0.0059 -1.5,1.5 h -1.4941 z m 0,3 h 7.5 v 4.5 h -7.5 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path1060" />
+  </g>
+  <path
+     style="color:#232629;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.7679983,17 v 1.333333 H 1.268 v 1.333334 H 8.7679983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.767997 V 17 H 8.7679983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.25,-0.594666 -1.25,-1.333333 0,-0.738667 0.5575,-1.333333 1.25,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/projectSaveAs-remote.svg	Fri Jun 07 13:58:16 2024 +0200
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   viewBox="0 0 22 22"
+   id="svg10"
+   sodipodi:docname="projectSaveAs-remote.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview12"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="46.681818"
+     inkscape:cx="11.407011"
+     inkscape:cy="11.214216"
+     inkscape:window-width="2580"
+     inkscape:window-height="1274"
+     inkscape:window-x="861"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg10" />
+  <defs
+     id="defs4">
+    <style
+       type="text/css"
+       id="style2">.ColorScheme-Text {
+        color:#eff0f1;
+      }</style>
+  </defs>
+  <g
+     id="g2428"
+     transform="matrix(0.74999663,0,0,0.74999663,2.7500034,0.25000337)">
+    <path
+       d="M 3.5,1 V 3.5 H 1 v 3.75 h 2.5 v 7.5 H 1 V 18.5 H 3.5 V 21 H 11 V 19.75 H 4.75 V 6 h 15 v 5 H 21 V 1 Z m 1.25,1.25 h 15 v 2.5 h -15 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path6" />
+    <path
+       class="ColorScheme-Text"
+       d="m 12,12.5 v 8.4969 h 4.5 V 20.46594 H 14 v -3.186 h 2.5 v -0.531 h -3 v 3.717 h -1 v -7.4339 H 14 v 2.655 h 4 v -2.655 h 0.14648 l 1.3535,1.4374 v 1.7486 h 0.5 v -1.9643 h -0.0039 l 0.0039,-0.0051 -1.6465,-1.7486 -0.0039,0.0051 v -0.0051 h -0.34961 z m 2.5,0.53203 h 1.9502 v 2.124 H 14.5 Z m 5.5,3.717 -0.49805,0.52788 h -0.0019 l -2.5,2.6539 0.0039,0.0051 -0.0019,0.53307 h -0.0019 v 0.531 h 1 l 0.0029,-0.0051 h 0.0068 l -0.0029,-0.0051 0.49316,-0.52374 2,-2.1219 -0.35254,-0.37647 -2.4951,2.6477 -0.29688,-0.31424 2.4961,-2.6488 0.2959,0.31528 0.35254,0.37336 0.49996,-0.531 z"
+       color="#eff0f1"
+       fill="#232629"
+       id="path8" />
+  </g>
+  <path
+     style="color:#232629;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.29099"
+     d="m 8.4999983,17 v 1.333333 H 1 v 1.333334 H 8.4999983 V 21 h 4.9999987 v -1.333333 h 7.499999 V 18.333333 H 13.499997 V 17 H 8.4999983 m 2.4999997,0.666667 c 0.6925,0 1.25,0.594666 1.25,1.333333 0,0.738667 -0.5575,1.333333 -1.25,1.333333 -0.6925,0 -1.25,-0.594666 -1.25,-1.333333 0,-0.738667 0.5575,-1.333333 1.25,-1.333333"
+     class="ColorScheme-Text"
+     id="path4"
+     sodipodi:nodetypes="ccccccccccccccsssc" />
+</svg>

eric ide

mercurial