QScintilla/Editor.py

branch
maintenance
changeset 6166
bace7fb85a01
parent 6097
bf18415da0c7
parent 6119
18fb5d765f3a
child 6206
a02b03b7bfec
--- a/QScintilla/Editor.py	Sun Feb 04 10:56:30 2018 +0100
+++ b/QScintilla/Editor.py	Fri Mar 02 19:35:16 2018 +0100
@@ -39,6 +39,8 @@
 
 import UI.PixmapCache
 
+from ThirdParty.EditorConfig import editorconfig
+
 EditorAutoCompletionListID = 1
 TemplateCompletionListID = 2
 
@@ -143,6 +145,13 @@
     RequestSyncToken = "REQUEST_SYNC"
     SyncToken = "SYNC"
     
+    VcsConflictMarkerLineRegExpList = (
+        r"""^<<<<<<< .*?$""",
+        r"""^\|\|\|\|\|\|\| .*?$""",
+        r"""^=======.*?$""",
+        r"""^>>>>>>> .*?$""",
+    )
+    
     def __init__(self, dbs, fn="", vm=None,
                  filetype="", editor=None, tv=None):
         """
@@ -192,11 +201,14 @@
         self.notcoveredMarkers = []  # just a list of marker handles
         self.showingNotcoveredMarkers = False
         
+        self.lexer_ = None
+        
+        self.__loadEditorConfig()
+        
         self.condHistory = []
-        self.lexer_ = None
         self.__lexerReset = False
         self.completer = None
-        self.encoding = Preferences.getEditor("DefaultEncoding")
+        self.encoding = self.__getEditorConfig("DefaultEncoding")
         self.apiLanguage = ''
         self.lastModified = 0
         self.line = -1
@@ -260,8 +272,6 @@
         self.modificationChanged.connect(self.__modificationChanged)
         self.cursorPositionChanged.connect(self.__cursorPositionChanged)
         self.modificationAttempted.connect(self.__modificationReadOnly)
-        self.userListActivated.connect(self.__completionListSelected)
-        self.SCN_CHARADDED.connect(self.__charAddedPermanent)
         
         # margins layout
         if QSCINTILLA_VERSION() >= 0x020301:
@@ -405,6 +415,9 @@
             Preferences.getEditor("AutoCompletionWatchdogTime"))
         self.__acWatchdog.timeout.connect(self.autoCompleteQScintilla)
         
+        self.userListActivated.connect(self.__completionListSelected)
+        self.SCN_CHARADDED.connect(self.__charAddedPermanent)
+        
         self.__completionListHookFunctions = {}
         self.__completionListAsyncHookFunctions = {}
         self.__setAutoCompletion()
@@ -1156,7 +1169,8 @@
         self.marginMenuActs["ClearBookmark"] = self.bmMarginMenu.addAction(
             self.tr('Clear all bookmarks'), self.clearBookmarks)
         
-        self.bmMarginMenu.aboutToShow.connect(self.__showContextMenuMargin)
+        self.bmMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.bmMarginMenu))
         
         # breakpoint margin
         self.bpMarginMenu = QMenu()
@@ -1180,7 +1194,8 @@
         self.marginMenuActs["ClearBreakpoint"] = self.bpMarginMenu.addAction(
             self.tr('Clear all breakpoints'), self.__menuClearBreakpoints)
         
-        self.bpMarginMenu.aboutToShow.connect(self.__showContextMenuMargin)
+        self.bpMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.bpMarginMenu))
         
         # indicator margin
         self.indicMarginMenu = QMenu()
@@ -1233,7 +1248,8 @@
             self.indicMarginMenu.addAction(
                 self.tr('Clear changes'), self.__reinitOnlineChangeTrace)
         
-        self.indicMarginMenu.aboutToShow.connect(self.__showContextMenuMargin)
+        self.indicMarginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.indicMarginMenu))
         
     def __initContextMenuUnifiedMargins(self):
         """
@@ -1313,7 +1329,8 @@
         self.marginMenuActs["LMBbreakpoints"].setCheckable(True)
         self.marginMenuActs["LMBbreakpoints"].setChecked(True)
         
