--- 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)