src/eric7/QScintilla/Editor.py

branch
server
changeset 10546
300487f5f517
parent 10539
4274f189ff78
child 10550
fd862db1f936
--- a/src/eric7/QScintilla/Editor.py	Fri Feb 02 14:55:14 2024 +0100
+++ b/src/eric7/QScintilla/Editor.py	Mon Feb 05 11:15:47 2024 +0100
@@ -68,6 +68,7 @@
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.Globals import recentNameBreakpointConditions
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
+from eric7.RemoteServerInterface import EricServerFileDialog
 from eric7.UI import PythonDisViewer
 from eric7.Utilities import MouseUtilities
 
@@ -264,6 +265,11 @@
         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
@@ -478,11 +484,10 @@
                         if not res:
                             raise OSError()
 
-                    self.readFile(self.fileName, True)
+                    self.readFile(self.fileName, createIt=True)
 
                 elif FileSystemUtilities.isRemoteFileName(self.fileName):
-                    # TODO: read the remote file from the server
-                    pass
+                    self.readFile(self.fileName, createIt=True,  isRemote=True)
 
                 self.__bindLexer(self.fileName)
                 self.__bindCompleter(self.fileName)
@@ -2160,8 +2165,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())
@@ -3489,7 +3494,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.
 
@@ -3503,26 +3508,44 @@
         @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")
+                    bText = self.__remotefsInterface.readFile(
+                        FileSystemUtilities.plainFileName(fn), create=True
                     )
+                    if encoding:
+                        txt, self.encoding = Utilities.decodeWithEncoding(
+                            bText, encoding
+                        )
+                    else:
+                        txt, self.encoding = Utilities.decode(bText)
                 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>"
@@ -3552,7 +3575,7 @@
 
             self.extractTasks()
 
-            self.lastModified = pathlib.Path(fn).stat().st_mtime
+            self.recordModificationTime(filename=fn)
 
     @pyqtSlot()
     def __convertTabs(self):
@@ -3604,7 +3627,10 @@
         @return flag indicating success
         @rtype bool
         """
-        config = self.__loadEditorConfigObject(fn)
+        if self.fileName and fn == self.fileName:
+            config = None
+        else:
+            config = self.__loadEditorConfigObject(fn)
 
         eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config)
         if eol is not None:
@@ -3626,7 +3652,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)
@@ -3646,28 +3672,40 @@
             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")
+                bText, self.encoding = Utilities.encode(
+                    txt, self.encoding, forcedEncoding=editorConfigEncoding
+                )
+                self.__remotefsInterface.writeFile(
+                    FileSystemUtilities.plainFileName(fn), bText, 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
         """
@@ -3681,7 +3719,13 @@
         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])
@@ -3693,14 +3737,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("."):
@@ -3711,10 +3767,16 @@
                 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),
@@ -3747,21 +3809,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):
@@ -3771,7 +3833,7 @@
         if saveas or self.fileName == "":
             saveas = True
 
-            fn = self.__getSaveFileName(path)
+            fn = self.__getSaveFileName(path=path, remote=remote)
             if not fn:
                 return False
 
@@ -3790,13 +3852,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)
@@ -3818,7 +3880,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)
@@ -3828,21 +3890,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):
         """
@@ -3903,7 +3965,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)
 
@@ -8019,6 +8081,15 @@
         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):
@@ -8028,7 +8099,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)
@@ -8201,7 +8276,7 @@
                 signal if there was an attribute change.
         @type bool
         """
-        if self.fileName == "" or not FileSystemUtilities.isPlainFileName(
+        if self.fileName == "" or FileSystemUtilities.isDeviceFileName(
             self.fileName
         ):
             return
@@ -8225,9 +8300,17 @@
         @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):
@@ -8235,13 +8318,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"""
@@ -8262,23 +8341,74 @@
                     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
-
-    @pyqtSlot()
-    def recordModificationTime(self):
+                    self.recordModificationTime()
+
+    @pyqtSlot()
+    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):
+                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):
+                if 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()
@@ -8298,11 +8428,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)
@@ -9703,7 +9828,7 @@
         """
         editorConfig = {}
 
-        if fileName and FileSystemUtilities.isPlainFileName(self.fileName):
+        if fileName and FileSystemUtilities.isPlainFileName(fileName):
             try:
                 editorConfig = editorconfig.get_properties(fileName)
             except editorconfig.EditorConfigError:
@@ -9741,16 +9866,16 @@
         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
-
+        ##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"]

eric ide

mercurial