-        self.marginMenu.aboutToShow.connect(self.__showContextMenuMargin)
+        self.marginMenu.aboutToShow.connect(
+            lambda: self.__showContextMenuMargin(self.marginMenu))
         
     def __exportMenuTriggered(self, act):
         """
@@ -2956,10 +2973,15 @@
         """
         QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
         
+        self.__loadEditorConfig(fileName=fn)
+        
         try:
             if createIt and not os.path.exists(fn):
                 f = open(fn, "w")
                 f.close()
+            if encoding == "":
+                encoding = self.__getEditorConfig("DefaultEncoding",
+                                                  nodefault=True)
             if encoding:
                 txt, self.encoding = Utilities.readEncodedFileWithEncoding(
                     fn, encoding)
@@ -2975,28 +2997,29 @@
                     .format(fn, str(why)))
             QApplication.restoreOverrideCursor()
             raise
-        fileEol = self.detectEolString(txt)
         
         modified = False
-        if (not Preferences.getEditor("TabForIndentation")) and \
+        
+        if (not self.__getEditorConfig("TabForIndentation")) and \
                 Preferences.getEditor("ConvertTabsOnLoad") and \
                 not (self.lexer_ and
                      self.lexer_.alwaysKeepTabs()):
-            txtExpanded = txt.expandtabs(Preferences.getEditor("TabWidth"))
+            txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth"))
             if txtExpanded != txt:
                 modified = True
             txt = txtExpanded
-            del txtExpanded
         
         self.setText(txt)
         
         # get eric specific flags
         self.__processFlags()
         
-        # perform automatic eol conversion
-        if Preferences.getEditor("AutomaticEOLConversion"):
+        # perform automatic EOL conversion
+        if self.__getEditorConfig("EOLMode", nodefault=True) or \
+           Preferences.getEditor("AutomaticEOLConversion"):
             self.convertEols(self.eolMode())
         else:
+            fileEol = self.detectEolString(txt)
             self.setEolModeByEolString(fileEol)
         
         self.extractTasks()
@@ -3027,19 +3050,25 @@
         @param backup flag indicating to save a backup (boolean)
         @return flag indicating success (boolean)
         """
-        if Preferences.getEditor("StripTrailingWhitespace"):
+        config = self.__loadEditorConfigObject(fn)
+        
+        eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config)
+        if eol is not None:
+            self.convertEols(eol)
+        
+        if self.__getEditorConfig("StripTrailingWhitespace", config=config):
             self.__removeTrailingWhitespace()
         
         txt = self.text()
-        # work around glitch in scintilla: always make sure,
-        # that the last line is terminated properly
-        eol = self.getLineSeparator()
-        if eol:
-            if len(txt) >= len(eol):
-                if txt[-len(eol):] != eol:
+        
+        if self.__getEditorConfig("InsertFinalNewline", config=config):
+            eol = self.getLineSeparator()
+            if eol:
+                if len(txt) >= len(eol):
+                    if txt[-len(eol):] != eol:
+                        txt += eol
+                else:
                     txt += eol
-            else:
-                txt += eol
         
         # create a backup file, if the option is set
         createBackup = backup and Preferences.getEditor("CreateBackupFile")
@@ -3066,7 +3095,10 @@
         
         # now write text to the file fn
         try:
-            self.encoding = Utilities.writeEncodedFile(fn, txt, self.encoding)
+            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)
             return True
@@ -3186,11 +3218,18 @@
             # save to project, if a project is loaded
             if self.project.isOpen() and \
                     self.project.startswithProjectPath(fn):
-                self.setEolModeByEolString(self.project.getEolString())
+                editorConfigEol = self.__getEditorConfig(
+                    "EOLMode", nodefault=True,
+                    config=self.__loadEditorConfigObject(fn))
+                if editorConfigEol is not None:
+                    self.setEolMode(editorConfigEol)
+                else:
+                    self.setEolModeByEolString(self.project.getEolString())
                 self.convertEols(self.eolMode())
         else:
             fn = self.fileName
         
+        self.__loadEditorConfig(fn)
         self.editorAboutToBeSaved.emit(self.fileName)
         if self.writeFile(fn):
             if saveas:
@@ -3253,6 +3292,8 @@
         self.fileName = fn
         self.setWindowTitle(self.fileName)
         
+        self.__loadEditorConfig()
+        
         if self.lexer_ is None:
             self.setLanguage(self.fileName)
         
@@ -4313,17 +4354,24 @@
             self.setMarginWidth(
                 self.__linenoMargin, '8' * (len(str(self.lines())) + 1))
     
-    def __setTextDisplay(self):
-        """
-        Private method to configure the text display.
-        """
-        self.setTabWidth(Preferences.getEditor("TabWidth"))
-        self.setIndentationWidth(Preferences.getEditor("IndentWidth"))
+    def __setTabAndIndent(self):
+        """
+        Private method to set indentation size and style and tab width.
+        """
+        self.setTabWidth(self.__getEditorConfig("TabWidth"))
+        self.setIndentationWidth(self.__getEditorConfig("IndentWidth"))
         if self.lexer_ and self.lexer_.alwaysKeepTabs():
             self.setIndentationsUseTabs(True)
         else:
             self.setIndentationsUseTabs(
-                Preferences.getEditor("TabForIndentation"))
+                self.__getEditorConfig("TabForIndentation"))
+    
+    def __setTextDisplay(self):
+        """
+        Private method to configure the text display.
+        """
+        self.__setTabAndIndent()
+        
         self.setTabIndents(Preferences.getEditor("TabIndents"))
         self.setBackspaceUnindents(Preferences.getEditor("TabIndents"))
         self.setIndentationGuides(Preferences.getEditor("IndentationGuides"))
@@ -4440,9 +4488,14 @@
         if self.fileName and \
            self.project.isOpen() and \
            self.project.isProjectFile(self.fileName):
-            self.setEolModeByEolString(self.project.getEolString())
-        else:
-            eolMode = Preferences.getEditor("EOLMode")
+            eolMode = self.__getEditorConfig("EOLMode", nodefault=True)
+            if eolMode is None:
+                eolStr = self.project.getEolString()
+                self.setEolModeByEolString(eolStr)
+            else:
+                self.setEolMode(eolMode)
+        else:
+            eolMode = self.__getEditorConfig("EOLMode")
             eolMode = QsciScintilla.EolMode(eolMode)
             self.setEolMode(eolMode)
         self.__eolChanged()
@@ -5325,10 +5378,13 @@
         
         self.showMenu.emit("Graphics", self.graphicsMenu, self)
         
-    def __showContextMenuMargin(self):
+    def __showContextMenuMargin(self, menu):
         """
         Private slot handling the aboutToShow signal of the margins context
         menu.
+        
+        @param menu reference to the menu to be shown
+        @type QMenu
         """
         if self.fileName and self.isPyFile():
             self.marginMenuActs["Breakpoint"].setEnabled(True)
@@ -5421,7 +5477,7 @@
             self.marginMenuActs["NextChangeMarker"].setEnabled(False)
             self.marginMenuActs["ClearChangeMarkers"].setEnabled(False)
         
-        self.showMenu.emit("Margin", self.sender(), self)
+        self.showMenu.emit("Margin", menu, self)
         
     def __showContextMenuChecks(self):
         """
@@ -6064,6 +6120,30 @@
                 self.tr("No syntax error message available."))
     
     ###########################################################################
+    ## VCS conflict marker handling methods below
+    ###########################################################################
+
+    def getVcsConflictMarkerLines(self):
+        """
+        Public method to determine the lines containing a VCS conflict marker.
+        
+        @return list of line numbers containg a VCS conflict marker
+        @rtype list of int
+        """
+        conflictMarkerLines = []
+        
+        for searchRe in Editor.VcsConflictMarkerLineRegExpList:
+            ok = self.findFirstTarget(searchRe, True, False, False, 0, 0)
+            while ok:
+                spos = self.getFoundTarget()[0]
+                line = self.lineIndexFromPosition(spos)[0]
+                conflictMarkerLines.append(line)
+                
+                ok = self.findNextTarget()
+        
+        return conflictMarkerLines
+    
+    ###########################################################################
     ## Warning handling methods below
     ###########################################################################
     
@@ -7249,7 +7329,11 @@
             self.__setSpellingLanguage(self.project.getProjectSpellLanguage(),
                                        pwl=pwl, pel=pel)
         
-        self.setEolModeByEolString(self.project.getEolString())
+        editorConfigEol = self.__getEditorConfig("EOLMode", nodefault=True)
+        if editorConfigEol is not None:
+            self.setEolMode(editorConfigEol)
+        else:
+            self.setEolModeByEolString(self.project.getEolString())
         self.convertEols(self.eolMode())
     
     def addedToProject(self):
@@ -7285,7 +7369,7 @@
             pass
     
     #######################################################################
-    ## Spellchecking related methods
+    ## Spell checking related methods
     #######################################################################
     
     def __setSpellingLanguage(self, language, pwl="", pel=""):
@@ -8016,3 +8100,123 @@
         """
         txt = self.selectedText()
         e5App().getObject("Shell").executeLines(txt)
+    
+    #######################################################################
+    ## Methods implementing the interface to EditorConfig
+    #######################################################################
+    
+    def __loadEditorConfig(self, fileName=""):
+        """
+        Private method to load the EditorConfig properties.
+        
+        @param fileName name of the file
+        @type str
+        """
+        if not fileName:
+            fileName = self.fileName
+        
+        self.__editorConfig = self.__loadEditorConfigObject(fileName)
+        
+        if fileName:
+            self.__setTabAndIndent()
+    
+    def __loadEditorConfigObject(self, fileName):
+        """
+        Private method to load the EditorConfig properties for the given
+        file name.
+        
+        @param fileName name of the file
+        @type str
+        @return EditorConfig dictionary
+        @rtype dict
+        """
+        editorConfig = {}
+        
+        if fileName:
+            try:
+                editorConfig = editorconfig.get_properties(fileName)
+            except editorconfig.EditorConfigError:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("EditorConfig Properties"),
+                    self.tr("""<p>The EditorConfig properties for file"""
+                            """ <b>{0}</b> could not be loaded.</p>""")
+                    .format(fileName))
+        
+        return editorConfig
+    
+    def __getEditorConfig(self, option, nodefault=False, config=None):
+        """
+        Private method to get the requested option via EditorConfig.
+        
+        If there is no EditorConfig defined, the equivalent built-in option
+        will be used (Preferences.getEditor(). The option must be given as the
+        Preferences option key. The mapping to the EditorConfig option name
+        will be done within this method.
+        
+        @param option Preferences option key
+        @type str
+        @param nodefault flag indicating to not get the default value from
+            Preferences but return None instead
+        @type bool
+        @param config reference to an EditorConfig object or None
+        @type dict
+        @return value of requested setting or None if nothing was found and
+            nodefault parameter was True
+        @rtype any
+        """
+        if config is None:
+            config = self.__editorConfig
+        
+        if not config:
+            if nodefault:
+                return None
+            else:
+                return Preferences.getEditor(option)
+        
+        try:
+            if option == "EOLMode":
+                value = config["end_of_line"]
+                if value == "lf":
+                    value = QsciScintilla.EolUnix
+                elif value == "crlf":
+                    value = QsciScintilla.EolWindows
+                elif value == "cr":
+                    value = QsciScintilla.EolMac
+                else:
+                    value = None
+            elif option == "DefaultEncoding":
+                value = config["charset"]
+            elif option == "InsertFinalNewline":
+                value = Utilities.toBool(config["insert_final_newline"])
+            elif option == "StripTrailingWhitespace":
+                value = Utilities.toBool(config["trim_trailing_whitespace"])
+            elif option == "TabWidth":
+                value = int(config["tab_width"])
+            elif option == "IndentWidth":
+                value = config["indent_size"]
+                if value == "tab":
+                    value = self.__getEditorConfig("TabWidth", config=config)
+                else:
+                    value = int(value)
+            elif option == "TabForIndentation":
+                value = config["indent_style"] == "tab"
+        except KeyError:
+            value = None
+        
+        if value is None and not nodefault:
+            # use Preferences in case of error
+            value = Preferences.getEditor(option)
+        
+        return value
+    
+    def getEditorConfig(self, option):
+        """
+        Public method to get the requested option via EditorConfig.
+        
+        @param option Preferences option key
+        @type str
+        @return value of requested setting
+        @rtype any
+        """
+        return self.__getEditorConfig(option)

eric ide

mercurial